From 3b8f0076a123ab87a51a701519affc8d7ac4f156 Mon Sep 17 00:00:00 2001 From: gauthier-th Date: Sat, 9 Aug 2025 15:12:09 +0200 Subject: [PATCH] fix: rewrite the AddUserAvatarCacheFields migration for Overseerr merge --- server/datasource.ts | 2 +- server/index.ts | 8 +-- server/lib/overseerrMerge.ts | 99 ++++++++++++++++++++++++++++-------- 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/server/datasource.ts b/server/datasource.ts index d474658c..ba9b28ee 100644 --- a/server/datasource.ts +++ b/server/datasource.ts @@ -77,7 +77,7 @@ const postgresDevConfig: DataSourceOptions = { database: process.env.DB_NAME ?? 'seerr', ssl: buildSslConfig(), synchronize: false, - migrationsRun: true, + migrationsRun: false, logging: boolFromEnv('DB_LOG_QUERIES'), entities: ['server/entity/**/*.ts'], migrations: ['server/migration/postgres/**/*.ts'], diff --git a/server/index.ts b/server/index.ts index dc6bb147..091180a7 100644 --- a/server/index.ts +++ b/server/index.ts @@ -60,10 +60,12 @@ if (!appDataPermissions()) { app .prepare() .then(async () => { - const dbConnection = await dataSource.initialize(); - // Run Overseerr to Seerr migration - await checkOverseerrMerge(dbConnection); + await checkOverseerrMerge(); + + const dbConnection = dataSource.isInitialized + ? dataSource + : await dataSource.initialize(); // Run migrations in production if (process.env.NODE_ENV === 'production') { diff --git a/server/lib/overseerrMerge.ts b/server/lib/overseerrMerge.ts index 6fca25b4..b8f76253 100644 --- a/server/lib/overseerrMerge.ts +++ b/server/lib/overseerrMerge.ts @@ -1,19 +1,47 @@ import { MediaServerType } from '@server/constants/server'; -import { getRepository } from '@server/datasource'; +import dataSource, { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; import Settings from '@server/lib/settings'; import logger from '@server/logger'; -import type { DataSource } from 'typeorm'; +import type { MigrationInterface, MixedList, QueryRunner } from 'typeorm'; -const checkOverseerrMerge = async (dbConnection: DataSource) => { +const checkOverseerrMerge = async (): Promise => { // Load settings without running migrations const settings = await new Settings().load(undefined, true); if (settings.main.mediaServerType) { - return; // The application has already been migrated + return false; // The application has already been migrated } - // Add fake migration record to prevent running migrations again + // Open the database connection to get the migrations + const dbConnection = await dataSource.initialize(); + const migrations = dbConnection.migrations; + await dbConnection.destroy(); + // We have to replace a migration not working with Overseerr with a custom one + try { + // Apply a filter to replace the specific migration + // eslint-disable-next-line @typescript-eslint/ban-types + const newMigrations: MixedList = migrations?.map( + (migration) => + migration.name === 'AddUserAvatarCacheFields1743107645301' + ? AddUserAvatarCacheFields1743107645301 + : migration.constructor + ); + dataSource.setOptions({ + ...dataSource.options, + migrations: newMigrations, + }); + } catch (error) { + logger.error('Failed to load migrations for Overseerr merge', { + label: 'Seerr Migration', + error: error.message, + }); + process.exit(1); + } + // Reopen the database connection with the updated migrations + await dataSource.initialize(); + + // Add fake migration record to prevent running the already existing Overseerr migration again try { await dbConnection.query( `INSERT INTO migrations (timestamp,name) VALUES (1743023610704, 'UpdateWebPush1743023610704')` @@ -23,20 +51,17 @@ const checkOverseerrMerge = async (dbConnection: DataSource) => { 'Failed to insert migration record for UpdateWebPush1743023610704', { label: 'Seerr Migration', - message: error.message, + error: error.message, } ); + process.exit(1); } - // Set media server type to Plex (default for Overseerr) - settings.main.mediaServerType = MediaServerType.PLEX; - - // Replace default Overseerr values with Seerr values - if (settings.main.applicationTitle === 'Overseerr') { - settings.main.applicationTitle = 'Seerr'; - } - if (settings.notifications.agents.email.options.senderName === 'Overseerr') { - settings.notifications.agents.email.options.senderName = 'Seerr'; + // Manually run the migration to update the database schema + if (process.env.NODE_ENV === 'production') { + await dbConnection.query('PRAGMA foreign_keys=OFF'); + await dbConnection.runMigrations(); + await dbConnection.query('PRAGMA foreign_keys=ON'); } // MediaStatus.Blacklisted was added before MediaStatus.Deleted in Jellyseerr @@ -50,22 +75,52 @@ const checkOverseerrMerge = async (dbConnection: DataSource) => { } catch (error) { logger.error('Failed to update Media status from Blacklisted to Deleted', { label: 'Seerr Migration', - message: error.message, + error: error.message, }); + process.exit(1); } - // Save updated settings + // Set media server type to Plex (default for Overseerr) + settings.main.mediaServerType = MediaServerType.PLEX; + + // Replace default Overseerr values with Seerr values + if (settings.main.applicationTitle === 'Overseerr') { + settings.main.applicationTitle = 'Seerr'; + } + if (settings.notifications.agents.email.options.senderName === 'Overseerr') { + settings.notifications.agents.email.options.senderName = 'Seerr'; + } + + // Save the updated settings try { await settings.save(); - logger.info('Successfully migrated Overseerr to Seerr', { - label: 'Seerr Migration', - }); } catch (error) { - logger.error('Failed to save updated settings after Overseerr migration', { + logger.error('Failed to save updated settings for Overseerr merge', { label: 'Seerr Migration', - message: error.message, + error: error.message, }); + process.exit(1); } + + logger.info('Yeah! Overseerr to Seerr migration completed successfully!', { + label: 'Seerr Migration', + }); + + return true; }; +class AddUserAvatarCacheFields1743107645301 implements MigrationInterface { + name = 'AddUserAvatarCacheFields1743107645301'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" ADD "avatarETag" varchar`); + await queryRunner.query(`ALTER TABLE "user" ADD "avatarVersion" varchar`); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async down(queryRunner: QueryRunner): Promise { + return; + } +} + export default checkOverseerrMerge;