diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index e39b6e4f12..f9c983833f 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -1055,6 +1055,22 @@ export interface CheckExistingAssetsResponseDto { */ 'existingIds': Array; } +/** + * + * @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 diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index ed84fd439f..403ff2c363 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -49,11 +49,9 @@ 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 | +| Variable | Description | Default | Services | +| :--------------------------------- | :------------------------------- | :--------------------------: | :------------ | +| `REVERSE_GEOCODING_DUMP_DIRECTORY` | Reverse Geocoding Dump Directory | `./.reverse-geocoding-dump/` | microservices | ## Ports diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index f10fa425c5..60dc2f1521 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -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 diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 889f20658d..f1e4193fd2 100644 Binary files a/mobile/openapi/README.md and b/mobile/openapi/README.md differ diff --git a/mobile/openapi/doc/CitiesFile.md b/mobile/openapi/doc/CitiesFile.md new file mode 100644 index 0000000000..9acca959c7 Binary files /dev/null and b/mobile/openapi/doc/CitiesFile.md differ diff --git a/mobile/openapi/doc/ServerFeaturesDto.md b/mobile/openapi/doc/ServerFeaturesDto.md index c5f7946f30..8c772e3b91 100644 Binary files a/mobile/openapi/doc/ServerFeaturesDto.md and b/mobile/openapi/doc/ServerFeaturesDto.md differ diff --git a/mobile/openapi/doc/SystemConfigDto.md b/mobile/openapi/doc/SystemConfigDto.md index 40d528b401..f91a540635 100644 Binary files a/mobile/openapi/doc/SystemConfigDto.md and b/mobile/openapi/doc/SystemConfigDto.md differ diff --git a/mobile/openapi/doc/SystemConfigReverseGeocodingDto.md b/mobile/openapi/doc/SystemConfigReverseGeocodingDto.md new file mode 100644 index 0000000000..36eab47477 Binary files /dev/null and b/mobile/openapi/doc/SystemConfigReverseGeocodingDto.md differ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 1ff40f5ed6..e332902a3e 100644 Binary files a/mobile/openapi/lib/api.dart and b/mobile/openapi/lib/api.dart differ diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index e20a8d6984..c9678d94fe 100644 Binary files a/mobile/openapi/lib/api_client.dart and b/mobile/openapi/lib/api_client.dart differ diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 3b6099f79d..986305f02e 100644 Binary files a/mobile/openapi/lib/api_helper.dart and b/mobile/openapi/lib/api_helper.dart differ diff --git a/mobile/openapi/lib/model/cities_file.dart b/mobile/openapi/lib/model/cities_file.dart new file mode 100644 index 0000000000..96f5d8e573 Binary files /dev/null and b/mobile/openapi/lib/model/cities_file.dart differ diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart index 07a725232f..5175b99d78 100644 Binary files a/mobile/openapi/lib/model/server_features_dto.dart and b/mobile/openapi/lib/model/server_features_dto.dart differ diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index 2fe46a039a..1468279708 100644 Binary files a/mobile/openapi/lib/model/system_config_dto.dart and b/mobile/openapi/lib/model/system_config_dto.dart differ diff --git a/mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart b/mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart new file mode 100644 index 0000000000..727e5534fd Binary files /dev/null and b/mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart differ diff --git a/mobile/openapi/test/cities_file_test.dart b/mobile/openapi/test/cities_file_test.dart new file mode 100644 index 0000000000..cfe63b7548 Binary files /dev/null and b/mobile/openapi/test/cities_file_test.dart differ diff --git a/mobile/openapi/test/server_features_dto_test.dart b/mobile/openapi/test/server_features_dto_test.dart index 6838e12563..864a489c90 100644 Binary files a/mobile/openapi/test/server_features_dto_test.dart and b/mobile/openapi/test/server_features_dto_test.dart differ diff --git a/mobile/openapi/test/system_config_dto_test.dart b/mobile/openapi/test/system_config_dto_test.dart index 8951d16ca8..1ada973063 100644 Binary files a/mobile/openapi/test/system_config_dto_test.dart and b/mobile/openapi/test/system_config_dto_test.dart differ diff --git a/mobile/openapi/test/system_config_reverse_geocoding_dto_test.dart b/mobile/openapi/test/system_config_reverse_geocoding_dto_test.dart new file mode 100644 index 0000000000..12f7655ead Binary files /dev/null and b/mobile/openapi/test/system_config_reverse_geocoding_dto_test.dart differ diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index f5f58868ad..21a905f6bd 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -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": { diff --git a/server/src/domain/metadata/geocoding.repository.ts b/server/src/domain/metadata/geocoding.repository.ts index 07e8ee4990..4e0dce87a7 100644 --- a/server/src/domain/metadata/geocoding.repository.ts +++ b/server/src/domain/metadata/geocoding.repository.ts @@ -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; + init(options: Partial): Promise; reverseGeocode(point: GeoPoint): Promise; deleteCache(): Promise; } diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts index 105fc1bd2d..119aaf86ed 100644 --- a/server/src/domain/server-info/server-info.dto.ts +++ b/server/src/domain/server-info/server-info.dto.ts @@ -90,6 +90,7 @@ export class ServerFeaturesDto implements FeatureFlags { configFile!: boolean; facialRecognition!: boolean; map!: boolean; + reverseGeocoding!: boolean; oauth!: boolean; oauthAutoLaunch!: boolean; passwordLogin!: boolean; 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 4363e379b6..7a1e8ebcee 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -151,6 +151,7 @@ describe(ServerInfoService.name, () => { clipEncode: true, facialRecognition: true, map: true, + reverseGeocoding: true, oauth: false, oauthAutoLaunch: false, passwordLogin: true, diff --git a/server/src/domain/system-config/dto/system-config-reverse-geocoding.dto.ts b/server/src/domain/system-config/dto/system-config-reverse-geocoding.dto.ts new file mode 100644 index 0000000000..be20a02c79 --- /dev/null +++ b/server/src/domain/system-config/dto/system-config-reverse-geocoding.dto.ts @@ -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; +} 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 9ec7de6225..b4099c2b22 100644 --- a/server/src/domain/system-config/dto/system-config.dto.ts +++ b/server/src/domain/system-config/dto/system-config.dto.ts @@ -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() diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index 57a2f71f96..0dc35cc103 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -1,6 +1,7 @@ import { AudioCodec, CQMode, + CitiesFile, Colorspace, SystemConfig, SystemConfigEntity, @@ -81,6 +82,10 @@ export const defaults = Object.freeze({ 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', 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 67484e06d4..fc50839ba7 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -1,6 +1,7 @@ import { AudioCodec, CQMode, + CitiesFile, Colorspace, SystemConfig, SystemConfigEntity, @@ -80,6 +81,10 @@ const updatedConfig = Object.freeze({ enabled: true, tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', }, + reverseGeocoding: { + enabled: true, + citiesFileOverride: CitiesFile.CITIES_500, + }, oauth: { autoLaunch: true, autoRegister: true, diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index 825e22dbee..e8d9f5f1a3 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -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; diff --git a/server/src/infra/infra.config.ts b/server/src/infra/infra.config.ts index 81bb61bf1e..a3bcd10072 100644 --- a/server/src/infra/infra.config.ts +++ b/server/src/infra/infra.config.ts @@ -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/'; diff --git a/server/src/infra/repositories/geocoding.repository.ts b/server/src/infra/repositories/geocoding.repository.ts index 7b7b763756..eae72a904f 100644 --- a/server/src/infra/repositories/geocoding.repository.ts +++ b/server/src/infra/repositories/geocoding.repository.ts @@ -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 => new Promise((resolve) => geocoder.init(localGeocodingConfig, resolve)); const lookup = promisify(geocoder.lookUp).bind(geocoder); @Injectable() export class GeocodingRepository implements IGeocodingRepository { private logger = new Logger(GeocodingRepository.name); - async init(): Promise { - await init(); + async init(options: Partial): Promise { + return new Promise((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 }); diff --git a/server/src/microservices/processors/metadata-extraction.processor.ts b/server/src/microservices/processors/metadata-extraction.processor.ts index 9da6903f6b..10d1d27f27 100644 --- a/server/src/microservices/processors/metadata-extraction.processor.ts +++ b/server/src/microservices/processors/metadata-extraction.processor.ts @@ -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 = (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; } diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index e1f6b7ee50..c686b78be2 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -85,6 +85,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { configFile: false, facialRecognition: true, map: true, + reverseGeocoding: true, oauth: false, oauthAutoLaunch: false, passwordLogin: true, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index e39b6e4f12..f9c983833f 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1055,6 +1055,22 @@ export interface CheckExistingAssetsResponseDto { */ 'existingIds': Array; } +/** + * + * @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 diff --git a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte index dff3c65a36..df9059fced 100644 --- a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte +++ b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte @@ -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,30 +77,81 @@ } -
- {#await getConfigs() then} +
+ {#await refreshConfig() then}
-
- +
+ +
+ -
+
- + +
+ + + +

+ Manage Reverse Geocoding settings +

+
+
+ + +
+ + +
diff --git a/web/src/lib/components/admin-page/settings/setting-accordion.svelte b/web/src/lib/components/admin-page/settings/setting-accordion.svelte index 9acdfb392e..1d9290e617 100755 --- a/web/src/lib/components/admin-page/settings/setting-accordion.svelte +++ b/web/src/lib/components/admin-page/settings/setting-accordion.svelte @@ -14,7 +14,9 @@ {title} -

{subtitle}

+ +

{subtitle}

+