diff --git a/backend/src/app-init.ts b/backend/src/app-init.ts index 3f4263857..a9ac8e6da 100644 --- a/backend/src/app-init.ts +++ b/backend/src/app-init.ts @@ -15,6 +15,7 @@ import { AuthConfig } from './config/auth.config'; import { MediaConfig } from './config/media.config'; import { ErrorExceptionMapping } from './errors/error-mapping'; import { ConsoleLoggerService } from './logger/console-logger.service'; +import { runMigrations } from './migrate'; import { SessionService } from './sessions/session.service'; import { setupSessionMiddleware } from './utils/session'; import { setupValidationPipe } from './utils/setup-pipes'; @@ -44,11 +45,9 @@ export async function setupApp( ); } - logger.log('Starting database migrations... ', 'AppBootstrap'); const knexConnectionToken = getConnectionToken(); const knex: Knex = app.get(knexConnectionToken); - await knex.migrate.latest(); - logger.log('Finished database migrations... ', 'AppBootstrap'); + await runMigrations(knex, logger); // Setup session handling setupSessionMiddleware( diff --git a/backend/src/migrate.ts b/backend/src/migrate.ts new file mode 100644 index 000000000..aa6add403 --- /dev/null +++ b/backend/src/migrate.ts @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { Knex } from 'knex'; + +import { ConsoleLoggerService } from './logger/console-logger.service'; + +interface CompletedMigration { + name: string; +} + +interface PendingMigration { + file: string; + directory: string; +} + +/** + * Runs the database migrations and informs the user about already completed and still pending ones + * + * @param knex The configured Knex instance to use + * @param logger The console logger service to use + */ +export async function runMigrations( + knex: Knex, + logger: ConsoleLoggerService, +): Promise { + logger.log('Checking for pending database migrations... ', 'runMigrations'); + try { + const [completedMigrations, pendingMigrations] = + (await knex.migrate.list()) as [CompletedMigration[], PendingMigration[]]; + logger.log( + `Found ${completedMigrations.length} already completed migrations and ${pendingMigrations.length} pending migrations.`, + 'runMigrations', + ); + for (const migration of completedMigrations) { + logger.log( + `Already applied migration ${migration.name}`, + 'runMigrations', + ); + } + for (const migration of pendingMigrations) { + logger.log(`Applying migration ${migration.file}`, 'runMigrations'); + await knex.migrate.up(); + logger.log('✅', 'runMigrations'); + } + } catch (error: unknown) { + logger.error( + `Error while migrating database: ${String(error)}`, + 'runMigrations', + ); + } + + logger.log('Finished database migrations... ', 'runMigrations'); +}