diff --git a/server/index.ts b/server/index.ts index a817eb10..dc6bb147 100644 --- a/server/index.ts +++ b/server/index.ts @@ -16,6 +16,7 @@ import SlackAgent from '@server/lib/notifications/agents/slack'; import TelegramAgent from '@server/lib/notifications/agents/telegram'; import WebhookAgent from '@server/lib/notifications/agents/webhook'; import WebPushAgent from '@server/lib/notifications/agents/webpush'; +import checkOverseerrMerge from '@server/lib/overseerrMerge'; import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; import clearCookies from '@server/middleware/clearcookies'; @@ -61,6 +62,9 @@ app .then(async () => { const dbConnection = await dataSource.initialize(); + // Run Overseerr to Seerr migration + await checkOverseerrMerge(dbConnection); + // Run migrations in production if (process.env.NODE_ENV === 'production') { if (isPgsql) { diff --git a/server/lib/overseerrMerge.ts b/server/lib/overseerrMerge.ts new file mode 100644 index 00000000..6fca25b4 --- /dev/null +++ b/server/lib/overseerrMerge.ts @@ -0,0 +1,71 @@ +import { MediaServerType } from '@server/constants/server'; +import { 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'; + +const checkOverseerrMerge = async (dbConnection: DataSource) => { + // Load settings without running migrations + const settings = await new Settings().load(undefined, true); + + if (settings.main.mediaServerType) { + return; // The application has already been migrated + } + + // Add fake migration record to prevent running migrations again + try { + await dbConnection.query( + `INSERT INTO migrations (timestamp,name) VALUES (1743023610704, 'UpdateWebPush1743023610704')` + ); + } catch (error) { + logger.error( + 'Failed to insert migration record for UpdateWebPush1743023610704', + { + label: 'Seerr Migration', + message: error.message, + } + ); + } + + // 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'; + } + + // MediaStatus.Blacklisted was added before MediaStatus.Deleted in Jellyseerr + try { + const mediaRepository = getRepository(Media); + const mediaToUpdate = await mediaRepository.find({ where: { status: 6 } }); + for (const media of mediaToUpdate) { + media.status = 7; + await mediaRepository.save(media); + } + } catch (error) { + logger.error('Failed to update Media status from Blacklisted to Deleted', { + label: 'Seerr Migration', + message: error.message, + }); + } + + // Save 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', { + label: 'Seerr Migration', + message: error.message, + }); + } +}; + +export default checkOverseerrMerge; diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 2491577d..64de550a 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -764,9 +764,13 @@ class Settings { * This will load settings from file unless an optional argument of the object structure * is passed in. * @param overrideSettings If passed in, will override all existing settings with these + * @param skipMigrations If true, will skip running migrations on the loaded settings * values */ - public async load(overrideSettings?: AllSettings): Promise { + public async load( + overrideSettings?: AllSettings, + raw = false + ): Promise { if (overrideSettings) { this.data = overrideSettings; return this; @@ -779,10 +783,12 @@ class Settings { await this.save(); } - if (data) { + if (data && !raw) { const parsedJson = JSON.parse(data); const migratedData = await runMigrations(parsedJson, SETTINGS_PATH); this.data = merge(this.data, migratedData); + } else if (data) { + this.data = JSON.parse(data); } // generate keys and ids if it's missing diff --git a/server/lib/settings/migrations/0000_overseerr_merge.ts b/server/lib/settings/migrations/0000_overseerr_merge.ts deleted file mode 100644 index 302ae2a9..00000000 --- a/server/lib/settings/migrations/0000_overseerr_merge.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { MediaServerType } from '@server/constants/server'; -import type { AllSettings } from '@server/lib/settings'; - -import { getRepository } from '@server/datasource'; -import Media from '@server/entity/Media'; - -const overseerrMerge = async (settings: any): Promise => { - if (settings.main.mediaServerType) { - return settings; // already migrated - } - const newSettings = { ...settings }; - newSettings.main.mediaServerType = MediaServerType.PLEX; - - // New name - newSettings.main.applicationTitle = 'Seerr'; - newSettings.notifications.agents.email.options.senderName = 'Seerr'; - - // MediaStatus.Blacklisted was added before MediaStatus.Deleted in Jellyseerr - const mediaRepository = getRepository(Media); - const mediaToUpdate = await mediaRepository.find({ where: { status: 6 } }); - - for (const media of mediaToUpdate) { - media.status = 7; - await mediaRepository.save(media); - } - - return newSettings; -}; - -export default overseerrMerge;