Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ dist
pgdata
redis-data
logs
drizzle

# Environment
.env
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ COPY --from=builder --chown=node:node /usr/src/app/docker-entrypoint.sh ./docker
COPY --from=builder --chown=node:node /usr/src/app/.env.example ./.env.example
COPY --from=builder --chown=node:node /usr/src/app/exchanges ./exchanges
COPY --from=builder --chown=node:node /usr/src/app/drizzle ./drizzle
COPY --from=builder --chown=node:node /usr/src/app/scripts ./scripts

RUN chmod +x /usr/src/app/docker-entrypoint.sh

Expand Down
10 changes: 9 additions & 1 deletion docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,13 @@ until pg_isready -h "${POSTGRESQL_HOST:-postgres}" -p "${POSTGRESQL_PORT:-5432}"
sleep 1
done

echo "Postgres is available. Starting app..."
echo "Postgres is available. Running database migrations..."
node /usr/src/app/scripts/migrate.js

if [ $? -ne 0 ]; then
echo "ERROR: Database migrations failed!"
exit 1
fi

echo "Database migrations completed successfully. Starting app..."
exec "$@"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"drizzle:generate": "npx drizzle-kit generate",
"drizzle:generate:custom": "npx drizzle-kit generate --custom",
"drizzle:migrate": "npx drizzle-kit migrate",
"drizzle:migrate": "node scripts/migrate.js",
"drizzle:push": "npx drizzle-kit push",
"drizzle:studio": "npx drizzle-kit studio",
"start": "node --env-file=.env build"
Expand Down
17 changes: 17 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,20 @@
- vite (빌드 도구)
- Zod (데이터 검증)

---
## Database Migrations / 데이터베이스 마이그레이션

Database migrations run automatically before the application starts when using Docker.

For manual migration in development:
```bash
npm run drizzle:migrate
```

Docker 사용 시 데이터베이스 마이그레이션은 애플리케이션 시작 전에 자동으로 실행됩니다.

개발 환경에서 수동으로 마이그레이션 실행:
```bash
npm run drizzle:migrate
```

138 changes: 138 additions & 0 deletions scripts/migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/env node
/**
* Standalone migration script
* Runs database migrations independently from application startup
*/

import postgres from 'postgres';
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Simple logger for migration script
const logger = {
info: (...args) => console.log('[INFO]', ...args),
error: (...args) => console.error('[ERROR]', ...args),
debug: (...args) => console.log('[DEBUG]', ...args)
};

// Read environment variables
const POSTGRESQL_USER = process.env.POSTGRESQL_USER || 'postgres';
const POSTGRESQL_PASSWORD = process.env.POSTGRESQL_PASSWORD || 'postgres';
const POSTGRESQL_HOST = process.env.POSTGRESQL_HOST || 'localhost';
const POSTGRESQL_PORT = process.env.POSTGRESQL_PORT || '5432';
const POSTGRESQL_NAME = process.env.POSTGRESQL_NAME || 'pjsedb';
const POSTGRESQL_SSL = process.env.POSTGRESQL_SSL || 'disable';

const encodedPassword = encodeURIComponent(POSTGRESQL_PASSWORD);
const dbUrl = `postgres://${POSTGRESQL_USER}:${encodedPassword}@${POSTGRESQL_HOST}:${POSTGRESQL_PORT}/${POSTGRESQL_NAME}`;

const msToSec = (v) => v ? Math.max(1, Math.ceil(v / 1000)) : undefined;

// Create PostgreSQL client
const postgresClient = postgres(dbUrl, {
max: 10,
idle_timeout: msToSec(10000),
max_lifetime: msToSec(60000),
ssl: POSTGRESQL_SSL === 'disable' ? false : POSTGRESQL_SSL,
});

/**
* Run database migrations
*/
async function runMigrations() {
const migrationFolder = join(dirname(__dirname), 'drizzle');

try {
// Test database connection
await postgresClient`SELECT 1`;
logger.info('Connected to PostgreSQL');

// Create migrations meta table
await postgresClient`
CREATE TABLE IF NOT EXISTS drizzle_migrations (
id SERIAL PRIMARY KEY,
hash TEXT NOT NULL,
created_at BIGINT
)
`;

// Check if migration directory exists
try {
await readdir(migrationFolder);
} catch (error) {
if (error.code === 'ENOENT') {
logger.info(`Migration folder '${migrationFolder}' does not exist. No migrations to run.`);
return;
}
throw error;
}

// Read migration files
const files = await readdir(migrationFolder);
const sqlFiles = files
.filter(f => f.endsWith('.sql'))
.sort(); // Sort by filename

if (sqlFiles.length === 0) {
logger.info('No migration files found');
return;
}

logger.info(`Found ${sqlFiles.length} migration file(s)`);

for (const file of sqlFiles) {
const filePath = join(migrationFolder, file);
const sql = await readFile(filePath, 'utf-8');

// Execute in transaction with migration existence check inside for atomicity
await postgresClient.begin(async (tx) => {
// Check if migration already applied (inside transaction for atomicity)
const [existing] = await tx`
SELECT id FROM drizzle_migrations WHERE hash = ${file}
`;

if (!existing) {
logger.info(`Running migration: ${file}`);

// Execute SQL
await tx.unsafe(sql);

// Record execution
await tx.unsafe(
'INSERT INTO drizzle_migrations (hash, created_at) VALUES ($1, $2)',
[file, Date.now()]
);

logger.info(`Migration completed: ${file}`);
} else {
logger.debug(`Migration already applied: ${file}`);
}
});
}

logger.info('All migrations completed successfully');
} catch (error) {
logger.error('Migration error:', error);
throw error;
} finally {
// Close connection
await postgresClient.end({ timeout: 5 });
logger.info('PostgreSQL connection closed');
}
}

// Run migrations and exit
runMigrations()
.then(() => {
logger.info('Migration script completed successfully');
process.exit(0);
})
.catch((error) => {
logger.error('Migration script failed:', error);
process.exit(1);
});
3 changes: 1 addition & 2 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Handle, ServerInit } from '@sveltejs/kit';
import { paraglideMiddleware } from '$lib/paraglide/server';
import { initPostgres, runMigrations } from "$lib/server/postgresql/db";
import { initPostgres } from "$lib/server/postgresql/db";
import { initRedis } from "$lib/server/redis/db";
import { sequence } from "@sveltejs/kit/hooks";
import { Cron } from "croner";
Expand All @@ -26,7 +26,6 @@ export const init: ServerInit = async () => {

await initPostgres();
await initRedis();
await runMigrations();

if (!newLoad) {
newLoad = true;
Expand Down