diff --git a/Dockerfile b/Dockerfile index 685d33b..041ab5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,6 +65,9 @@ COPY --from=builder /app/deploy/ ./ # Copy compiled output COPY --from=builder /workspace/barazo-api/dist/ ./dist/ +# Copy Drizzle migration files (applied on startup) +COPY --from=builder /workspace/barazo-api/drizzle/ ./drizzle/ + # Create plugins directory for runtime plugin loading RUN mkdir -p /app/plugins && chown barazo:nodejs /app/plugins diff --git a/src/app.ts b/src/app.ts index ed7d562..d96721d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,7 +14,7 @@ import type { Env } from './config/env.js' import { getCommunityDid } from './config/env.js' import { createSingleResolver, registerCommunityResolver } from './middleware/community-resolver.js' import type { CommunityResolver } from './middleware/community-resolver.js' -import { createDb } from './db/index.js' +import { createDb, runMigrations } from './db/index.js' import { createCache } from './cache/index.js' import { FirehoseService } from './firehose/service.js' import { createOAuthClient } from './auth/oauth-client.js' @@ -106,7 +106,11 @@ export async function buildApp(env: Env) { trustProxy: true, }) - // Database + // Database -- run migrations before creating the main connection pool + const migrationsFolder = new URL('../drizzle', import.meta.url).pathname + await runMigrations(env.DATABASE_URL, migrationsFolder) + app.log.info('Database migrations applied') + const { db, client: dbClient } = createDb(env.DATABASE_URL) app.decorate('db', db) app.decorate('env', env) diff --git a/src/db/index.ts b/src/db/index.ts index 243ad00..80d4313 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,4 +1,5 @@ import { drizzle } from 'drizzle-orm/postgres-js' +import { migrate } from 'drizzle-orm/postgres-js/migrator' import postgres from 'postgres' import * as schema from './schema/index.js' @@ -14,4 +15,15 @@ export function createDb(databaseUrl: string) { return { db, client } } +/** + * Run pending Drizzle migrations against the database. + * Idempotent -- already-applied migrations are skipped. + */ +export async function runMigrations(databaseUrl: string, migrationsFolder: string): Promise { + const migrationClient = postgres(databaseUrl, { max: 1 }) + const migrationDb = drizzle(migrationClient) + await migrate(migrationDb, { migrationsFolder }) + await migrationClient.end() +} + export type Database = ReturnType['db'] diff --git a/tests/unit/routes/health.test.ts b/tests/unit/routes/health.test.ts index cc66c56..d19aa65 100644 --- a/tests/unit/routes/health.test.ts +++ b/tests/unit/routes/health.test.ts @@ -6,7 +6,9 @@ import type { FastifyInstance } from 'fastify' const mockExecute = vi.fn().mockResolvedValue([{ '?column?': 1 }]) const mockDb = { execute: mockExecute, - select: vi.fn().mockReturnValue({ from: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }) }), + select: vi + .fn() + .mockReturnValue({ from: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }) }), insert: vi.fn(), update: vi.fn(), delete: vi.fn(), @@ -18,6 +20,7 @@ const mockClient = { } vi.mock('../../../src/db/index.js', () => ({ createDb: () => ({ db: mockDb, client: mockClient }), + runMigrations: vi.fn().mockResolvedValue(undefined), })) // Mock cache to avoid real Valkey connection