You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	feat(server, web)!: Move reverse geocoding settings to the UI (#4222)
* feat: reverse geocoding settings * chore: open api * re-init geocoder if precision has been updated * update docs * chore: update verbiage * fix: re-init logic * fix: reset to default --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
		
							
								
								
									
										49
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										49
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -1055,6 +1055,22 @@ export interface CheckExistingAssetsResponseDto { | ||||
|      */ | ||||
|     'existingIds': Array<string>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @enum {string} | ||||
|  */ | ||||
| 
 | ||||
| export const CitiesFile = { | ||||
|     Cities15000: 'cities15000', | ||||
|     Cities5000: 'cities5000', | ||||
|     Cities1000: 'cities1000', | ||||
|     Cities500: 'cities500' | ||||
| } as const; | ||||
| 
 | ||||
| export type CitiesFile = typeof CitiesFile[keyof typeof CitiesFile]; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -2650,6 +2666,12 @@ export interface ServerFeaturesDto { | ||||
|      * @memberof ServerFeaturesDto | ||||
|      */ | ||||
|     'passwordLogin': boolean; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
|      * @memberof ServerFeaturesDto | ||||
|      */ | ||||
|     'reverseGeocoding': boolean; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
| @@ -3093,6 +3115,12 @@ export interface SystemConfigDto { | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'passwordLogin': SystemConfigPasswordLoginDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigReverseGeocodingDto} | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'reverseGeocoding': SystemConfigReverseGeocodingDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigStorageTemplateDto} | ||||
| @@ -3438,6 +3466,27 @@ export interface SystemConfigPasswordLoginDto { | ||||
|      */ | ||||
|     'enabled': boolean; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface SystemConfigReverseGeocodingDto | ||||
|  */ | ||||
| export interface SystemConfigReverseGeocodingDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {CitiesFile} | ||||
|      * @memberof SystemConfigReverseGeocodingDto | ||||
|      */ | ||||
|     'citiesFileOverride': CitiesFile; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
|      * @memberof SystemConfigReverseGeocodingDto | ||||
|      */ | ||||
|     'enabled': boolean; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|   | ||||
| @@ -50,9 +50,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N | ||||
| ## Geocoding | ||||
|  | ||||
| | Variable                           | Description                      |           Default            | Services      | | ||||
| | :--------------------------------- | :---------------------------------- | :--------------------------: | :------------ | | ||||
| | `DISABLE_REVERSE_GEOCODING`        | Disable Reverse Geocoding Precision |           `false`            | microservices | | ||||
| | `REVERSE_GEOCODING_PRECISION`      | Reverse Geocoding Precision         |             `3`              | microservices | | ||||
| | :--------------------------------- | :------------------------------- | :--------------------------: | :------------ | | ||||
| | `REVERSE_GEOCODING_DUMP_DIRECTORY` | Reverse Geocoding Dump Directory | `./.reverse-geocoding-dump/` | microservices | | ||||
|  | ||||
| ## Ports | ||||
|   | ||||
							
								
								
									
										6
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							| @@ -43,6 +43,7 @@ doc/CheckDuplicateAssetDto.md | ||||
| doc/CheckDuplicateAssetResponseDto.md | ||||
| doc/CheckExistingAssetsDto.md | ||||
| doc/CheckExistingAssetsResponseDto.md | ||||
| doc/CitiesFile.md | ||||
| doc/ClassificationConfig.md | ||||
| doc/Colorspace.md | ||||
| doc/CreateAlbumDto.md | ||||
| @@ -126,6 +127,7 @@ doc/SystemConfigMachineLearningDto.md | ||||
| doc/SystemConfigMapDto.md | ||||
| doc/SystemConfigOAuthDto.md | ||||
| doc/SystemConfigPasswordLoginDto.md | ||||
| doc/SystemConfigReverseGeocodingDto.md | ||||
| doc/SystemConfigStorageTemplateDto.md | ||||
| doc/SystemConfigTemplateStorageOptionDto.md | ||||
| doc/SystemConfigThumbnailDto.md | ||||
| @@ -207,6 +209,7 @@ lib/model/check_duplicate_asset_dto.dart | ||||
| lib/model/check_duplicate_asset_response_dto.dart | ||||
| lib/model/check_existing_assets_dto.dart | ||||
| lib/model/check_existing_assets_response_dto.dart | ||||
| lib/model/cities_file.dart | ||||
| lib/model/classification_config.dart | ||||
| lib/model/clip_config.dart | ||||
| lib/model/clip_mode.dart | ||||
| @@ -284,6 +287,7 @@ lib/model/system_config_machine_learning_dto.dart | ||||
| lib/model/system_config_map_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_storage_template_dto.dart | ||||
| lib/model/system_config_template_storage_option_dto.dart | ||||
| lib/model/system_config_thumbnail_dto.dart | ||||
| @@ -343,6 +347,7 @@ test/check_duplicate_asset_dto_test.dart | ||||
| test/check_duplicate_asset_response_dto_test.dart | ||||
| test/check_existing_assets_dto_test.dart | ||||
| test/check_existing_assets_response_dto_test.dart | ||||
| test/cities_file_test.dart | ||||
| test/classification_config_test.dart | ||||
| test/clip_config_test.dart | ||||
| test/clip_mode_test.dart | ||||
| @@ -429,6 +434,7 @@ test/system_config_machine_learning_dto_test.dart | ||||
| test/system_config_map_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_storage_template_dto_test.dart | ||||
| test/system_config_template_storage_option_dto_test.dart | ||||
| test/system_config_thumbnail_dto_test.dart | ||||
|   | ||||
							
								
								
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @@ -227,6 +227,7 @@ Class | Method | HTTP request | Description | ||||
|  - [CheckDuplicateAssetResponseDto](doc//CheckDuplicateAssetResponseDto.md) | ||||
|  - [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md) | ||||
|  - [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md) | ||||
|  - [CitiesFile](doc//CitiesFile.md) | ||||
|  - [ClassificationConfig](doc//ClassificationConfig.md) | ||||
|  - [Colorspace](doc//Colorspace.md) | ||||
|  - [CreateAlbumDto](doc//CreateAlbumDto.md) | ||||
| @@ -301,6 +302,7 @@ Class | Method | HTTP request | Description | ||||
|  - [SystemConfigMapDto](doc//SystemConfigMapDto.md) | ||||
|  - [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md) | ||||
|  - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) | ||||
|  - [SystemConfigReverseGeocodingDto](doc//SystemConfigReverseGeocodingDto.md) | ||||
|  - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) | ||||
|  - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) | ||||
|  - [SystemConfigThumbnailDto](doc//SystemConfigThumbnailDto.md) | ||||
|   | ||||
							
								
								
									
										14
									
								
								mobile/openapi/doc/CitiesFile.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/doc/CitiesFile.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| # openapi.model.CitiesFile | ||||
| 
 | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| 
 | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/ServerFeaturesDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/ServerFeaturesDto.md
									
									
									
										generated
									
									
									
								
							| @@ -15,6 +15,7 @@ Name | Type | Description | Notes | ||||
| **oauth** | **bool** |  |  | ||||
| **oauthAutoLaunch** | **bool** |  |  | ||||
| **passwordLogin** | **bool** |  |  | ||||
| **reverseGeocoding** | **bool** |  |  | ||||
| **search** | **bool** |  |  | ||||
| **sidecar** | **bool** |  |  | ||||
| **tagImage** | **bool** |  |  | ||||
|   | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/SystemConfigDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/SystemConfigDto.md
									
									
									
										generated
									
									
									
								
							| @@ -14,6 +14,7 @@ Name | Type | Description | Notes | ||||
| **map** | [**SystemConfigMapDto**](SystemConfigMapDto.md) |  |  | ||||
| **oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) |  |  | ||||
| **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) |  |  | ||||
| **reverseGeocoding** | [**SystemConfigReverseGeocodingDto**](SystemConfigReverseGeocodingDto.md) |  |  | ||||
| **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) |  |  | ||||
| **thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) |  |  | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/SystemConfigReverseGeocodingDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/SystemConfigReverseGeocodingDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| # openapi.model.SystemConfigReverseGeocodingDto | ||||
| 
 | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **citiesFileOverride** | [**CitiesFile**](CitiesFile.md) |  |  | ||||
| **enabled** | **bool** |  |  | ||||
| 
 | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										2
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							| @@ -80,6 +80,7 @@ part 'model/check_duplicate_asset_dto.dart'; | ||||
| part 'model/check_duplicate_asset_response_dto.dart'; | ||||
| part 'model/check_existing_assets_dto.dart'; | ||||
| part 'model/check_existing_assets_response_dto.dart'; | ||||
| part 'model/cities_file.dart'; | ||||
| part 'model/classification_config.dart'; | ||||
| part 'model/colorspace.dart'; | ||||
| part 'model/create_album_dto.dart'; | ||||
| @@ -154,6 +155,7 @@ part 'model/system_config_machine_learning_dto.dart'; | ||||
| part 'model/system_config_map_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_storage_template_dto.dart'; | ||||
| part 'model/system_config_template_storage_option_dto.dart'; | ||||
| part 'model/system_config_thumbnail_dto.dart'; | ||||
|   | ||||
							
								
								
									
										4
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							| @@ -251,6 +251,8 @@ class ApiClient { | ||||
|           return CheckExistingAssetsDto.fromJson(value); | ||||
|         case 'CheckExistingAssetsResponseDto': | ||||
|           return CheckExistingAssetsResponseDto.fromJson(value); | ||||
|         case 'CitiesFile': | ||||
|           return CitiesFileTypeTransformer().decode(value); | ||||
|         case 'ClassificationConfig': | ||||
|           return ClassificationConfig.fromJson(value); | ||||
|         case 'Colorspace': | ||||
| @@ -399,6 +401,8 @@ class ApiClient { | ||||
|           return SystemConfigOAuthDto.fromJson(value); | ||||
|         case 'SystemConfigPasswordLoginDto': | ||||
|           return SystemConfigPasswordLoginDto.fromJson(value); | ||||
|         case 'SystemConfigReverseGeocodingDto': | ||||
|           return SystemConfigReverseGeocodingDto.fromJson(value); | ||||
|         case 'SystemConfigStorageTemplateDto': | ||||
|           return SystemConfigStorageTemplateDto.fromJson(value); | ||||
|         case 'SystemConfigTemplateStorageOptionDto': | ||||
|   | ||||
							
								
								
									
										3
									
								
								mobile/openapi/lib/api_helper.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/lib/api_helper.dart
									
									
									
										generated
									
									
									
								
							| @@ -70,6 +70,9 @@ String parameterToString(dynamic value) { | ||||
|   if (value is CQMode) { | ||||
|     return CQModeTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   if (value is CitiesFile) { | ||||
|     return CitiesFileTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   if (value is Colorspace) { | ||||
|     return ColorspaceTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   | ||||
							
								
								
									
										91
									
								
								mobile/openapi/lib/model/cities_file.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								mobile/openapi/lib/model/cities_file.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| // | ||||
| // 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 CitiesFile { | ||||
|   /// Instantiate a new enum with the provided [value]. | ||||
|   const CitiesFile._(this.value); | ||||
| 
 | ||||
|   /// The underlying value of this enum member. | ||||
|   final String value; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => value; | ||||
| 
 | ||||
|   String toJson() => value; | ||||
| 
 | ||||
|   static const cities15000 = CitiesFile._(r'cities15000'); | ||||
|   static const cities5000 = CitiesFile._(r'cities5000'); | ||||
|   static const cities1000 = CitiesFile._(r'cities1000'); | ||||
|   static const cities500 = CitiesFile._(r'cities500'); | ||||
| 
 | ||||
|   /// List of all possible values in this [enum][CitiesFile]. | ||||
|   static const values = <CitiesFile>[ | ||||
|     cities15000, | ||||
|     cities5000, | ||||
|     cities1000, | ||||
|     cities500, | ||||
|   ]; | ||||
| 
 | ||||
|   static CitiesFile? fromJson(dynamic value) => CitiesFileTypeTransformer().decode(value); | ||||
| 
 | ||||
|   static List<CitiesFile>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <CitiesFile>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = CitiesFile.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Transformation class that can [encode] an instance of [CitiesFile] to String, | ||||
| /// and [decode] dynamic data back to [CitiesFile]. | ||||
| class CitiesFileTypeTransformer { | ||||
|   factory CitiesFileTypeTransformer() => _instance ??= const CitiesFileTypeTransformer._(); | ||||
| 
 | ||||
|   const CitiesFileTypeTransformer._(); | ||||
| 
 | ||||
|   String encode(CitiesFile data) => data.value; | ||||
| 
 | ||||
|   /// Decodes a [dynamic value][data] to a CitiesFile. | ||||
|   /// | ||||
|   /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, | ||||
|   /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] | ||||
|   /// cannot be decoded successfully, then an [UnimplementedError] is thrown. | ||||
|   /// | ||||
|   /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, | ||||
|   /// and users are still using an old app with the old code. | ||||
|   CitiesFile? decode(dynamic data, {bool allowNull = true}) { | ||||
|     if (data != null) { | ||||
|       switch (data) { | ||||
|         case r'cities15000': return CitiesFile.cities15000; | ||||
|         case r'cities5000': return CitiesFile.cities5000; | ||||
|         case r'cities1000': return CitiesFile.cities1000; | ||||
|         case r'cities500': return CitiesFile.cities500; | ||||
|         default: | ||||
|           if (!allowNull) { | ||||
|             throw ArgumentError('Unknown enum value to decode: $data'); | ||||
|           } | ||||
|       } | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   /// Singleton [CitiesFileTypeTransformer] instance. | ||||
|   static CitiesFileTypeTransformer? _instance; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/server_features_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/server_features_dto.dart
									
									
									
										generated
									
									
									
								
							| @@ -20,6 +20,7 @@ class ServerFeaturesDto { | ||||
|     required this.oauth, | ||||
|     required this.oauthAutoLaunch, | ||||
|     required this.passwordLogin, | ||||
|     required this.reverseGeocoding, | ||||
|     required this.search, | ||||
|     required this.sidecar, | ||||
|     required this.tagImage, | ||||
| @@ -39,6 +40,8 @@ class ServerFeaturesDto { | ||||
| 
 | ||||
|   bool passwordLogin; | ||||
| 
 | ||||
|   bool reverseGeocoding; | ||||
| 
 | ||||
|   bool search; | ||||
| 
 | ||||
|   bool sidecar; | ||||
| @@ -54,6 +57,7 @@ class ServerFeaturesDto { | ||||
|      other.oauth == oauth && | ||||
|      other.oauthAutoLaunch == oauthAutoLaunch && | ||||
|      other.passwordLogin == passwordLogin && | ||||
|      other.reverseGeocoding == reverseGeocoding && | ||||
|      other.search == search && | ||||
|      other.sidecar == sidecar && | ||||
|      other.tagImage == tagImage; | ||||
| @@ -68,12 +72,13 @@ class ServerFeaturesDto { | ||||
|     (oauth.hashCode) + | ||||
|     (oauthAutoLaunch.hashCode) + | ||||
|     (passwordLogin.hashCode) + | ||||
|     (reverseGeocoding.hashCode) + | ||||
|     (search.hashCode) + | ||||
|     (sidecar.hashCode) + | ||||
|     (tagImage.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, search=$search, sidecar=$sidecar, tagImage=$tagImage]'; | ||||
|   String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, tagImage=$tagImage]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
| @@ -84,6 +89,7 @@ class ServerFeaturesDto { | ||||
|       json[r'oauth'] = this.oauth; | ||||
|       json[r'oauthAutoLaunch'] = this.oauthAutoLaunch; | ||||
|       json[r'passwordLogin'] = this.passwordLogin; | ||||
|       json[r'reverseGeocoding'] = this.reverseGeocoding; | ||||
|       json[r'search'] = this.search; | ||||
|       json[r'sidecar'] = this.sidecar; | ||||
|       json[r'tagImage'] = this.tagImage; | ||||
| @@ -105,6 +111,7 @@ class ServerFeaturesDto { | ||||
|         oauth: mapValueOfType<bool>(json, r'oauth')!, | ||||
|         oauthAutoLaunch: mapValueOfType<bool>(json, r'oauthAutoLaunch')!, | ||||
|         passwordLogin: mapValueOfType<bool>(json, r'passwordLogin')!, | ||||
|         reverseGeocoding: mapValueOfType<bool>(json, r'reverseGeocoding')!, | ||||
|         search: mapValueOfType<bool>(json, r'search')!, | ||||
|         sidecar: mapValueOfType<bool>(json, r'sidecar')!, | ||||
|         tagImage: mapValueOfType<bool>(json, r'tagImage')!, | ||||
| @@ -162,6 +169,7 @@ class ServerFeaturesDto { | ||||
|     'oauth', | ||||
|     'oauthAutoLaunch', | ||||
|     'passwordLogin', | ||||
|     'reverseGeocoding', | ||||
|     'search', | ||||
|     'sidecar', | ||||
|     'tagImage', | ||||
|   | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/system_config_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/system_config_dto.dart
									
									
									
										generated
									
									
									
								
							| @@ -19,6 +19,7 @@ class SystemConfigDto { | ||||
|     required this.map, | ||||
|     required this.oauth, | ||||
|     required this.passwordLogin, | ||||
|     required this.reverseGeocoding, | ||||
|     required this.storageTemplate, | ||||
|     required this.thumbnail, | ||||
|   }); | ||||
| @@ -35,6 +36,8 @@ class SystemConfigDto { | ||||
| 
 | ||||
|   SystemConfigPasswordLoginDto passwordLogin; | ||||
| 
 | ||||
|   SystemConfigReverseGeocodingDto reverseGeocoding; | ||||
| 
 | ||||
|   SystemConfigStorageTemplateDto storageTemplate; | ||||
| 
 | ||||
|   SystemConfigThumbnailDto thumbnail; | ||||
| @@ -47,6 +50,7 @@ class SystemConfigDto { | ||||
|      other.map == map && | ||||
|      other.oauth == oauth && | ||||
|      other.passwordLogin == passwordLogin && | ||||
|      other.reverseGeocoding == reverseGeocoding && | ||||
|      other.storageTemplate == storageTemplate && | ||||
|      other.thumbnail == thumbnail; | ||||
| 
 | ||||
| @@ -59,11 +63,12 @@ class SystemConfigDto { | ||||
|     (map.hashCode) + | ||||
|     (oauth.hashCode) + | ||||
|     (passwordLogin.hashCode) + | ||||
|     (reverseGeocoding.hashCode) + | ||||
|     (storageTemplate.hashCode) + | ||||
|     (thumbnail.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, machineLearning=$machineLearning, map=$map, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate, thumbnail=$thumbnail]'; | ||||
|   String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, machineLearning=$machineLearning, map=$map, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, thumbnail=$thumbnail]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
| @@ -73,6 +78,7 @@ class SystemConfigDto { | ||||
|       json[r'map'] = this.map; | ||||
|       json[r'oauth'] = this.oauth; | ||||
|       json[r'passwordLogin'] = this.passwordLogin; | ||||
|       json[r'reverseGeocoding'] = this.reverseGeocoding; | ||||
|       json[r'storageTemplate'] = this.storageTemplate; | ||||
|       json[r'thumbnail'] = this.thumbnail; | ||||
|     return json; | ||||
| @@ -92,6 +98,7 @@ class SystemConfigDto { | ||||
|         map: SystemConfigMapDto.fromJson(json[r'map'])!, | ||||
|         oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, | ||||
|         passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, | ||||
|         reverseGeocoding: SystemConfigReverseGeocodingDto.fromJson(json[r'reverseGeocoding'])!, | ||||
|         storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, | ||||
|         thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!, | ||||
|       ); | ||||
| @@ -147,6 +154,7 @@ class SystemConfigDto { | ||||
|     'map', | ||||
|     'oauth', | ||||
|     'passwordLogin', | ||||
|     'reverseGeocoding', | ||||
|     'storageTemplate', | ||||
|     'thumbnail', | ||||
|   }; | ||||
|   | ||||
							
								
								
									
										106
									
								
								mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| // | ||||
| // 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 SystemConfigReverseGeocodingDto { | ||||
|   /// Returns a new [SystemConfigReverseGeocodingDto] instance. | ||||
|   SystemConfigReverseGeocodingDto({ | ||||
|     required this.citiesFileOverride, | ||||
|     required this.enabled, | ||||
|   }); | ||||
| 
 | ||||
|   CitiesFile citiesFileOverride; | ||||
| 
 | ||||
|   bool enabled; | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is SystemConfigReverseGeocodingDto && | ||||
|      other.citiesFileOverride == citiesFileOverride && | ||||
|      other.enabled == enabled; | ||||
| 
 | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (citiesFileOverride.hashCode) + | ||||
|     (enabled.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'SystemConfigReverseGeocodingDto[citiesFileOverride=$citiesFileOverride, enabled=$enabled]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|       json[r'citiesFileOverride'] = this.citiesFileOverride; | ||||
|       json[r'enabled'] = this.enabled; | ||||
|     return json; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns a new [SystemConfigReverseGeocodingDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static SystemConfigReverseGeocodingDto? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       return SystemConfigReverseGeocodingDto( | ||||
|         citiesFileOverride: CitiesFile.fromJson(json[r'citiesFileOverride'])!, | ||||
|         enabled: mapValueOfType<bool>(json, r'enabled')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   static List<SystemConfigReverseGeocodingDto> listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <SystemConfigReverseGeocodingDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = SystemConfigReverseGeocodingDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| 
 | ||||
|   static Map<String, SystemConfigReverseGeocodingDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, SystemConfigReverseGeocodingDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigReverseGeocodingDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   // maps a json object with a list of SystemConfigReverseGeocodingDto-objects as value to a dart map | ||||
|   static Map<String, List<SystemConfigReverseGeocodingDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<SystemConfigReverseGeocodingDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       // ignore: parameter_assignments | ||||
|       json = json.cast<String, dynamic>(); | ||||
|       for (final entry in json.entries) { | ||||
|         map[entry.key] = SystemConfigReverseGeocodingDto.listFromJson(entry.value, growable: growable,); | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'citiesFileOverride', | ||||
|     'enabled', | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										21
									
								
								mobile/openapi/test/cities_file_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								mobile/openapi/test/cities_file_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| // | ||||
| // 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 CitiesFile | ||||
| void main() { | ||||
| 
 | ||||
|   group('test CitiesFile', () { | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
| @@ -51,6 +51,11 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // bool reverseGeocoding | ||||
|     test('to test the property `reverseGeocoding`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // bool search | ||||
|     test('to test the property `search`', () async { | ||||
|       // TODO | ||||
|   | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/system_config_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/system_config_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @@ -46,6 +46,11 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // SystemConfigReverseGeocodingDto reverseGeocoding | ||||
|     test('to test the property `reverseGeocoding`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // SystemConfigStorageTemplateDto storageTemplate | ||||
|     test('to test the property `storageTemplate`', () async { | ||||
|       // TODO | ||||
|   | ||||
							
								
								
									
										32
									
								
								mobile/openapi/test/system_config_reverse_geocoding_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								mobile/openapi/test/system_config_reverse_geocoding_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| // | ||||
| // 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 SystemConfigReverseGeocodingDto | ||||
| void main() { | ||||
|   // final instance = SystemConfigReverseGeocodingDto(); | ||||
| 
 | ||||
|   group('test SystemConfigReverseGeocodingDto', () { | ||||
|     // CitiesFile citiesFileOverride | ||||
|     test('to test the property `citiesFileOverride`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // bool enabled | ||||
|     test('to test the property `enabled`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
| @@ -5914,6 +5914,15 @@ | ||||
|         ], | ||||
|         "type": "object" | ||||
|       }, | ||||
|       "CitiesFile": { | ||||
|         "enum": [ | ||||
|           "cities15000", | ||||
|           "cities5000", | ||||
|           "cities1000", | ||||
|           "cities500" | ||||
|         ], | ||||
|         "type": "string" | ||||
|       }, | ||||
|       "ClassificationConfig": { | ||||
|         "properties": { | ||||
|           "enabled": { | ||||
| @@ -7229,6 +7238,9 @@ | ||||
|           "passwordLogin": { | ||||
|             "type": "boolean" | ||||
|           }, | ||||
|           "reverseGeocoding": { | ||||
|             "type": "boolean" | ||||
|           }, | ||||
|           "search": { | ||||
|             "type": "boolean" | ||||
|           }, | ||||
| @@ -7244,6 +7256,7 @@ | ||||
|           "configFile", | ||||
|           "facialRecognition", | ||||
|           "map", | ||||
|           "reverseGeocoding", | ||||
|           "oauth", | ||||
|           "oauthAutoLaunch", | ||||
|           "passwordLogin", | ||||
| @@ -7590,6 +7603,9 @@ | ||||
|           "passwordLogin": { | ||||
|             "$ref": "#/components/schemas/SystemConfigPasswordLoginDto" | ||||
|           }, | ||||
|           "reverseGeocoding": { | ||||
|             "$ref": "#/components/schemas/SystemConfigReverseGeocodingDto" | ||||
|           }, | ||||
|           "storageTemplate": { | ||||
|             "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" | ||||
|           }, | ||||
| @@ -7603,6 +7619,7 @@ | ||||
|           "map", | ||||
|           "oauth", | ||||
|           "passwordLogin", | ||||
|           "reverseGeocoding", | ||||
|           "storageTemplate", | ||||
|           "job", | ||||
|           "thumbnail" | ||||
| @@ -7843,6 +7860,21 @@ | ||||
|         ], | ||||
|         "type": "object" | ||||
|       }, | ||||
|       "SystemConfigReverseGeocodingDto": { | ||||
|         "properties": { | ||||
|           "citiesFileOverride": { | ||||
|             "$ref": "#/components/schemas/CitiesFile" | ||||
|           }, | ||||
|           "enabled": { | ||||
|             "type": "boolean" | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|           "citiesFileOverride", | ||||
|           "enabled" | ||||
|         ], | ||||
|         "type": "object" | ||||
|       }, | ||||
|       "SystemConfigStorageTemplateDto": { | ||||
|         "properties": { | ||||
|           "template": { | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import { InitOptions } from 'local-reverse-geocoder'; | ||||
|  | ||||
| export const IGeocodingRepository = 'IGeocodingRepository'; | ||||
|  | ||||
| export interface GeoPoint { | ||||
| @@ -12,7 +14,7 @@ export interface ReverseGeocodeResult { | ||||
| } | ||||
|  | ||||
| export interface IGeocodingRepository { | ||||
|   init(): Promise<void>; | ||||
|   init(options: Partial<InitOptions>): Promise<void>; | ||||
|   reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult>; | ||||
|   deleteCache(): Promise<void>; | ||||
| } | ||||
|   | ||||
| @@ -90,6 +90,7 @@ export class ServerFeaturesDto implements FeatureFlags { | ||||
|   configFile!: boolean; | ||||
|   facialRecognition!: boolean; | ||||
|   map!: boolean; | ||||
|   reverseGeocoding!: boolean; | ||||
|   oauth!: boolean; | ||||
|   oauthAutoLaunch!: boolean; | ||||
|   passwordLogin!: boolean; | ||||
|   | ||||
| @@ -151,6 +151,7 @@ describe(ServerInfoService.name, () => { | ||||
|         clipEncode: true, | ||||
|         facialRecognition: true, | ||||
|         map: true, | ||||
|         reverseGeocoding: true, | ||||
|         oauth: false, | ||||
|         oauthAutoLaunch: false, | ||||
|         passwordLogin: true, | ||||
|   | ||||
| @@ -0,0 +1,12 @@ | ||||
| import { CitiesFile } from '@app/infra/entities'; | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
| import { IsBoolean, IsEnum } from 'class-validator'; | ||||
|  | ||||
| export class SystemConfigReverseGeocodingDto { | ||||
|   @IsBoolean() | ||||
|   enabled!: boolean; | ||||
|  | ||||
|   @IsEnum(CitiesFile) | ||||
|   @ApiProperty({ enum: CitiesFile, enumName: 'CitiesFile' }) | ||||
|   citiesFileOverride!: CitiesFile; | ||||
| } | ||||
| @@ -8,6 +8,7 @@ import { SystemConfigMachineLearningDto } from './system-config-machine-learning | ||||
| import { SystemConfigMapDto } from './system-config-map.dto'; | ||||
| import { SystemConfigOAuthDto } from './system-config-oauth.dto'; | ||||
| import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; | ||||
| import { SystemConfigReverseGeocodingDto } from './system-config-reverse-geocoding.dto'; | ||||
| import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; | ||||
|  | ||||
| export class SystemConfigDto implements SystemConfig { | ||||
| @@ -36,6 +37,11 @@ export class SystemConfigDto implements SystemConfig { | ||||
|   @IsObject() | ||||
|   passwordLogin!: SystemConfigPasswordLoginDto; | ||||
|  | ||||
|   @Type(() => SystemConfigReverseGeocodingDto) | ||||
|   @ValidateNested() | ||||
|   @IsObject() | ||||
|   reverseGeocoding!: SystemConfigReverseGeocodingDto; | ||||
|  | ||||
|   @Type(() => SystemConfigStorageTemplateDto) | ||||
|   @ValidateNested() | ||||
|   @IsObject() | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { | ||||
|   AudioCodec, | ||||
|   CQMode, | ||||
|   CitiesFile, | ||||
|   Colorspace, | ||||
|   SystemConfig, | ||||
|   SystemConfigEntity, | ||||
| @@ -81,6 +82,10 @@ export const defaults = Object.freeze<SystemConfig>({ | ||||
|     enabled: true, | ||||
|     tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', | ||||
|   }, | ||||
|   reverseGeocoding: { | ||||
|     enabled: true, | ||||
|     citiesFileOverride: CitiesFile.CITIES_500, | ||||
|   }, | ||||
|   oauth: { | ||||
|     enabled: false, | ||||
|     issuerUrl: '', | ||||
| @@ -115,6 +120,7 @@ export enum FeatureFlag { | ||||
|   FACIAL_RECOGNITION = 'facialRecognition', | ||||
|   TAG_IMAGE = 'tagImage', | ||||
|   MAP = 'map', | ||||
|   REVERSE_GEOCODING = 'reverseGeocoding', | ||||
|   SIDECAR = 'sidecar', | ||||
|   SEARCH = 'search', | ||||
|   OAUTH = 'oauth', | ||||
| @@ -177,6 +183,7 @@ export class SystemConfigCore { | ||||
|       [FeatureFlag.FACIAL_RECOGNITION]: mlEnabled && config.machineLearning.facialRecognition.enabled, | ||||
|       [FeatureFlag.TAG_IMAGE]: mlEnabled && config.machineLearning.classification.enabled, | ||||
|       [FeatureFlag.MAP]: config.map.enabled, | ||||
|       [FeatureFlag.REVERSE_GEOCODING]: config.reverseGeocoding.enabled, | ||||
|       [FeatureFlag.SIDECAR]: true, | ||||
|       [FeatureFlag.SEARCH]: process.env.TYPESENSE_ENABLED !== 'false', | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { | ||||
|   AudioCodec, | ||||
|   CQMode, | ||||
|   CitiesFile, | ||||
|   Colorspace, | ||||
|   SystemConfig, | ||||
|   SystemConfigEntity, | ||||
| @@ -80,6 +81,10 @@ const updatedConfig = Object.freeze<SystemConfig>({ | ||||
|     enabled: true, | ||||
|     tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', | ||||
|   }, | ||||
|   reverseGeocoding: { | ||||
|     enabled: true, | ||||
|     citiesFileOverride: CitiesFile.CITIES_500, | ||||
|   }, | ||||
|   oauth: { | ||||
|     autoLaunch: true, | ||||
|     autoRegister: true, | ||||
|   | ||||
| @@ -64,6 +64,9 @@ export enum SystemConfigKey { | ||||
|   MAP_ENABLED = 'map.enabled', | ||||
|   MAP_TILE_URL = 'map.tileUrl', | ||||
|  | ||||
|   REVERSE_GEOCODING_ENABLED = 'reverseGeocoding.enabled', | ||||
|   REVERSE_GEOCODING_CITIES_FILE_OVERRIDE = 'reverseGeocoding.citiesFileOverride', | ||||
|  | ||||
|   OAUTH_ENABLED = 'oauth.enabled', | ||||
|   OAUTH_ISSUER_URL = 'oauth.issuerUrl', | ||||
|   OAUTH_CLIENT_ID = 'oauth.clientId', | ||||
| @@ -130,6 +133,13 @@ export enum Colorspace { | ||||
|   P3 = 'p3', | ||||
| } | ||||
|  | ||||
| export enum CitiesFile { | ||||
|   CITIES_15000 = 'cities15000', | ||||
|   CITIES_5000 = 'cities5000', | ||||
|   CITIES_1000 = 'cities1000', | ||||
|   CITIES_500 = 'cities500', | ||||
| } | ||||
|  | ||||
| export interface SystemConfig { | ||||
|   ffmpeg: { | ||||
|     crf: number; | ||||
| @@ -175,6 +185,10 @@ export interface SystemConfig { | ||||
|     enabled: boolean; | ||||
|     tileUrl: string; | ||||
|   }; | ||||
|   reverseGeocoding: { | ||||
|     enabled: boolean; | ||||
|     citiesFileOverride: CitiesFile; | ||||
|   }; | ||||
|   oauth: { | ||||
|     enabled: boolean; | ||||
|     issuerUrl: string; | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import { QueueName } from '@app/domain'; | ||||
| import { RegisterQueueOptions } from '@nestjs/bullmq'; | ||||
| import { QueueOptions } from 'bullmq'; | ||||
| import { RedisOptions } from 'ioredis'; | ||||
| import { InitOptions } from 'local-reverse-geocoder'; | ||||
| import { ConfigurationOptions } from 'typesense/lib/Typesense/Configuration'; | ||||
|  | ||||
| function parseRedisConfig(): RedisOptions { | ||||
| @@ -72,20 +71,5 @@ function parseTypeSenseConfig(): ConfigurationOptions { | ||||
|  | ||||
| export const typesenseConfig: ConfigurationOptions = parseTypeSenseConfig(); | ||||
|  | ||||
| function parseLocalGeocodingConfig(): InitOptions { | ||||
|   const precision = Number(process.env.REVERSE_GEOCODING_PRECISION); | ||||
|  | ||||
|   return { | ||||
|     citiesFileOverride: precision ? ['cities15000', 'cities5000', 'cities1000', 'cities500'][precision] : undefined, | ||||
|     load: { | ||||
|       admin1: true, | ||||
|       admin2: true, | ||||
|       admin3And4: false, | ||||
|       alternateNames: false, | ||||
|     }, | ||||
|     countries: [], | ||||
|     dumpDirectory: process.env.REVERSE_GEOCODING_DUMP_DIRECTORY || process.cwd() + '/.reverse-geocoding-dump/', | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export const localGeocodingConfig: InitOptions = parseLocalGeocodingConfig(); | ||||
| export const REVERSE_GEOCODING_DUMP_DIRECTORY = | ||||
|   process.env.REVERSE_GEOCODING_DUMP_DIRECTORY || process.cwd() + '/.reverse-geocoding-dump/'; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { GeoPoint, IGeocodingRepository, ReverseGeocodeResult } from '@app/domain'; | ||||
| import { localGeocodingConfig } from '@app/infra'; | ||||
| import { REVERSE_GEOCODING_DUMP_DIRECTORY } from '@app/infra'; | ||||
| import { Injectable, Logger } from '@nestjs/common'; | ||||
| import { readdir, rm } from 'fs/promises'; | ||||
| import { getName } from 'i18n-iso-countries'; | ||||
| import geocoder, { AddressObject } from 'local-reverse-geocoder'; | ||||
| import geocoder, { AddressObject, InitOptions } from 'local-reverse-geocoder'; | ||||
| import path from 'path'; | ||||
| import { promisify } from 'util'; | ||||
|  | ||||
| @@ -18,19 +18,33 @@ export type GeoData = AddressObject & { | ||||
|   admin2Code?: AdminCode | string; | ||||
| }; | ||||
|  | ||||
| const init = (): Promise<void> => new Promise<void>((resolve) => geocoder.init(localGeocodingConfig, resolve)); | ||||
| const lookup = promisify<GeoPoint[], number, AddressObject[][]>(geocoder.lookUp).bind(geocoder); | ||||
|  | ||||
| @Injectable() | ||||
| export class GeocodingRepository implements IGeocodingRepository { | ||||
|   private logger = new Logger(GeocodingRepository.name); | ||||
|  | ||||
|   async init(): Promise<void> { | ||||
|     await init(); | ||||
|   async init(options: Partial<InitOptions>): Promise<void> { | ||||
|     return new Promise<void>((resolve) => { | ||||
|       geocoder.init( | ||||
|         { | ||||
|           load: { | ||||
|             admin1: true, | ||||
|             admin2: true, | ||||
|             admin3And4: false, | ||||
|             alternateNames: false, | ||||
|           }, | ||||
|           countries: [], | ||||
|           dumpDirectory: REVERSE_GEOCODING_DUMP_DIRECTORY, | ||||
|           ...options, | ||||
|         }, | ||||
|         resolve, | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   async deleteCache() { | ||||
|     const dumpDirectory = localGeocodingConfig.dumpDirectory; | ||||
|     const dumpDirectory = REVERSE_GEOCODING_DUMP_DIRECTORY; | ||||
|     if (dumpDirectory) { | ||||
|       // delete contents | ||||
|       const items = await readdir(dumpDirectory, { withFileTypes: true }); | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { | ||||
|   FeatureFlag, | ||||
|   IAlbumRepository, | ||||
|   IAssetRepository, | ||||
|   IBaseJob, | ||||
| @@ -7,17 +8,18 @@ import { | ||||
|   IGeocodingRepository, | ||||
|   IJobRepository, | ||||
|   IStorageRepository, | ||||
|   ISystemConfigRepository, | ||||
|   JobName, | ||||
|   JOBS_ASSET_PAGINATION_SIZE, | ||||
|   QueueName, | ||||
|   StorageCore, | ||||
|   StorageFolder, | ||||
|   SystemConfigCore, | ||||
|   usePagination, | ||||
|   WithoutProperty, | ||||
| } from '@app/domain'; | ||||
| import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; | ||||
| import { Inject, Logger } from '@nestjs/common'; | ||||
| import { ConfigService } from '@nestjs/config'; | ||||
| import { DefaultReadTaskOptions, ExifDateTime, exiftool, ReadTaskOptions, Tags } from 'exiftool-vendored'; | ||||
| import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime'; | ||||
| import * as geotz from 'geo-tz'; | ||||
| @@ -51,8 +53,9 @@ const validate = <T>(value: T): T | null => (typeof value === 'string' ? null : | ||||
|  | ||||
| export class MetadataExtractionProcessor { | ||||
|   private logger = new Logger(MetadataExtractionProcessor.name); | ||||
|   private reverseGeocodingEnabled: boolean; | ||||
|   private storageCore: StorageCore; | ||||
|   private configCore: SystemConfigCore; | ||||
|   private oldCities?: string; | ||||
|  | ||||
|   constructor( | ||||
|     @Inject(IAssetRepository) private assetRepository: IAssetRepository, | ||||
| @@ -61,31 +64,35 @@ export class MetadataExtractionProcessor { | ||||
|     @Inject(IGeocodingRepository) private geocodingRepository: IGeocodingRepository, | ||||
|     @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, | ||||
|     @Inject(IStorageRepository) private storageRepository: IStorageRepository, | ||||
|  | ||||
|     configService: ConfigService, | ||||
|     @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, | ||||
|   ) { | ||||
|     this.reverseGeocodingEnabled = !configService.get('DISABLE_REVERSE_GEOCODING'); | ||||
|     this.storageCore = new StorageCore(storageRepository); | ||||
|     this.configCore = new SystemConfigCore(configRepository); | ||||
|     this.configCore.config$.subscribe(() => this.init()); | ||||
|   } | ||||
|  | ||||
|   async init(deleteCache = false) { | ||||
|     this.logger.log(`Reverse geocoding is ${this.reverseGeocodingEnabled ? 'enabled' : 'disabled'}`); | ||||
|     if (!this.reverseGeocodingEnabled) { | ||||
|     const { reverseGeocoding } = await this.configCore.getConfig(); | ||||
|     const { citiesFileOverride } = reverseGeocoding; | ||||
|  | ||||
|     if (!reverseGeocoding.enabled) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       if (deleteCache) { | ||||
|         await this.geocodingRepository.deleteCache(); | ||||
|       } else if (this.oldCities && this.oldCities === citiesFileOverride) { | ||||
|         return; | ||||
|       } | ||||
|       this.logger.log('Initializing Reverse Geocoding'); | ||||
|  | ||||
|       await this.jobRepository.pause(QueueName.METADATA_EXTRACTION); | ||||
|       await this.geocodingRepository.init(); | ||||
|       await this.geocodingRepository.init({ citiesFileOverride }); | ||||
|       await this.jobRepository.resume(QueueName.METADATA_EXTRACTION); | ||||
|  | ||||
|       this.logger.log('Reverse Geocoding Initialized'); | ||||
|     } catch (error: any) { | ||||
|       this.logger.log(`Initialized local reverse geocoder with ${citiesFileOverride}`); | ||||
|       this.oldCities = citiesFileOverride; | ||||
|     } catch (error: Error | any) { | ||||
|       this.logger.error(`Unable to initialize reverse geocoding: ${error}`, error?.stack); | ||||
|     } | ||||
|   } | ||||
| @@ -161,7 +168,7 @@ export class MetadataExtractionProcessor { | ||||
|  | ||||
|   private async applyReverseGeocoding(asset: AssetEntity, exifData: ExifEntity) { | ||||
|     const { latitude, longitude } = exifData; | ||||
|     if (!this.reverseGeocodingEnabled || !longitude || !latitude) { | ||||
|     if (!(await this.configCore.hasFeature(FeatureFlag.REVERSE_GEOCODING)) || !longitude || !latitude) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -85,6 +85,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { | ||||
|         configFile: false, | ||||
|         facialRecognition: true, | ||||
|         map: true, | ||||
|         reverseGeocoding: true, | ||||
|         oauth: false, | ||||
|         oauthAutoLaunch: false, | ||||
|         passwordLogin: true, | ||||
|   | ||||
							
								
								
									
										49
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										49
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -1055,6 +1055,22 @@ export interface CheckExistingAssetsResponseDto { | ||||
|      */ | ||||
|     'existingIds': Array<string>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @enum {string} | ||||
|  */ | ||||
| 
 | ||||
| export const CitiesFile = { | ||||
|     Cities15000: 'cities15000', | ||||
|     Cities5000: 'cities5000', | ||||
|     Cities1000: 'cities1000', | ||||
|     Cities500: 'cities500' | ||||
| } as const; | ||||
| 
 | ||||
| export type CitiesFile = typeof CitiesFile[keyof typeof CitiesFile]; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -2650,6 +2666,12 @@ export interface ServerFeaturesDto { | ||||
|      * @memberof ServerFeaturesDto | ||||
|      */ | ||||
|     'passwordLogin': boolean; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
|      * @memberof ServerFeaturesDto | ||||
|      */ | ||||
|     'reverseGeocoding': boolean; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
| @@ -3093,6 +3115,12 @@ export interface SystemConfigDto { | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'passwordLogin': SystemConfigPasswordLoginDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigReverseGeocodingDto} | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'reverseGeocoding': SystemConfigReverseGeocodingDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigStorageTemplateDto} | ||||
| @@ -3438,6 +3466,27 @@ export interface SystemConfigPasswordLoginDto { | ||||
|      */ | ||||
|     'enabled': boolean; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface SystemConfigReverseGeocodingDto | ||||
|  */ | ||||
| export interface SystemConfigReverseGeocodingDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {CitiesFile} | ||||
|      * @memberof SystemConfigReverseGeocodingDto | ||||
|      */ | ||||
|     'citiesFileOverride': CitiesFile; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {boolean} | ||||
|      * @memberof SystemConfigReverseGeocodingDto | ||||
|      */ | ||||
|     'enabled': boolean; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|   | ||||
| @@ -4,23 +4,25 @@ | ||||
|     NotificationType, | ||||
|   } from '$lib/components/shared-components/notification/notification'; | ||||
|   import { handleError } from '$lib/utils/handle-error'; | ||||
|   import { api, SystemConfigMapDto } from '@api'; | ||||
|   import { isEqual } from 'lodash-es'; | ||||
|   import { api, CitiesFile, SystemConfigDto } from '@api'; | ||||
|   import { cloneDeep, isEqual } from 'lodash-es'; | ||||
|   import { fade } from 'svelte/transition'; | ||||
|   import SettingAccordion from '../setting-accordion.svelte'; | ||||
|   import SettingButtonsRow from '../setting-buttons-row.svelte'; | ||||
|   import SettingSwitch from '../setting-switch.svelte'; | ||||
|   import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; | ||||
|   import SettingSwitch from '../setting-switch.svelte'; | ||||
|   import SettingSelect from '../setting-select.svelte'; | ||||
|  | ||||
|   export let mapConfig: SystemConfigMapDto; // this is the config that is being edited | ||||
|   export let config: SystemConfigDto; // this is the config that is being edited | ||||
|   export let disabled = false; | ||||
|  | ||||
|   let savedConfig: SystemConfigMapDto; | ||||
|   let defaultConfig: SystemConfigMapDto; | ||||
|   let savedConfig: SystemConfigDto; | ||||
|   let defaultConfig: SystemConfigDto; | ||||
|  | ||||
|   async function getConfigs() { | ||||
|   async function refreshConfig() { | ||||
|     [savedConfig, defaultConfig] = await Promise.all([ | ||||
|       api.systemConfigApi.getConfig().then((res) => res.data.map), | ||||
|       api.systemConfigApi.getDefaults().then((res) => res.data.map), | ||||
|       api.systemConfigApi.getConfig().then((res) => res.data), | ||||
|       api.systemConfigApi.getDefaults().then((res) => res.data), | ||||
|     ]); | ||||
|   } | ||||
|  | ||||
| @@ -28,11 +30,21 @@ | ||||
|     try { | ||||
|       const { data: current } = await api.systemConfigApi.getConfig(); | ||||
|       const { data: updated } = await api.systemConfigApi.updateConfig({ | ||||
|         systemConfigDto: { ...current, map: mapConfig }, | ||||
|         systemConfigDto: { | ||||
|           ...current, | ||||
|           map: { | ||||
|             enabled: config.map.enabled, | ||||
|             tileUrl: config.map.tileUrl, | ||||
|           }, | ||||
|           reverseGeocoding: { | ||||
|             enabled: config.reverseGeocoding.enabled, | ||||
|             citiesFileOverride: config.reverseGeocoding.citiesFileOverride, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
|  | ||||
|       mapConfig = { ...updated.map }; | ||||
|       savedConfig = { ...updated.map }; | ||||
|       config = cloneDeep(updated); | ||||
|       savedConfig = cloneDeep(updated); | ||||
|  | ||||
|       notificationController.show({ message: 'Settings saved', type: NotificationType.Info }); | ||||
|     } catch (error) { | ||||
| @@ -43,8 +55,8 @@ | ||||
|   async function reset() { | ||||
|     const { data: resetConfig } = await api.systemConfigApi.getConfig(); | ||||
|  | ||||
|     mapConfig = { ...resetConfig.map }; | ||||
|     savedConfig = { ...resetConfig.map }; | ||||
|     config = cloneDeep(resetConfig); | ||||
|     savedConfig = cloneDeep(resetConfig); | ||||
|  | ||||
|     notificationController.show({ | ||||
|       message: 'Reset settings to the recent saved settings', | ||||
| @@ -55,8 +67,8 @@ | ||||
|   async function resetToDefault() { | ||||
|     const { data: configs } = await api.systemConfigApi.getDefaults(); | ||||
|  | ||||
|     mapConfig = { ...configs.map }; | ||||
|     defaultConfig = { ...configs.map }; | ||||
|     config = cloneDeep(configs); | ||||
|     defaultConfig = cloneDeep(configs); | ||||
|  | ||||
|     notificationController.show({ | ||||
|       message: 'Reset map settings to default', | ||||
| @@ -65,12 +77,19 @@ | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <div> | ||||
|   {#await getConfigs() then} | ||||
| <div class="mt-2"> | ||||
|   {#await refreshConfig() then} | ||||
|     <div in:fade={{ duration: 500 }}> | ||||
|       <form autocomplete="off" on:submit|preventDefault> | ||||
|         <div class="flex flex-col gap-4"> | ||||
|           <SettingAccordion title="Map Settings" subtitle="Manage map settings"> | ||||
|             <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <SettingSwitch title="ENABLED" {disabled} subtitle="Enable map features" bind:checked={mapConfig.enabled} /> | ||||
|               <SettingSwitch | ||||
|                 title="ENABLED" | ||||
|                 {disabled} | ||||
|                 subtitle="Enable map features" | ||||
|                 bind:checked={config.map.enabled} | ||||
|               /> | ||||
|  | ||||
|               <hr /> | ||||
|  | ||||
| @@ -78,17 +97,61 @@ | ||||
|                 inputType={SettingInputFieldType.TEXT} | ||||
|                 label="Tile URL" | ||||
|                 desc="URL to a leaflet compatible tile server" | ||||
|             bind:value={mapConfig.tileUrl} | ||||
|                 bind:value={config.map.tileUrl} | ||||
|                 required={true} | ||||
|             disabled={disabled || !mapConfig.enabled} | ||||
|             isEdited={mapConfig.tileUrl !== savedConfig.tileUrl} | ||||
|                 disabled={disabled || !config.map.enabled} | ||||
|                 isEdited={config.map.tileUrl !== savedConfig.map.tileUrl} | ||||
|               /> | ||||
|             </div></SettingAccordion | ||||
|           > | ||||
|  | ||||
|           <SettingAccordion title="Reverse Geocoding Settings"> | ||||
|             <svelte:fragment slot="subtitle"> | ||||
|               <p class="text-sm dark:text-immich-dark-fg"> | ||||
|                 Manage <a | ||||
|                   href="https://immich.app/docs/features/reverse-geocoding" | ||||
|                   class="underline" | ||||
|                   target="_blank" | ||||
|                   rel="noreferrer">Reverse Geocoding</a | ||||
|                 > settings | ||||
|               </p> | ||||
|             </svelte:fragment> | ||||
|             <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|               <SettingSwitch | ||||
|                 title="ENABLED" | ||||
|                 {disabled} | ||||
|                 subtitle="Enable reverse geocoding" | ||||
|                 bind:checked={config.reverseGeocoding.enabled} | ||||
|               /> | ||||
|  | ||||
|               <hr /> | ||||
|  | ||||
|               <SettingSelect | ||||
|                 label="Precision" | ||||
|                 desc="Set reverse geocoding precision" | ||||
|                 name="reverse-geocoding-precision" | ||||
|                 bind:value={config.reverseGeocoding.citiesFileOverride} | ||||
|                 options={[ | ||||
|                   { value: CitiesFile.Cities500, text: 'Cities with more than 500 people' }, | ||||
|                   { value: CitiesFile.Cities1000, text: 'Cities with more than 1000 people' }, | ||||
|                   { value: CitiesFile.Cities5000, text: 'Cities with more than 5000 people' }, | ||||
|                   { value: CitiesFile.Cities15000, text: 'Cities with more than 15000 people' }, | ||||
|                 ]} | ||||
|                 disabled={disabled || !config.reverseGeocoding.enabled} | ||||
|                 isEdited={config.reverseGeocoding.citiesFileOverride !== | ||||
|                   savedConfig.reverseGeocoding.citiesFileOverride} | ||||
|               /> | ||||
|             </div></SettingAccordion | ||||
|           > | ||||
|  | ||||
|           <SettingButtonsRow | ||||
|             on:reset={reset} | ||||
|             on:save={saveSetting} | ||||
|             on:reset-to-default={resetToDefault} | ||||
|             showResetToDefault={!isEqual(savedConfig, defaultConfig)} | ||||
|             showResetToDefault={!isEqual( | ||||
|               { ...savedConfig.map, ...savedConfig.reverseGeocoding }, | ||||
|               { ...defaultConfig.map, ...defaultConfig.reverseGeocoding }, | ||||
|             )} | ||||
|             {disabled} | ||||
|           /> | ||||
|         </div> | ||||
|   | ||||
| @@ -14,7 +14,9 @@ | ||||
|         {title} | ||||
|       </h2> | ||||
|  | ||||
|       <slot name="subtitle"> | ||||
|         <p class="text-sm dark:text-immich-dark-fg">{subtitle}</p> | ||||
|       </slot> | ||||
|     </div> | ||||
|  | ||||
|     <button | ||||
|   | ||||
| @@ -10,6 +10,7 @@ export const featureFlags = writable<FeatureFlags>({ | ||||
|   sidecar: true, | ||||
|   tagImage: true, | ||||
|   map: true, | ||||
|   reverseGeocoding: true, | ||||
|   search: true, | ||||
|   oauth: false, | ||||
|   oauthAutoLaunch: false, | ||||
|   | ||||
| @@ -67,12 +67,12 @@ | ||||
|       <JobSettings disabled={$featureFlags.configFile} jobConfig={configs.job} /> | ||||
|     </SettingAccordion> | ||||
|  | ||||
|     <SettingAccordion title="Machine Learning Settings" subtitle="Manage model settings"> | ||||
|     <SettingAccordion title="Machine Learning Settings" subtitle="Manage machine learning features and settings"> | ||||
|       <MachineLearningSettings disabled={$featureFlags.configFile} machineLearningConfig={configs.machineLearning} /> | ||||
|     </SettingAccordion> | ||||
|  | ||||
|     <SettingAccordion title="Map Settings" subtitle="Manage map settings"> | ||||
|       <MapSettings disabled={$featureFlags.configFile} mapConfig={configs.map} /> | ||||
|     <SettingAccordion title="Map & GPS Settings" subtitle="Manage map related features and setting"> | ||||
|       <MapSettings disabled={$featureFlags.configFile} config={configs} /> | ||||
|     </SettingAccordion> | ||||
|  | ||||
|     <SettingAccordion title="OAuth Authentication" subtitle="Manage the login with OAuth settings"> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user