fix: rewrite the AddUserAvatarCacheFields migration for Overseerr merge

This commit is contained in:
gauthier-th
2025-08-09 15:12:09 +02:00
parent d7b5be4bad
commit 3b8f0076a1
3 changed files with 83 additions and 26 deletions

View File

@@ -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'],

View File

@@ -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') {

View File

@@ -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<boolean> => {
// 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<string | Function> = 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<void> {
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<void> {
return;
}
}
export default checkOverseerrMerge;