diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 05cac4bcffcf4..3c2980ef80a16 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -3007,6 +3007,12 @@ export interface SearchResponseDto { * @interface ServerConfigDto */ export interface ServerConfigDto { + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'externalDomain': string; /** * * @type {boolean} @@ -3590,6 +3596,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'reverseGeocoding': SystemConfigReverseGeocodingDto; + /** + * + * @type {SystemConfigServerDto} + * @memberof SystemConfigDto + */ + 'server': SystemConfigServerDto; /** * * @type {SystemConfigStorageTemplateDto} @@ -4014,6 +4026,19 @@ export interface SystemConfigReverseGeocodingDto { */ 'enabled': boolean; } +/** + * + * @export + * @interface SystemConfigServerDto + */ +export interface SystemConfigServerDto { + /** + * + * @type {string} + * @memberof SystemConfigServerDto + */ + 'externalDomain': string; +} /** * * @export diff --git a/mobile/lib/modules/shared_link/ui/shared_link_item.dart b/mobile/lib/modules/shared_link/ui/shared_link_item.dart index f41dec639c95d..b147ea3c54ed1 100644 --- a/mobile/lib/modules/shared_link/ui/shared_link_item.dart +++ b/mobile/lib/modules/shared_link/ui/shared_link_item.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -71,7 +72,11 @@ class SharedLinkItem extends ConsumerWidget { final imageSize = math.min(context.width / 4, 100.0); void copyShareLinkToClipboard() { - final serverUrl = getServerUrl(); + final externalDomain = ref.read( + serverInfoProvider.select((s) => s.serverConfig.externalDomain), + ); + final serverUrl = + externalDomain.isNotEmpty ? externalDomain : getServerUrl(); if (serverUrl == null) { ImmichToast.show( context: context, diff --git a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart index a2d7bfde2a01b..e96fff56aba61 100644 --- a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart +++ b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/modules/shared_link/services/shared_link.service.dart'; +import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/utils/url_helper.dart'; @@ -353,7 +354,11 @@ class SharedLinkEditPage extends HookConsumerWidget { expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(), ); ref.invalidate(sharedLinksStateProvider); - final serverUrl = getServerUrl(); + final externalDomain = ref.read( + serverInfoProvider.select((s) => s.serverConfig.externalDomain), + ); + final serverUrl = + externalDomain.isNotEmpty ? externalDomain : getServerUrl(); if (newLink != null && serverUrl != null) { newShareLink.value = "$serverUrl/share/${newLink.key}"; copyLinkToClipboard(); diff --git a/mobile/lib/shared/models/server_info/server_config.model.dart b/mobile/lib/shared/models/server_info/server_config.model.dart index cdb99987e730b..7833301cc6ee2 100644 --- a/mobile/lib/shared/models/server_info/server_config.model.dart +++ b/mobile/lib/shared/models/server_info/server_config.model.dart @@ -2,35 +2,40 @@ import 'package:openapi/api.dart'; class ServerConfig { final int trashDays; + final String externalDomain; const ServerConfig({ required this.trashDays, + required this.externalDomain, }); ServerConfig copyWith({ int? trashDays, + String? externalDomain, }) { return ServerConfig( trashDays: trashDays ?? this.trashDays, + externalDomain: externalDomain ?? this.externalDomain, ); } @override - String toString() { - return 'ServerConfig(trashDays: $trashDays)'; - } + String toString() => + 'ServerConfig(trashDays: $trashDays, externalDomain: $externalDomain)'; - ServerConfig.fromDto(ServerConfigDto dto) : trashDays = dto.trashDays; + ServerConfig.fromDto(ServerConfigDto dto) + : trashDays = dto.trashDays, + externalDomain = dto.externalDomain; @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is ServerConfig && other.trashDays == trashDays; + return other is ServerConfig && + other.trashDays == trashDays && + other.externalDomain == externalDomain; } @override - int get hashCode { - return trashDays.hashCode; - } + int get hashCode => trashDays.hashCode ^ externalDomain.hashCode; } diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index b7389824b6d51..0f2d9d1c49dd8 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -29,6 +29,7 @@ class ServerInfoNotifier extends StateNotifier { ), serverConfig: const ServerConfig( trashDays: 30, + externalDomain: '', ), serverDiskInfo: const ServerDiskInfo( diskAvailable: "0", @@ -74,7 +75,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["major"]! > serverVersion.major) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(), + versionMismatchErrorMessage: + "profile_drawer_server_out_of_date_major".tr(), ); return; } @@ -82,7 +84,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["major"]! < serverVersion.major) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(), + versionMismatchErrorMessage: + "profile_drawer_client_out_of_date_major".tr(), ); return; } @@ -90,7 +93,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["minor"]! > serverVersion.minor) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_minor".tr(), + versionMismatchErrorMessage: + "profile_drawer_server_out_of_date_minor".tr(), ); return; } @@ -98,7 +102,8 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["minor"]! < serverVersion.minor) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_minor".tr(), + versionMismatchErrorMessage: + "profile_drawer_client_out_of_date_minor".tr(), ); return; } diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index b8aef8e3dbc67..2eaa4a5bb7e23 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -149,6 +149,7 @@ doc/SystemConfigNewVersionCheckDto.md doc/SystemConfigOAuthDto.md doc/SystemConfigPasswordLoginDto.md doc/SystemConfigReverseGeocodingDto.md +doc/SystemConfigServerDto.md doc/SystemConfigStorageTemplateDto.md doc/SystemConfigTemplateStorageOptionDto.md doc/SystemConfigThemeDto.md @@ -335,6 +336,7 @@ lib/model/system_config_new_version_check_dto.dart lib/model/system_config_o_auth_dto.dart lib/model/system_config_password_login_dto.dart lib/model/system_config_reverse_geocoding_dto.dart +lib/model/system_config_server_dto.dart lib/model/system_config_storage_template_dto.dart lib/model/system_config_template_storage_option_dto.dart lib/model/system_config_theme_dto.dart @@ -508,6 +510,7 @@ test/system_config_new_version_check_dto_test.dart test/system_config_o_auth_dto_test.dart test/system_config_password_login_dto_test.dart test/system_config_reverse_geocoding_dto_test.dart +test/system_config_server_dto_test.dart test/system_config_storage_template_dto_test.dart test/system_config_template_storage_option_dto_test.dart test/system_config_theme_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 9b4e57c9b6dc2..068aa8aa2d01e 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -341,6 +341,7 @@ Class | Method | HTTP request | Description - [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md) - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) - [SystemConfigReverseGeocodingDto](doc//SystemConfigReverseGeocodingDto.md) + - [SystemConfigServerDto](doc//SystemConfigServerDto.md) - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) - [SystemConfigThemeDto](doc//SystemConfigThemeDto.md) diff --git a/mobile/openapi/doc/ServerConfigDto.md b/mobile/openapi/doc/ServerConfigDto.md index fd543257be6e1..b2406daf7417e 100644 --- a/mobile/openapi/doc/ServerConfigDto.md +++ b/mobile/openapi/doc/ServerConfigDto.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**externalDomain** | **String** | | **isInitialized** | **bool** | | **loginPageMessage** | **String** | | **oauthButtonText** | **String** | | diff --git a/mobile/openapi/doc/SystemConfigDto.md b/mobile/openapi/doc/SystemConfigDto.md index 403260659a6c1..51bf203ff78e3 100644 --- a/mobile/openapi/doc/SystemConfigDto.md +++ b/mobile/openapi/doc/SystemConfigDto.md @@ -18,6 +18,7 @@ Name | Type | Description | Notes **oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) | | **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) | | **reverseGeocoding** | [**SystemConfigReverseGeocodingDto**](SystemConfigReverseGeocodingDto.md) | | +**server** | [**SystemConfigServerDto**](SystemConfigServerDto.md) | | **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) | | **theme** | [**SystemConfigThemeDto**](SystemConfigThemeDto.md) | | **thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) | | diff --git a/mobile/openapi/doc/SystemConfigServerDto.md b/mobile/openapi/doc/SystemConfigServerDto.md new file mode 100644 index 0000000000000..f749fb587d2c3 --- /dev/null +++ b/mobile/openapi/doc/SystemConfigServerDto.md @@ -0,0 +1,15 @@ +# openapi.model.SystemConfigServerDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**externalDomain** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 32c60889def8b..2bc825f45799c 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -177,6 +177,7 @@ part 'model/system_config_new_version_check_dto.dart'; part 'model/system_config_o_auth_dto.dart'; part 'model/system_config_password_login_dto.dart'; part 'model/system_config_reverse_geocoding_dto.dart'; +part 'model/system_config_server_dto.dart'; part 'model/system_config_storage_template_dto.dart'; part 'model/system_config_template_storage_option_dto.dart'; part 'model/system_config_theme_dto.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index b9dfe3505bb7d..ec8f84bae9aa4 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -441,6 +441,8 @@ class ApiClient { return SystemConfigPasswordLoginDto.fromJson(value); case 'SystemConfigReverseGeocodingDto': return SystemConfigReverseGeocodingDto.fromJson(value); + case 'SystemConfigServerDto': + return SystemConfigServerDto.fromJson(value); case 'SystemConfigStorageTemplateDto': return SystemConfigStorageTemplateDto.fromJson(value); case 'SystemConfigTemplateStorageOptionDto': diff --git a/mobile/openapi/lib/model/server_config_dto.dart b/mobile/openapi/lib/model/server_config_dto.dart index ffe5b2af3dbe7..abe5983966a68 100644 --- a/mobile/openapi/lib/model/server_config_dto.dart +++ b/mobile/openapi/lib/model/server_config_dto.dart @@ -13,12 +13,15 @@ part of openapi.api; class ServerConfigDto { /// Returns a new [ServerConfigDto] instance. ServerConfigDto({ + required this.externalDomain, required this.isInitialized, required this.loginPageMessage, required this.oauthButtonText, required this.trashDays, }); + String externalDomain; + bool isInitialized; String loginPageMessage; @@ -29,6 +32,7 @@ class ServerConfigDto { @override bool operator ==(Object other) => identical(this, other) || other is ServerConfigDto && + other.externalDomain == externalDomain && other.isInitialized == isInitialized && other.loginPageMessage == loginPageMessage && other.oauthButtonText == oauthButtonText && @@ -37,16 +41,18 @@ class ServerConfigDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (externalDomain.hashCode) + (isInitialized.hashCode) + (loginPageMessage.hashCode) + (oauthButtonText.hashCode) + (trashDays.hashCode); @override - String toString() => 'ServerConfigDto[isInitialized=$isInitialized, loginPageMessage=$loginPageMessage, oauthButtonText=$oauthButtonText, trashDays=$trashDays]'; + String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, loginPageMessage=$loginPageMessage, oauthButtonText=$oauthButtonText, trashDays=$trashDays]'; Map toJson() { final json = {}; + json[r'externalDomain'] = this.externalDomain; json[r'isInitialized'] = this.isInitialized; json[r'loginPageMessage'] = this.loginPageMessage; json[r'oauthButtonText'] = this.oauthButtonText; @@ -62,6 +68,7 @@ class ServerConfigDto { final json = value.cast(); return ServerConfigDto( + externalDomain: mapValueOfType(json, r'externalDomain')!, isInitialized: mapValueOfType(json, r'isInitialized')!, loginPageMessage: mapValueOfType(json, r'loginPageMessage')!, oauthButtonText: mapValueOfType(json, r'oauthButtonText')!, @@ -113,6 +120,7 @@ class ServerConfigDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'externalDomain', 'isInitialized', 'loginPageMessage', 'oauthButtonText', diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index e7214c201a0f8..0d64d6bc3bed6 100644 --- a/mobile/openapi/lib/model/system_config_dto.dart +++ b/mobile/openapi/lib/model/system_config_dto.dart @@ -23,6 +23,7 @@ class SystemConfigDto { required this.oauth, required this.passwordLogin, required this.reverseGeocoding, + required this.server, required this.storageTemplate, required this.theme, required this.thumbnail, @@ -49,6 +50,8 @@ class SystemConfigDto { SystemConfigReverseGeocodingDto reverseGeocoding; + SystemConfigServerDto server; + SystemConfigStorageTemplateDto storageTemplate; SystemConfigThemeDto theme; @@ -69,6 +72,7 @@ class SystemConfigDto { other.oauth == oauth && other.passwordLogin == passwordLogin && other.reverseGeocoding == reverseGeocoding && + other.server == server && other.storageTemplate == storageTemplate && other.theme == theme && other.thumbnail == thumbnail && @@ -87,13 +91,14 @@ class SystemConfigDto { (oauth.hashCode) + (passwordLogin.hashCode) + (reverseGeocoding.hashCode) + + (server.hashCode) + (storageTemplate.hashCode) + (theme.hashCode) + (thumbnail.hashCode) + (trash.hashCode); @override - String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, theme=$theme, thumbnail=$thumbnail, trash=$trash]'; + String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, newVersionCheck=$newVersionCheck, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, theme=$theme, thumbnail=$thumbnail, trash=$trash]'; Map toJson() { final json = {}; @@ -107,6 +112,7 @@ class SystemConfigDto { json[r'oauth'] = this.oauth; json[r'passwordLogin'] = this.passwordLogin; json[r'reverseGeocoding'] = this.reverseGeocoding; + json[r'server'] = this.server; json[r'storageTemplate'] = this.storageTemplate; json[r'theme'] = this.theme; json[r'thumbnail'] = this.thumbnail; @@ -132,6 +138,7 @@ class SystemConfigDto { oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, reverseGeocoding: SystemConfigReverseGeocodingDto.fromJson(json[r'reverseGeocoding'])!, + server: SystemConfigServerDto.fromJson(json[r'server'])!, storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, theme: SystemConfigThemeDto.fromJson(json[r'theme'])!, thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!, @@ -193,6 +200,7 @@ class SystemConfigDto { 'oauth', 'passwordLogin', 'reverseGeocoding', + 'server', 'storageTemplate', 'theme', 'thumbnail', diff --git a/mobile/openapi/lib/model/system_config_server_dto.dart b/mobile/openapi/lib/model/system_config_server_dto.dart new file mode 100644 index 0000000000000..ffd0ac69c3c9c --- /dev/null +++ b/mobile/openapi/lib/model/system_config_server_dto.dart @@ -0,0 +1,98 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigServerDto { + /// Returns a new [SystemConfigServerDto] instance. + SystemConfigServerDto({ + required this.externalDomain, + }); + + String externalDomain; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigServerDto && + other.externalDomain == externalDomain; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (externalDomain.hashCode); + + @override + String toString() => 'SystemConfigServerDto[externalDomain=$externalDomain]'; + + Map toJson() { + final json = {}; + json[r'externalDomain'] = this.externalDomain; + return json; + } + + /// Returns a new [SystemConfigServerDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigServerDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return SystemConfigServerDto( + externalDomain: mapValueOfType(json, r'externalDomain')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigServerDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigServerDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigServerDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigServerDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'externalDomain', + }; +} + diff --git a/mobile/openapi/test/server_config_dto_test.dart b/mobile/openapi/test/server_config_dto_test.dart index d43e47823e174..ffd373bf2f679 100644 --- a/mobile/openapi/test/server_config_dto_test.dart +++ b/mobile/openapi/test/server_config_dto_test.dart @@ -16,6 +16,11 @@ void main() { // final instance = ServerConfigDto(); group('test ServerConfigDto', () { + // String externalDomain + test('to test the property `externalDomain`', () async { + // TODO + }); + // bool isInitialized test('to test the property `isInitialized`', () async { // TODO diff --git a/mobile/openapi/test/system_config_dto_test.dart b/mobile/openapi/test/system_config_dto_test.dart index 5398f77601a16..5f41549870eeb 100644 --- a/mobile/openapi/test/system_config_dto_test.dart +++ b/mobile/openapi/test/system_config_dto_test.dart @@ -66,6 +66,11 @@ void main() { // TODO }); + // SystemConfigServerDto server + test('to test the property `server`', () async { + // TODO + }); + // SystemConfigStorageTemplateDto storageTemplate test('to test the property `storageTemplate`', () async { // TODO diff --git a/mobile/openapi/test/system_config_server_dto_test.dart b/mobile/openapi/test/system_config_server_dto_test.dart new file mode 100644 index 0000000000000..4d5dcae2b6465 --- /dev/null +++ b/mobile/openapi/test/system_config_server_dto_test.dart @@ -0,0 +1,27 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for SystemConfigServerDto +void main() { + // final instance = SystemConfigServerDto(); + + group('test SystemConfigServerDto', () { + // String externalDomain + test('to test the property `externalDomain`', () async { + // TODO + }); + + + }); + +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 9d23c8e555635..4b2d620ea0e40 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -8593,6 +8593,9 @@ }, "ServerConfigDto": { "properties": { + "externalDomain": { + "type": "string" + }, "isInitialized": { "type": "boolean" }, @@ -8610,7 +8613,8 @@ "trashDays", "oauthButtonText", "loginPageMessage", - "isInitialized" + "isInitialized", + "externalDomain" ], "type": "object" }, @@ -9039,6 +9043,9 @@ "reverseGeocoding": { "$ref": "#/components/schemas/SystemConfigReverseGeocodingDto" }, + "server": { + "$ref": "#/components/schemas/SystemConfigServerDto" + }, "storageTemplate": { "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" }, @@ -9066,7 +9073,8 @@ "thumbnail", "trash", "theme", - "library" + "library", + "server" ], "type": "object" }, @@ -9359,6 +9367,17 @@ ], "type": "object" }, + "SystemConfigServerDto": { + "properties": { + "externalDomain": { + "type": "string" + } + }, + "required": [ + "externalDomain" + ], + "type": "object" + }, "SystemConfigStorageTemplateDto": { "properties": { "enabled": { diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts index e8c68d559bfc9..32ac51aa43547 100644 --- a/server/src/domain/server-info/server-info.dto.ts +++ b/server/src/domain/server-info/server-info.dto.ts @@ -86,6 +86,7 @@ export class ServerConfigDto { @ApiProperty({ type: 'integer' }) trashDays!: number; isInitialized!: boolean; + externalDomain!: string; } export class ServerFeaturesDto implements FeatureFlags { diff --git a/server/src/domain/server-info/server-info.service.spec.ts b/server/src/domain/server-info/server-info.service.spec.ts index f40c16e822b55..d093399c7757d 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -184,6 +184,7 @@ describe(ServerInfoService.name, () => { loginPageMessage: '', oauthButtonText: 'Login with OAuth', trashDays: 30, + externalDomain: '', }); expect(configMock.load).toHaveBeenCalled(); }); diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index 5a1bed5674a3e..b13e3c9fad670 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -89,6 +89,7 @@ export class ServerInfoService { trashDays: config.trash.days, oauthButtonText: config.oauth.buttonText, isInitialized, + externalDomain: config.server.externalDomain, }; } diff --git a/server/src/domain/system-config/dto/system-config-server.dto.ts b/server/src/domain/system-config/dto/system-config-server.dto.ts new file mode 100644 index 0000000000000..0b5cb35508e57 --- /dev/null +++ b/server/src/domain/system-config/dto/system-config-server.dto.ts @@ -0,0 +1,6 @@ +import { IsString } from 'class-validator'; + +export class SystemConfigServerDto { + @IsString() + externalDomain!: string; +} diff --git a/server/src/domain/system-config/dto/system-config.dto.ts b/server/src/domain/system-config/dto/system-config.dto.ts index 6fbfeced2b970..122d78ca61b35 100644 --- a/server/src/domain/system-config/dto/system-config.dto.ts +++ b/server/src/domain/system-config/dto/system-config.dto.ts @@ -11,6 +11,7 @@ import { SystemConfigNewVersionCheckDto } from './system-config-new-version-chec import { SystemConfigOAuthDto } from './system-config-oauth.dto'; import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; import { SystemConfigReverseGeocodingDto } from './system-config-reverse-geocoding.dto'; +import { SystemConfigServerDto } from './system-config-server.dto'; import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; import { SystemConfigThemeDto } from './system-config-theme.dto'; import { SystemConfigThumbnailDto } from './system-config-thumbnail.dto'; @@ -86,6 +87,11 @@ export class SystemConfigDto implements SystemConfig { @ValidateNested() @IsObject() library!: SystemConfigLibraryDto; + + @Type(() => SystemConfigServerDto) + @ValidateNested() + @IsObject() + server!: SystemConfigServerDto; } export function mapConfig(config: SystemConfig): SystemConfigDto { diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index 5717e03085597..8aaab1c9ed385 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -127,6 +127,9 @@ export const defaults = Object.freeze({ cronExpression: CronExpression.EVERY_DAY_AT_MIDNIGHT, }, }, + server: { + externalDomain: '', + }, }); export enum FeatureFlag { diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index 98624778652a6..1f71a0beb84f4 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -100,6 +100,9 @@ const updatedConfig = Object.freeze({ passwordLogin: { enabled: true, }, + server: { + externalDomain: '', + }, storageTemplate: { enabled: false, hashVerificationEnabled: true, diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index 5c10c027f02ab..9033ec387ef92 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -84,6 +84,8 @@ export enum SystemConfigKey { PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled', + SERVER_EXTERNAL_DOMAIN = 'server.externalDomain', + STORAGE_TEMPLATE_ENABLED = 'storageTemplate.enabled', STORAGE_TEMPLATE_HASH_VERIFICATION_ENABLED = 'storageTemplate.hashVerificationEnabled', STORAGE_TEMPLATE = 'storageTemplate.template', @@ -244,4 +246,7 @@ export interface SystemConfig { cronExpression: string; }; }; + server: { + externalDomain: string; + }; } diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index f2839aa50a41d..d2d54d0790506 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -97,6 +97,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { oauthButtonText: 'Login with OAuth', trashDays: 30, isInitialized: true, + externalDomain: '', }); }); }); diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 05cac4bcffcf4..3c2980ef80a16 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -3007,6 +3007,12 @@ export interface SearchResponseDto { * @interface ServerConfigDto */ export interface ServerConfigDto { + /** + * + * @type {string} + * @memberof ServerConfigDto + */ + 'externalDomain': string; /** * * @type {boolean} @@ -3590,6 +3596,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'reverseGeocoding': SystemConfigReverseGeocodingDto; + /** + * + * @type {SystemConfigServerDto} + * @memberof SystemConfigDto + */ + 'server': SystemConfigServerDto; /** * * @type {SystemConfigStorageTemplateDto} @@ -4014,6 +4026,19 @@ export interface SystemConfigReverseGeocodingDto { */ 'enabled': boolean; } +/** + * + * @export + * @interface SystemConfigServerDto + */ +export interface SystemConfigServerDto { + /** + * + * @type {string} + * @memberof SystemConfigServerDto + */ + 'externalDomain': string; +} /** * * @export diff --git a/web/src/api/utils.ts b/web/src/api/utils.ts index 8dd0fb4ca75ad..f2f8f50cc50e5 100644 --- a/web/src/api/utils.ts +++ b/web/src/api/utils.ts @@ -19,6 +19,10 @@ export const copyToClipboard = async (secret: string) => { } }; +export const makeSharedLinkUrl = (externalDomain: string, key: string) => { + return `${externalDomain || window.location.origin}/share/${key}`; +}; + export const oauth = { isCallback: (location: Location) => { const search = location.search; diff --git a/web/src/lib/components/admin-page/settings/server/server-settings.svelte b/web/src/lib/components/admin-page/settings/server/server-settings.svelte new file mode 100644 index 0000000000000..16c1eabfc5fbc --- /dev/null +++ b/web/src/lib/components/admin-page/settings/server/server-settings.svelte @@ -0,0 +1,107 @@ + + +
+ {#await getConfigs() then} +
+
+
+ +
+ handleReset(detail)} + on:save={saveSetting} + showResetToDefault={!isEqual(savedConfig, defaultConfig)} + {disabled} + /> +
+
+
+
+ {/await} +
diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte index cec2686c7ec84..483ed9bd28289 100644 --- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte +++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte @@ -5,7 +5,7 @@ import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte'; import Button from '$lib/components/elements/buttons/button.svelte'; import { handleError } from '$lib/utils/handle-error'; - import { api, copyToClipboard, SharedLinkResponseDto, SharedLinkType } from '@api'; + import { api, copyToClipboard, makeSharedLinkUrl, SharedLinkResponseDto, SharedLinkType } from '@api'; import { createEventDispatcher, onMount } from 'svelte'; import Icon from '$lib/components/elements/icon.svelte'; import BaseModal from '../base-modal.svelte'; @@ -13,6 +13,7 @@ import DropdownButton from '../dropdown-button.svelte'; import { notificationController, NotificationType } from '../notification/notification'; import { mdiLink } from '@mdi/js'; + import { serverConfig } from '$lib/stores/server-config.store'; export let albumId: string | undefined = undefined; export let assetIds: string[] = []; @@ -82,7 +83,7 @@ showMetadata, }, }); - sharedLink = `${window.location.origin}/share/${data.key}`; + sharedLink = makeSharedLinkUrl($serverConfig.externalDomain, data.key); } catch (e) { handleError(e, 'Failed to create shared link'); } @@ -182,7 +183,7 @@ {:else}
Individual shared | {editingLink.description}{editingLink.description || ''}
{/if} diff --git a/web/src/lib/stores/server-config.store.ts b/web/src/lib/stores/server-config.store.ts index 8843aaee2d0ac..73798631ab073 100644 --- a/web/src/lib/stores/server-config.store.ts +++ b/web/src/lib/stores/server-config.store.ts @@ -26,6 +26,7 @@ export const serverConfig = writable({ loginPageMessage: '', trashDays: 30, isInitialized: false, + externalDomain: '', }); export const loadConfig = async () => { diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte index 6fd9a7afa4ba9..cb7b0d5fa1257 100644 --- a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte +++ b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte @@ -1,6 +1,6 @@ diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte index 2a6fce5aec4fa..cabe88183eea9 100644 --- a/web/src/routes/admin/system-settings/+page.svelte +++ b/web/src/routes/admin/system-settings/+page.svelte @@ -9,6 +9,7 @@ import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte'; import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte'; import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte'; + import ServerSettings from '$lib/components/admin-page/settings/server/server-settings.svelte'; import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte'; import ThemeSettings from '$lib/components/admin-page/settings/theme/theme-settings.svelte'; import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; @@ -95,6 +96,10 @@ + + + +