From 803110606bde7eacda1db4c8103a8bdef96ae1df Mon Sep 17 00:00:00 2001 From: static Date: Mon, 20 Jan 2025 19:15:15 +0900 Subject: [PATCH] =?UTF-8?q?Production=20=ED=99=98=EA=B2=BD=EC=97=90?= =?UTF-8?q?=EC=84=9C=EC=9D=98=20DB=20=EC=9E=90=EB=8F=99=20Migration=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 15 ++++++------ docker-compose.yaml | 1 + src/hooks.server.ts | 6 ++--- src/lib/server/db/client.ts | 4 +-- src/lib/server/db/hsk.ts | 4 +-- src/lib/server/db/kysely.ts | 35 ++++++++++++++++++++++++--- src/lib/server/db/mek.ts | 4 +-- src/lib/server/db/migrations/index.ts | 5 ++++ src/lib/server/db/session.ts | 6 ++--- src/lib/server/loadenv.ts | 1 + 10 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 src/lib/server/db/migrations/index.ts diff --git a/Dockerfile b/Dockerfile index eec42f6..f809fbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,10 @@ FROM node:22-alpine AS base WORKDIR /app +RUN apk add --no-cache bash curl && \ + curl -o /usr/local/bin/wait-for-it https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && \ + chmod +x /usr/local/bin/wait-for-it + RUN npm install -g pnpm@9 COPY pnpm-lock.yaml . @@ -10,10 +14,9 @@ FROM base AS build RUN pnpm fetch COPY . . -RUN pnpm install --offline -RUN pnpm build - -RUN sed -i "s/http\.createServer()/http.createServer({ requestTimeout: 0 })/g" ./build/index.js +RUN pnpm install --offline && \ + pnpm build && \ + sed -i "s/http\.createServer()/http.createServer({ requestTimeout: 0 })/g" ./build/index.js # Deploy Stage FROM base @@ -23,9 +26,7 @@ COPY package.json . RUN pnpm install --offline --prod COPY --from=build /app/build ./build -COPY drizzle ./drizzle EXPOSE 3000 ENV BODY_SIZE_LIMIT=Infinity - -CMD ["node", "./build/index.js"] +CMD ["bash", "-c", "wait-for-it ${DATABASE_HOST:-localhost}:${DATABASE_PORT:-5432} -- node ./build/index.js"] diff --git a/docker-compose.yaml b/docker-compose.yaml index b14f0df..dc7f392 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -27,6 +27,7 @@ services: database: image: postgres:17.2-alpine restart: on-failure + user: ${CONTAINER_UID:-0}:${CONTAINER_GID:-0} volumes: - ./data/database:/var/lib/postgresql/data environment: diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 9dac88c..6f94a7e 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -2,15 +2,15 @@ import type { ServerInit } from "@sveltejs/kit"; import { sequence } from "@sveltejs/kit/hooks"; import schedule from "node-schedule"; import { cleanupExpiredUserClientChallenges } from "$lib/server/db/client"; -import { migrateDB } from "$lib/server/db/drizzle"; +import { migrateDB } from "$lib/server/db/kysely"; import { cleanupExpiredSessions, cleanupExpiredSessionUpgradeChallenges, } from "$lib/server/db/session"; import { authenticate, setAgentInfo } from "$lib/server/middlewares"; -export const init: ServerInit = () => { - migrateDB(); +export const init: ServerInit = async () => { + await migrateDB(); schedule.scheduleJob("0 * * * *", () => { cleanupExpiredUserClientChallenges(); diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index 4fd23f3..08f2c98 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import { IntegrityError } from "./error"; import db from "./kysely"; import type { UserClientState } from "./schema"; @@ -91,7 +91,7 @@ export const createUserClient = async (userId: number, clientId: number) => { try { await db.insertInto("user_client").values({ user_id: userId, client_id: clientId }).execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("User client already exists"); } throw e; diff --git a/src/lib/server/db/hsk.ts b/src/lib/server/db/hsk.ts index a1bf66b..4673cae 100644 --- a/src/lib/server/db/hsk.ts +++ b/src/lib/server/db/hsk.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import { IntegrityError } from "./error"; import db from "./kysely"; import type { HskState } from "./schema"; @@ -40,7 +40,7 @@ export const registerInitialHsk = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("HSK already registered"); } throw e; diff --git a/src/lib/server/db/kysely.ts b/src/lib/server/db/kysely.ts index 9665bb3..302049e 100644 --- a/src/lib/server/db/kysely.ts +++ b/src/lib/server/db/kysely.ts @@ -1,10 +1,11 @@ -import { Kysely, PostgresDialect } from "kysely"; -import { Pool } from "pg"; +import { Kysely, PostgresDialect, Migrator } from "kysely"; +import pg from "pg"; import env from "$lib/server/loadenv"; +import migrations from "./migrations"; import type { Database } from "./schema"; const dialect = new PostgresDialect({ - pool: new Pool({ + pool: new pg.Pool({ host: env.database.host, port: env.database.port, user: env.database.user, @@ -15,6 +16,32 @@ const dialect = new PostgresDialect({ const db = new Kysely({ dialect }); -// TODO: Migration +export const migrateDB = async () => { + if (env.nodeEnv !== "production") return; + + const migrator = new Migrator({ + db, + provider: { + async getMigrations() { + return migrations; + }, + }, + }); + const { error, results } = await migrator.migrateToLatest(); + if (error) { + const migration = results?.find(({ status }) => status === "Error"); + if (migration) { + console.error(`Migration "${migration.migrationName}" failed.`); + } + console.error(error); + process.exit(1); + } + + if (results?.length === 0) { + console.log("Database is up-to-date."); + } else { + console.log("Database migration completed."); + } +}; export default db; diff --git a/src/lib/server/db/mek.ts b/src/lib/server/db/mek.ts index 4d57013..ff3c999 100644 --- a/src/lib/server/db/mek.ts +++ b/src/lib/server/db/mek.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import { IntegrityError } from "./error"; import db from "./kysely"; import type { MekState } from "./schema"; @@ -52,7 +52,7 @@ export const registerInitialMek = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("MEK already registered"); } throw e; diff --git a/src/lib/server/db/migrations/index.ts b/src/lib/server/db/migrations/index.ts new file mode 100644 index 0000000..6caca84 --- /dev/null +++ b/src/lib/server/db/migrations/index.ts @@ -0,0 +1,5 @@ +import * as Initial1737357000 from "./1737357000-Initial"; + +export default { + "1737357000-Initial": Initial1737357000, +}; diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index cd4d558..727f795 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -1,4 +1,4 @@ -import { DatabaseError } from "pg"; +import pg from "pg"; import env from "$lib/server/loadenv"; import { IntegrityError } from "./error"; import db from "./kysely"; @@ -25,7 +25,7 @@ export const createSession = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Session already exists"); } throw e; @@ -105,7 +105,7 @@ export const registerSessionUpgradeChallenge = async ( }) .execute(); } catch (e) { - if (e instanceof DatabaseError && e.code === "23505") { + if (e instanceof pg.DatabaseError && e.code === "23505") { throw new IntegrityError("Challenge already registered"); } throw e; diff --git a/src/lib/server/loadenv.ts b/src/lib/server/loadenv.ts index baffdb0..d6f4675 100644 --- a/src/lib/server/loadenv.ts +++ b/src/lib/server/loadenv.ts @@ -8,6 +8,7 @@ if (!building) { } export default { + nodeEnv: env.NODE_ENV || "development", database: { host: env.DATABASE_HOST, port: env.DATABASE_PORT ? parseInt(env.DATABASE_PORT, 10) : undefined,