From db5ef0f95b98af7494970ceae35b178ff92538dc Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Tue, 28 Jul 2026 19:02:27 +0200 Subject: [PATCH 1/5] feat: separate shared link domain input --- docs/docs/install/config-file.md | 1 + e2e/src/specs/server/api/server.e2e-spec.ts | 1 + e2e/src/ui/mock-network/base-network.ts | 1 + i18n/da.json | 2 ++ i18n/en.json | 2 ++ i18n/en_GB.json | 2 ++ .../shared_link/shared_link_edit.page.dart | 8 +++---- .../lib/providers/server_info.provider.dart | 1 + .../openapi/lib/model/server_config_dto.dart | 11 ++++++++- .../lib/model/system_config_server_dto.dart | 15 +++++++++--- open-api/immich-openapi-specs.json | 12 +++++++++- packages/sdk/src/fetch-client.ts | 4 ++++ server/src/config.ts | 2 ++ server/src/dtos/server.dto.ts | 1 + server/src/dtos/system-config.dto.ts | 6 +++++ server/src/services/server.service.spec.ts | 1 + server/src/services/server.service.ts | 1 + server/src/services/shared-link.service.ts | 4 ++-- .../services/system-config.service.spec.ts | 23 +++++++++++++++++++ server/src/utils/config.ts | 11 +++++++++ server/src/utils/misc.ts | 3 +++ .../lib/services/shared-link.service.spec.ts | 2 +- web/src/lib/services/shared-link.service.ts | 2 +- .../system-settings/ServerSettings.svelte | 8 +++++++ 24 files changed, 111 insertions(+), 13 deletions(-) diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md index c8ebeffbcd..d02b4f5275 100644 --- a/docs/docs/install/config-file.md +++ b/docs/docs/install/config-file.md @@ -213,6 +213,7 @@ The default configuration looks like this: }, "server": { "externalDomain": "", + "sharedLinkDomain": "", "loginPageMessage": "", "publicUsers": true }, diff --git a/e2e/src/specs/server/api/server.e2e-spec.ts b/e2e/src/specs/server/api/server.e2e-spec.ts index 53857a31fa..f6ede25a7e 100644 --- a/e2e/src/specs/server/api/server.e2e-spec.ts +++ b/e2e/src/specs/server/api/server.e2e-spec.ts @@ -138,6 +138,7 @@ describe('/server', () => { userDeleteDelay: 7, isInitialized: true, externalDomain: '', + sharedLinkDomain: '', publicUsers: true, isOnboarded: false, maintenanceMode: false, diff --git a/e2e/src/ui/mock-network/base-network.ts b/e2e/src/ui/mock-network/base-network.ts index af8d1dbfef..6acaaf5750 100644 --- a/e2e/src/ui/mock-network/base-network.ts +++ b/e2e/src/ui/mock-network/base-network.ts @@ -148,6 +148,7 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI isInitialized: true, isOnboarded: true, externalDomain: '', + sharedLinkDomain: '', publicUsers: true, mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json', diff --git a/i18n/da.json b/i18n/da.json index 9daed30b52..1cae0bafa7 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -342,6 +342,8 @@ "send_welcome_email": "Send velkomst-email", "server_external_domain_settings": "Eksternt domæne", "server_external_domain_settings_description": "Domæne brugt til eksterne links", + "server_shared_link_domain_settings": "Delt link domæne", + "server_shared_link_domain_settings_description": "Domæne brugt til delte links", "server_public_users": "Offentlige brugere", "server_public_users_description": "Alle brugere (navn og e-mail) vises, når en bruger tilføjes til delte album. Når den er deaktiveret, vil brugerlisten kun være tilgængelig for administratorbrugere.", "server_settings": "Serverindstillinger", diff --git a/i18n/en.json b/i18n/en.json index 1dbd50e294..307b0af88a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -342,6 +342,8 @@ "send_welcome_email": "Send welcome email", "server_external_domain_settings": "External domain", "server_external_domain_settings_description": "Domain used for external links", + "server_shared_link_domain_settings": "Shared link domain", + "server_shared_link_domain_settings_description": "Domain used for shared links", "server_public_users": "Public Users", "server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.", "server_settings": "Server Settings", diff --git a/i18n/en_GB.json b/i18n/en_GB.json index a077b444d3..332aeabeb4 100644 --- a/i18n/en_GB.json +++ b/i18n/en_GB.json @@ -342,6 +342,8 @@ "send_welcome_email": "Send welcome email", "server_external_domain_settings": "External domain", "server_external_domain_settings_description": "Domain used for external links", + "server_shared_link_domain_settings": "Shared link domain", + "server_shared_link_domain_settings_description": "Domain used for shared links", "server_public_users": "Public Users", "server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.", "server_settings": "Server Settings", diff --git a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart index 23c24a64b0..f330386a7c 100644 --- a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart @@ -31,8 +31,8 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final themeData = context.themeData; final colorScheme = context.colorScheme; - final externalDomain = ref.watch(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - final displayServerUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + final sharedLinkDomain = ref.watch(serverInfoProvider.select((s) => s.serverConfig.sharedLinkDomain)); + final displayServerUrl = sharedLinkDomain.isNotEmpty ? sharedLinkDomain : getServerUrl(); final expiryPresets = <(Duration, String)>[ (Duration.zero, context.t.never), (const Duration(minutes: 30), context.t.shared_link_edit_expire_after_option_minutes(count: 30)), @@ -345,9 +345,9 @@ class SharedLinkEditPage extends HookConsumerWidget { if (!context.mounted) { return; } - final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); + final sharedLinkDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.sharedLinkDomain)); - final serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + final serverUrl = sharedLinkDomain.isNotEmpty ? sharedLinkDomain : getServerUrl(); if (newLink != null) { newShareLink.value = buildSharedLinkUrl(baseUrl: serverUrl, slug: newLink.slug, key: newLink.key) ?? ''; diff --git a/mobile/lib/providers/server_info.provider.dart b/mobile/lib/providers/server_info.provider.dart index 98300894f9..229300aaa9 100644 --- a/mobile/lib/providers/server_info.provider.dart +++ b/mobile/lib/providers/server_info.provider.dart @@ -20,6 +20,7 @@ class ServerInfoNotifier extends StateNotifier { trashDays: 30, oauthButtonText: '', externalDomain: '', + sharedLinkDomain: '', mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json', mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', ), diff --git a/mobile/openapi/lib/model/server_config_dto.dart b/mobile/openapi/lib/model/server_config_dto.dart index 0eaaec7c7f..04815b3e2f 100644 --- a/mobile/openapi/lib/model/server_config_dto.dart +++ b/mobile/openapi/lib/model/server_config_dto.dart @@ -23,6 +23,7 @@ class ServerConfigDto { required this.minFaces, required this.oauthButtonText, required this.publicUsers, + required this.sharedLinkDomain, required this.trashDays, required this.userDeleteDelay, }); @@ -60,6 +61,9 @@ class ServerConfigDto { /// Whether public user registration is enabled bool publicUsers; + /// Shared link domain URL + String sharedLinkDomain; + /// Number of days before trashed assets are permanently deleted /// /// Minimum value: -9007199254740991 @@ -84,6 +88,7 @@ class ServerConfigDto { other.minFaces == minFaces && other.oauthButtonText == oauthButtonText && other.publicUsers == publicUsers && + other.sharedLinkDomain == sharedLinkDomain && other.trashDays == trashDays && other.userDeleteDelay == userDeleteDelay; @@ -100,11 +105,12 @@ class ServerConfigDto { (minFaces.hashCode) + (oauthButtonText.hashCode) + (publicUsers.hashCode) + + (sharedLinkDomain.hashCode) + (trashDays.hashCode) + (userDeleteDelay.hashCode); @override - String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, isOnboarded=$isOnboarded, loginPageMessage=$loginPageMessage, maintenanceMode=$maintenanceMode, mapDarkStyleUrl=$mapDarkStyleUrl, mapLightStyleUrl=$mapLightStyleUrl, minFaces=$minFaces, oauthButtonText=$oauthButtonText, publicUsers=$publicUsers, trashDays=$trashDays, userDeleteDelay=$userDeleteDelay]'; + String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, isOnboarded=$isOnboarded, loginPageMessage=$loginPageMessage, maintenanceMode=$maintenanceMode, mapDarkStyleUrl=$mapDarkStyleUrl, mapLightStyleUrl=$mapLightStyleUrl, minFaces=$minFaces, oauthButtonText=$oauthButtonText, publicUsers=$publicUsers, sharedLinkDomain=$sharedLinkDomain, trashDays=$trashDays, userDeleteDelay=$userDeleteDelay]'; Map toJson() { final json = {}; @@ -118,6 +124,7 @@ class ServerConfigDto { json[r'minFaces'] = this.minFaces; json[r'oauthButtonText'] = this.oauthButtonText; json[r'publicUsers'] = this.publicUsers; + json[r'sharedLinkDomain'] = this.sharedLinkDomain; json[r'trashDays'] = this.trashDays; json[r'userDeleteDelay'] = this.userDeleteDelay; return json; @@ -142,6 +149,7 @@ class ServerConfigDto { minFaces: mapValueOfType(json, r'minFaces')!, oauthButtonText: mapValueOfType(json, r'oauthButtonText')!, publicUsers: mapValueOfType(json, r'publicUsers')!, + sharedLinkDomain: mapValueOfType(json, r'sharedLinkDomain')!, trashDays: mapValueOfType(json, r'trashDays')!, userDeleteDelay: mapValueOfType(json, r'userDeleteDelay')!, ); @@ -201,6 +209,7 @@ class ServerConfigDto { 'minFaces', 'oauthButtonText', 'publicUsers', + 'sharedLinkDomain', 'trashDays', 'userDeleteDelay', }; diff --git a/mobile/openapi/lib/model/system_config_server_dto.dart b/mobile/openapi/lib/model/system_config_server_dto.dart index 200f75f7c6..870736feaa 100644 --- a/mobile/openapi/lib/model/system_config_server_dto.dart +++ b/mobile/openapi/lib/model/system_config_server_dto.dart @@ -16,6 +16,7 @@ class SystemConfigServerDto { required this.externalDomain, required this.loginPageMessage, required this.publicUsers, + required this.sharedLinkDomain, }); /// External domain @@ -27,27 +28,33 @@ class SystemConfigServerDto { /// Public users bool publicUsers; + /// Shared link domain + String sharedLinkDomain; + @override bool operator ==(Object other) => identical(this, other) || other is SystemConfigServerDto && other.externalDomain == externalDomain && other.loginPageMessage == loginPageMessage && - other.publicUsers == publicUsers; + other.publicUsers == publicUsers && + other.sharedLinkDomain == sharedLinkDomain; @override int get hashCode => // ignore: unnecessary_parenthesis (externalDomain.hashCode) + (loginPageMessage.hashCode) + - (publicUsers.hashCode); + (publicUsers.hashCode) + + (sharedLinkDomain.hashCode); @override - String toString() => 'SystemConfigServerDto[externalDomain=$externalDomain, loginPageMessage=$loginPageMessage, publicUsers=$publicUsers]'; + String toString() => 'SystemConfigServerDto[externalDomain=$externalDomain, loginPageMessage=$loginPageMessage, publicUsers=$publicUsers, sharedLinkDomain=$sharedLinkDomain]'; Map toJson() { final json = {}; json[r'externalDomain'] = this.externalDomain; json[r'loginPageMessage'] = this.loginPageMessage; json[r'publicUsers'] = this.publicUsers; + json[r'sharedLinkDomain'] = this.sharedLinkDomain; return json; } @@ -63,6 +70,7 @@ class SystemConfigServerDto { externalDomain: mapValueOfType(json, r'externalDomain')!, loginPageMessage: mapValueOfType(json, r'loginPageMessage')!, publicUsers: mapValueOfType(json, r'publicUsers')!, + sharedLinkDomain: mapValueOfType(json, r'sharedLinkDomain')!, ); } return null; @@ -113,6 +121,7 @@ class SystemConfigServerDto { 'externalDomain', 'loginPageMessage', 'publicUsers', + 'sharedLinkDomain', }; } diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index dd5d4dce45..34b8a65f6d 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -22503,6 +22503,10 @@ "description": "Whether public user registration is enabled", "type": "boolean" }, + "sharedLinkDomain": { + "description": "Shared link domain URL", + "type": "string" + }, "trashDays": { "description": "Number of days before trashed assets are permanently deleted", "maximum": 9007199254740991, @@ -22527,6 +22531,7 @@ "minFaces", "oauthButtonText", "publicUsers", + "sharedLinkDomain", "trashDays", "userDeleteDelay" ], @@ -26412,12 +26417,17 @@ "publicUsers": { "description": "Public users", "type": "boolean" + }, + "sharedLinkDomain": { + "description": "Shared link domain", + "type": "string" } }, "required": [ "externalDomain", "loginPageMessage", - "publicUsers" + "publicUsers", + "sharedLinkDomain" ], "type": "object" }, diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts index a7e0d9511e..912a45f0b7 100644 --- a/packages/sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -2013,6 +2013,8 @@ export type ServerConfigDto = { oauthButtonText: string; /** Whether public user registration is enabled */ publicUsers: boolean; + /** Shared link domain URL */ + sharedLinkDomain: string; /** Number of days before trashed assets are permanently deleted */ trashDays: number; /** Delay in days before deleted users are permanently removed */ @@ -2581,6 +2583,8 @@ export type SystemConfigServerDto = { loginPageMessage: string; /** Public users */ publicUsers: boolean; + /** Shared link domain */ + sharedLinkDomain: string; }; export type SystemConfigStorageTemplateDto = { /** Enabled */ diff --git a/server/src/config.ts b/server/src/config.ts index 55304080a3..08ac74b7a8 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -208,6 +208,7 @@ export type SystemConfig = { }; server: { externalDomain: string; + sharedLinkDomain: string; loginPageMessage: string; publicUsers: boolean; }; @@ -418,6 +419,7 @@ export const defaults = Object.freeze({ }, server: { externalDomain: '', + sharedLinkDomain: '', loginPageMessage: '', publicUsers: true, }, diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index 0215558aab..1c7b6f3811 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -120,6 +120,7 @@ const ServerConfigSchema = z isInitialized: z.boolean().describe('Whether the server has been initialized'), isOnboarded: z.boolean().describe('Whether the admin has completed onboarding'), externalDomain: z.string().describe('External domain URL'), + sharedLinkDomain: z.string().describe('Shared link domain URL'), publicUsers: z.boolean().describe('Whether public user registration is enabled'), mapDarkStyleUrl: z.string().describe('Map dark style URL'), mapLightStyleUrl: z.string().describe('Map light style URL'), diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index a50b7abe87..33e2bf713d 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -300,6 +300,12 @@ const SystemConfigServerSchema = z error: 'External domain must be an empty string or a valid URL', }) .describe('External domain'), + sharedLinkDomain: z + .string() + .refine((url) => url.length === 0 || z.url().safeParse(url).success, { + error: 'Shared link domain must be an empty string or a valid URL', + }) + .describe('Shared link domain'), loginPageMessage: z.string().describe('Login page message'), publicUsers: configBool.describe('Public users'), }) diff --git a/server/src/services/server.service.spec.ts b/server/src/services/server.service.spec.ts index e1575a496a..f2171c449d 100644 --- a/server/src/services/server.service.spec.ts +++ b/server/src/services/server.service.spec.ts @@ -164,6 +164,7 @@ describe(ServerService.name, () => { isInitialized: undefined, isOnboarded: false, externalDomain: '', + sharedLinkDomain: '', publicUsers: true, mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json', diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index ad212292ba..660368c000 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -124,6 +124,7 @@ export class ServerService extends BaseService { isInitialized, isOnboarded: onboarding?.isOnboarded || false, externalDomain: config.server.externalDomain, + sharedLinkDomain: config.server.sharedLinkDomain, publicUsers: config.server.publicUsers, mapDarkStyleUrl: config.map.darkStyle, mapLightStyleUrl: config.map.lightStyle, diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts index 41b1d5403c..4fda24f959 100644 --- a/server/src/services/shared-link.service.ts +++ b/server/src/services/shared-link.service.ts @@ -13,7 +13,7 @@ import { } from 'src/dtos/shared-link.dto'; import { Permission, SharedLinkType } from 'src/enum'; import { BaseService } from 'src/services/base.service'; -import { getExternalDomain, OpenGraphTags } from 'src/utils/misc'; +import { getSharedLinkDomain, OpenGraphTags } from 'src/utils/misc'; @Injectable() export class SharedLinkService extends BaseService { @@ -232,7 +232,7 @@ export class SharedLinkService extends BaseService { return { title: sharedLink.album ? sharedLink.album.albumName : 'Public Share', description: sharedLink.description || `${assetCount} shared photos & videos`, - imageUrl: new URL(imagePath, getExternalDomain(config.server, defaultDomain)).href, + imageUrl: new URL(imagePath, getSharedLinkDomain(config.server, defaultDomain)).href, }; } diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index d5404048c0..1c08a7911d 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -181,6 +181,7 @@ const updatedConfig = Object.freeze({ }, server: { externalDomain: '', + sharedLinkDomain: '', loginPageMessage: '', publicUsers: true, }, @@ -425,6 +426,28 @@ describe(SystemConfigService.name, () => { }); } + const sharedLinkDomainTests = [ + { should: 'with a trailing slash', sharedLinkDomain: 'https://demo.immich.app/' }, + { should: 'without a trailing slash', sharedLinkDomain: 'https://demo.immich.app' }, + { should: 'with a port', sharedLinkDomain: 'https://demo.immich.app:42', result: 'https://demo.immich.app:42' }, + { + should: 'with basic auth', + sharedLinkDomain: 'https://user:password@example.com:123', + result: 'https://user:password@example.com:123', + }, + ]; + + for (const { should, sharedLinkDomain, result } of sharedLinkDomainTests) { + it(`should normalize an external domain ${should}`, async () => { + mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); + const partialConfig = { server: { sharedLinkDomain } }; + mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify(partialConfig)); + + const config = await sut.getSystemConfig(); + expect(config.server.sharedLinkDomain).toEqual(result ?? 'https://demo.immich.app'); + }); + } + it('should warn for unknown options in yaml', async () => { mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.yaml' })); const partialConfig = ` diff --git a/server/src/utils/config.ts b/server/src/utils/config.ts index a6073471d1..9953ced26c 100644 --- a/server/src/utils/config.ts +++ b/server/src/utils/config.ts @@ -126,6 +126,17 @@ const buildConfig = async (repos: RepoDeps) => { config.server.externalDomain = externalDomain; } + if (config.server.sharedLinkDomain.length > 0) { + const domain = new URL(config.server.sharedLinkDomain); + + const sharedLinkDomain = + domain.password && domain.username + ? `${domain.protocol}//${domain.username}:${domain.password}@${domain.host}` + : domain.origin; + + config.server.sharedLinkDomain = sharedLinkDomain; + } + if (!config.ffmpeg.acceptedVideoCodecs.includes(config.ffmpeg.targetVideoCodec)) { config.ffmpeg.acceptedVideoCodecs.push(config.ffmpeg.targetVideoCodec); } diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 0e7a9e6cdc..5a19cd2afd 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -53,6 +53,9 @@ export const getMethodNames = (instance: any) => { export const getExternalDomain = (server: SystemConfig['server'], defaultDomain = 'https://my.immich.app') => server.externalDomain || defaultDomain; +export const getSharedLinkDomain = (server: SystemConfig['server'], defaultDomain = 'https://my.immich.app') => + server.sharedLinkDomain || defaultDomain; + /** * @returns a list of strings representing the keys of the object in dot notation */ diff --git a/web/src/lib/services/shared-link.service.spec.ts b/web/src/lib/services/shared-link.service.spec.ts index e59b527245..8e9ad5328c 100644 --- a/web/src/lib/services/shared-link.service.spec.ts +++ b/web/src/lib/services/shared-link.service.spec.ts @@ -4,7 +4,7 @@ import { sharedLinkFactory } from '@test-data/factories/shared-link-factory'; vi.mock(import('$lib/managers/server-config-manager.svelte'), () => ({ serverConfigManager: { - value: { externalDomain: 'http://localhost:2283' } as ServerConfigDto, + value: { sharedLinkDomain: 'http://localhost:2283' } as ServerConfigDto, init: vi.fn(), loadServerConfig: vi.fn(), }, diff --git a/web/src/lib/services/shared-link.service.ts b/web/src/lib/services/shared-link.service.ts index 885caee7d8..fc77b22ee9 100644 --- a/web/src/lib/services/shared-link.service.ts +++ b/web/src/lib/services/shared-link.service.ts @@ -61,7 +61,7 @@ export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLin export const asUrl = (sharedLink: SharedLinkResponseDto) => { const path = Route.viewSharedLink(sharedLink); - return new URL(path, serverConfigManager.value.externalDomain || location.origin).href; + return new URL(path, serverConfigManager.value.sharedLinkDomain || location.origin).href; }; export const handleCreateSharedLink = async (dto: SharedLinkCreateDto) => { diff --git a/web/src/routes/admin/system-settings/ServerSettings.svelte b/web/src/routes/admin/system-settings/ServerSettings.svelte index 45be7733e3..0ed8a8ff22 100644 --- a/web/src/routes/admin/system-settings/ServerSettings.svelte +++ b/web/src/routes/admin/system-settings/ServerSettings.svelte @@ -25,6 +25,14 @@ isEdited={configToEdit.server.externalDomain !== config.server.externalDomain} /> + + Date: Tue, 28 Jul 2026 21:30:06 +0200 Subject: [PATCH 2/5] docs: document shared links input field --- docs/docs/administration/system-settings.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs/administration/system-settings.md b/docs/docs/administration/system-settings.md index 7dc9c08db3..731b104eec 100644 --- a/docs/docs/administration/system-settings.md +++ b/docs/docs/administration/system-settings.md @@ -168,7 +168,11 @@ Override the default notifications text with notification templates. More inform ### External Domain -Overrides the domain name in shared links and email notifications. The URL should not include a trailing slash. +Overrides the domain name in email notifications. The URL should not include a trailing slash. + +### Shared Link Domain + +Overrides the domain name in shared links. The URL should not include a trailing slash. ### Welcome Message From 9902e7f4fa9b004d9886ffe1e2c3a97443af135f Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Tue, 28 Jul 2026 21:58:09 +0200 Subject: [PATCH 3/5] fix: missing or unnecessary sharedLinkDomain refactor: i18n formatting --- i18n/da.json | 4 ++-- i18n/en.json | 4 ++-- i18n/en_GB.json | 4 ++-- .../lib/models/server_info/server_config.model.dart | 11 ++++++++--- mobile/lib/utils/openapi_patching.dart | 4 ++++ 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/i18n/da.json b/i18n/da.json index 1cae0bafa7..29dd608dfa 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -342,12 +342,12 @@ "send_welcome_email": "Send velkomst-email", "server_external_domain_settings": "Eksternt domæne", "server_external_domain_settings_description": "Domæne brugt til eksterne links", - "server_shared_link_domain_settings": "Delt link domæne", - "server_shared_link_domain_settings_description": "Domæne brugt til delte links", "server_public_users": "Offentlige brugere", "server_public_users_description": "Alle brugere (navn og e-mail) vises, når en bruger tilføjes til delte album. Når den er deaktiveret, vil brugerlisten kun være tilgængelig for administratorbrugere.", "server_settings": "Serverindstillinger", "server_settings_description": "Administrér serverindstillinger", + "server_shared_link_domain_settings": "Delt link domæne", + "server_shared_link_domain_settings_description": "Domæne brugt til delte links", "server_stats_page_description": "Admin server statistikside", "server_welcome_message": "Velkomstbesked", "server_welcome_message_description": "En besked som bliver vist på loginsiden.", diff --git a/i18n/en.json b/i18n/en.json index 307b0af88a..acf7243273 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -342,12 +342,12 @@ "send_welcome_email": "Send welcome email", "server_external_domain_settings": "External domain", "server_external_domain_settings_description": "Domain used for external links", - "server_shared_link_domain_settings": "Shared link domain", - "server_shared_link_domain_settings_description": "Domain used for shared links", "server_public_users": "Public Users", "server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.", "server_settings": "Server Settings", "server_settings_description": "Manage server settings", + "server_shared_link_domain_settings": "Shared link domain", + "server_shared_link_domain_settings_description": "Domain used for shared links", "server_stats_page_description": "Admin server statistics page", "server_welcome_message": "Welcome message", "server_welcome_message_description": "A message that is displayed on the login page.", diff --git a/i18n/en_GB.json b/i18n/en_GB.json index 332aeabeb4..18124d6b94 100644 --- a/i18n/en_GB.json +++ b/i18n/en_GB.json @@ -342,12 +342,12 @@ "send_welcome_email": "Send welcome email", "server_external_domain_settings": "External domain", "server_external_domain_settings_description": "Domain used for external links", - "server_shared_link_domain_settings": "Shared link domain", - "server_shared_link_domain_settings_description": "Domain used for shared links", "server_public_users": "Public Users", "server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.", "server_settings": "Server Settings", "server_settings_description": "Manage server settings", + "server_shared_link_domain_settings": "Shared link domain", + "server_shared_link_domain_settings_description": "Domain used for shared links", "server_stats_page_description": "Admin server statistics page", "server_welcome_message": "Welcome message", "server_welcome_message_description": "A message that is displayed on the login page.", diff --git a/mobile/lib/models/server_info/server_config.model.dart b/mobile/lib/models/server_info/server_config.model.dart index 15bbe41485..27db859eda 100644 --- a/mobile/lib/models/server_info/server_config.model.dart +++ b/mobile/lib/models/server_info/server_config.model.dart @@ -4,6 +4,7 @@ class ServerConfig { final int trashDays; final String oauthButtonText; final String externalDomain; + final String sharedLinkDomain; final String mapDarkStyleUrl; final String mapLightStyleUrl; @@ -11,6 +12,7 @@ class ServerConfig { required this.trashDays, required this.oauthButtonText, required this.externalDomain, + required this.sharedLinkDomain, required this.mapDarkStyleUrl, required this.mapLightStyleUrl, }); @@ -20,6 +22,7 @@ class ServerConfig { trashDays: trashDays ?? this.trashDays, oauthButtonText: oauthButtonText ?? this.oauthButtonText, externalDomain: externalDomain ?? this.externalDomain, + sharedLinkDomain: sharedLinkDomain ?? this.sharedLinkDomain, mapDarkStyleUrl: mapDarkStyleUrl, mapLightStyleUrl: mapLightStyleUrl, ); @@ -27,12 +30,13 @@ class ServerConfig { @override String toString() => - 'ServerConfig(trashDays: $trashDays, oauthButtonText: $oauthButtonText, externalDomain: $externalDomain)'; + 'ServerConfig(trashDays: $trashDays, oauthButtonText: $oauthButtonText, externalDomain: $externalDomain, sharedLinkDomain: $sharedLinkDomain)'; ServerConfig.fromDto(ServerConfigDto dto) : trashDays = dto.trashDays, oauthButtonText = dto.oauthButtonText, externalDomain = dto.externalDomain, + sharedLinkDomain = dto.sharedLinkDomain, mapDarkStyleUrl = dto.mapDarkStyleUrl, mapLightStyleUrl = dto.mapLightStyleUrl; @@ -44,9 +48,10 @@ class ServerConfig { return other.trashDays == trashDays && other.oauthButtonText == oauthButtonText && - other.externalDomain == externalDomain; + other.externalDomain == externalDomain && + other.sharedLinkDomain == sharedLinkDomain; } @override - int get hashCode => trashDays.hashCode ^ oauthButtonText.hashCode ^ externalDomain.hashCode; + int get hashCode => trashDays.hashCode ^ oauthButtonText.hashCode ^ externalDomain.hashCode ^ sharedLinkDomain.hashCode; } diff --git a/mobile/lib/utils/openapi_patching.dart b/mobile/lib/utils/openapi_patching.dart index 711a4a3163..22b80d1df8 100644 --- a/mobile/lib/utils/openapi_patching.dart +++ b/mobile/lib/utils/openapi_patching.dart @@ -32,6 +32,10 @@ final Map> openApiPatches = { 'mapLightStyleUrl': 'https://tiles.immich.cloud/v1/style/light.json', 'mapDarkStyleUrl': 'https://tiles.immich.cloud/v1/style/dark.json', 'minFaces': 3, + 'sharedLinkDomain': '', + }, + 'SystemConfigServerDto': { + 'sharedLinkDomain': '', }, 'UserResponseDto': {'profileChangedAt': _now}, 'AssetResponseDto': {'visibility': 'timeline', 'createdAt': _now, 'isEdited': false}, From 78aeff85732d5af3da9cf290db1dcf652fb437ea Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Sat, 1 Aug 2026 00:15:00 +0200 Subject: [PATCH 4/5] fix: dead code --- mobile/lib/models/server_info/server_config.model.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/lib/models/server_info/server_config.model.dart b/mobile/lib/models/server_info/server_config.model.dart index 27db859eda..3c92f7c968 100644 --- a/mobile/lib/models/server_info/server_config.model.dart +++ b/mobile/lib/models/server_info/server_config.model.dart @@ -17,7 +17,7 @@ class ServerConfig { required this.mapLightStyleUrl, }); - ServerConfig copyWith({int? trashDays, String? oauthButtonText, String? externalDomain}) { + ServerConfig copyWith({int? trashDays, String? oauthButtonText, String? externalDomain, String? sharedLinkDomain}) { return ServerConfig( trashDays: trashDays ?? this.trashDays, oauthButtonText: oauthButtonText ?? this.oauthButtonText, @@ -53,5 +53,6 @@ class ServerConfig { } @override - int get hashCode => trashDays.hashCode ^ oauthButtonText.hashCode ^ externalDomain.hashCode ^ sharedLinkDomain.hashCode; + int get hashCode => + trashDays.hashCode ^ oauthButtonText.hashCode ^ externalDomain.hashCode ^ sharedLinkDomain.hashCode; } From 4247ad64abe865dbad1bc9ae94f58c496051ca83 Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Mon, 3 Aug 2026 17:20:11 +0200 Subject: [PATCH 5/5] refactor: format openapi_patching.dart --- mobile/lib/utils/openapi_patching.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mobile/lib/utils/openapi_patching.dart b/mobile/lib/utils/openapi_patching.dart index d3591f2332..b87c4e19e6 100644 --- a/mobile/lib/utils/openapi_patching.dart +++ b/mobile/lib/utils/openapi_patching.dart @@ -34,9 +34,7 @@ final Map> openApiPatches = { 'minFaces': 3, 'sharedLinkDomain': '', }, - 'SystemConfigServerDto': { - 'sharedLinkDomain': '', - }, + 'SystemConfigServerDto': {'sharedLinkDomain': ''}, 'UserResponseDto': {'profileChangedAt': _now}, 'AssetResponseDto': {'visibility': 'timeline', 'createdAt': _now, 'isEdited': false}, 'UserAdminResponseDto': {'profileChangedAt': _now},