diff --git a/server/lib/settings.ts b/server/lib/settings/index.ts similarity index 94% rename from server/lib/settings.ts rename to server/lib/settings/index.ts index f746ce884..bb7e1ca5a 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings/index.ts @@ -1,10 +1,12 @@ import { MediaServerType } from '@server/constants/server'; +import { Permission } from '@server/lib/permissions'; +import { SettingsMigrator } from '@server/lib/settings/settingsMigrator'; +import logger from '@server/logger'; import { randomUUID } from 'crypto'; import fs from 'fs'; import { merge } from 'lodash'; import path from 'path'; import webpush from 'web-push'; -import { Permission } from './permissions'; export interface Library { id: string; @@ -276,7 +278,7 @@ export type JobId = | 'image-cache-cleanup' | 'availability-sync'; -interface AllSettings { +export interface AllSettings { clientId: string; vapidPublic: string; vapidPrivate: string; @@ -641,38 +643,20 @@ class Settings { if (data) { const parsedJson = JSON.parse(data); - const oldJellyfinSettings = parsedJson.jellyfin; - if (oldJellyfinSettings && oldJellyfinSettings.hostname) { - const { hostname } = oldJellyfinSettings; - const protocolMatch = hostname.match(/^(https?):\/\//i); - const useSsl = - protocolMatch && protocolMatch[1].toLowerCase() === 'https'; + SettingsMigrator.migrateSettings(parsedJson) + .then((migrated) => { + this.data = Object.assign(this.data, migrated); + this.data = merge(this.data, migrated); - const remainingUrl = hostname.replace(/^(https?):\/\//i, ''); - const urlMatch = remainingUrl.match(/^([^:]+)(:([0-9]+))?(\/.*)?$/); - - delete oldJellyfinSettings.hostname; - - if (urlMatch) { - const [, ip, , port, urlBase] = urlMatch; - this.data.jellyfin = { - ...this.data.jellyfin, - ip, - port: port || (useSsl ? 443 : 80), - useSsl, - urlBase: urlBase ? urlBase.replace(/\/$/, '') : '', - }; - } - } - - if (parsedJson.jellyfin && parsedJson.jellyfin.hostname) { - delete parsedJson.jellyfin.hostname; - } - - this.data = merge(this.data, parsedJson); - - this.save(); + this.save(); + }) + .catch((error) => { + logger.error('Error migrating settings', { + label: 'Settings', + error: error, + }); + }); } return this; } diff --git a/server/lib/settings/migrations/remove-jellyfin-hostname-migration.ts b/server/lib/settings/migrations/remove-jellyfin-hostname-migration.ts new file mode 100644 index 000000000..b3ce56378 --- /dev/null +++ b/server/lib/settings/migrations/remove-jellyfin-hostname-migration.ts @@ -0,0 +1,33 @@ +import type { AllSettings } from '@server/lib/settings'; + +export default function removeHostnameMigration(settings: any): AllSettings { + const oldJellyfinSettings = settings.jellyfin; + + if (oldJellyfinSettings && oldJellyfinSettings.hostname) { + const { hostname } = oldJellyfinSettings; + const protocolMatch = hostname.match(/^(https?):\/\//i); + const useSsl = protocolMatch && protocolMatch[1].toLowerCase() === 'https'; + + const remainingUrl = hostname.replace(/^(https?):\/\//i, ''); + const urlMatch = remainingUrl.match(/^([^:]+)(:([0-9]+))?(\/.*)?$/); + + delete oldJellyfinSettings.hostname; + + if (urlMatch) { + const [, ip, , port, urlBase] = urlMatch; + settings.jellyfin = { + ...settings.jellyfin, + ip, + port: port || (useSsl ? 443 : 80), + useSsl, + urlBase: urlBase ? urlBase.replace(/\/$/, '') : '', + }; + } + } + + if (settings.jellyfin && settings.jellyfin.hostname) { + delete settings.jellyfin.hostname; + } + + return settings; +} diff --git a/server/lib/settings/settingsMigrator.ts b/server/lib/settings/settingsMigrator.ts new file mode 100644 index 000000000..ba25b234e --- /dev/null +++ b/server/lib/settings/settingsMigrator.ts @@ -0,0 +1,33 @@ +import type { AllSettings } from '@server/lib/settings'; +import fs from 'fs'; +import path from 'path'; + +export class SettingsMigrator { + static async migrateSettings(settings: AllSettings): Promise { + const migrations = await this.loadMigrations(); + for (const migration of Object.values(migrations)) { + settings = await migration(settings); + } + return settings; + } + + private static async loadMigrations(): Promise< + ((settings: AllSettings) => AllSettings)[] + > { + const migrationDir = path.join(__dirname, 'migrations'); + const migrationFiles = fs.readdirSync(migrationDir); + const migrationScripts: ((settings: AllSettings) => AllSettings)[] = []; + + for (const file of migrationFiles) { + if (file.endsWith('.js') || file.endsWith('.ts')) { + const scriptPath = path.join(migrationDir, file); + const migrationScript = (await import(scriptPath)).default; + if (typeof migrationScript === 'function') { + migrationScripts.push(migrationScript); + } + } + } + + return migrationScripts; + } +} diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 5c0cf0b27..82b6d5852 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -986,7 +986,7 @@ "components.Settings.plexlibraries": "Plex Libraries", "components.Settings.plexlibrariesDescription": "The libraries Jellyseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.", "components.Settings.plexsettings": "Plex Settings", - "components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Overseerr scans your Plex libraries to determine content availability.", + "components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Jellyseerr scans your Plex libraries to determine content availability.", "components.Settings.port": "Port", "components.Settings.radarrsettings": "Radarr Settings", "components.Settings.restartrequiredTooltip": "Jellyseerr must be restarted for changes to this setting to take effect",