From 35767591d282a746c7f619967b6aee2909632162 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 11 Nov 2023 15:06:19 -0600 Subject: [PATCH 01/88] feat(web): show partners assets on the main timeline (#4933) --- cli/src/api/open-api/api.ts | 264 +++++++++++++++++- .../providers/authentication.provider.dart | 7 +- .../partner/services/partner.service.dart | 2 +- .../lib/routing/tab_navigation_observer.dart | 5 +- mobile/lib/shared/models/user.dart | 24 +- mobile/lib/shared/models/user.g.dart | 123 ++++++-- mobile/lib/shared/services/user.service.dart | 2 +- mobile/openapi/.openapi-generator/FILES | 6 + mobile/openapi/README.md | 3 + mobile/openapi/doc/AssetApi.md | 12 +- mobile/openapi/doc/PartnerApi.md | 66 ++++- mobile/openapi/doc/PartnerResponseDto.md | 29 ++ mobile/openapi/doc/UpdatePartnerDto.md | 15 + mobile/openapi/lib/api.dart | 2 + mobile/openapi/lib/api/asset_api.dart | 26 +- mobile/openapi/lib/api/partner_api.dart | 62 +++- mobile/openapi/lib/api_client.dart | 4 + .../lib/model/partner_response_dto.dart | 240 ++++++++++++++++ .../openapi/lib/model/update_partner_dto.dart | 98 +++++++ mobile/openapi/test/asset_api_test.dart | 4 +- mobile/openapi/test/partner_api_test.dart | 9 +- .../test/partner_response_dto_test.dart | 97 +++++++ .../openapi/test/update_partner_dto_test.dart | 27 ++ server/immich-openapi-specs.json | 152 +++++++++- server/src/domain/access/access.core.ts | 5 + server/src/domain/asset/asset.service.spec.ts | 78 +++++- server/src/domain/asset/asset.service.ts | 41 ++- .../src/domain/asset/dto/time-bucket.dto.ts | 5 + server/src/domain/partner/partner.dto.ts | 11 + .../domain/partner/partner.service.spec.ts | 13 +- server/src/domain/partner/partner.service.ts | 36 ++- .../domain/repositories/access.repository.ts | 4 + .../domain/repositories/asset.repository.ts | 2 +- .../domain/repositories/partner.repository.ts | 1 + .../immich/controllers/partner.controller.ts | 18 +- server/src/infra/entities/partner.entity.ts | 5 +- ...562570201-AdddInTimelineToPartnersTable.ts | 14 + .../infra/repositories/access.repository.ts | 11 + .../infra/repositories/asset.repository.ts | 8 +- .../infra/repositories/partner.repository.ts | 18 +- server/test/e2e/asset.e2e-spec.ts | 46 +++ server/test/e2e/partner.e2e-spec.ts | 20 ++ server/test/fixtures/partner.stub.ts | 2 + .../repositories/access.repository.mock.ts | 5 + .../repositories/partner.repository.mock.ts | 1 + web/src/api/open-api/api.ts | 264 +++++++++++++++++- .../components/album-page/album-viewer.svelte | 8 +- .../photos-page/actions/archive-action.svelte | 4 +- .../actions/asset-job-actions.svelte | 6 +- .../photos-page/actions/delete-assets.svelte | 10 +- .../actions/favorite-action.svelte | 5 +- .../photos-page/actions/stack-action.svelte | 4 +- .../asset-select-control-bar.svelte | 11 +- .../partner-settings.svelte | 149 ++++++++-- .../user-settings-list.svelte | 3 +- .../(user)/partners/[userId]/+page.svelte | 2 +- web/src/routes/(user)/photos/+page.svelte | 8 +- .../(user)/user-settings/+page.server.ts | 2 - .../routes/(user)/user-settings/+page.svelte | 2 +- 59 files changed, 1929 insertions(+), 172 deletions(-) create mode 100644 mobile/openapi/doc/PartnerResponseDto.md create mode 100644 mobile/openapi/doc/UpdatePartnerDto.md create mode 100644 mobile/openapi/lib/model/partner_response_dto.dart create mode 100644 mobile/openapi/lib/model/update_partner_dto.dart create mode 100644 mobile/openapi/test/partner_response_dto_test.dart create mode 100644 mobile/openapi/test/update_partner_dto_test.dart create mode 100644 server/src/domain/partner/partner.dto.ts create mode 100644 server/src/infra/migrations/1699562570201-AdddInTimelineToPartnersTable.ts diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 798d388d7..496fa5441 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2361,6 +2361,103 @@ export interface OAuthConfigResponseDto { */ 'url'?: string; } +/** + * + * @export + * @interface PartnerResponseDto + */ +export interface PartnerResponseDto { + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'deletedAt': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'email': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'externalPath': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'firstName': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'id': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'inTimeline'?: boolean; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'isAdmin': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'lastName': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'oauthId': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'profileImagePath': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'shouldChangePassword': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'storageLabel': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'updatedAt': string; +} /** * * @export @@ -4220,6 +4317,19 @@ export interface UpdateLibraryDto { */ 'name'?: string; } +/** + * + * @export + * @interface UpdatePartnerDto + */ +export interface UpdatePartnerDto { + /** + * + * @type {boolean} + * @memberof UpdatePartnerDto + */ + 'inTimeline': boolean; +} /** * * @export @@ -7274,11 +7384,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBucket', 'size', size) // verify required parameter 'timeBucket' is not null or undefined @@ -7336,6 +7447,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (timeBucket !== undefined) { localVarQueryParameter['timeBucket'] = timeBucket; } @@ -7365,11 +7480,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBuckets', 'size', size) const localVarPath = `/asset/time-buckets`; @@ -7425,6 +7541,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (key !== undefined) { localVarQueryParameter['key'] = key; } @@ -8227,12 +8347,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8245,12 +8366,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8547,7 +8669,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * @@ -8556,7 +8678,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * Get all asset of a device that are in the database, ID only. @@ -9043,6 +9165,13 @@ export interface AssetApiGetTimeBucketRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBucket + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9113,6 +9242,13 @@ export interface AssetApiGetTimeBucketsRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9592,7 +9728,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9603,7 +9739,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -12312,6 +12448,54 @@ export const PartnerApiAxiosParamCreator = function (configuration?: Configurati let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner: async (id: string, updatePartnerDto: UpdatePartnerDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('updatePartner', 'id', id) + // verify required parameter 'updatePartnerDto' is not null or undefined + assertParamExists('updatePartner', 'updatePartnerDto', updatePartnerDto) + const localVarPath = `/partner/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(updatePartnerDto, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -12333,7 +12517,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.createPartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12343,7 +12527,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { const localVarAxiosArgs = await localVarAxiosParamCreator.getPartners(direction, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12357,6 +12541,17 @@ export const PartnerApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.removePartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updatePartner(id: string, updatePartnerDto: UpdatePartnerDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updatePartner(id, updatePartnerDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -12373,7 +12568,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.createPartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, /** @@ -12382,7 +12577,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { + getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getPartners(requestParameters.direction, options).then((request) => request(axios, basePath)); }, /** @@ -12394,6 +12589,15 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.removePartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(axios, basePath)); + }, }; }; @@ -12439,6 +12643,27 @@ export interface PartnerApiRemovePartnerRequest { readonly id: string } +/** + * Request parameters for updatePartner operation in PartnerApi. + * @export + * @interface PartnerApiUpdatePartnerRequest + */ +export interface PartnerApiUpdatePartnerRequest { + /** + * + * @type {string} + * @memberof PartnerApiUpdatePartner + */ + readonly id: string + + /** + * + * @type {UpdatePartnerDto} + * @memberof PartnerApiUpdatePartner + */ + readonly updatePartnerDto: UpdatePartnerDto +} + /** * PartnerApi - object-oriented interface * @export @@ -12478,6 +12703,17 @@ export class PartnerApi extends BaseAPI { public removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig) { return PartnerApiFp(this.configuration).removePartner(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PartnerApi + */ + public updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig) { + return PartnerApiFp(this.configuration).updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index 33d2c0f3e..ad2103786 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -187,12 +187,15 @@ class AuthenticationNotifier extends StateNotifier { if (userResponseDto != null) { Store.put(StoreKey.deviceId, deviceId); Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); - Store.put(StoreKey.currentUser, User.fromDto(userResponseDto)); + Store.put( + StoreKey.currentUser, + User.fromUserDto(userResponseDto), + ); Store.put(StoreKey.serverUrl, serverUrl); Store.put(StoreKey.accessToken, accessToken); shouldChangePassword = userResponseDto.shouldChangePassword; - user = User.fromDto(userResponseDto); + user = User.fromUserDto(userResponseDto); retResult = true; } else { diff --git a/mobile/lib/modules/partner/services/partner.service.dart b/mobile/lib/modules/partner/services/partner.service.dart index 42fcdb438..0605e56ef 100644 --- a/mobile/lib/modules/partner/services/partner.service.dart +++ b/mobile/lib/modules/partner/services/partner.service.dart @@ -36,7 +36,7 @@ class PartnerService { final userDtos = await _apiService.partnerApi.getPartners(direction._value); if (userDtos != null) { - return userDtos.map((u) => User.fromDto(u)).toList(); + return userDtos.map((u) => User.fromPartnerDto(u)).toList(); } } catch (e) { _log.warning("failed to get partners for direction $direction:\n$e"); diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart index 5a4ee4398..dafbedd31 100644 --- a/mobile/lib/routing/tab_navigation_observer.dart +++ b/mobile/lib/routing/tab_navigation_observer.dart @@ -63,7 +63,10 @@ class TabNavigationObserver extends AutoRouterObserver { return; } - Store.put(StoreKey.currentUser, User.fromDto(userResponseDto)); + Store.put( + StoreKey.currentUser, + User.fromUserDto(userResponseDto), + ); ref.read(serverInfoProvider.notifier).getServerVersion(); } catch (e) { debugPrint("Error refreshing user info $e"); diff --git a/mobile/lib/shared/models/user.dart b/mobile/lib/shared/models/user.dart index df742a154..59f01c7dc 100644 --- a/mobile/lib/shared/models/user.dart +++ b/mobile/lib/shared/models/user.dart @@ -18,11 +18,12 @@ class User { this.isPartnerSharedWith = false, this.profileImagePath = '', this.memoryEnabled = true, + this.inTimeline = false, }); Id get isarId => fastHash(id); - User.fromDto(UserResponseDto dto) + User.fromUserDto(UserResponseDto dto) : id = dto.id, updatedAt = dto.updatedAt, email = dto.email, @@ -34,6 +35,19 @@ class User { isAdmin = dto.isAdmin, memoryEnabled = dto.memoriesEnabled; + User.fromPartnerDto(PartnerResponseDto dto) + : id = dto.id, + updatedAt = dto.updatedAt, + email = dto.email, + firstName = dto.firstName, + lastName = dto.lastName, + isPartnerSharedBy = false, + isPartnerSharedWith = false, + profileImagePath = dto.profileImagePath, + isAdmin = dto.isAdmin, + memoryEnabled = dto.memoriesEnabled, + inTimeline = dto.inTimeline; + @Index(unique: true, replace: false, type: IndexType.hash) String id; DateTime updatedAt; @@ -45,6 +59,8 @@ class User { bool isAdmin; String profileImagePath; bool? memoryEnabled; + bool? inTimeline; + @Backlink(to: 'owner') final IsarLinks albums = IsarLinks(); @Backlink(to: 'sharedUsers') @@ -62,7 +78,8 @@ class User { isPartnerSharedWith == other.isPartnerSharedWith && profileImagePath == other.profileImagePath && isAdmin == other.isAdmin && - memoryEnabled == other.memoryEnabled; + memoryEnabled == other.memoryEnabled && + inTimeline == other.inTimeline; } @override @@ -77,5 +94,6 @@ class User { isPartnerSharedWith.hashCode ^ profileImagePath.hashCode ^ isAdmin.hashCode ^ - memoryEnabled.hashCode; + memoryEnabled.hashCode ^ + inTimeline.hashCode; } diff --git a/mobile/lib/shared/models/user.g.dart b/mobile/lib/shared/models/user.g.dart index 687a784c0..af3bdadf6 100644 --- a/mobile/lib/shared/models/user.g.dart +++ b/mobile/lib/shared/models/user.g.dart @@ -32,38 +32,43 @@ const UserSchema = CollectionSchema( name: r'id', type: IsarType.string, ), - r'isAdmin': PropertySchema( + r'inTimeline': PropertySchema( id: 3, + name: r'inTimeline', + type: IsarType.bool, + ), + r'isAdmin': PropertySchema( + id: 4, name: r'isAdmin', type: IsarType.bool, ), r'isPartnerSharedBy': PropertySchema( - id: 4, + id: 5, name: r'isPartnerSharedBy', type: IsarType.bool, ), r'isPartnerSharedWith': PropertySchema( - id: 5, + id: 6, name: r'isPartnerSharedWith', type: IsarType.bool, ), r'lastName': PropertySchema( - id: 6, + id: 7, name: r'lastName', type: IsarType.string, ), r'memoryEnabled': PropertySchema( - id: 7, + id: 8, name: r'memoryEnabled', type: IsarType.bool, ), r'profileImagePath': PropertySchema( - id: 8, + id: 9, name: r'profileImagePath', type: IsarType.string, ), r'updatedAt': PropertySchema( - id: 9, + id: 10, name: r'updatedAt', type: IsarType.dateTime, ) @@ -134,13 +139,14 @@ void _userSerialize( writer.writeString(offsets[0], object.email); writer.writeString(offsets[1], object.firstName); writer.writeString(offsets[2], object.id); - writer.writeBool(offsets[3], object.isAdmin); - writer.writeBool(offsets[4], object.isPartnerSharedBy); - writer.writeBool(offsets[5], object.isPartnerSharedWith); - writer.writeString(offsets[6], object.lastName); - writer.writeBool(offsets[7], object.memoryEnabled); - writer.writeString(offsets[8], object.profileImagePath); - writer.writeDateTime(offsets[9], object.updatedAt); + writer.writeBool(offsets[3], object.inTimeline); + writer.writeBool(offsets[4], object.isAdmin); + writer.writeBool(offsets[5], object.isPartnerSharedBy); + writer.writeBool(offsets[6], object.isPartnerSharedWith); + writer.writeString(offsets[7], object.lastName); + writer.writeBool(offsets[8], object.memoryEnabled); + writer.writeString(offsets[9], object.profileImagePath); + writer.writeDateTime(offsets[10], object.updatedAt); } User _userDeserialize( @@ -153,13 +159,14 @@ User _userDeserialize( email: reader.readString(offsets[0]), firstName: reader.readString(offsets[1]), id: reader.readString(offsets[2]), - isAdmin: reader.readBool(offsets[3]), - isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false, - isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false, - lastName: reader.readString(offsets[6]), - memoryEnabled: reader.readBoolOrNull(offsets[7]), - profileImagePath: reader.readStringOrNull(offsets[8]) ?? '', - updatedAt: reader.readDateTime(offsets[9]), + inTimeline: reader.readBoolOrNull(offsets[3]), + isAdmin: reader.readBool(offsets[4]), + isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false, + isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false, + lastName: reader.readString(offsets[7]), + memoryEnabled: reader.readBoolOrNull(offsets[8]), + profileImagePath: reader.readStringOrNull(offsets[9]) ?? '', + updatedAt: reader.readDateTime(offsets[10]), ); return object; } @@ -178,18 +185,20 @@ P _userDeserializeProp

( case 2: return (reader.readString(offset)) as P; case 3: - return (reader.readBool(offset)) as P; + return (reader.readBoolOrNull(offset)) as P; case 4: - return (reader.readBoolOrNull(offset) ?? false) as P; + return (reader.readBool(offset)) as P; case 5: return (reader.readBoolOrNull(offset) ?? false) as P; case 6: - return (reader.readString(offset)) as P; + return (reader.readBoolOrNull(offset) ?? false) as P; case 7: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readString(offset)) as P; case 8: - return (reader.readStringOrNull(offset) ?? '') as P; + return (reader.readBoolOrNull(offset)) as P; case 9: + return (reader.readStringOrNull(offset) ?? '') as P; + case 10: return (reader.readDateTime(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -769,6 +778,32 @@ extension UserQueryFilter on QueryBuilder { }); } + QueryBuilder inTimelineIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'inTimeline', + )); + }); + } + + QueryBuilder inTimelineIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'inTimeline', + )); + }); + } + + QueryBuilder inTimelineEqualTo( + bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'inTimeline', + value: value, + )); + }); + } + QueryBuilder isAdminEqualTo(bool value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( @@ -1341,6 +1376,18 @@ extension UserQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.asc); + }); + } + + QueryBuilder sortByInTimelineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.desc); + }); + } + QueryBuilder sortByIsAdmin() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'isAdmin', Sort.asc); @@ -1463,6 +1510,18 @@ extension UserQuerySortThenBy on QueryBuilder { }); } + QueryBuilder thenByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.asc); + }); + } + + QueryBuilder thenByInTimelineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.desc); + }); + } + QueryBuilder thenByIsAdmin() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'isAdmin', Sort.asc); @@ -1582,6 +1641,12 @@ extension UserQueryWhereDistinct on QueryBuilder { }); } + QueryBuilder distinctByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'inTimeline'); + }); + } + QueryBuilder distinctByIsAdmin() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'isAdmin'); @@ -1653,6 +1718,12 @@ extension UserQueryProperty on QueryBuilder { }); } + QueryBuilder inTimelineProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'inTimeline'); + }); + } + QueryBuilder isAdminProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'isAdmin'); diff --git a/mobile/lib/shared/services/user.service.dart b/mobile/lib/shared/services/user.service.dart index 51e3cbc25..16f7b7b0e 100644 --- a/mobile/lib/shared/services/user.service.dart +++ b/mobile/lib/shared/services/user.service.dart @@ -40,7 +40,7 @@ class UserService { Future?> _getAllUsers({required bool isAll}) async { try { final dto = await _apiService.userApi.getAllUsers(isAll); - return dto?.map(User.fromDto).toList(); + return dto?.map(User.fromUserDto).toList(); } catch (e) { _log.warning("Failed get all users:\n$e"); return null; diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 57854e1b7..c4f967976 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -91,6 +91,7 @@ doc/OAuthCallbackDto.md doc/OAuthConfigDto.md doc/OAuthConfigResponseDto.md doc/PartnerApi.md +doc/PartnerResponseDto.md doc/PathEntityType.md doc/PathType.md doc/PeopleResponseDto.md @@ -159,6 +160,7 @@ doc/TranscodePolicy.md doc/UpdateAlbumDto.md doc/UpdateAssetDto.md doc/UpdateLibraryDto.md +doc/UpdatePartnerDto.md doc/UpdateStackParentDto.md doc/UpdateTagDto.md doc/UpdateUserDto.md @@ -273,6 +275,7 @@ lib/model/o_auth_authorize_response_dto.dart lib/model/o_auth_callback_dto.dart lib/model/o_auth_config_dto.dart lib/model/o_auth_config_response_dto.dart +lib/model/partner_response_dto.dart lib/model/path_entity_type.dart lib/model/path_type.dart lib/model/people_response_dto.dart @@ -335,6 +338,7 @@ lib/model/transcode_policy.dart lib/model/update_album_dto.dart lib/model/update_asset_dto.dart lib/model/update_library_dto.dart +lib/model/update_partner_dto.dart lib/model/update_stack_parent_dto.dart lib/model/update_tag_dto.dart lib/model/update_user_dto.dart @@ -432,6 +436,7 @@ test/o_auth_callback_dto_test.dart test/o_auth_config_dto_test.dart test/o_auth_config_response_dto_test.dart test/partner_api_test.dart +test/partner_response_dto_test.dart test/path_entity_type_test.dart test/path_type_test.dart test/people_response_dto_test.dart @@ -500,6 +505,7 @@ test/transcode_policy_test.dart test/update_album_dto_test.dart test/update_asset_dto_test.dart test/update_library_dto_test.dart +test/update_partner_dto_test.dart test/update_stack_parent_dto_test.dart test/update_tag_dto_test.dart test/update_user_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 0bc2cc1e3..551169a99 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -152,6 +152,7 @@ Class | Method | HTTP request | Description *PartnerApi* | [**createPartner**](doc//PartnerApi.md#createpartner) | **POST** /partner/{id} | *PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner | *PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} | +*PartnerApi* | [**updatePartner**](doc//PartnerApi.md#updatepartner) | **PUT** /partner/{id} | *PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person | *PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} | *PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | @@ -283,6 +284,7 @@ Class | Method | HTTP request | Description - [OAuthCallbackDto](doc//OAuthCallbackDto.md) - [OAuthConfigDto](doc//OAuthConfigDto.md) - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) + - [PartnerResponseDto](doc//PartnerResponseDto.md) - [PathEntityType](doc//PathEntityType.md) - [PathType](doc//PathType.md) - [PeopleResponseDto](doc//PeopleResponseDto.md) @@ -345,6 +347,7 @@ Class | Method | HTTP request | Description - [UpdateAlbumDto](doc//UpdateAlbumDto.md) - [UpdateAssetDto](doc//UpdateAssetDto.md) - [UpdateLibraryDto](doc//UpdateLibraryDto.md) + - [UpdatePartnerDto](doc//UpdatePartnerDto.md) - [UpdateStackParentDto](doc//UpdateStackParentDto.md) - [UpdateTagDto](doc//UpdateTagDto.md) - [UpdateUserDto](doc//UpdateUserDto.md) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 811841947..2938a075b 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -1005,7 +1005,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getTimeBucket** -> List getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key) +> List getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key) @@ -1037,10 +1037,11 @@ final isArchived = true; // bool | final isFavorite = true; // bool | final isTrashed = true; // bool | final withStacked = true; // bool | +final withPartners = true; // bool | final key = key_example; // String | try { - final result = api_instance.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key); + final result = api_instance.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key); print(result); } catch (e) { print('Exception when calling AssetApi->getTimeBucket: $e\n'); @@ -1060,6 +1061,7 @@ Name | Type | Description | Notes **isFavorite** | **bool**| | [optional] **isTrashed** | **bool**| | [optional] **withStacked** | **bool**| | [optional] + **withPartners** | **bool**| | [optional] **key** | **String**| | [optional] ### Return type @@ -1078,7 +1080,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getTimeBuckets** -> List getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key) +> List getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key) @@ -1109,10 +1111,11 @@ final isArchived = true; // bool | final isFavorite = true; // bool | final isTrashed = true; // bool | final withStacked = true; // bool | +final withPartners = true; // bool | final key = key_example; // String | try { - final result = api_instance.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key); + final result = api_instance.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key); print(result); } catch (e) { print('Exception when calling AssetApi->getTimeBuckets: $e\n'); @@ -1131,6 +1134,7 @@ Name | Type | Description | Notes **isFavorite** | **bool**| | [optional] **isTrashed** | **bool**| | [optional] **withStacked** | **bool**| | [optional] + **withPartners** | **bool**| | [optional] **key** | **String**| | [optional] ### Return type diff --git a/mobile/openapi/doc/PartnerApi.md b/mobile/openapi/doc/PartnerApi.md index 937978bef..5b4624e0a 100644 --- a/mobile/openapi/doc/PartnerApi.md +++ b/mobile/openapi/doc/PartnerApi.md @@ -12,10 +12,11 @@ Method | HTTP request | Description [**createPartner**](PartnerApi.md#createpartner) | **POST** /partner/{id} | [**getPartners**](PartnerApi.md#getpartners) | **GET** /partner | [**removePartner**](PartnerApi.md#removepartner) | **DELETE** /partner/{id} | +[**updatePartner**](PartnerApi.md#updatepartner) | **PUT** /partner/{id} | # **createPartner** -> UserResponseDto createPartner(id) +> PartnerResponseDto createPartner(id) @@ -56,7 +57,7 @@ Name | Type | Description | Notes ### Return type -[**UserResponseDto**](UserResponseDto.md) +[**PartnerResponseDto**](PartnerResponseDto.md) ### Authorization @@ -70,7 +71,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getPartners** -> List getPartners(direction) +> List getPartners(direction) @@ -111,7 +112,7 @@ Name | Type | Description | Notes ### Return type -[**List**](UserResponseDto.md) +[**List**](PartnerResponseDto.md) ### Authorization @@ -178,3 +179,60 @@ void (empty response body) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **updatePartner** +> PartnerResponseDto updatePartner(id, updatePartnerDto) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = PartnerApi(); +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final updatePartnerDto = UpdatePartnerDto(); // UpdatePartnerDto | + +try { + final result = api_instance.updatePartner(id, updatePartnerDto); + print(result); +} catch (e) { + print('Exception when calling PartnerApi->updatePartner: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **id** | **String**| | + **updatePartnerDto** | [**UpdatePartnerDto**](UpdatePartnerDto.md)| | + +### Return type + +[**PartnerResponseDto**](PartnerResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/mobile/openapi/doc/PartnerResponseDto.md b/mobile/openapi/doc/PartnerResponseDto.md new file mode 100644 index 000000000..887664e89 --- /dev/null +++ b/mobile/openapi/doc/PartnerResponseDto.md @@ -0,0 +1,29 @@ +# openapi.model.PartnerResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**createdAt** | [**DateTime**](DateTime.md) | | +**deletedAt** | [**DateTime**](DateTime.md) | | +**email** | **String** | | +**externalPath** | **String** | | +**firstName** | **String** | | +**id** | **String** | | +**inTimeline** | **bool** | | [optional] +**isAdmin** | **bool** | | +**lastName** | **String** | | +**memoriesEnabled** | **bool** | | [optional] +**oauthId** | **String** | | +**profileImagePath** | **String** | | +**shouldChangePassword** | **bool** | | +**storageLabel** | **String** | | +**updatedAt** | [**DateTime**](DateTime.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/UpdatePartnerDto.md b/mobile/openapi/doc/UpdatePartnerDto.md new file mode 100644 index 000000000..b336c419e --- /dev/null +++ b/mobile/openapi/doc/UpdatePartnerDto.md @@ -0,0 +1,15 @@ +# openapi.model.UpdatePartnerDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**inTimeline** | **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) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 7e4ed3a7b..ed0b51f88 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -124,6 +124,7 @@ part 'model/o_auth_authorize_response_dto.dart'; part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; part 'model/o_auth_config_response_dto.dart'; +part 'model/partner_response_dto.dart'; part 'model/path_entity_type.dart'; part 'model/path_type.dart'; part 'model/people_response_dto.dart'; @@ -186,6 +187,7 @@ part 'model/transcode_policy.dart'; part 'model/update_album_dto.dart'; part 'model/update_asset_dto.dart'; part 'model/update_library_dto.dart'; +part 'model/update_partner_dto.dart'; part 'model/update_stack_parent_dto.dart'; part 'model/update_tag_dto.dart'; part 'model/update_user_dto.dart'; diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 0d3c2bfe8..429a320c2 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -997,8 +997,10 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { + Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { // ignore: prefer_const_declarations final path = r'/asset/time-bucket'; @@ -1030,6 +1032,9 @@ class AssetApi { } if (withStacked != null) { queryParams.addAll(_queryParams('', 'withStacked', withStacked)); + } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); } queryParams.addAll(_queryParams('', 'timeBucket', timeBucket)); if (key != null) { @@ -1070,9 +1075,11 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { - final response = await getTimeBucketWithHttpInfo(size, timeBucket, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, key: key, ); + Future?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { + final response = await getTimeBucketWithHttpInfo(size, timeBucket, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, withPartners: withPartners, key: key, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -1108,8 +1115,10 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { + Future getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { // ignore: prefer_const_declarations final path = r'/asset/time-buckets'; @@ -1142,6 +1151,9 @@ class AssetApi { if (withStacked != null) { queryParams.addAll(_queryParams('', 'withStacked', withStacked)); } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); + } if (key != null) { queryParams.addAll(_queryParams('', 'key', key)); } @@ -1178,9 +1190,11 @@ class AssetApi { /// /// * [bool] withStacked: /// + /// * [bool] withPartners: + /// /// * [String] key: - Future?> getTimeBuckets(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, String? key, }) async { - final response = await getTimeBucketsWithHttpInfo(size, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, key: key, ); + Future?> getTimeBuckets(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, bool? withStacked, bool? withPartners, String? key, }) async { + final response = await getTimeBucketsWithHttpInfo(size, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, withStacked: withStacked, withPartners: withPartners, key: key, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/partner_api.dart b/mobile/openapi/lib/api/partner_api.dart index cf374aafe..886b37a5b 100644 --- a/mobile/openapi/lib/api/partner_api.dart +++ b/mobile/openapi/lib/api/partner_api.dart @@ -49,7 +49,7 @@ class PartnerApi { /// Parameters: /// /// * [String] id (required): - Future createPartner(String id,) async { + Future createPartner(String id,) async { final response = await createPartnerWithHttpInfo(id,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -58,7 +58,7 @@ class PartnerApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; } return null; @@ -98,7 +98,7 @@ class PartnerApi { /// Parameters: /// /// * [String] direction (required): - Future?> getPartners(String direction,) async { + Future?> getPartners(String direction,) async { final response = await getPartnersWithHttpInfo(direction,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -108,8 +108,8 @@ class PartnerApi { // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { final responseBody = await _decodeBodyBytes(response); - return (await apiClient.deserializeAsync(responseBody, 'List') as List) - .cast() + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() .toList(); } @@ -155,4 +155,56 @@ class PartnerApi { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } } + + /// Performs an HTTP 'PUT /partner/{id}' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdatePartnerDto] updatePartnerDto (required): + Future updatePartnerWithHttpInfo(String id, UpdatePartnerDto updatePartnerDto,) async { + // ignore: prefer_const_declarations + final path = r'/partner/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = updatePartnerDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdatePartnerDto] updatePartnerDto (required): + Future updatePartner(String id, UpdatePartnerDto updatePartnerDto,) async { + final response = await updatePartnerWithHttpInfo(id, updatePartnerDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; + + } + return null; + } } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index dfdd5b4ef..c03a469ae 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -337,6 +337,8 @@ class ApiClient { return OAuthConfigDto.fromJson(value); case 'OAuthConfigResponseDto': return OAuthConfigResponseDto.fromJson(value); + case 'PartnerResponseDto': + return PartnerResponseDto.fromJson(value); case 'PathEntityType': return PathEntityTypeTypeTransformer().decode(value); case 'PathType': @@ -461,6 +463,8 @@ class ApiClient { return UpdateAssetDto.fromJson(value); case 'UpdateLibraryDto': return UpdateLibraryDto.fromJson(value); + case 'UpdatePartnerDto': + return UpdatePartnerDto.fromJson(value); case 'UpdateStackParentDto': return UpdateStackParentDto.fromJson(value); case 'UpdateTagDto': diff --git a/mobile/openapi/lib/model/partner_response_dto.dart b/mobile/openapi/lib/model/partner_response_dto.dart new file mode 100644 index 000000000..ae6e23098 --- /dev/null +++ b/mobile/openapi/lib/model/partner_response_dto.dart @@ -0,0 +1,240 @@ +// +// 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 PartnerResponseDto { + /// Returns a new [PartnerResponseDto] instance. + PartnerResponseDto({ + required this.createdAt, + required this.deletedAt, + required this.email, + required this.externalPath, + required this.firstName, + required this.id, + this.inTimeline, + required this.isAdmin, + required this.lastName, + this.memoriesEnabled, + required this.oauthId, + required this.profileImagePath, + required this.shouldChangePassword, + required this.storageLabel, + required this.updatedAt, + }); + + DateTime createdAt; + + DateTime? deletedAt; + + String email; + + String? externalPath; + + String firstName; + + String id; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? inTimeline; + + bool isAdmin; + + String lastName; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? memoriesEnabled; + + String oauthId; + + String profileImagePath; + + bool shouldChangePassword; + + String? storageLabel; + + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is PartnerResponseDto && + other.createdAt == createdAt && + other.deletedAt == deletedAt && + other.email == email && + other.externalPath == externalPath && + other.firstName == firstName && + other.id == id && + other.inTimeline == inTimeline && + other.isAdmin == isAdmin && + other.lastName == lastName && + other.memoriesEnabled == memoriesEnabled && + other.oauthId == oauthId && + other.profileImagePath == profileImagePath && + other.shouldChangePassword == shouldChangePassword && + other.storageLabel == storageLabel && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (email.hashCode) + + (externalPath == null ? 0 : externalPath!.hashCode) + + (firstName.hashCode) + + (id.hashCode) + + (inTimeline == null ? 0 : inTimeline!.hashCode) + + (isAdmin.hashCode) + + (lastName.hashCode) + + (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) + + (oauthId.hashCode) + + (profileImagePath.hashCode) + + (shouldChangePassword.hashCode) + + (storageLabel == null ? 0 : storageLabel!.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'PartnerResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, firstName=$firstName, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, lastName=$lastName, memoriesEnabled=$memoriesEnabled, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + json[r'email'] = this.email; + if (this.externalPath != null) { + json[r'externalPath'] = this.externalPath; + } else { + // json[r'externalPath'] = null; + } + json[r'firstName'] = this.firstName; + json[r'id'] = this.id; + if (this.inTimeline != null) { + json[r'inTimeline'] = this.inTimeline; + } else { + // json[r'inTimeline'] = null; + } + json[r'isAdmin'] = this.isAdmin; + json[r'lastName'] = this.lastName; + if (this.memoriesEnabled != null) { + json[r'memoriesEnabled'] = this.memoriesEnabled; + } else { + // json[r'memoriesEnabled'] = null; + } + json[r'oauthId'] = this.oauthId; + json[r'profileImagePath'] = this.profileImagePath; + json[r'shouldChangePassword'] = this.shouldChangePassword; + if (this.storageLabel != null) { + json[r'storageLabel'] = this.storageLabel; + } else { + // json[r'storageLabel'] = null; + } + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [PartnerResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PartnerResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return PartnerResponseDto( + createdAt: mapDateTime(json, r'createdAt', '')!, + deletedAt: mapDateTime(json, r'deletedAt', ''), + email: mapValueOfType(json, r'email')!, + externalPath: mapValueOfType(json, r'externalPath'), + firstName: mapValueOfType(json, r'firstName')!, + id: mapValueOfType(json, r'id')!, + inTimeline: mapValueOfType(json, r'inTimeline'), + isAdmin: mapValueOfType(json, r'isAdmin')!, + lastName: mapValueOfType(json, r'lastName')!, + memoriesEnabled: mapValueOfType(json, r'memoriesEnabled'), + oauthId: mapValueOfType(json, r'oauthId')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, + storageLabel: mapValueOfType(json, r'storageLabel'), + updatedAt: mapDateTime(json, r'updatedAt', '')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PartnerResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PartnerResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PartnerResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PartnerResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'deletedAt', + 'email', + 'externalPath', + 'firstName', + 'id', + 'isAdmin', + 'lastName', + 'oauthId', + 'profileImagePath', + 'shouldChangePassword', + 'storageLabel', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/update_partner_dto.dart b/mobile/openapi/lib/model/update_partner_dto.dart new file mode 100644 index 000000000..6a8aaf161 --- /dev/null +++ b/mobile/openapi/lib/model/update_partner_dto.dart @@ -0,0 +1,98 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UpdatePartnerDto { + /// Returns a new [UpdatePartnerDto] instance. + UpdatePartnerDto({ + required this.inTimeline, + }); + + bool inTimeline; + + @override + bool operator ==(Object other) => identical(this, other) || other is UpdatePartnerDto && + other.inTimeline == inTimeline; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (inTimeline.hashCode); + + @override + String toString() => 'UpdatePartnerDto[inTimeline=$inTimeline]'; + + Map toJson() { + final json = {}; + json[r'inTimeline'] = this.inTimeline; + return json; + } + + /// Returns a new [UpdatePartnerDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UpdatePartnerDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return UpdatePartnerDto( + inTimeline: mapValueOfType(json, r'inTimeline')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UpdatePartnerDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UpdatePartnerDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UpdatePartnerDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UpdatePartnerDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'inTimeline', + }; +} + diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index e241c34ca..ec7a2182d 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -110,12 +110,12 @@ void main() { // TODO }); - //Future> getTimeBucket(TimeBucketSize size, String timeBucket, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, String key }) async + //Future> getTimeBucket(TimeBucketSize size, String timeBucket, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, bool withPartners, String key }) async test('test getTimeBucket', () async { // TODO }); - //Future> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, String key }) async + //Future> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, bool withStacked, bool withPartners, String key }) async test('test getTimeBuckets', () async { // TODO }); diff --git a/mobile/openapi/test/partner_api_test.dart b/mobile/openapi/test/partner_api_test.dart index fa5a59d2a..daaf65644 100644 --- a/mobile/openapi/test/partner_api_test.dart +++ b/mobile/openapi/test/partner_api_test.dart @@ -17,12 +17,12 @@ void main() { // final instance = PartnerApi(); group('tests for PartnerApi', () { - //Future createPartner(String id) async + //Future createPartner(String id) async test('test createPartner', () async { // TODO }); - //Future> getPartners(String direction) async + //Future> getPartners(String direction) async test('test getPartners', () async { // TODO }); @@ -32,5 +32,10 @@ void main() { // TODO }); + //Future updatePartner(String id, UpdatePartnerDto updatePartnerDto) async + test('test updatePartner', () async { + // TODO + }); + }); } diff --git a/mobile/openapi/test/partner_response_dto_test.dart b/mobile/openapi/test/partner_response_dto_test.dart new file mode 100644 index 000000000..14092a44f --- /dev/null +++ b/mobile/openapi/test/partner_response_dto_test.dart @@ -0,0 +1,97 @@ +// +// 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 PartnerResponseDto +void main() { + // final instance = PartnerResponseDto(); + + group('test PartnerResponseDto', () { + // DateTime createdAt + test('to test the property `createdAt`', () async { + // TODO + }); + + // DateTime deletedAt + test('to test the property `deletedAt`', () async { + // TODO + }); + + // String email + test('to test the property `email`', () async { + // TODO + }); + + // String externalPath + test('to test the property `externalPath`', () async { + // TODO + }); + + // String firstName + test('to test the property `firstName`', () async { + // TODO + }); + + // String id + test('to test the property `id`', () async { + // TODO + }); + + // bool inTimeline + test('to test the property `inTimeline`', () async { + // TODO + }); + + // bool isAdmin + test('to test the property `isAdmin`', () async { + // TODO + }); + + // String lastName + test('to test the property `lastName`', () async { + // TODO + }); + + // bool memoriesEnabled + test('to test the property `memoriesEnabled`', () async { + // TODO + }); + + // String oauthId + test('to test the property `oauthId`', () async { + // TODO + }); + + // String profileImagePath + test('to test the property `profileImagePath`', () async { + // TODO + }); + + // bool shouldChangePassword + test('to test the property `shouldChangePassword`', () async { + // TODO + }); + + // String storageLabel + test('to test the property `storageLabel`', () async { + // TODO + }); + + // DateTime updatedAt + test('to test the property `updatedAt`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/update_partner_dto_test.dart b/mobile/openapi/test/update_partner_dto_test.dart new file mode 100644 index 000000000..ca569914b --- /dev/null +++ b/mobile/openapi/test/update_partner_dto_test.dart @@ -0,0 +1,27 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for UpdatePartnerDto +void main() { + // final instance = UpdatePartnerDto(); + + group('test UpdatePartnerDto', () { + // bool inTimeline + test('to test the property `inTimeline`', () async { + // TODO + }); + + + }); + +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 193586cbd..782b69649 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2071,6 +2071,14 @@ "type": "boolean" } }, + { + "name": "withPartners", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, { "name": "timeBucket", "required": true, @@ -2199,6 +2207,14 @@ "type": "boolean" } }, + { + "name": "withPartners", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, { "name": "key", "required": false, @@ -3491,7 +3507,7 @@ "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/UserResponseDto" + "$ref": "#/components/schemas/PartnerResponseDto" }, "type": "array" } @@ -3568,7 +3584,57 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserResponseDto" + "$ref": "#/components/schemas/PartnerResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Partner" + ] + }, + "put": { + "operationId": "updatePartner", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePartnerDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerResponseDto" } } }, @@ -7572,6 +7638,77 @@ ], "type": "object" }, + "PartnerResponseDto": { + "properties": { + "createdAt": { + "format": "date-time", + "type": "string" + }, + "deletedAt": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "email": { + "type": "string" + }, + "externalPath": { + "nullable": true, + "type": "string" + }, + "firstName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "inTimeline": { + "type": "boolean" + }, + "isAdmin": { + "type": "boolean" + }, + "lastName": { + "type": "string" + }, + "memoriesEnabled": { + "type": "boolean" + }, + "oauthId": { + "type": "string" + }, + "profileImagePath": { + "type": "string" + }, + "shouldChangePassword": { + "type": "boolean" + }, + "storageLabel": { + "nullable": true, + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "firstName", + "lastName", + "email", + "profileImagePath", + "storageLabel", + "externalPath", + "shouldChangePassword", + "isAdmin", + "createdAt", + "deletedAt", + "updatedAt", + "oauthId" + ], + "type": "object" + }, "PathEntityType": { "enum": [ "asset", @@ -8982,6 +9119,17 @@ }, "type": "object" }, + "UpdatePartnerDto": { + "properties": { + "inTimeline": { + "type": "boolean" + } + }, + "required": [ + "inTimeline" + ], + "type": "object" + }, "UpdateStackParentDto": { "properties": { "newParentId": { diff --git a/server/src/domain/access/access.core.ts b/server/src/domain/access/access.core.ts index 88abd79b1..16527de98 100644 --- a/server/src/domain/access/access.core.ts +++ b/server/src/domain/access/access.core.ts @@ -40,6 +40,8 @@ export enum Permission { PERSON_READ = 'person.read', PERSON_WRITE = 'person.write', PERSON_MERGE = 'person.merge', + + PARTNER_UPDATE = 'partner.update', } let instance: AccessCore | null; @@ -242,6 +244,9 @@ export class AccessCore { case Permission.PERSON_MERGE: return this.repository.person.hasOwnerAccess(authUser.id, id); + case Permission.PARTNER_UPDATE: + return this.repository.partner.hasUpdateAccess(authUser.id, id); + default: return false; } diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index 7b93935f6..45687282f 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -10,6 +10,7 @@ import { newCommunicationRepositoryMock, newCryptoRepositoryMock, newJobRepositoryMock, + newPartnerRepositoryMock, newStorageRepositoryMock, newSystemConfigRepositoryMock, } from '@test'; @@ -23,6 +24,7 @@ import { ICommunicationRepository, ICryptoRepository, IJobRepository, + IPartnerRepository, IStorageRepository, ISystemConfigRepository, JobItem, @@ -164,6 +166,7 @@ describe(AssetService.name, () => { let storageMock: jest.Mocked; let communicationMock: jest.Mocked; let configMock: jest.Mocked; + let partnerMock: jest.Mocked; it('should work', () => { expect(sut).toBeDefined(); @@ -177,7 +180,18 @@ describe(AssetService.name, () => { jobMock = newJobRepositoryMock(); storageMock = newStorageRepositoryMock(); configMock = newSystemConfigRepositoryMock(); - sut = new AssetService(accessMock, assetMock, cryptoMock, jobMock, configMock, storageMock, communicationMock); + partnerMock = newPartnerRepositoryMock(); + + sut = new AssetService( + accessMock, + assetMock, + cryptoMock, + jobMock, + configMock, + storageMock, + communicationMock, + partnerMock, + ); when(assetMock.getById) .calledWith(assetStub.livePhotoStillAsset.id) @@ -327,7 +341,7 @@ describe(AssetService.name, () => { size: TimeBucketSize.DAY, }), ).resolves.toEqual(expect.arrayContaining([{ timeBucket: 'bucket', count: 1 }])); - expect(assetMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.DAY, userId: authStub.admin.id }); + expect(assetMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.DAY, userIds: [authStub.admin.id] }); }); }); @@ -363,7 +377,7 @@ describe(AssetService.name, () => { size: TimeBucketSize.DAY, timeBucket: 'bucket', isArchived: true, - userId: authStub.admin.id, + userIds: [authStub.admin.id], }); }); @@ -380,9 +394,65 @@ describe(AssetService.name, () => { expect(assetMock.getTimeBucket).toBeCalledWith('bucket', { size: TimeBucketSize.DAY, timeBucket: 'bucket', - userId: authStub.admin.id, + userIds: [authStub.admin.id], }); }); + + it('should throw an error if withParners is true and isArchived true or undefined', async () => { + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isArchived: true, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isArchived: undefined, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + }); + + it('should throw an error if withParners is true and isFavorite is either true or false', async () => { + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isFavorite: true, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isFavorite: false, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + }); + + it('should throw an error if withParners is true and isTrash is true', async () => { + await expect( + sut.getTimeBucket(authStub.admin, { + size: TimeBucketSize.DAY, + timeBucket: 'bucket', + isTrashed: true, + withPartners: true, + userId: authStub.admin.id, + }), + ).rejects.toThrowError(BadRequestException); + }); }); describe('downloadFile', () => { diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index 34da78680..3b9b56412 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -16,9 +16,11 @@ import { ICommunicationRepository, ICryptoRepository, IJobRepository, + IPartnerRepository, IStorageRepository, ISystemConfigRepository, ImmichReadStream, + TimeBucketOptions, } from '../repositories'; import { StorageCore, StorageFolder } from '../storage'; import { SystemConfigCore } from '../system-config'; @@ -83,6 +85,7 @@ export class AssetService { @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository, + @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, ) { this.access = AccessCore.create(accessRepository); this.configCore = SystemConfigCore.create(configRepository); @@ -187,11 +190,25 @@ export class AssetService { await this.access.requirePermission(authUser, Permission.ARCHIVE_READ, [dto.userId]); } } + + if (dto.withPartners) { + const requestedArchived = dto.isArchived === true || dto.isArchived === undefined; + const requestedFavorite = dto.isFavorite === true || dto.isFavorite === false; + const requestedTrash = dto.isTrashed === true; + + if (requestedArchived || requestedFavorite || requestedTrash) { + throw new BadRequestException( + 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', + ); + } + } } async getTimeBuckets(authUser: AuthUserDto, dto: TimeBucketDto): Promise { await this.timeBucketChecks(authUser, dto); - return this.assetRepository.getTimeBuckets(dto); + const timeBucketOptions = await this.buildTimeBucketOptions(authUser, dto); + + return this.assetRepository.getTimeBuckets(timeBucketOptions); } async getTimeBucket( @@ -199,7 +216,8 @@ export class AssetService { dto: TimeBucketAssetDto, ): Promise { await this.timeBucketChecks(authUser, dto); - const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, dto); + const timeBucketOptions = await this.buildTimeBucketOptions(authUser, dto); + const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions); if (authUser.isShowMetadata) { return assets.map((asset) => mapAsset(asset, { withStack: true })); } else { @@ -207,6 +225,25 @@ export class AssetService { } } + async buildTimeBucketOptions(authUser: AuthUserDto, dto: TimeBucketDto): Promise { + const { userId, ...options } = dto; + let userIds: string[] | undefined = undefined; + + if (userId) { + userIds = [userId]; + + if (dto.withPartners) { + const partners = await this.partnerRepository.getAll(authUser.id); + const partnersIds = partners + .filter((partner) => partner.sharedBy && partner.sharedWith && partner.inTimeline) + .map((partner) => partner.sharedById); + + userIds.push(...partnersIds); + } + } + + return { ...options, userIds }; + } async downloadFile(authUser: AuthUserDto, id: string): Promise { await this.access.requirePermission(authUser, Permission.ASSET_DOWNLOAD, id); diff --git a/server/src/domain/asset/dto/time-bucket.dto.ts b/server/src/domain/asset/dto/time-bucket.dto.ts index db2f1ecd0..849b8713f 100644 --- a/server/src/domain/asset/dto/time-bucket.dto.ts +++ b/server/src/domain/asset/dto/time-bucket.dto.ts @@ -38,6 +38,11 @@ export class TimeBucketDto { @IsBoolean() @Transform(toBoolean) withStacked?: boolean; + + @Optional() + @IsBoolean() + @Transform(toBoolean) + withPartners?: boolean; } export class TimeBucketAssetDto extends TimeBucketDto { diff --git a/server/src/domain/partner/partner.dto.ts b/server/src/domain/partner/partner.dto.ts new file mode 100644 index 000000000..17afcad5d --- /dev/null +++ b/server/src/domain/partner/partner.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmpty } from 'class-validator'; +import { UserResponseDto } from '../user'; + +export class UpdatePartnerDto { + @IsNotEmpty() + inTimeline!: boolean; +} + +export class PartnerResponseDto extends UserResponseDto { + inTimeline?: boolean; +} diff --git a/server/src/domain/partner/partner.service.spec.ts b/server/src/domain/partner/partner.service.spec.ts index 2bc561194..0bae95aa7 100644 --- a/server/src/domain/partner/partner.service.spec.ts +++ b/server/src/domain/partner/partner.service.spec.ts @@ -1,11 +1,11 @@ import { BadRequestException } from '@nestjs/common'; import { authStub, newPartnerRepositoryMock, partnerStub } from '@test'; -import { UserResponseDto } from '../index'; -import { IPartnerRepository, PartnerDirection } from '../repositories'; +import { IAccessRepository, IPartnerRepository, PartnerDirection } from '../repositories'; +import { PartnerResponseDto } from './partner.dto'; import { PartnerService } from './partner.service'; const responseDto = { - admin: { + admin: { email: 'admin@test.com', firstName: 'admin_first_name', id: 'admin_id', @@ -20,8 +20,9 @@ const responseDto = { updatedAt: new Date('2021-01-01'), externalPath: null, memoriesEnabled: true, + inTimeline: true, }, - user1: { + user1: { email: 'immich@test.com', firstName: 'immich_first_name', id: 'user-id', @@ -36,16 +37,18 @@ const responseDto = { updatedAt: new Date('2021-01-01'), externalPath: null, memoriesEnabled: true, + inTimeline: true, }, }; describe(PartnerService.name, () => { let sut: PartnerService; let partnerMock: jest.Mocked; + let accessMock: jest.Mocked; beforeEach(async () => { partnerMock = newPartnerRepositoryMock(); - sut = new PartnerService(partnerMock); + sut = new PartnerService(partnerMock, accessMock); }); it('should work', () => { diff --git a/server/src/domain/partner/partner.service.ts b/server/src/domain/partner/partner.service.ts index 797938a8d..93600a5c0 100644 --- a/server/src/domain/partner/partner.service.ts +++ b/server/src/domain/partner/partner.service.ts @@ -1,14 +1,22 @@ import { PartnerEntity } from '@app/infra/entities'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { AccessCore, Permission } from '../access'; import { AuthUserDto } from '../auth'; -import { IPartnerRepository, PartnerDirection, PartnerIds } from '../repositories'; -import { UserResponseDto, mapUser } from '../user'; +import { IAccessRepository, IPartnerRepository, PartnerDirection, PartnerIds } from '../repositories'; +import { mapUser } from '../user'; +import { PartnerResponseDto, UpdatePartnerDto } from './partner.dto'; @Injectable() export class PartnerService { - constructor(@Inject(IPartnerRepository) private repository: IPartnerRepository) {} + private access: AccessCore; + constructor( + @Inject(IPartnerRepository) private repository: IPartnerRepository, + @Inject(IAccessRepository) accessRepository: IAccessRepository, + ) { + this.access = AccessCore.create(accessRepository); + } - async create(authUser: AuthUserDto, sharedWithId: string): Promise { + async create(authUser: AuthUserDto, sharedWithId: string): Promise { const partnerId: PartnerIds = { sharedById: authUser.id, sharedWithId }; const exists = await this.repository.get(partnerId); if (exists) { @@ -29,7 +37,7 @@ export class PartnerService { await this.repository.remove(partner); } - async getAll(authUser: AuthUserDto, direction: PartnerDirection): Promise { + async getAll(authUser: AuthUserDto, direction: PartnerDirection): Promise { const partners = await this.repository.getAll(authUser.id); const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId'; return partners @@ -38,8 +46,22 @@ export class PartnerService { .map((partner) => this.map(partner, direction)); } - private map(partner: PartnerEntity, direction: PartnerDirection): UserResponseDto { + async update(authUser: AuthUserDto, sharedById: string, dto: UpdatePartnerDto): Promise { + await this.access.requirePermission(authUser, Permission.PARTNER_UPDATE, sharedById); + const partnerId: PartnerIds = { sharedById, sharedWithId: authUser.id }; + + const entity = await this.repository.update({ ...partnerId, inTimeline: dto.inTimeline }); + return this.map(entity, PartnerDirection.SharedWith); + } + + private map(partner: PartnerEntity, direction: PartnerDirection): PartnerResponseDto { // this is opposite to return the non-me user of the "partner" - return mapUser(direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy); + const user = mapUser( + direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy, + ) as PartnerResponseDto; + + user.inTimeline = partner.inTimeline; + + return user; } } diff --git a/server/src/domain/repositories/access.repository.ts b/server/src/domain/repositories/access.repository.ts index f9ceb6f52..9c009719d 100644 --- a/server/src/domain/repositories/access.repository.ts +++ b/server/src/domain/repositories/access.repository.ts @@ -35,4 +35,8 @@ export interface IAccessRepository { person: { hasOwnerAccess(userId: string, personId: string): Promise; }; + + partner: { + hasUpdateAccess(userId: string, partnerId: string): Promise; + }; } diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/domain/repositories/asset.repository.ts index da8f8547e..2ceabad35 100644 --- a/server/src/domain/repositories/asset.repository.ts +++ b/server/src/domain/repositories/asset.repository.ts @@ -65,7 +65,7 @@ export interface TimeBucketOptions { isTrashed?: boolean; albumId?: string; personId?: string; - userId?: string; + userIds?: string[]; withStacked?: boolean; } diff --git a/server/src/domain/repositories/partner.repository.ts b/server/src/domain/repositories/partner.repository.ts index fd436932c..f0409b67a 100644 --- a/server/src/domain/repositories/partner.repository.ts +++ b/server/src/domain/repositories/partner.repository.ts @@ -17,4 +17,5 @@ export interface IPartnerRepository { get(partner: PartnerIds): Promise; create(partner: PartnerIds): Promise; remove(entity: PartnerEntity): Promise; + update(entity: Partial): Promise; } diff --git a/server/src/immich/controllers/partner.controller.ts b/server/src/immich/controllers/partner.controller.ts index 0ecec54cd..5f9f004f9 100644 --- a/server/src/immich/controllers/partner.controller.ts +++ b/server/src/immich/controllers/partner.controller.ts @@ -1,5 +1,6 @@ -import { AuthUserDto, PartnerDirection, PartnerService, UserResponseDto } from '@app/domain'; -import { Controller, Delete, Get, Param, Post, Query } from '@nestjs/common'; +import { AuthUserDto, PartnerDirection, PartnerService } from '@app/domain'; +import { PartnerResponseDto, UpdatePartnerDto } from '@app/domain/partner/partner.dto'; +import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common'; import { ApiQuery, ApiTags } from '@nestjs/swagger'; import { AuthUser, Authenticated } from '../app.guard'; import { UseValidation } from '../app.utils'; @@ -17,15 +18,24 @@ export class PartnerController { getPartners( @AuthUser() authUser: AuthUserDto, @Query('direction') direction: PartnerDirection, - ): Promise { + ): Promise { return this.service.getAll(authUser, direction); } @Post(':id') - createPartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { + createPartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.create(authUser, id); } + @Put(':id') + updatePartner( + @AuthUser() authUser: AuthUserDto, + @Param() { id }: UUIDParamDto, + @Body() dto: UpdatePartnerDto, + ): Promise { + return this.service.update(authUser, id, dto); + } + @Delete(':id') removePartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(authUser, id); diff --git a/server/src/infra/entities/partner.entity.ts b/server/src/infra/entities/partner.entity.ts index d7be83082..35d32e4c9 100644 --- a/server/src/infra/entities/partner.entity.ts +++ b/server/src/infra/entities/partner.entity.ts @@ -1,4 +1,4 @@ -import { CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; +import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; import { UserEntity } from './user.entity'; @@ -23,4 +23,7 @@ export class PartnerEntity { @UpdateDateColumn({ type: 'timestamptz' }) updatedAt!: Date; + + @Column({ type: 'boolean', default: false }) + inTimeline!: boolean; } diff --git a/server/src/infra/migrations/1699562570201-AdddInTimelineToPartnersTable.ts b/server/src/infra/migrations/1699562570201-AdddInTimelineToPartnersTable.ts new file mode 100644 index 000000000..59c2f229e --- /dev/null +++ b/server/src/infra/migrations/1699562570201-AdddInTimelineToPartnersTable.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AdddInTimelineToPartnersTable1699562570201 implements MigrationInterface { + name = 'AdddInTimelineToPartnersTable1699562570201' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "partners" ADD "inTimeline" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "partners" DROP COLUMN "inTimeline"`); + } + +} diff --git a/server/src/infra/repositories/access.repository.ts b/server/src/infra/repositories/access.repository.ts index aff498ac3..fa5862885 100644 --- a/server/src/infra/repositories/access.repository.ts +++ b/server/src/infra/repositories/access.repository.ts @@ -249,4 +249,15 @@ export class AccessRepository implements IAccessRepository { }); }, }; + + partner = { + hasUpdateAccess: (userId: string, partnerId: string): Promise => { + return this.partnerRepository.exist({ + where: { + sharedById: partnerId, + sharedWithId: userId, + }, + }); + }, + }; } diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 5112fc9f6..3f0b439a3 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -519,7 +519,7 @@ export class AssetRepository implements IAssetRepository { } private getBuilder(options: TimeBucketOptions) { - const { isArchived, isFavorite, isTrashed, albumId, personId, userId, withStacked } = options; + const { isArchived, isFavorite, isTrashed, albumId, personId, userIds, withStacked } = options; let builder = this.repository .createQueryBuilder('asset') @@ -532,11 +532,11 @@ export class AssetRepository implements IAssetRepository { builder = builder.leftJoin('asset.albums', 'album').andWhere('album.id = :albumId', { albumId }); } - if (userId) { - builder = builder.andWhere('asset.ownerId = :userId', { userId }); + if (userIds) { + builder = builder.andWhere('asset.ownerId IN (:...userIds )', { userIds }); } - if (isArchived != undefined) { + if (isArchived !== undefined) { builder = builder.andWhere('asset.isArchived = :isArchived', { isArchived }); } diff --git a/server/src/infra/repositories/partner.repository.ts b/server/src/infra/repositories/partner.repository.ts index c56d8b075..b5b876558 100644 --- a/server/src/infra/repositories/partner.repository.ts +++ b/server/src/infra/repositories/partner.repository.ts @@ -1,7 +1,7 @@ import { IPartnerRepository, PartnerIds } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { DeepPartial, Repository } from 'typeorm'; import { PartnerEntity } from '../entities'; @Injectable() @@ -16,12 +16,22 @@ export class PartnerRepository implements IPartnerRepository { return this.repository.findOne({ where: { sharedById, sharedWithId } }); } - async create({ sharedById, sharedWithId }: PartnerIds): Promise { - await this.repository.save({ sharedBy: { id: sharedById }, sharedWith: { id: sharedWithId } }); - return this.repository.findOneOrFail({ where: { sharedById, sharedWithId } }); + create({ sharedById, sharedWithId }: PartnerIds): Promise { + return this.save({ sharedBy: { id: sharedById }, sharedWith: { id: sharedWithId } }); } async remove(entity: PartnerEntity): Promise { await this.repository.remove(entity); } + + update(entity: Partial): Promise { + return this.save(entity); + } + + private async save(entity: DeepPartial): Promise { + await this.repository.save(entity); + return this.repository.findOneOrFail({ + where: { sharedById: entity.sharedById, sharedWithId: entity.sharedWithId }, + }); + } } diff --git a/server/test/e2e/asset.e2e-spec.ts b/server/test/e2e/asset.e2e-spec.ts index 7736f085f..70be784c7 100644 --- a/server/test/e2e/asset.e2e-spec.ts +++ b/server/test/e2e/asset.e2e-spec.ts @@ -584,6 +584,52 @@ describe(`${AssetController.name} (e2e)`, () => { ]), ); }); + + it('should return error if time bucket is requested with partners asset and archived', async () => { + const req1 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isArchived: true }); + + expect(req1.status).toBe(400); + expect(req1.body).toEqual(errorStub.badRequest()); + + const req2 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isArchived: undefined }); + + expect(req2.status).toBe(400); + expect(req2.body).toEqual(errorStub.badRequest()); + }); + + it('should return error if time bucket is requested with partners asset and favorite', async () => { + const req1 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isFavorite: true }); + + expect(req1.status).toBe(400); + expect(req1.body).toEqual(errorStub.badRequest()); + + const req2 = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isFavorite: false }); + + expect(req2.status).toBe(400); + expect(req2.body).toEqual(errorStub.badRequest()); + }); + + it('should return error if time bucket is requested with partners asset and trash', async () => { + const req = await request(server) + .get('/asset/time-buckets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ size: TimeBucketSize.MONTH, withPartners: true, isTrashed: true }); + + expect(req.status).toBe(400); + expect(req.body).toEqual(errorStub.badRequest()); + }); }); describe('GET /asset/map-marker', () => { diff --git a/server/test/e2e/partner.e2e-spec.ts b/server/test/e2e/partner.e2e-spec.ts index 82a09dcc8..f6b8e144f 100644 --- a/server/test/e2e/partner.e2e-spec.ts +++ b/server/test/e2e/partner.e2e-spec.ts @@ -115,6 +115,26 @@ describe(`${PartnerController.name} (e2e)`, () => { }); }); + describe('PUT /partner/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(server).put(`/partner/${user2.userId}`); + + expect(status).toBe(401); + expect(body).toEqual(errorStub.unauthorized); + }); + + it('should update partner', async () => { + await repository.create({ sharedById: user2.userId, sharedWithId: user1.userId }); + const { status, body } = await request(server) + .put(`/partner/${user2.userId}`) + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ inTimeline: false }); + + expect(status).toBe(200); + expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: false })); + }); + }); + describe('DELETE /partner/:id', () => { it('should require authentication', async () => { const { status, body } = await request(server).delete(`/partner/${user2.userId}`); diff --git a/server/test/fixtures/partner.stub.ts b/server/test/fixtures/partner.stub.ts index dc54332ea..05c1c67d6 100644 --- a/server/test/fixtures/partner.stub.ts +++ b/server/test/fixtures/partner.stub.ts @@ -9,6 +9,7 @@ export const partnerStub = { sharedBy: userStub.admin, sharedWith: userStub.user1, sharedWithId: userStub.user1.id, + inTimeline: true, }), user1ToAdmin1: Object.freeze({ createdAt: new Date('2023-02-23T05:06:29.716Z'), @@ -17,5 +18,6 @@ export const partnerStub = { sharedById: userStub.user1.id, sharedWithId: userStub.admin.id, sharedWith: userStub.admin, + inTimeline: true, }), }; diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index 6abfc7c9e..8f1e9355d 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -8,6 +8,7 @@ export interface IAccessRepositoryMock { library: jest.Mocked; timeline: jest.Mocked; person: jest.Mocked; + partner: jest.Mocked; } export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => { @@ -50,5 +51,9 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => person: { hasOwnerAccess: jest.fn(), }, + + partner: { + hasUpdateAccess: jest.fn(), + }, }; }; diff --git a/server/test/repositories/partner.repository.mock.ts b/server/test/repositories/partner.repository.mock.ts index a283325d2..1e839ae4f 100644 --- a/server/test/repositories/partner.repository.mock.ts +++ b/server/test/repositories/partner.repository.mock.ts @@ -6,5 +6,6 @@ export const newPartnerRepositoryMock = (): jest.Mocked => { remove: jest.fn(), getAll: jest.fn(), get: jest.fn(), + update: jest.fn(), }; }; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 798d388d7..496fa5441 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2361,6 +2361,103 @@ export interface OAuthConfigResponseDto { */ 'url'?: string; } +/** + * + * @export + * @interface PartnerResponseDto + */ +export interface PartnerResponseDto { + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'createdAt': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'deletedAt': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'email': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'externalPath': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'firstName': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'id': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'inTimeline'?: boolean; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'isAdmin': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'lastName': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'oauthId': string; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'profileImagePath': string; + /** + * + * @type {boolean} + * @memberof PartnerResponseDto + */ + 'shouldChangePassword': boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'storageLabel': string | null; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'updatedAt': string; +} /** * * @export @@ -4220,6 +4317,19 @@ export interface UpdateLibraryDto { */ 'name'?: string; } +/** + * + * @export + * @interface UpdatePartnerDto + */ +export interface UpdatePartnerDto { + /** + * + * @type {boolean} + * @memberof UpdatePartnerDto + */ + 'inTimeline': boolean; +} /** * * @export @@ -7274,11 +7384,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBucket', 'size', size) // verify required parameter 'timeBucket' is not null or undefined @@ -7336,6 +7447,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (timeBucket !== undefined) { localVarQueryParameter['timeBucket'] = timeBucket; } @@ -7365,11 +7480,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'size' is not null or undefined assertParamExists('getTimeBuckets', 'size', size) const localVarPath = `/asset/time-buckets`; @@ -7425,6 +7541,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['withStacked'] = withStacked; } + if (withPartners !== undefined) { + localVarQueryParameter['withPartners'] = withPartners; + } + if (key !== undefined) { localVarQueryParameter['key'] = key; } @@ -8227,12 +8347,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8245,12 +8366,13 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {boolean} [isFavorite] * @param {boolean} [isTrashed] * @param {boolean} [withStacked] + * @param {boolean} [withPartners] * @param {string} [key] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, key, options); + async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, withStacked?: boolean, withPartners?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, withStacked, withPartners, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8547,7 +8669,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * @@ -8556,7 +8678,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @throws {RequiredError} */ getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(axios, basePath)); + return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); }, /** * Get all asset of a device that are in the database, ID only. @@ -9043,6 +9165,13 @@ export interface AssetApiGetTimeBucketRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBucket + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9113,6 +9242,13 @@ export interface AssetApiGetTimeBucketsRequest { */ readonly withStacked?: boolean + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly withPartners?: boolean + /** * * @type {string} @@ -9592,7 +9728,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBucket(requestParameters: AssetApiGetTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9603,7 +9739,7 @@ export class AssetApi extends BaseAPI { * @memberof AssetApi */ public getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } /** @@ -12312,6 +12448,54 @@ export const PartnerApiAxiosParamCreator = function (configuration?: Configurati let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner: async (id: string, updatePartnerDto: UpdatePartnerDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('updatePartner', 'id', id) + // verify required parameter 'updatePartnerDto' is not null or undefined + assertParamExists('updatePartner', 'updatePartnerDto', updatePartnerDto) + const localVarPath = `/partner/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(updatePartnerDto, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -12333,7 +12517,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async createPartner(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.createPartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12343,7 +12527,7 @@ export const PartnerApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + async getPartners(direction: 'shared-by' | 'shared-with', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { const localVarAxiosArgs = await localVarAxiosParamCreator.getPartners(direction, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -12357,6 +12541,17 @@ export const PartnerApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.removePartner(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id + * @param {UpdatePartnerDto} updatePartnerDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updatePartner(id: string, updatePartnerDto: UpdatePartnerDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updatePartner(id, updatePartnerDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -12373,7 +12568,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + createPartner(requestParameters: PartnerApiCreatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.createPartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, /** @@ -12382,7 +12577,7 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { + getPartners(requestParameters: PartnerApiGetPartnersRequest, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getPartners(requestParameters.direction, options).then((request) => request(axios, basePath)); }, /** @@ -12394,6 +12589,15 @@ export const PartnerApiFactory = function (configuration?: Configuration, basePa removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.removePartner(requestParameters.id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(axios, basePath)); + }, }; }; @@ -12439,6 +12643,27 @@ export interface PartnerApiRemovePartnerRequest { readonly id: string } +/** + * Request parameters for updatePartner operation in PartnerApi. + * @export + * @interface PartnerApiUpdatePartnerRequest + */ +export interface PartnerApiUpdatePartnerRequest { + /** + * + * @type {string} + * @memberof PartnerApiUpdatePartner + */ + readonly id: string + + /** + * + * @type {UpdatePartnerDto} + * @memberof PartnerApiUpdatePartner + */ + readonly updatePartnerDto: UpdatePartnerDto +} + /** * PartnerApi - object-oriented interface * @export @@ -12478,6 +12703,17 @@ export class PartnerApi extends BaseAPI { public removePartner(requestParameters: PartnerApiRemovePartnerRequest, options?: AxiosRequestConfig) { return PartnerApiFp(this.configuration).removePartner(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {PartnerApiUpdatePartnerRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PartnerApi + */ + public updatePartner(requestParameters: PartnerApiUpdatePartnerRequest, options?: AxiosRequestConfig) { + return PartnerApiFp(this.configuration).updatePartner(requestParameters.id, requestParameters.updatePartnerDto, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 2333a55f0..5b10ab1e8 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -97,8 +97,12 @@

- {#if $isMultiSelectState} - assetInteractionStore.clearMultiselect()}> + {#if $isMultiSelectState && user} + assetInteractionStore.clearMultiselect()} + > {#if sharedLink.allowDownload} diff --git a/web/src/lib/components/photos-page/actions/archive-action.svelte b/web/src/lib/components/photos-page/actions/archive-action.svelte index dff6f908a..a72bc106a 100644 --- a/web/src/lib/components/photos-page/actions/archive-action.svelte +++ b/web/src/lib/components/photos-page/actions/archive-action.svelte @@ -20,14 +20,14 @@ let loading = false; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const handleArchive = async () => { const isArchived = !unarchive; loading = true; try { - const assets = Array.from(getAssets()).filter((asset) => asset.isArchived !== isArchived); + const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isArchived !== isArchived); const ids = assets.map(({ id }) => id); if (ids.length > 0) { diff --git a/web/src/lib/components/photos-page/actions/asset-job-actions.svelte b/web/src/lib/components/photos-page/actions/asset-job-actions.svelte index 3e678cdf0..296197a71 100644 --- a/web/src/lib/components/photos-page/actions/asset-job-actions.svelte +++ b/web/src/lib/components/photos-page/actions/asset-job-actions.svelte @@ -14,13 +14,13 @@ AssetJobName.TranscodeVideo, ]; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); - $: isAllVideos = Array.from(getAssets()).every((asset) => asset.type === AssetTypeEnum.Video); + $: isAllVideos = Array.from(getOwnedAssets()).every((asset) => asset.type === AssetTypeEnum.Video); const handleRunJob = async (name: AssetJobName) => { try { - const ids = Array.from(getAssets()).map(({ id }) => id); + const ids = Array.from(getOwnedAssets()).map(({ id }) => id); await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: ids, name } }); notificationController.show({ message: api.getAssetJobMessage(name), type: NotificationType.Info }); clearSelect(); diff --git a/web/src/lib/components/photos-page/actions/delete-assets.svelte b/web/src/lib/components/photos-page/actions/delete-assets.svelte index c5461b2e0..68d133ae9 100644 --- a/web/src/lib/components/photos-page/actions/delete-assets.svelte +++ b/web/src/lib/components/photos-page/actions/delete-assets.svelte @@ -17,7 +17,7 @@ export let menuItem = false; export let force = !$featureFlags.trash; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const dispatch = createEventDispatcher(); @@ -37,7 +37,7 @@ loading = true; try { - const ids = Array.from(getAssets()) + const ids = Array.from(getOwnedAssets()) .filter((a) => !a.isExternal) .map((a) => a.id); await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } }); @@ -75,7 +75,7 @@ {#if isShowConfirmation} (isShowConfirmation = false)} @@ -84,8 +84,8 @@

Are you sure you want to permanently delete - {#if getAssets().size > 1} - these {getAssets().size} assets? This will also remove them from their album(s). + {#if getOwnedAssets().size > 1} + these {getOwnedAssets().size} assets? This will also remove them from their album(s). {:else} this asset? This will also remove it from its album(s). {/if} diff --git a/web/src/lib/components/photos-page/actions/favorite-action.svelte b/web/src/lib/components/photos-page/actions/favorite-action.svelte index 218a75d31..b246670f6 100644 --- a/web/src/lib/components/photos-page/actions/favorite-action.svelte +++ b/web/src/lib/components/photos-page/actions/favorite-action.svelte @@ -20,14 +20,15 @@ let loading = false; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const handleFavorite = async () => { const isFavorite = !removeFavorite; loading = true; try { - const assets = Array.from(getAssets()).filter((asset) => asset.isFavorite !== isFavorite); + const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isFavorite !== isFavorite); + const ids = assets.map(({ id }) => id); if (ids.length > 0) { diff --git a/web/src/lib/components/photos-page/actions/stack-action.svelte b/web/src/lib/components/photos-page/actions/stack-action.svelte index 876379756..c622998fe 100644 --- a/web/src/lib/components/photos-page/actions/stack-action.svelte +++ b/web/src/lib/components/photos-page/actions/stack-action.svelte @@ -10,11 +10,11 @@ export let onStack: OnStack | undefined = undefined; - const { getAssets, clearSelect } = getAssetControlContext(); + const { clearSelect, getOwnedAssets } = getAssetControlContext(); const handleStack = async () => { try { - const assets = Array.from(getAssets()); + const assets = Array.from(getOwnedAssets()); const parent = assets.at(0); if (parent == undefined) { diff --git a/web/src/lib/components/photos-page/asset-select-control-bar.svelte b/web/src/lib/components/photos-page/asset-select-control-bar.svelte index bc6fb823c..5ce4a9ebd 100644 --- a/web/src/lib/components/photos-page/asset-select-control-bar.svelte +++ b/web/src/lib/components/photos-page/asset-select-control-bar.svelte @@ -9,7 +9,8 @@ export interface AssetControlContext { // Wrap assets in a function, because context isn't reactive. - getAssets: () => Set; + getAssets: () => Set; // All assets includes partners' assets + getOwnedAssets: () => Set; // Only assets owned by the user clearSelect: () => void; } @@ -25,8 +26,14 @@ export let assets: Set; export let clearSelect: () => void; + export let ownerId: string | undefined = undefined; - setContext({ getAssets: () => assets, clearSelect }); + setContext({ + getAssets: () => assets, + getOwnedAssets: () => + ownerId !== undefined ? new Set(Array.from(assets).filter((asset) => asset.ownerId === ownerId)) : assets, + clearSelect, + }); diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte index aabe911ef..ee5fe8a09 100644 --- a/web/src/lib/components/user-settings-page/partner-settings.svelte +++ b/web/src/lib/components/user-settings-page/partner-settings.svelte @@ -1,22 +1,71 @@

{#if partners.length > 0} -
- {#each partners as partner (partner.id)} -
- -
-

- {partner.firstName} - {partner.lastName} -

-

- {partner.email} -

+ {#each partners as partner (partner.user.id)} +
+
+
+ +
+

+ {partner.user.firstName} + {partner.user.lastName} +

+

+ {partner.user.email} +

+
- (removePartner = partner)} - icon={mdiClose} - size={'16'} - title="Remove partner" - /> + + {#if partner.sharedByMe} + (removePartner = partner.user)} + icon={mdiClose} + size={'16'} + title="Stop sharing your photos with this user" + /> + {/if}
- {/each} -
+ +
+ + {#if partner.sharedByMe} +
+

SHARED WITH {partner.user.firstName.toUpperCase()}

+

{partner.user.firstName} can access

+
    +
  • + All your photos and videos except those in Archived and Deleted +
  • +
  • + The location where your photos were taken +
  • +
+ {/if} + + + {#if partner.sharedWithMe} +
+

PHOTOS FROM {partner.user.firstName.toUpperCase()}

+ handleShowOnTimelineChanged(partner, detail)} + /> + {/if} +
+
+ {/each} {/if} -
+ +
diff --git a/web/src/lib/components/user-settings-page/user-settings-list.svelte b/web/src/lib/components/user-settings-page/user-settings-list.svelte index 3a90da0b3..6bce7ac0e 100644 --- a/web/src/lib/components/user-settings-page/user-settings-list.svelte +++ b/web/src/lib/components/user-settings-page/user-settings-list.svelte @@ -18,7 +18,6 @@ export let keys: APIKeyResponseDto[] = []; export let devices: AuthDeviceResponseDto[] = []; - export let partners: UserResponseDto[] = []; let oauthOpen = false; if (browser) { @@ -61,7 +60,7 @@ - + diff --git a/web/src/routes/(user)/partners/[userId]/+page.svelte b/web/src/routes/(user)/partners/[userId]/+page.svelte index a52303d83..9beec5b8d 100644 --- a/web/src/routes/(user)/partners/[userId]/+page.svelte +++ b/web/src/routes/(user)/partners/[userId]/+page.svelte @@ -16,7 +16,7 @@ export let data: PageData; - const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false }); + const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false, withStacked: true }); const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index fb0f9f969..7515d747b 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -26,7 +26,7 @@ let { isViewing: showAssetViewer } = assetViewingStore; let handleEscapeKey = false; - const assetStore = new AssetStore({ isArchived: false, withStacked: true }); + const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true }); const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; @@ -48,7 +48,11 @@ {#if $isMultiSelectState} - assetInteractionStore.clearMultiselect()}> + assetInteractionStore.clearMultiselect()} + > (handleEscapeKey = true)} /> diff --git a/web/src/routes/(user)/user-settings/+page.server.ts b/web/src/routes/(user)/user-settings/+page.server.ts index a6aa5e15a..346582638 100644 --- a/web/src/routes/(user)/user-settings/+page.server.ts +++ b/web/src/routes/(user)/user-settings/+page.server.ts @@ -10,13 +10,11 @@ export const load = (async ({ parent, locals }) => { const { data: keys } = await locals.api.keyApi.getApiKeys(); const { data: devices } = await locals.api.authenticationApi.getAuthDevices(); - const { data: partners } = await locals.api.partnerApi.getPartners({ direction: 'shared-by' }); return { user, keys, devices, - partners, meta: { title: 'Settings', }, diff --git a/web/src/routes/(user)/user-settings/+page.svelte b/web/src/routes/(user)/user-settings/+page.svelte index d63a28e90..32d95a18d 100644 --- a/web/src/routes/(user)/user-settings/+page.svelte +++ b/web/src/routes/(user)/user-settings/+page.svelte @@ -9,7 +9,7 @@
- +
From 26fd797ac99552b936871bdcfaaef755c5c5cf0c Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Sat, 11 Nov 2023 23:31:58 +0100 Subject: [PATCH 02/88] chore: re-enable renovate (#4955) * Chore: reenable renovate * chore(renovate): Don't group major updates * chore(renovate): Use matchFileNames --- renovate.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/renovate.json b/renovate.json index a379beb13..f5ad25da7 100644 --- a/renovate.json +++ b/renovate.json @@ -3,17 +3,19 @@ "extends": ["config:base"], "packageRules": [ { - "matchPaths": ["mobile"], - "groupName": "mobile" + "matchFileNames": ["mobile/**"], + "groupName": "mobile", + "matchUpdateTypes": ["minor", "patch"] }, { - "matchPaths": ["server"], - "groupName": "server" + "matchFileNames": ["server/**"], + "groupName": "server", + "matchUpdateTypes": ["minor", "patch"] }, { - "matchPaths": ["web"], - "groupName": "web" + "matchFileNames": ["web/**"], + "groupName": "web", + "matchUpdateTypes": ["minor", "patch"] } ], - "enabled": false } From ad4cbf20de9c6adda7edf46c38e57fba4eb0285e Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Sat, 11 Nov 2023 23:32:39 +0100 Subject: [PATCH 03/88] refactor: map (#4957) * fix mixed up lng lat for asset map * minor cleanup * update packages --- server/assets/style-dark.json | 1 - server/assets/style-light.json | 1 - web/package-lock.json | 48 +++++++++---------- web/package.json | 4 +- .../shared-components/map/map.svelte | 15 +++--- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/server/assets/style-dark.json b/server/assets/style-dark.json index 43ddb8c1c..9c4d39c6f 100644 --- a/server/assets/style-dark.json +++ b/server/assets/style-dark.json @@ -1,7 +1,6 @@ { "version": 8, "name": "Immich Map", - "metadata": { "maputnik:renderer": "mbgljs" }, "sources": { "immich-map": { "type": "vector", diff --git a/server/assets/style-light.json b/server/assets/style-light.json index ac372d4ca..7d4124f22 100644 --- a/server/assets/style-light.json +++ b/server/assets/style-light.json @@ -1,7 +1,6 @@ { "version": 8, "name": "Immich Map", - "metadata": { "maputnik:renderer": "mbgljs" }, "sources": { "immich-map": { "type": "vector", diff --git a/web/package-lock.json b/web/package-lock.json index 408cf8563..a381627d8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -19,11 +19,11 @@ "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.2.1", - "maplibre-gl": "^3.5.2", + "maplibre-gl": "^3.6.0", "socket.io-client": "^4.6.1", "svelte-loading-spinners": "^0.3.4", "svelte-local-storage-store": "^0.5.0", - "svelte-maplibre": "^0.6.0", + "svelte-maplibre": "^0.7.0", "thumbhash": "^0.1.1" }, "devDependencies": { @@ -3490,9 +3490,9 @@ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" }, "node_modules/@types/geojson": { - "version": "7946.0.12", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.12.tgz", - "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA==" + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" }, "node_modules/@types/graceful-fs": { "version": "4.1.7", @@ -3614,14 +3614,14 @@ "dev": true }, "node_modules/@types/mapbox__point-geometry": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.3.tgz", - "integrity": "sha512-2W46IOXlu7vC8m3+M5rDqSnuY22GFxxx3xhkoyqyPWrD+eP2iAwNst0A1+umLYjCTJMJTSpiofphn9h9k+Kw+w==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==" }, "node_modules/@types/mapbox__vector-tile": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.3.tgz", - "integrity": "sha512-d263B3KCQtXKVZMHpMJrEW5EeLBsQ8jvAS9nhpUKC5hHIlQaACG9PWkW8qxEeNuceo9120AwPjeS91uNa4ltqA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", @@ -3635,9 +3635,9 @@ "dev": true }, "node_modules/@types/pbf": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.4.tgz", - "integrity": "sha512-SOFlLGZkLbEXJRwcWCqeP/Koyaf/uAqLXHUsdo/nMfjLsNd8kqauwHe9GBOljSmpcHp/LC6kOjo3SidGjNirVA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" }, "node_modules/@types/pug": { "version": "2.0.7", @@ -3664,9 +3664,9 @@ "dev": true }, "node_modules/@types/supercluster": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.2.tgz", - "integrity": "sha512-qMhofL945Z4njQUuntadexAgPtpiBC014WvVqU70Prj42LC77Xgmz04us7hSMmwjs7KbgAwGBmje+FSOvDbP0Q==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", "dependencies": { "@types/geojson": "*" } @@ -9587,9 +9587,9 @@ } }, "node_modules/maplibre-gl": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.5.2.tgz", - "integrity": "sha512-deqYA/RiEyXMGroZMDbOWNQTLnFsxREC+mDkQnuyCUNdBWm1KHafsXJYZP7rlLa5RLQNq05IAUAizY9aHTpIUw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-3.6.0.tgz", + "integrity": "sha512-l+jBu+bMy96FOV4em7FgjMH77ewlOtLPXLAem/Q44y4+0vTGsJvPksJSoLoedmikcSff2QN20VZFo3+Zg0UJPQ==", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", @@ -11480,14 +11480,14 @@ } }, "node_modules/svelte-maplibre": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-0.6.0.tgz", - "integrity": "sha512-/iTUkJtJpAH22aXAkEysDhWQa6d8J+WpvxenU1k6r5CASVGc4TyYLe7kQt3CkhlNSIP05EpkADvJJMeUMcnbvw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-0.7.0.tgz", + "integrity": "sha512-8Mm3MEr0mCq4en5ZmuemCxnv82ljd4mNzTt/pC+X3CTKEcfoVyJgr2PaDu8Znu3DOxUR378XBlG1z5Dw3amnvA==", "dependencies": { "d3-geo": "^3.1.0", "just-compare": "^2.3.0", "just-flush": "^2.3.0", - "maplibre-gl": "^3.0.0", + "maplibre-gl": "^3.5.0", "pmtiles": "^2.10.0" }, "peerDependencies": { diff --git a/web/package.json b/web/package.json index 1a4c6521a..fe618948c 100644 --- a/web/package.json +++ b/web/package.json @@ -70,11 +70,11 @@ "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", "luxon": "^3.2.1", - "maplibre-gl": "^3.5.2", + "maplibre-gl": "^3.6.0", "socket.io-client": "^4.6.1", "svelte-loading-spinners": "^0.3.4", "svelte-local-storage-store": "^0.5.0", - "svelte-maplibre": "^0.6.0", + "svelte-maplibre": "^0.7.0", "thumbhash": "^0.1.1" } } diff --git a/web/src/lib/components/shared-components/map/map.svelte b/web/src/lib/components/shared-components/map/map.svelte index 6501e89ea..b806c60ce 100644 --- a/web/src/lib/components/shared-components/map/map.svelte +++ b/web/src/lib/components/shared-components/map/map.svelte @@ -14,9 +14,9 @@ ScaleControl, Popup, } from 'svelte-maplibre'; - import { mapSettings } from '$lib/stores/preferences.store'; + import { colorTheme, mapSettings } from '$lib/stores/preferences.store'; import { MapMarkerResponseDto, api } from '@api'; - import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl'; + import { LngLat, type GeoJSONSource, type LngLatLike, type StyleSpecification } from 'maplibre-gl'; import type { Feature, Geometry, GeoJsonProperties, Point } from 'geojson'; import Icon from '$lib/components/elements/icon.svelte'; import { mdiCog } from '@mdi/js'; @@ -29,7 +29,9 @@ export let simplified = false; $: style = (async () => { - const { data } = await api.systemConfigApi.getMapStyle({ theme: $mapSettings.allowDarkMode ? 'dark' : 'light' }); + const { data } = await api.systemConfigApi.getMapStyle({ + theme: $mapSettings.allowDarkMode ? $colorTheme : 'light', + }); return data as StyleSpecification; })(); @@ -74,16 +76,17 @@ const asMarker = (feature: Feature): MapMarkerResponseDto => { const featurePoint = feature as FeaturePoint; + const coords = LngLat.convert(featurePoint.geometry.coordinates as [number, number]); return { - lat: featurePoint.geometry.coordinates[0], - lon: featurePoint.geometry.coordinates[1], + lat: coords.lat, + lon: coords.lng, id: featurePoint.properties.id, }; }; {#await style then style} - + {#if !simplified} From a9b6acec286a3cca28f7fbcab21ab03c6c095645 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 16:54:55 -0600 Subject: [PATCH 04/88] chore(deps): update ghcr.io/nginxinc/nginx-unprivileged:1.25.0-alpine3.17 docker digest to 5ebb90a (#4960) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- nginx/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 3b8e6f333..a64dcfb2c 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/nginxinc/nginx-unprivileged:1.25.0-alpine3.17@sha256:e57300e9f60e521c5af3ec8fdc710285a371647e8033bcb8a36020c4394db3e3 +FROM ghcr.io/nginxinc/nginx-unprivileged:1.25.0-alpine3.17@sha256:5ebb90a0dd5ce841d8527abcfee4081a48de86560cd26dc64a6b1212ef59bf36 COPY LICENSE /licenses/LICENSE.txt COPY LICENSE /LICENSE From 394e0dfe37009160635b39926af256fe9fe2c6b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:58:01 -0500 Subject: [PATCH 05/88] chore(deps): update redis:6.2-alpine docker digest to 3995fe6 (#4964) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.dev.yml | 2 +- docker/docker-compose.prod.yml | 2 +- docker/docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index ba03a1822..df2334927 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -123,7 +123,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3 + image: redis:6.2-alpine@sha256:3995fe6ea6a619313e31046bd3c8643f9e70f8f2b294ff82659d409b47d06abb database: container_name: immich_postgres diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index d9e33b020..3982f8658 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -79,7 +79,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3 + image: redis:6.2-alpine@sha256:3995fe6ea6a619313e31046bd3c8643f9e70f8f2b294ff82659d409b47d06abb restart: always database: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2ea9e18fa..efd009fa1 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -66,7 +66,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3 + image: redis:6.2-alpine@sha256:3995fe6ea6a619313e31046bd3c8643f9e70f8f2b294ff82659d409b47d06abb restart: always database: From 413ab2c538907f1bbdb9c5ff6dbf4115914e9a7a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:58:46 -0500 Subject: [PATCH 06/88] chore(deps): update postgres:14-alpine docker digest to 874f566 (#4963) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.dev.yml | 2 +- docker/docker-compose.prod.yml | 2 +- docker/docker-compose.test.yml | 2 +- docker/docker-compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index df2334927..a00a24add 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -127,7 +127,7 @@ services: database: container_name: immich_postgres - image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441 + image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92 env_file: - .env environment: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 3982f8658..0f2a2a49f 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -84,7 +84,7 @@ services: database: container_name: immich_postgres - image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441 + image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92 env_file: - .env environment: diff --git a/docker/docker-compose.test.yml b/docker/docker-compose.test.yml index df965aa1f..b88be11ce 100644 --- a/docker/docker-compose.test.yml +++ b/docker/docker-compose.test.yml @@ -23,7 +23,7 @@ services: - database database: - image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441 + image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92 command: -c fsync=off environment: POSTGRES_PASSWORD: postgres diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index efd009fa1..1669d4b39 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -71,7 +71,7 @@ services: database: container_name: immich_postgres - image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441 + image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92 env_file: - .env environment: From 7fca0d8da5e56f626a1e08c5a66843b2ce717236 Mon Sep 17 00:00:00 2001 From: Brian Austin <13002992+brianjaustin@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:03:32 -0500 Subject: [PATCH 07/88] fix: replace first and last name with single field (#4915) --- cli/src/api/open-api/api.ts | 104 +--- docs/docs/administration/server-commands.md | 3 +- mobile/assets/i18n/en-US.json | 2 +- .../activities/models/activity.model.dart | 3 +- .../activities/views/activities_page.dart | 2 +- .../album/views/album_options_part.dart | 4 +- .../models/authentication_state.model.dart | 20 +- .../providers/authentication.provider.dart | 9 +- .../login/ui/change_password_form.dart | 3 +- .../lib/modules/partner/ui/partner_list.dart | 2 +- .../partner/views/partner_detail_page.dart | 4 +- .../modules/partner/views/partner_page.dart | 4 +- mobile/lib/shared/models/album.dart | 7 +- mobile/lib/shared/models/user.dart | 18 +- mobile/lib/shared/models/user.g.dart | 582 ++++++------------ .../app_bar_dialog/app_bar_profile_info.dart | 2 +- mobile/lib/shared/ui/user_avatar.dart | 5 +- mobile/lib/shared/ui/user_circle_avatar.dart | 2 +- mobile/openapi/doc/CreateUserDto.md | 3 +- mobile/openapi/doc/LoginResponseDto.md | 3 +- mobile/openapi/doc/PartnerResponseDto.md | 3 +- mobile/openapi/doc/SignUpDto.md | 3 +- mobile/openapi/doc/UpdateUserDto.md | 3 +- mobile/openapi/doc/UsageByUserDto.md | 3 +- mobile/openapi/doc/UserDto.md | 3 +- mobile/openapi/doc/UserResponseDto.md | 3 +- mobile/openapi/lib/model/create_user_dto.dart | 26 +- .../openapi/lib/model/login_response_dto.dart | 24 +- .../lib/model/partner_response_dto.dart | 26 +- mobile/openapi/lib/model/sign_up_dto.dart | 24 +- mobile/openapi/lib/model/update_user_dto.dart | 41 +- .../openapi/lib/model/usage_by_user_dto.dart | 24 +- mobile/openapi/lib/model/user_dto.dart | 24 +- .../openapi/lib/model/user_response_dto.dart | 26 +- mobile/openapi/test/create_user_dto_test.dart | 15 +- .../openapi/test/login_response_dto_test.dart | 9 +- .../test/partner_response_dto_test.dart | 13 +- mobile/openapi/test/sign_up_dto_test.dart | 9 +- mobile/openapi/test/update_user_dto_test.dart | 13 +- .../openapi/test/usage_by_user_dto_test.dart | 9 +- mobile/openapi/test/user_dto_test.dart | 9 +- .../openapi/test/user_response_dto_test.dart | 13 +- mobile/test/sync_service_test.dart | 3 +- server/immich-openapi-specs.json | 78 +-- server/package-lock.json | 5 - .../commands/reset-admin-password.command.ts | 4 +- server/src/domain/auth/auth.dto.ts | 13 +- server/src/domain/auth/auth.service.spec.ts | 5 +- server/src/domain/auth/auth.service.ts | 7 +- .../domain/partner/partner.service.spec.ts | 6 +- .../domain/repositories/user.repository.ts | 3 +- .../src/domain/server-info/server-info.dto.ts | 4 +- .../server-info/server-info.service.spec.ts | 18 +- .../domain/server-info/server-info.service.ts | 3 +- .../domain/user/dto/create-user.dto.spec.ts | 12 +- server/src/domain/user/dto/create-user.dto.ts | 15 +- server/src/domain/user/dto/update-user.dto.ts | 7 +- .../user/response-dto/user-response.dto.ts | 6 +- server/src/domain/user/user.service.spec.ts | 9 +- server/src/infra/entities/user.entity.ts | 5 +- .../1699322864544-UserNameConsolidation.ts | 21 + .../src/infra/repositories/user.repository.ts | 3 +- server/test/e2e/activity.e2e-spec.ts | 9 +- server/test/e2e/album.e2e-spec.ts | 6 +- server/test/e2e/asset.e2e-spec.ts | 6 +- server/test/e2e/auth.e2e-spec.ts | 20 +- server/test/e2e/library.e2e-spec.ts | 6 +- server/test/e2e/partner.e2e-spec.ts | 6 +- server/test/e2e/server-info.e2e-spec.ts | 5 +- server/test/e2e/shared-link.e2e-spec.ts | 3 +- server/test/e2e/system-config.e2e-spec.ts | 3 +- server/test/e2e/user.e2e-spec.ts | 24 +- server/test/fixtures/auth.stub.ts | 15 +- server/test/fixtures/user.stub.ts | 21 +- web/src/api/open-api/api.ts | 104 +--- .../admin-page/delete-confirm-dialoge.svelte | 2 +- .../admin-page/restore-dialoge.svelte | 2 +- .../server-stats/server-stats-panel.svelte | 2 +- .../components/album-page/album-card.svelte | 3 +- .../album-page/album-options.svelte | 4 +- .../album-page/share-info-modal.svelte | 8 +- .../album-page/user-selection-modal.svelte | 5 +- .../asset-viewer/activity-viewer.svelte | 9 +- .../asset-viewer/detail-panel.svelte | 3 +- .../forms/admin-registration-form.svelte | 15 +- .../components/forms/create-user-form.svelte | 15 +- .../components/forms/edit-user-form.svelte | 23 +- .../navigation-bar/account-info-panel.svelte | 3 +- .../navigation-bar/navigation-bar.svelte | 2 +- .../shared-components/user-avatar.svelte | 7 +- .../partner-selection-modal.svelte | 3 +- .../partner-settings.svelte | 11 +- .../user-profile-settings.svelte | 17 +- .../(user)/partners/[userId]/+page.svelte | 3 +- web/src/routes/(user)/sharing/+page.svelte | 3 +- .../routes/admin/user-management/+page.svelte | 8 +- .../routes/auth/change-password/+page.svelte | 3 +- web/src/test-data/factories/user-factory.ts | 3 +- 98 files changed, 567 insertions(+), 1147 deletions(-) create mode 100644 server/src/infra/migrations/1699322864544-UserNameConsolidation.ts diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 496fa5441..8fb2c1b3d 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -1341,24 +1341,18 @@ export interface CreateUserDto { * @memberof CreateUserDto */ 'externalPath'?: string | null; - /** - * - * @type {string} - * @memberof CreateUserDto - */ - 'firstName': string; - /** - * - * @type {string} - * @memberof CreateUserDto - */ - 'lastName': string; /** * * @type {boolean} * @memberof CreateUserDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof CreateUserDto + */ + 'name': string; /** * * @type {string} @@ -2137,12 +2131,6 @@ export interface LoginResponseDto { * @memberof LoginResponseDto */ 'accessToken': string; - /** - * - * @type {string} - * @memberof LoginResponseDto - */ - 'firstName': string; /** * * @type {boolean} @@ -2154,7 +2142,7 @@ export interface LoginResponseDto { * @type {string} * @memberof LoginResponseDto */ - 'lastName': string; + 'name': string; /** * * @type {string} @@ -2391,12 +2379,6 @@ export interface PartnerResponseDto { * @memberof PartnerResponseDto */ 'externalPath': string | null; - /** - * - * @type {string} - * @memberof PartnerResponseDto - */ - 'firstName': string; /** * * @type {string} @@ -2415,18 +2397,18 @@ export interface PartnerResponseDto { * @memberof PartnerResponseDto */ 'isAdmin': boolean; - /** - * - * @type {string} - * @memberof PartnerResponseDto - */ - 'lastName': string; /** * * @type {boolean} * @memberof PartnerResponseDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'name': string; /** * * @type {string} @@ -3431,13 +3413,7 @@ export interface SignUpDto { * @type {string} * @memberof SignUpDto */ - 'firstName': string; - /** - * - * @type {string} - * @memberof SignUpDto - */ - 'lastName': string; + 'name': string; /** * * @type {string} @@ -4380,12 +4356,6 @@ export interface UpdateUserDto { * @memberof UpdateUserDto */ 'externalPath'?: string; - /** - * - * @type {string} - * @memberof UpdateUserDto - */ - 'firstName'?: string; /** * * @type {string} @@ -4398,18 +4368,18 @@ export interface UpdateUserDto { * @memberof UpdateUserDto */ 'isAdmin'?: boolean; - /** - * - * @type {string} - * @memberof UpdateUserDto - */ - 'lastName'?: string; /** * * @type {boolean} * @memberof UpdateUserDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof UpdateUserDto + */ + 'name'?: string; /** * * @type {string} @@ -4447,12 +4417,6 @@ export interface UsageByUserDto { * @memberof UsageByUserDto */ 'usage': number; - /** - * - * @type {string} - * @memberof UsageByUserDto - */ - 'userFirstName': string; /** * * @type {string} @@ -4464,7 +4428,7 @@ export interface UsageByUserDto { * @type {string} * @memberof UsageByUserDto */ - 'userLastName': string; + 'userName': string; /** * * @type {number} @@ -4484,12 +4448,6 @@ export interface UserDto { * @memberof UserDto */ 'email': string; - /** - * - * @type {string} - * @memberof UserDto - */ - 'firstName': string; /** * * @type {string} @@ -4501,7 +4459,7 @@ export interface UserDto { * @type {string} * @memberof UserDto */ - 'lastName': string; + 'name': string; /** * * @type {string} @@ -4539,12 +4497,6 @@ export interface UserResponseDto { * @memberof UserResponseDto */ 'externalPath': string | null; - /** - * - * @type {string} - * @memberof UserResponseDto - */ - 'firstName': string; /** * * @type {string} @@ -4557,18 +4509,18 @@ export interface UserResponseDto { * @memberof UserResponseDto */ 'isAdmin': boolean; - /** - * - * @type {string} - * @memberof UserResponseDto - */ - 'lastName': string; /** * * @type {boolean} * @memberof UserResponseDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof UserResponseDto + */ + 'name': string; /** * * @type {string} diff --git a/docs/docs/administration/server-commands.md b/docs/docs/administration/server-commands.md index d8cd2119d..fc235f5a5 100644 --- a/docs/docs/administration/server-commands.md +++ b/docs/docs/administration/server-commands.md @@ -51,8 +51,7 @@ immich-admin list-users { id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53', email: 'immich@example.com.com', - firstName: 'Immich', - lastName: 'Admin', + name: 'Immich Admin', storageLabel: 'admin', externalPath: null, profileImagePath: 'upload/profile/e65e6f88-2a30-4dbe-8dd9-1885f4889b53/e65e6f88-2a30-4dbe-8dd9-1885f4889b53.jpg', diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index ccb4195c3..5a176ab96 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -114,7 +114,7 @@ "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", "cache_settings_title": "Caching Settings", "change_password_form_confirm_password": "Confirm Password", - "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", + "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_new_password": "New Password", "change_password_form_password_mismatch": "Passwords do not match", "change_password_form_reenter_new_password": "Re-enter New Password", diff --git a/mobile/lib/modules/activities/models/activity.model.dart b/mobile/lib/modules/activities/models/activity.model.dart index 417ba4a86..2db626f54 100644 --- a/mobile/lib/modules/activities/models/activity.model.dart +++ b/mobile/lib/modules/activities/models/activity.model.dart @@ -48,8 +48,7 @@ class Activity { : ActivityType.like, user = User( email: dto.user.email, - firstName: dto.user.firstName, - lastName: dto.user.lastName, + name: dto.user.name, profileImagePath: dto.user.profileImagePath, id: dto.user.id, // Placeholder values diff --git a/mobile/lib/modules/activities/views/activities_page.dart b/mobile/lib/modules/activities/views/activities_page.dart index b744fc476..1cfd48b5b 100644 --- a/mobile/lib/modules/activities/views/activities_page.dart +++ b/mobile/lib/modules/activities/views/activities_page.dart @@ -61,7 +61,7 @@ class ActivitiesPage extends HookConsumerWidget { mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max, children: [ Text( - "${activity.user.firstName} ${activity.user.lastName}", + activity.user.name, style: textStyle, overflow: TextOverflow.ellipsis, ), diff --git a/mobile/lib/modules/album/views/album_options_part.dart b/mobile/lib/modules/album/views/album_options_part.dart index 88f0d7d3c..8d45d36b4 100644 --- a/mobile/lib/modules/album/views/album_options_part.dart +++ b/mobile/lib/modules/album/views/album_options_part.dart @@ -124,7 +124,7 @@ class AlbumOptionsPage extends HookConsumerWidget { ) : const SizedBox(), title: Text( - album.owner.value?.firstName ?? "", + album.owner.value?.name ?? "", style: const TextStyle( fontWeight: FontWeight.bold, ), @@ -155,7 +155,7 @@ class AlbumOptionsPage extends HookConsumerWidget { radius: 22, ), title: Text( - user.firstName, + user.name, style: const TextStyle( fontWeight: FontWeight.bold, ), diff --git a/mobile/lib/modules/login/models/authentication_state.model.dart b/mobile/lib/modules/login/models/authentication_state.model.dart index 648670ca6..9dcd320c8 100644 --- a/mobile/lib/modules/login/models/authentication_state.model.dart +++ b/mobile/lib/modules/login/models/authentication_state.model.dart @@ -3,8 +3,7 @@ class AuthenticationState { final String userId; final String userEmail; final bool isAuthenticated; - final String firstName; - final String lastName; + final String name; final bool isAdmin; final bool shouldChangePassword; final String profileImagePath; @@ -13,8 +12,7 @@ class AuthenticationState { required this.userId, required this.userEmail, required this.isAuthenticated, - required this.firstName, - required this.lastName, + required this.name, required this.isAdmin, required this.shouldChangePassword, required this.profileImagePath, @@ -25,8 +23,7 @@ class AuthenticationState { String? userId, String? userEmail, bool? isAuthenticated, - String? firstName, - String? lastName, + String? name, bool? isAdmin, bool? shouldChangePassword, String? profileImagePath, @@ -36,8 +33,7 @@ class AuthenticationState { userId: userId ?? this.userId, userEmail: userEmail ?? this.userEmail, isAuthenticated: isAuthenticated ?? this.isAuthenticated, - firstName: firstName ?? this.firstName, - lastName: lastName ?? this.lastName, + name: name ?? this.name, isAdmin: isAdmin ?? this.isAdmin, shouldChangePassword: shouldChangePassword ?? this.shouldChangePassword, profileImagePath: profileImagePath ?? this.profileImagePath, @@ -46,7 +42,7 @@ class AuthenticationState { @override String toString() { - return 'AuthenticationState(deviceId: $deviceId, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, firstName: $firstName, lastName: $lastName, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword, profileImagePath: $profileImagePath)'; + return 'AuthenticationState(deviceId: $deviceId, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, name: $name, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword, profileImagePath: $profileImagePath)'; } @override @@ -58,8 +54,7 @@ class AuthenticationState { other.userId == userId && other.userEmail == userEmail && other.isAuthenticated == isAuthenticated && - other.firstName == firstName && - other.lastName == lastName && + other.name == name && other.isAdmin == isAdmin && other.shouldChangePassword == shouldChangePassword && other.profileImagePath == profileImagePath; @@ -71,8 +66,7 @@ class AuthenticationState { userId.hashCode ^ userEmail.hashCode ^ isAuthenticated.hashCode ^ - firstName.hashCode ^ - lastName.hashCode ^ + name.hashCode ^ isAdmin.hashCode ^ shouldChangePassword.hashCode ^ profileImagePath.hashCode; diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index ad2103786..14a094963 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -26,8 +26,7 @@ class AuthenticationNotifier extends StateNotifier { deviceId: "", userId: "", userEmail: "", - firstName: '', - lastName: '', + name: '', profileImagePath: '', isAdmin: false, shouldChangePassword: false, @@ -117,8 +116,7 @@ class AuthenticationNotifier extends StateNotifier { deviceId: "", userId: "", userEmail: "", - firstName: '', - lastName: '', + name: '', profileImagePath: '', isAdmin: false, shouldChangePassword: false, @@ -208,8 +206,7 @@ class AuthenticationNotifier extends StateNotifier { isAuthenticated: true, userId: user.id, userEmail: user.email, - firstName: user.firstName, - lastName: user.lastName, + name: user.name, profileImagePath: user.profileImagePath, isAdmin: user.isAdmin, shouldChangePassword: shouldChangePassword, diff --git a/mobile/lib/modules/login/ui/change_password_form.dart b/mobile/lib/modules/login/ui/change_password_form.dart index 884a26b6d..560967821 100644 --- a/mobile/lib/modules/login/ui/change_password_form.dart +++ b/mobile/lib/modules/login/ui/change_password_form.dart @@ -46,8 +46,7 @@ class ChangePasswordForm extends HookConsumerWidget { child: Text( 'change_password_form_description'.tr( namedArgs: { - 'firstName': authState.firstName, - 'lastName': authState.lastName, + 'name': authState.name, }, ), style: TextStyle( diff --git a/mobile/lib/modules/partner/ui/partner_list.dart b/mobile/lib/modules/partner/ui/partner_list.dart index b1111fdcf..58810fec5 100644 --- a/mobile/lib/modules/partner/ui/partner_list.dart +++ b/mobile/lib/modules/partner/ui/partner_list.dart @@ -24,7 +24,7 @@ class PartnerList extends HookConsumerWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), leading: userAvatar(context, p, radius: 30), title: Text( - "${p.firstName} ${p.lastName}'s photos", + "${p.name}'s photos", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, diff --git a/mobile/lib/modules/partner/views/partner_detail_page.dart b/mobile/lib/modules/partner/views/partner_detail_page.dart index 995eebd3f..2aecdb35c 100644 --- a/mobile/lib/modules/partner/views/partner_detail_page.dart +++ b/mobile/lib/modules/partner/views/partner_detail_page.dart @@ -25,7 +25,7 @@ class PartnerDetailPage extends HookConsumerWidget { return Scaffold( appBar: AppBar( - title: Text("${partner.firstName} ${partner.lastName}"), + title: Text(partner.name), elevation: 0, centerTitle: false, ), @@ -34,7 +34,7 @@ class PartnerDetailPage extends HookConsumerWidget { ? Padding( padding: const EdgeInsets.all(16), child: Text( - "It seems ${partner.firstName} does not have any photos...\n" + "It seems ${partner.name} does not have any photos...\n" "Or your server version does not match the app version."), ) : ImmichAssetGrid( diff --git a/mobile/lib/modules/partner/views/partner_page.dart b/mobile/lib/modules/partner/views/partner_page.dart index 61c639746..bb567b62b 100644 --- a/mobile/lib/modules/partner/views/partner_page.dart +++ b/mobile/lib/modules/partner/views/partner_page.dart @@ -41,7 +41,7 @@ class PartnerPage extends HookConsumerWidget { padding: const EdgeInsets.only(right: 8), child: userAvatar(context, u), ), - Text("${u.firstName} ${u.lastName}"), + Text(u.name), ], ), ), @@ -71,7 +71,7 @@ class PartnerPage extends HookConsumerWidget { return ConfirmDialog( title: "partner_page_stop_sharing_title", content: - "partner_page_stop_sharing_content".tr(args: [u.firstName]), + "partner_page_stop_sharing_content".tr(args: [u.name]), onOk: () => ref.read(partnerServiceProvider).removePartner(u), ); }, diff --git a/mobile/lib/shared/models/album.dart b/mobile/lib/shared/models/album.dart index ceba60742..26b1ab16e 100644 --- a/mobile/lib/shared/models/album.dart +++ b/mobile/lib/shared/models/album.dart @@ -68,11 +68,8 @@ class Album { } final name = []; - if (owner.value?.firstName != null) { - name.add(owner.value!.firstName); - } - if (owner.value?.lastName != null) { - name.add(owner.value!.lastName); + if (owner.value?.name != null) { + name.add(owner.value!.name); } return name.join(' '); diff --git a/mobile/lib/shared/models/user.dart b/mobile/lib/shared/models/user.dart index 59f01c7dc..a247fb21e 100644 --- a/mobile/lib/shared/models/user.dart +++ b/mobile/lib/shared/models/user.dart @@ -11,8 +11,7 @@ class User { required this.id, required this.updatedAt, required this.email, - required this.firstName, - required this.lastName, + required this.name, required this.isAdmin, this.isPartnerSharedBy = false, this.isPartnerSharedWith = false, @@ -27,8 +26,7 @@ class User { : id = dto.id, updatedAt = dto.updatedAt, email = dto.email, - firstName = dto.firstName, - lastName = dto.lastName, + name = dto.name, isPartnerSharedBy = false, isPartnerSharedWith = false, profileImagePath = dto.profileImagePath, @@ -39,8 +37,7 @@ class User { : id = dto.id, updatedAt = dto.updatedAt, email = dto.email, - firstName = dto.firstName, - lastName = dto.lastName, + name = dto.name, isPartnerSharedBy = false, isPartnerSharedWith = false, profileImagePath = dto.profileImagePath, @@ -52,8 +49,7 @@ class User { String id; DateTime updatedAt; String email; - String firstName; - String lastName; + String name; bool isPartnerSharedBy; bool isPartnerSharedWith; bool isAdmin; @@ -72,8 +68,7 @@ class User { return id == other.id && updatedAt.isAtSameMomentAs(other.updatedAt) && email == other.email && - firstName == other.firstName && - lastName == other.lastName && + name == other.name && isPartnerSharedBy == other.isPartnerSharedBy && isPartnerSharedWith == other.isPartnerSharedWith && profileImagePath == other.profileImagePath && @@ -88,8 +83,7 @@ class User { id.hashCode ^ updatedAt.hashCode ^ email.hashCode ^ - firstName.hashCode ^ - lastName.hashCode ^ + name.hashCode ^ isPartnerSharedBy.hashCode ^ isPartnerSharedWith.hashCode ^ profileImagePath.hashCode ^ diff --git a/mobile/lib/shared/models/user.g.dart b/mobile/lib/shared/models/user.g.dart index af3bdadf6..f5167c2a8 100644 --- a/mobile/lib/shared/models/user.g.dart +++ b/mobile/lib/shared/models/user.g.dart @@ -22,53 +22,48 @@ const UserSchema = CollectionSchema( name: r'email', type: IsarType.string, ), - r'firstName': PropertySchema( - id: 1, - name: r'firstName', - type: IsarType.string, - ), r'id': PropertySchema( - id: 2, + id: 1, name: r'id', type: IsarType.string, ), r'inTimeline': PropertySchema( - id: 3, + id: 2, name: r'inTimeline', type: IsarType.bool, ), r'isAdmin': PropertySchema( - id: 4, + id: 3, name: r'isAdmin', type: IsarType.bool, ), r'isPartnerSharedBy': PropertySchema( - id: 5, + id: 4, name: r'isPartnerSharedBy', type: IsarType.bool, ), r'isPartnerSharedWith': PropertySchema( - id: 6, + id: 5, name: r'isPartnerSharedWith', type: IsarType.bool, ), - r'lastName': PropertySchema( - id: 7, - name: r'lastName', - type: IsarType.string, - ), r'memoryEnabled': PropertySchema( - id: 8, + id: 6, name: r'memoryEnabled', type: IsarType.bool, ), + r'name': PropertySchema( + id: 7, + name: r'name', + type: IsarType.string, + ), r'profileImagePath': PropertySchema( - id: 9, + id: 8, name: r'profileImagePath', type: IsarType.string, ), r'updatedAt': PropertySchema( - id: 10, + id: 9, name: r'updatedAt', type: IsarType.dateTime, ) @@ -123,9 +118,8 @@ int _userEstimateSize( ) { var bytesCount = offsets.last; bytesCount += 3 + object.email.length * 3; - bytesCount += 3 + object.firstName.length * 3; bytesCount += 3 + object.id.length * 3; - bytesCount += 3 + object.lastName.length * 3; + bytesCount += 3 + object.name.length * 3; bytesCount += 3 + object.profileImagePath.length * 3; return bytesCount; } @@ -137,16 +131,15 @@ void _userSerialize( Map> allOffsets, ) { writer.writeString(offsets[0], object.email); - writer.writeString(offsets[1], object.firstName); - writer.writeString(offsets[2], object.id); - writer.writeBool(offsets[3], object.inTimeline); - writer.writeBool(offsets[4], object.isAdmin); - writer.writeBool(offsets[5], object.isPartnerSharedBy); - writer.writeBool(offsets[6], object.isPartnerSharedWith); - writer.writeString(offsets[7], object.lastName); - writer.writeBool(offsets[8], object.memoryEnabled); - writer.writeString(offsets[9], object.profileImagePath); - writer.writeDateTime(offsets[10], object.updatedAt); + writer.writeString(offsets[1], object.id); + writer.writeBool(offsets[2], object.inTimeline); + writer.writeBool(offsets[3], object.isAdmin); + writer.writeBool(offsets[4], object.isPartnerSharedBy); + writer.writeBool(offsets[5], object.isPartnerSharedWith); + writer.writeBool(offsets[6], object.memoryEnabled); + writer.writeString(offsets[7], object.name); + writer.writeString(offsets[8], object.profileImagePath); + writer.writeDateTime(offsets[9], object.updatedAt); } User _userDeserialize( @@ -157,16 +150,15 @@ User _userDeserialize( ) { final object = User( email: reader.readString(offsets[0]), - firstName: reader.readString(offsets[1]), - id: reader.readString(offsets[2]), - inTimeline: reader.readBoolOrNull(offsets[3]), - isAdmin: reader.readBool(offsets[4]), - isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false, - isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false, - lastName: reader.readString(offsets[7]), - memoryEnabled: reader.readBoolOrNull(offsets[8]), - profileImagePath: reader.readStringOrNull(offsets[9]) ?? '', - updatedAt: reader.readDateTime(offsets[10]), + id: reader.readString(offsets[1]), + inTimeline: reader.readBoolOrNull(offsets[2]), + isAdmin: reader.readBool(offsets[3]), + isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false, + isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false, + memoryEnabled: reader.readBoolOrNull(offsets[6]), + name: reader.readString(offsets[7]), + profileImagePath: reader.readStringOrNull(offsets[8]) ?? '', + updatedAt: reader.readDateTime(offsets[9]), ); return object; } @@ -183,22 +175,20 @@ P _userDeserializeProp

( case 1: return (reader.readString(offset)) as P; case 2: - return (reader.readString(offset)) as P; - case 3: return (reader.readBoolOrNull(offset)) as P; - case 4: + case 3: return (reader.readBool(offset)) as P; + case 4: + return (reader.readBoolOrNull(offset) ?? false) as P; case 5: return (reader.readBoolOrNull(offset) ?? false) as P; case 6: - return (reader.readBoolOrNull(offset) ?? false) as P; + return (reader.readBoolOrNull(offset)) as P; case 7: return (reader.readString(offset)) as P; case 8: - return (reader.readBoolOrNull(offset)) as P; - case 9: return (reader.readStringOrNull(offset) ?? '') as P; - case 10: + case 9: return (reader.readDateTime(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -520,136 +510,6 @@ extension UserQueryFilter on QueryBuilder { }); } - QueryBuilder firstNameEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'firstName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'firstName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'firstName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'firstName', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'firstName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'firstName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameContains( - String value, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'firstName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameMatches( - String pattern, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'firstName', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder firstNameIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'firstName', - value: '', - )); - }); - } - - QueryBuilder firstNameIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'firstName', - value: '', - )); - }); - } - QueryBuilder idEqualTo( String value, { bool caseSensitive = true, @@ -885,135 +745,6 @@ extension UserQueryFilter on QueryBuilder { }); } - QueryBuilder lastNameEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'lastName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'lastName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'lastName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'lastName', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'lastName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'lastName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameContains(String value, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'lastName', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameMatches( - String pattern, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'lastName', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder lastNameIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'lastName', - value: '', - )); - }); - } - - QueryBuilder lastNameIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'lastName', - value: '', - )); - }); - } - QueryBuilder memoryEnabledIsNull() { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(const FilterCondition.isNull( @@ -1040,6 +771,134 @@ extension UserQueryFilter on QueryBuilder { }); } + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } + QueryBuilder profileImagePathEqualTo( String value, { bool caseSensitive = true, @@ -1352,18 +1211,6 @@ extension UserQuerySortBy on QueryBuilder { }); } - QueryBuilder sortByFirstName() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'firstName', Sort.asc); - }); - } - - QueryBuilder sortByFirstNameDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'firstName', Sort.desc); - }); - } - QueryBuilder sortById() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'id', Sort.asc); @@ -1424,18 +1271,6 @@ extension UserQuerySortBy on QueryBuilder { }); } - QueryBuilder sortByLastName() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastName', Sort.asc); - }); - } - - QueryBuilder sortByLastNameDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastName', Sort.desc); - }); - } - QueryBuilder sortByMemoryEnabled() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'memoryEnabled', Sort.asc); @@ -1448,6 +1283,18 @@ extension UserQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + QueryBuilder sortByProfileImagePath() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'profileImagePath', Sort.asc); @@ -1486,18 +1333,6 @@ extension UserQuerySortThenBy on QueryBuilder { }); } - QueryBuilder thenByFirstName() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'firstName', Sort.asc); - }); - } - - QueryBuilder thenByFirstNameDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'firstName', Sort.desc); - }); - } - QueryBuilder thenById() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'id', Sort.asc); @@ -1570,18 +1405,6 @@ extension UserQuerySortThenBy on QueryBuilder { }); } - QueryBuilder thenByLastName() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastName', Sort.asc); - }); - } - - QueryBuilder thenByLastNameDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastName', Sort.desc); - }); - } - QueryBuilder thenByMemoryEnabled() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'memoryEnabled', Sort.asc); @@ -1594,6 +1417,18 @@ extension UserQuerySortThenBy on QueryBuilder { }); } + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + QueryBuilder thenByProfileImagePath() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'profileImagePath', Sort.asc); @@ -1627,13 +1462,6 @@ extension UserQueryWhereDistinct on QueryBuilder { }); } - QueryBuilder distinctByFirstName( - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'firstName', caseSensitive: caseSensitive); - }); - } - QueryBuilder distinctById( {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -1665,19 +1493,19 @@ extension UserQueryWhereDistinct on QueryBuilder { }); } - QueryBuilder distinctByLastName( - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'lastName', caseSensitive: caseSensitive); - }); - } - QueryBuilder distinctByMemoryEnabled() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'memoryEnabled'); }); } + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } + QueryBuilder distinctByProfileImagePath( {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -1706,12 +1534,6 @@ extension UserQueryProperty on QueryBuilder { }); } - QueryBuilder firstNameProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'firstName'); - }); - } - QueryBuilder idProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'id'); @@ -1742,18 +1564,18 @@ extension UserQueryProperty on QueryBuilder { }); } - QueryBuilder lastNameProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'lastName'); - }); - } - QueryBuilder memoryEnabledProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'memoryEnabled'); }); } + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } + QueryBuilder profileImagePathProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'profileImagePath'); diff --git a/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart index 4f1f7db86..c4951e339 100644 --- a/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart @@ -132,7 +132,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { ), ), title: Text( - "${authState.firstName} ${authState.lastName}", + authState.name, style: TextStyle( color: context.primaryColor, fontWeight: FontWeight.bold, diff --git a/mobile/lib/shared/ui/user_avatar.dart b/mobile/lib/shared/ui/user_avatar.dart index 382e44eff..95f76de43 100644 --- a/mobile/lib/shared/ui/user_avatar.dart +++ b/mobile/lib/shared/ui/user_avatar.dart @@ -7,8 +7,7 @@ import 'package:immich_mobile/shared/models/user.dart'; Widget userAvatar(BuildContext context, User u, {double? radius}) { final url = "${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${u.id}"; - final firstNameFirstLetter = u.firstName.isNotEmpty ? u.firstName[0] : ""; - final lastNameFirstLetter = u.lastName.isNotEmpty ? u.lastName[0] : ""; + final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : ""; return CircleAvatar( radius: radius, backgroundColor: context.primaryColor.withAlpha(50), @@ -19,6 +18,6 @@ Widget userAvatar(BuildContext context, User u, {double? radius}) { ), // silence errors if user has no profile image, use initials as fallback onForegroundImageError: (exception, stackTrace) {}, - child: Text((firstNameFirstLetter + lastNameFirstLetter).toUpperCase()), + child: Text(nameFirstLetter.toUpperCase()), ); } diff --git a/mobile/lib/shared/ui/user_circle_avatar.dart b/mobile/lib/shared/ui/user_circle_avatar.dart index 98df36265..5920758a7 100644 --- a/mobile/lib/shared/ui/user_circle_avatar.dart +++ b/mobile/lib/shared/ui/user_circle_avatar.dart @@ -43,7 +43,7 @@ class UserCircleAvatar extends ConsumerWidget { '${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}'; final textIcon = Text( - user.firstName[0].toUpperCase(), + user.name[0].toUpperCase(), style: TextStyle( fontWeight: FontWeight.bold, color: context.isDarkTheme ? Colors.black : Colors.white, diff --git a/mobile/openapi/doc/CreateUserDto.md b/mobile/openapi/doc/CreateUserDto.md index 0a25b4df4..08fa8665a 100644 --- a/mobile/openapi/doc/CreateUserDto.md +++ b/mobile/openapi/doc/CreateUserDto.md @@ -10,9 +10,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **email** | **String** | | **externalPath** | **String** | | [optional] -**firstName** | **String** | | -**lastName** | **String** | | **memoriesEnabled** | **bool** | | [optional] +**name** | **String** | | **password** | **String** | | **storageLabel** | **String** | | [optional] diff --git a/mobile/openapi/doc/LoginResponseDto.md b/mobile/openapi/doc/LoginResponseDto.md index e344ef124..bd37dc420 100644 --- a/mobile/openapi/doc/LoginResponseDto.md +++ b/mobile/openapi/doc/LoginResponseDto.md @@ -9,9 +9,8 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **accessToken** | **String** | | -**firstName** | **String** | | **isAdmin** | **bool** | | -**lastName** | **String** | | +**name** | **String** | | **profileImagePath** | **String** | | **shouldChangePassword** | **bool** | | **userEmail** | **String** | | diff --git a/mobile/openapi/doc/PartnerResponseDto.md b/mobile/openapi/doc/PartnerResponseDto.md index 887664e89..f7133bbf7 100644 --- a/mobile/openapi/doc/PartnerResponseDto.md +++ b/mobile/openapi/doc/PartnerResponseDto.md @@ -12,12 +12,11 @@ Name | Type | Description | Notes **deletedAt** | [**DateTime**](DateTime.md) | | **email** | **String** | | **externalPath** | **String** | | -**firstName** | **String** | | **id** | **String** | | **inTimeline** | **bool** | | [optional] **isAdmin** | **bool** | | -**lastName** | **String** | | **memoriesEnabled** | **bool** | | [optional] +**name** | **String** | | **oauthId** | **String** | | **profileImagePath** | **String** | | **shouldChangePassword** | **bool** | | diff --git a/mobile/openapi/doc/SignUpDto.md b/mobile/openapi/doc/SignUpDto.md index 92294df02..9706c7c54 100644 --- a/mobile/openapi/doc/SignUpDto.md +++ b/mobile/openapi/doc/SignUpDto.md @@ -9,8 +9,7 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **email** | **String** | | -**firstName** | **String** | | -**lastName** | **String** | | +**name** | **String** | | **password** | **String** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/UpdateUserDto.md b/mobile/openapi/doc/UpdateUserDto.md index ff6bc8d42..ffbe11253 100644 --- a/mobile/openapi/doc/UpdateUserDto.md +++ b/mobile/openapi/doc/UpdateUserDto.md @@ -10,11 +10,10 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **email** | **String** | | [optional] **externalPath** | **String** | | [optional] -**firstName** | **String** | | [optional] **id** | **String** | | **isAdmin** | **bool** | | [optional] -**lastName** | **String** | | [optional] **memoriesEnabled** | **bool** | | [optional] +**name** | **String** | | [optional] **password** | **String** | | [optional] **shouldChangePassword** | **bool** | | [optional] **storageLabel** | **String** | | [optional] diff --git a/mobile/openapi/doc/UsageByUserDto.md b/mobile/openapi/doc/UsageByUserDto.md index c1c5240b9..f7b1b3593 100644 --- a/mobile/openapi/doc/UsageByUserDto.md +++ b/mobile/openapi/doc/UsageByUserDto.md @@ -10,9 +10,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **photos** | **int** | | **usage** | **int** | | -**userFirstName** | **String** | | **userId** | **String** | | -**userLastName** | **String** | | +**userName** | **String** | | **videos** | **int** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/UserDto.md b/mobile/openapi/doc/UserDto.md index 617ceb9d3..c8b750a1d 100644 --- a/mobile/openapi/doc/UserDto.md +++ b/mobile/openapi/doc/UserDto.md @@ -9,9 +9,8 @@ import 'package:openapi/api.dart'; Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **email** | **String** | | -**firstName** | **String** | | **id** | **String** | | -**lastName** | **String** | | +**name** | **String** | | **profileImagePath** | **String** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/UserResponseDto.md b/mobile/openapi/doc/UserResponseDto.md index b6e42b33e..ddf7c574c 100644 --- a/mobile/openapi/doc/UserResponseDto.md +++ b/mobile/openapi/doc/UserResponseDto.md @@ -12,11 +12,10 @@ Name | Type | Description | Notes **deletedAt** | [**DateTime**](DateTime.md) | | **email** | **String** | | **externalPath** | **String** | | -**firstName** | **String** | | **id** | **String** | | **isAdmin** | **bool** | | -**lastName** | **String** | | **memoriesEnabled** | **bool** | | [optional] +**name** | **String** | | **oauthId** | **String** | | **profileImagePath** | **String** | | **shouldChangePassword** | **bool** | | diff --git a/mobile/openapi/lib/model/create_user_dto.dart b/mobile/openapi/lib/model/create_user_dto.dart index 1345ac995..b33a3043c 100644 --- a/mobile/openapi/lib/model/create_user_dto.dart +++ b/mobile/openapi/lib/model/create_user_dto.dart @@ -15,9 +15,8 @@ class CreateUserDto { CreateUserDto({ required this.email, this.externalPath, - required this.firstName, - required this.lastName, this.memoriesEnabled, + required this.name, required this.password, this.storageLabel, }); @@ -26,10 +25,6 @@ class CreateUserDto { String? externalPath; - String firstName; - - String lastName; - /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -38,6 +33,8 @@ class CreateUserDto { /// bool? memoriesEnabled; + String name; + String password; String? storageLabel; @@ -46,9 +43,8 @@ class CreateUserDto { bool operator ==(Object other) => identical(this, other) || other is CreateUserDto && other.email == email && other.externalPath == externalPath && - other.firstName == firstName && - other.lastName == lastName && other.memoriesEnabled == memoriesEnabled && + other.name == name && other.password == password && other.storageLabel == storageLabel; @@ -57,14 +53,13 @@ class CreateUserDto { // ignore: unnecessary_parenthesis (email.hashCode) + (externalPath == null ? 0 : externalPath!.hashCode) + - (firstName.hashCode) + - (lastName.hashCode) + (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) + + (name.hashCode) + (password.hashCode) + (storageLabel == null ? 0 : storageLabel!.hashCode); @override - String toString() => 'CreateUserDto[email=$email, externalPath=$externalPath, firstName=$firstName, lastName=$lastName, memoriesEnabled=$memoriesEnabled, password=$password, storageLabel=$storageLabel]'; + String toString() => 'CreateUserDto[email=$email, externalPath=$externalPath, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, storageLabel=$storageLabel]'; Map toJson() { final json = {}; @@ -74,13 +69,12 @@ class CreateUserDto { } else { // json[r'externalPath'] = null; } - json[r'firstName'] = this.firstName; - json[r'lastName'] = this.lastName; if (this.memoriesEnabled != null) { json[r'memoriesEnabled'] = this.memoriesEnabled; } else { // json[r'memoriesEnabled'] = null; } + json[r'name'] = this.name; json[r'password'] = this.password; if (this.storageLabel != null) { json[r'storageLabel'] = this.storageLabel; @@ -100,9 +94,8 @@ class CreateUserDto { return CreateUserDto( email: mapValueOfType(json, r'email')!, externalPath: mapValueOfType(json, r'externalPath'), - firstName: mapValueOfType(json, r'firstName')!, - lastName: mapValueOfType(json, r'lastName')!, memoriesEnabled: mapValueOfType(json, r'memoriesEnabled'), + name: mapValueOfType(json, r'name')!, password: mapValueOfType(json, r'password')!, storageLabel: mapValueOfType(json, r'storageLabel'), ); @@ -153,8 +146,7 @@ class CreateUserDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'email', - 'firstName', - 'lastName', + 'name', 'password', }; } diff --git a/mobile/openapi/lib/model/login_response_dto.dart b/mobile/openapi/lib/model/login_response_dto.dart index 94da05a50..0293d04de 100644 --- a/mobile/openapi/lib/model/login_response_dto.dart +++ b/mobile/openapi/lib/model/login_response_dto.dart @@ -14,9 +14,8 @@ class LoginResponseDto { /// Returns a new [LoginResponseDto] instance. LoginResponseDto({ required this.accessToken, - required this.firstName, required this.isAdmin, - required this.lastName, + required this.name, required this.profileImagePath, required this.shouldChangePassword, required this.userEmail, @@ -25,11 +24,9 @@ class LoginResponseDto { String accessToken; - String firstName; - bool isAdmin; - String lastName; + String name; String profileImagePath; @@ -42,9 +39,8 @@ class LoginResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is LoginResponseDto && other.accessToken == accessToken && - other.firstName == firstName && other.isAdmin == isAdmin && - other.lastName == lastName && + other.name == name && other.profileImagePath == profileImagePath && other.shouldChangePassword == shouldChangePassword && other.userEmail == userEmail && @@ -54,23 +50,21 @@ class LoginResponseDto { int get hashCode => // ignore: unnecessary_parenthesis (accessToken.hashCode) + - (firstName.hashCode) + (isAdmin.hashCode) + - (lastName.hashCode) + + (name.hashCode) + (profileImagePath.hashCode) + (shouldChangePassword.hashCode) + (userEmail.hashCode) + (userId.hashCode); @override - String toString() => 'LoginResponseDto[accessToken=$accessToken, firstName=$firstName, isAdmin=$isAdmin, lastName=$lastName, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, userEmail=$userEmail, userId=$userId]'; + String toString() => 'LoginResponseDto[accessToken=$accessToken, isAdmin=$isAdmin, name=$name, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, userEmail=$userEmail, userId=$userId]'; Map toJson() { final json = {}; json[r'accessToken'] = this.accessToken; - json[r'firstName'] = this.firstName; json[r'isAdmin'] = this.isAdmin; - json[r'lastName'] = this.lastName; + json[r'name'] = this.name; json[r'profileImagePath'] = this.profileImagePath; json[r'shouldChangePassword'] = this.shouldChangePassword; json[r'userEmail'] = this.userEmail; @@ -87,9 +81,8 @@ class LoginResponseDto { return LoginResponseDto( accessToken: mapValueOfType(json, r'accessToken')!, - firstName: mapValueOfType(json, r'firstName')!, isAdmin: mapValueOfType(json, r'isAdmin')!, - lastName: mapValueOfType(json, r'lastName')!, + name: mapValueOfType(json, r'name')!, profileImagePath: mapValueOfType(json, r'profileImagePath')!, shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, userEmail: mapValueOfType(json, r'userEmail')!, @@ -142,9 +135,8 @@ class LoginResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'accessToken', - 'firstName', 'isAdmin', - 'lastName', + 'name', 'profileImagePath', 'shouldChangePassword', 'userEmail', diff --git a/mobile/openapi/lib/model/partner_response_dto.dart b/mobile/openapi/lib/model/partner_response_dto.dart index ae6e23098..5ccef68d8 100644 --- a/mobile/openapi/lib/model/partner_response_dto.dart +++ b/mobile/openapi/lib/model/partner_response_dto.dart @@ -17,12 +17,11 @@ class PartnerResponseDto { required this.deletedAt, required this.email, required this.externalPath, - required this.firstName, required this.id, this.inTimeline, required this.isAdmin, - required this.lastName, this.memoriesEnabled, + required this.name, required this.oauthId, required this.profileImagePath, required this.shouldChangePassword, @@ -38,8 +37,6 @@ class PartnerResponseDto { String? externalPath; - String firstName; - String id; /// @@ -52,8 +49,6 @@ class PartnerResponseDto { bool isAdmin; - String lastName; - /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -62,6 +57,8 @@ class PartnerResponseDto { /// bool? memoriesEnabled; + String name; + String oauthId; String profileImagePath; @@ -78,12 +75,11 @@ class PartnerResponseDto { other.deletedAt == deletedAt && other.email == email && other.externalPath == externalPath && - other.firstName == firstName && other.id == id && other.inTimeline == inTimeline && other.isAdmin == isAdmin && - other.lastName == lastName && other.memoriesEnabled == memoriesEnabled && + other.name == name && other.oauthId == oauthId && other.profileImagePath == profileImagePath && other.shouldChangePassword == shouldChangePassword && @@ -97,12 +93,11 @@ class PartnerResponseDto { (deletedAt == null ? 0 : deletedAt!.hashCode) + (email.hashCode) + (externalPath == null ? 0 : externalPath!.hashCode) + - (firstName.hashCode) + (id.hashCode) + (inTimeline == null ? 0 : inTimeline!.hashCode) + (isAdmin.hashCode) + - (lastName.hashCode) + (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) + + (name.hashCode) + (oauthId.hashCode) + (profileImagePath.hashCode) + (shouldChangePassword.hashCode) + @@ -110,7 +105,7 @@ class PartnerResponseDto { (updatedAt.hashCode); @override - String toString() => 'PartnerResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, firstName=$firstName, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, lastName=$lastName, memoriesEnabled=$memoriesEnabled, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; + String toString() => 'PartnerResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -126,7 +121,6 @@ class PartnerResponseDto { } else { // json[r'externalPath'] = null; } - json[r'firstName'] = this.firstName; json[r'id'] = this.id; if (this.inTimeline != null) { json[r'inTimeline'] = this.inTimeline; @@ -134,12 +128,12 @@ class PartnerResponseDto { // json[r'inTimeline'] = null; } json[r'isAdmin'] = this.isAdmin; - json[r'lastName'] = this.lastName; if (this.memoriesEnabled != null) { json[r'memoriesEnabled'] = this.memoriesEnabled; } else { // json[r'memoriesEnabled'] = null; } + json[r'name'] = this.name; json[r'oauthId'] = this.oauthId; json[r'profileImagePath'] = this.profileImagePath; json[r'shouldChangePassword'] = this.shouldChangePassword; @@ -164,12 +158,11 @@ class PartnerResponseDto { deletedAt: mapDateTime(json, r'deletedAt', ''), email: mapValueOfType(json, r'email')!, externalPath: mapValueOfType(json, r'externalPath'), - firstName: mapValueOfType(json, r'firstName')!, id: mapValueOfType(json, r'id')!, inTimeline: mapValueOfType(json, r'inTimeline'), isAdmin: mapValueOfType(json, r'isAdmin')!, - lastName: mapValueOfType(json, r'lastName')!, memoriesEnabled: mapValueOfType(json, r'memoriesEnabled'), + name: mapValueOfType(json, r'name')!, oauthId: mapValueOfType(json, r'oauthId')!, profileImagePath: mapValueOfType(json, r'profileImagePath')!, shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, @@ -226,10 +219,9 @@ class PartnerResponseDto { 'deletedAt', 'email', 'externalPath', - 'firstName', 'id', 'isAdmin', - 'lastName', + 'name', 'oauthId', 'profileImagePath', 'shouldChangePassword', diff --git a/mobile/openapi/lib/model/sign_up_dto.dart b/mobile/openapi/lib/model/sign_up_dto.dart index e6b7f2abf..c6b4ee731 100644 --- a/mobile/openapi/lib/model/sign_up_dto.dart +++ b/mobile/openapi/lib/model/sign_up_dto.dart @@ -14,42 +14,36 @@ class SignUpDto { /// Returns a new [SignUpDto] instance. SignUpDto({ required this.email, - required this.firstName, - required this.lastName, + required this.name, required this.password, }); String email; - String firstName; - - String lastName; + String name; String password; @override bool operator ==(Object other) => identical(this, other) || other is SignUpDto && other.email == email && - other.firstName == firstName && - other.lastName == lastName && + other.name == name && other.password == password; @override int get hashCode => // ignore: unnecessary_parenthesis (email.hashCode) + - (firstName.hashCode) + - (lastName.hashCode) + + (name.hashCode) + (password.hashCode); @override - String toString() => 'SignUpDto[email=$email, firstName=$firstName, lastName=$lastName, password=$password]'; + String toString() => 'SignUpDto[email=$email, name=$name, password=$password]'; Map toJson() { final json = {}; json[r'email'] = this.email; - json[r'firstName'] = this.firstName; - json[r'lastName'] = this.lastName; + json[r'name'] = this.name; json[r'password'] = this.password; return json; } @@ -63,8 +57,7 @@ class SignUpDto { return SignUpDto( email: mapValueOfType(json, r'email')!, - firstName: mapValueOfType(json, r'firstName')!, - lastName: mapValueOfType(json, r'lastName')!, + name: mapValueOfType(json, r'name')!, password: mapValueOfType(json, r'password')!, ); } @@ -114,8 +107,7 @@ class SignUpDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'email', - 'firstName', - 'lastName', + 'name', 'password', }; } diff --git a/mobile/openapi/lib/model/update_user_dto.dart b/mobile/openapi/lib/model/update_user_dto.dart index 6202c6d1e..9d381cd4b 100644 --- a/mobile/openapi/lib/model/update_user_dto.dart +++ b/mobile/openapi/lib/model/update_user_dto.dart @@ -15,11 +15,10 @@ class UpdateUserDto { UpdateUserDto({ this.email, this.externalPath, - this.firstName, required this.id, this.isAdmin, - this.lastName, this.memoriesEnabled, + this.name, this.password, this.shouldChangePassword, this.storageLabel, @@ -41,14 +40,6 @@ class UpdateUserDto { /// String? externalPath; - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - String? firstName; - String id; /// @@ -65,7 +56,7 @@ class UpdateUserDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - String? lastName; + bool? memoriesEnabled; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -73,7 +64,7 @@ class UpdateUserDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? memoriesEnabled; + String? name; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -103,11 +94,10 @@ class UpdateUserDto { bool operator ==(Object other) => identical(this, other) || other is UpdateUserDto && other.email == email && other.externalPath == externalPath && - other.firstName == firstName && other.id == id && other.isAdmin == isAdmin && - other.lastName == lastName && other.memoriesEnabled == memoriesEnabled && + other.name == name && other.password == password && other.shouldChangePassword == shouldChangePassword && other.storageLabel == storageLabel; @@ -117,17 +107,16 @@ class UpdateUserDto { // ignore: unnecessary_parenthesis (email == null ? 0 : email!.hashCode) + (externalPath == null ? 0 : externalPath!.hashCode) + - (firstName == null ? 0 : firstName!.hashCode) + (id.hashCode) + (isAdmin == null ? 0 : isAdmin!.hashCode) + - (lastName == null ? 0 : lastName!.hashCode) + (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) + + (name == null ? 0 : name!.hashCode) + (password == null ? 0 : password!.hashCode) + (shouldChangePassword == null ? 0 : shouldChangePassword!.hashCode) + (storageLabel == null ? 0 : storageLabel!.hashCode); @override - String toString() => 'UpdateUserDto[email=$email, externalPath=$externalPath, firstName=$firstName, id=$id, isAdmin=$isAdmin, lastName=$lastName, memoriesEnabled=$memoriesEnabled, password=$password, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; + String toString() => 'UpdateUserDto[email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; Map toJson() { final json = {}; @@ -140,11 +129,6 @@ class UpdateUserDto { json[r'externalPath'] = this.externalPath; } else { // json[r'externalPath'] = null; - } - if (this.firstName != null) { - json[r'firstName'] = this.firstName; - } else { - // json[r'firstName'] = null; } json[r'id'] = this.id; if (this.isAdmin != null) { @@ -152,16 +136,16 @@ class UpdateUserDto { } else { // json[r'isAdmin'] = null; } - if (this.lastName != null) { - json[r'lastName'] = this.lastName; - } else { - // json[r'lastName'] = null; - } if (this.memoriesEnabled != null) { json[r'memoriesEnabled'] = this.memoriesEnabled; } else { // json[r'memoriesEnabled'] = null; } + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } if (this.password != null) { json[r'password'] = this.password; } else { @@ -190,11 +174,10 @@ class UpdateUserDto { return UpdateUserDto( email: mapValueOfType(json, r'email'), externalPath: mapValueOfType(json, r'externalPath'), - firstName: mapValueOfType(json, r'firstName'), id: mapValueOfType(json, r'id')!, isAdmin: mapValueOfType(json, r'isAdmin'), - lastName: mapValueOfType(json, r'lastName'), memoriesEnabled: mapValueOfType(json, r'memoriesEnabled'), + name: mapValueOfType(json, r'name'), password: mapValueOfType(json, r'password'), shouldChangePassword: mapValueOfType(json, r'shouldChangePassword'), storageLabel: mapValueOfType(json, r'storageLabel'), diff --git a/mobile/openapi/lib/model/usage_by_user_dto.dart b/mobile/openapi/lib/model/usage_by_user_dto.dart index 052ebaa9f..27d8a2673 100644 --- a/mobile/openapi/lib/model/usage_by_user_dto.dart +++ b/mobile/openapi/lib/model/usage_by_user_dto.dart @@ -15,9 +15,8 @@ class UsageByUserDto { UsageByUserDto({ required this.photos, required this.usage, - required this.userFirstName, required this.userId, - required this.userLastName, + required this.userName, required this.videos, }); @@ -25,11 +24,9 @@ class UsageByUserDto { int usage; - String userFirstName; - String userId; - String userLastName; + String userName; int videos; @@ -37,9 +34,8 @@ class UsageByUserDto { bool operator ==(Object other) => identical(this, other) || other is UsageByUserDto && other.photos == photos && other.usage == usage && - other.userFirstName == userFirstName && other.userId == userId && - other.userLastName == userLastName && + other.userName == userName && other.videos == videos; @override @@ -47,21 +43,19 @@ class UsageByUserDto { // ignore: unnecessary_parenthesis (photos.hashCode) + (usage.hashCode) + - (userFirstName.hashCode) + (userId.hashCode) + - (userLastName.hashCode) + + (userName.hashCode) + (videos.hashCode); @override - String toString() => 'UsageByUserDto[photos=$photos, usage=$usage, userFirstName=$userFirstName, userId=$userId, userLastName=$userLastName, videos=$videos]'; + String toString() => 'UsageByUserDto[photos=$photos, usage=$usage, userId=$userId, userName=$userName, videos=$videos]'; Map toJson() { final json = {}; json[r'photos'] = this.photos; json[r'usage'] = this.usage; - json[r'userFirstName'] = this.userFirstName; json[r'userId'] = this.userId; - json[r'userLastName'] = this.userLastName; + json[r'userName'] = this.userName; json[r'videos'] = this.videos; return json; } @@ -76,9 +70,8 @@ class UsageByUserDto { return UsageByUserDto( photos: mapValueOfType(json, r'photos')!, usage: mapValueOfType(json, r'usage')!, - userFirstName: mapValueOfType(json, r'userFirstName')!, userId: mapValueOfType(json, r'userId')!, - userLastName: mapValueOfType(json, r'userLastName')!, + userName: mapValueOfType(json, r'userName')!, videos: mapValueOfType(json, r'videos')!, ); } @@ -129,9 +122,8 @@ class UsageByUserDto { static const requiredKeys = { 'photos', 'usage', - 'userFirstName', 'userId', - 'userLastName', + 'userName', 'videos', }; } diff --git a/mobile/openapi/lib/model/user_dto.dart b/mobile/openapi/lib/model/user_dto.dart index e96588cf5..de26cc8f2 100644 --- a/mobile/openapi/lib/model/user_dto.dart +++ b/mobile/openapi/lib/model/user_dto.dart @@ -14,48 +14,42 @@ class UserDto { /// Returns a new [UserDto] instance. UserDto({ required this.email, - required this.firstName, required this.id, - required this.lastName, + required this.name, required this.profileImagePath, }); String email; - String firstName; - String id; - String lastName; + String name; String profileImagePath; @override bool operator ==(Object other) => identical(this, other) || other is UserDto && other.email == email && - other.firstName == firstName && other.id == id && - other.lastName == lastName && + other.name == name && other.profileImagePath == profileImagePath; @override int get hashCode => // ignore: unnecessary_parenthesis (email.hashCode) + - (firstName.hashCode) + (id.hashCode) + - (lastName.hashCode) + + (name.hashCode) + (profileImagePath.hashCode); @override - String toString() => 'UserDto[email=$email, firstName=$firstName, id=$id, lastName=$lastName, profileImagePath=$profileImagePath]'; + String toString() => 'UserDto[email=$email, id=$id, name=$name, profileImagePath=$profileImagePath]'; Map toJson() { final json = {}; json[r'email'] = this.email; - json[r'firstName'] = this.firstName; json[r'id'] = this.id; - json[r'lastName'] = this.lastName; + json[r'name'] = this.name; json[r'profileImagePath'] = this.profileImagePath; return json; } @@ -69,9 +63,8 @@ class UserDto { return UserDto( email: mapValueOfType(json, r'email')!, - firstName: mapValueOfType(json, r'firstName')!, id: mapValueOfType(json, r'id')!, - lastName: mapValueOfType(json, r'lastName')!, + name: mapValueOfType(json, r'name')!, profileImagePath: mapValueOfType(json, r'profileImagePath')!, ); } @@ -121,9 +114,8 @@ class UserDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'email', - 'firstName', 'id', - 'lastName', + 'name', 'profileImagePath', }; } diff --git a/mobile/openapi/lib/model/user_response_dto.dart b/mobile/openapi/lib/model/user_response_dto.dart index 488c9aafd..73b30881f 100644 --- a/mobile/openapi/lib/model/user_response_dto.dart +++ b/mobile/openapi/lib/model/user_response_dto.dart @@ -17,11 +17,10 @@ class UserResponseDto { required this.deletedAt, required this.email, required this.externalPath, - required this.firstName, required this.id, required this.isAdmin, - required this.lastName, this.memoriesEnabled, + required this.name, required this.oauthId, required this.profileImagePath, required this.shouldChangePassword, @@ -37,14 +36,10 @@ class UserResponseDto { String? externalPath; - String firstName; - String id; bool isAdmin; - String lastName; - /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -53,6 +48,8 @@ class UserResponseDto { /// bool? memoriesEnabled; + String name; + String oauthId; String profileImagePath; @@ -69,11 +66,10 @@ class UserResponseDto { other.deletedAt == deletedAt && other.email == email && other.externalPath == externalPath && - other.firstName == firstName && other.id == id && other.isAdmin == isAdmin && - other.lastName == lastName && other.memoriesEnabled == memoriesEnabled && + other.name == name && other.oauthId == oauthId && other.profileImagePath == profileImagePath && other.shouldChangePassword == shouldChangePassword && @@ -87,11 +83,10 @@ class UserResponseDto { (deletedAt == null ? 0 : deletedAt!.hashCode) + (email.hashCode) + (externalPath == null ? 0 : externalPath!.hashCode) + - (firstName.hashCode) + (id.hashCode) + (isAdmin.hashCode) + - (lastName.hashCode) + (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) + + (name.hashCode) + (oauthId.hashCode) + (profileImagePath.hashCode) + (shouldChangePassword.hashCode) + @@ -99,7 +94,7 @@ class UserResponseDto { (updatedAt.hashCode); @override - String toString() => 'UserResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, firstName=$firstName, id=$id, isAdmin=$isAdmin, lastName=$lastName, memoriesEnabled=$memoriesEnabled, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; + String toString() => 'UserResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -115,15 +110,14 @@ class UserResponseDto { } else { // json[r'externalPath'] = null; } - json[r'firstName'] = this.firstName; json[r'id'] = this.id; json[r'isAdmin'] = this.isAdmin; - json[r'lastName'] = this.lastName; if (this.memoriesEnabled != null) { json[r'memoriesEnabled'] = this.memoriesEnabled; } else { // json[r'memoriesEnabled'] = null; } + json[r'name'] = this.name; json[r'oauthId'] = this.oauthId; json[r'profileImagePath'] = this.profileImagePath; json[r'shouldChangePassword'] = this.shouldChangePassword; @@ -148,11 +142,10 @@ class UserResponseDto { deletedAt: mapDateTime(json, r'deletedAt', ''), email: mapValueOfType(json, r'email')!, externalPath: mapValueOfType(json, r'externalPath'), - firstName: mapValueOfType(json, r'firstName')!, id: mapValueOfType(json, r'id')!, isAdmin: mapValueOfType(json, r'isAdmin')!, - lastName: mapValueOfType(json, r'lastName')!, memoriesEnabled: mapValueOfType(json, r'memoriesEnabled'), + name: mapValueOfType(json, r'name')!, oauthId: mapValueOfType(json, r'oauthId')!, profileImagePath: mapValueOfType(json, r'profileImagePath')!, shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, @@ -209,10 +202,9 @@ class UserResponseDto { 'deletedAt', 'email', 'externalPath', - 'firstName', 'id', 'isAdmin', - 'lastName', + 'name', 'oauthId', 'profileImagePath', 'shouldChangePassword', diff --git a/mobile/openapi/test/create_user_dto_test.dart b/mobile/openapi/test/create_user_dto_test.dart index 9ce64c1e0..0820ef0ed 100644 --- a/mobile/openapi/test/create_user_dto_test.dart +++ b/mobile/openapi/test/create_user_dto_test.dart @@ -26,21 +26,16 @@ void main() { // TODO }); - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - - // String lastName - test('to test the property `lastName`', () async { - // TODO - }); - // bool memoriesEnabled test('to test the property `memoriesEnabled`', () async { // TODO }); + // String name + test('to test the property `name`', () async { + // TODO + }); + // String password test('to test the property `password`', () async { // TODO diff --git a/mobile/openapi/test/login_response_dto_test.dart b/mobile/openapi/test/login_response_dto_test.dart index 408b7fade..a8365ff06 100644 --- a/mobile/openapi/test/login_response_dto_test.dart +++ b/mobile/openapi/test/login_response_dto_test.dart @@ -21,18 +21,13 @@ void main() { // TODO }); - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - // bool isAdmin test('to test the property `isAdmin`', () async { // TODO }); - // String lastName - test('to test the property `lastName`', () async { + // String name + test('to test the property `name`', () async { // TODO }); diff --git a/mobile/openapi/test/partner_response_dto_test.dart b/mobile/openapi/test/partner_response_dto_test.dart index 14092a44f..495762d3b 100644 --- a/mobile/openapi/test/partner_response_dto_test.dart +++ b/mobile/openapi/test/partner_response_dto_test.dart @@ -36,11 +36,6 @@ void main() { // TODO }); - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - // String id test('to test the property `id`', () async { // TODO @@ -56,13 +51,13 @@ void main() { // TODO }); - // String lastName - test('to test the property `lastName`', () async { + // bool memoriesEnabled + test('to test the property `memoriesEnabled`', () async { // TODO }); - // bool memoriesEnabled - test('to test the property `memoriesEnabled`', () async { + // String name + test('to test the property `name`', () async { // TODO }); diff --git a/mobile/openapi/test/sign_up_dto_test.dart b/mobile/openapi/test/sign_up_dto_test.dart index 5b02f0435..3c255f726 100644 --- a/mobile/openapi/test/sign_up_dto_test.dart +++ b/mobile/openapi/test/sign_up_dto_test.dart @@ -21,13 +21,8 @@ void main() { // TODO }); - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - - // String lastName - test('to test the property `lastName`', () async { + // String name + test('to test the property `name`', () async { // TODO }); diff --git a/mobile/openapi/test/update_user_dto_test.dart b/mobile/openapi/test/update_user_dto_test.dart index 511de33af..039a1a244 100644 --- a/mobile/openapi/test/update_user_dto_test.dart +++ b/mobile/openapi/test/update_user_dto_test.dart @@ -26,11 +26,6 @@ void main() { // TODO }); - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - // String id test('to test the property `id`', () async { // TODO @@ -41,13 +36,13 @@ void main() { // TODO }); - // String lastName - test('to test the property `lastName`', () async { + // bool memoriesEnabled + test('to test the property `memoriesEnabled`', () async { // TODO }); - // bool memoriesEnabled - test('to test the property `memoriesEnabled`', () async { + // String name + test('to test the property `name`', () async { // TODO }); diff --git a/mobile/openapi/test/usage_by_user_dto_test.dart b/mobile/openapi/test/usage_by_user_dto_test.dart index 685d49e9f..51becb06f 100644 --- a/mobile/openapi/test/usage_by_user_dto_test.dart +++ b/mobile/openapi/test/usage_by_user_dto_test.dart @@ -26,18 +26,13 @@ void main() { // TODO }); - // String userFirstName - test('to test the property `userFirstName`', () async { - // TODO - }); - // String userId test('to test the property `userId`', () async { // TODO }); - // String userLastName - test('to test the property `userLastName`', () async { + // String userName + test('to test the property `userName`', () async { // TODO }); diff --git a/mobile/openapi/test/user_dto_test.dart b/mobile/openapi/test/user_dto_test.dart index fa1b7da86..e0866d12d 100644 --- a/mobile/openapi/test/user_dto_test.dart +++ b/mobile/openapi/test/user_dto_test.dart @@ -21,18 +21,13 @@ void main() { // TODO }); - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - // String id test('to test the property `id`', () async { // TODO }); - // String lastName - test('to test the property `lastName`', () async { + // String name + test('to test the property `name`', () async { // TODO }); diff --git a/mobile/openapi/test/user_response_dto_test.dart b/mobile/openapi/test/user_response_dto_test.dart index 9e2095607..28830a73f 100644 --- a/mobile/openapi/test/user_response_dto_test.dart +++ b/mobile/openapi/test/user_response_dto_test.dart @@ -36,11 +36,6 @@ void main() { // TODO }); - // String firstName - test('to test the property `firstName`', () async { - // TODO - }); - // String id test('to test the property `id`', () async { // TODO @@ -51,13 +46,13 @@ void main() { // TODO }); - // String lastName - test('to test the property `lastName`', () async { + // bool memoriesEnabled + test('to test the property `memoriesEnabled`', () async { // TODO }); - // bool memoriesEnabled - test('to test the property `memoriesEnabled`', () async { + // String name + test('to test the property `name`', () async { // TODO }); diff --git a/mobile/test/sync_service_test.dart b/mobile/test/sync_service_test.dart index b2543c663..9abb86352 100644 --- a/mobile/test/sync_service_test.dart +++ b/mobile/test/sync_service_test.dart @@ -62,8 +62,7 @@ void main() { id: "1", updatedAt: DateTime.now(), email: "a@b.c", - firstName: "first", - lastName: "last", + name: "first last", isAdmin: false, ); setUpAll(() async { diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 782b69649..b776e4b28 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -6844,15 +6844,12 @@ "nullable": true, "type": "string" }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, "memoriesEnabled": { "type": "boolean" }, + "name": { + "type": "string" + }, "password": { "type": "string" }, @@ -6864,8 +6861,7 @@ "required": [ "email", "password", - "firstName", - "lastName" + "name" ], "type": "object" }, @@ -7463,13 +7459,10 @@ "accessToken": { "type": "string" }, - "firstName": { - "type": "string" - }, "isAdmin": { "type": "boolean" }, - "lastName": { + "name": { "type": "string" }, "profileImagePath": { @@ -7489,8 +7482,7 @@ "accessToken", "userId", "userEmail", - "firstName", - "lastName", + "name", "profileImagePath", "isAdmin", "shouldChangePassword" @@ -7656,9 +7648,6 @@ "nullable": true, "type": "string" }, - "firstName": { - "type": "string" - }, "id": { "type": "string" }, @@ -7668,12 +7657,12 @@ "isAdmin": { "type": "boolean" }, - "lastName": { - "type": "string" - }, "memoriesEnabled": { "type": "boolean" }, + "name": { + "type": "string" + }, "oauthId": { "type": "string" }, @@ -7694,8 +7683,7 @@ }, "required": [ "id", - "firstName", - "lastName", + "name", "email", "profileImagePath", "storageLabel", @@ -8464,14 +8452,10 @@ "example": "testuser@email.com", "type": "string" }, - "firstName": { + "name": { "example": "Admin", "type": "string" }, - "lastName": { - "example": "Doe", - "type": "string" - }, "password": { "example": "password", "type": "string" @@ -8480,8 +8464,7 @@ "required": [ "email", "password", - "firstName", - "lastName" + "name" ], "type": "object" }, @@ -9163,9 +9146,6 @@ "externalPath": { "type": "string" }, - "firstName": { - "type": "string" - }, "id": { "format": "uuid", "type": "string" @@ -9173,12 +9153,12 @@ "isAdmin": { "type": "boolean" }, - "lastName": { - "type": "string" - }, "memoriesEnabled": { "type": "boolean" }, + "name": { + "type": "string" + }, "password": { "type": "string" }, @@ -9203,13 +9183,10 @@ "format": "int64", "type": "integer" }, - "userFirstName": { - "type": "string" - }, "userId": { "type": "string" }, - "userLastName": { + "userName": { "type": "string" }, "videos": { @@ -9218,8 +9195,7 @@ }, "required": [ "userId", - "userFirstName", - "userLastName", + "userName", "photos", "videos", "usage" @@ -9231,13 +9207,10 @@ "email": { "type": "string" }, - "firstName": { - "type": "string" - }, "id": { "type": "string" }, - "lastName": { + "name": { "type": "string" }, "profileImagePath": { @@ -9246,8 +9219,7 @@ }, "required": [ "id", - "firstName", - "lastName", + "name", "email", "profileImagePath" ], @@ -9271,21 +9243,18 @@ "nullable": true, "type": "string" }, - "firstName": { - "type": "string" - }, "id": { "type": "string" }, "isAdmin": { "type": "boolean" }, - "lastName": { - "type": "string" - }, "memoriesEnabled": { "type": "boolean" }, + "name": { + "type": "string" + }, "oauthId": { "type": "string" }, @@ -9306,8 +9275,7 @@ }, "required": [ "id", - "firstName", - "lastName", + "name", "email", "profileImagePath", "storageLabel", diff --git a/server/package-lock.json b/server/package-lock.json index 70c213b68..218fc6edd 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -16845,11 +16845,6 @@ "luxon": "^3.2.1" } }, - "cron-validator": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz", - "integrity": "sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==" - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/server/src/admin-cli/commands/reset-admin-password.command.ts b/server/src/admin-cli/commands/reset-admin-password.command.ts index f28780a7d..af36c590c 100644 --- a/server/src/admin-cli/commands/reset-admin-password.command.ts +++ b/server/src/admin-cli/commands/reset-admin-password.command.ts @@ -15,12 +15,12 @@ export class ResetAdminPasswordCommand extends CommandRunner { async run(): Promise { const ask = (admin: UserResponseDto) => { - const { id, oauthId, email, firstName, lastName } = admin; + const { id, oauthId, email, name } = admin; console.log(`Found Admin: - ID=${id} - OAuth ID=${oauthId} - Email=${email} -- Name=${firstName} ${lastName}`); +- Name=${name}`); return this.inquirer.ask<{ password: string }>('prompt-password', undefined).then(({ password }) => password); }; diff --git a/server/src/domain/auth/auth.dto.ts b/server/src/domain/auth/auth.dto.ts index c0703911d..d5ee275ad 100644 --- a/server/src/domain/auth/auth.dto.ts +++ b/server/src/domain/auth/auth.dto.ts @@ -33,8 +33,7 @@ export class LoginResponseDto { accessToken!: string; userId!: string; userEmail!: string; - firstName!: string; - lastName!: string; + name!: string; profileImagePath!: string; isAdmin!: boolean; shouldChangePassword!: boolean; @@ -45,8 +44,7 @@ export function mapLoginResponse(entity: UserEntity, accessToken: string): Login accessToken: accessToken, userId: entity.id, userEmail: entity.email, - firstName: entity.firstName, - lastName: entity.lastName, + name: entity.name, isAdmin: entity.isAdmin, profileImagePath: entity.profileImagePath, shouldChangePassword: entity.shouldChangePassword, @@ -62,12 +60,7 @@ export class SignUpDto extends LoginCredentialDto { @IsString() @IsNotEmpty() @ApiProperty({ example: 'Admin' }) - firstName!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'Doe' }) - lastName!: string; + name!: string; } export class ChangePasswordDto { diff --git a/server/src/domain/auth/auth.service.spec.ts b/server/src/domain/auth/auth.service.spec.ts index 3fb5d4d32..f915883a5 100644 --- a/server/src/domain/auth/auth.service.spec.ts +++ b/server/src/domain/auth/auth.service.spec.ts @@ -236,7 +236,7 @@ describe('AuthService', () => { }); describe('adminSignUp', () => { - const dto: SignUpDto = { email: 'test@immich.com', password: 'password', firstName: 'immich', lastName: 'admin' }; + const dto: SignUpDto = { email: 'test@immich.com', password: 'password', name: 'immich admin' }; it('should only allow one admin', async () => { userMock.getAdmin.mockResolvedValue({} as UserEntity); @@ -251,8 +251,7 @@ describe('AuthService', () => { id: 'admin', createdAt: new Date('2021-01-01'), email: 'test@immich.com', - firstName: 'immich', - lastName: 'admin', + name: 'immich admin', }); expect(userMock.getAdmin).toHaveBeenCalled(); expect(userMock.create).toHaveBeenCalled(); diff --git a/server/src/domain/auth/auth.service.ts b/server/src/domain/auth/auth.service.ts index 00aa41bfd..7fa3d6902 100644 --- a/server/src/domain/auth/auth.service.ts +++ b/server/src/domain/auth/auth.service.ts @@ -146,8 +146,7 @@ export class AuthService { const admin = await this.userCore.createUser({ isAdmin: true, email: dto.email, - firstName: dto.firstName, - lastName: dto.lastName, + name: dto.name, password: dto.password, storageLabel: 'admin', }); @@ -273,9 +272,9 @@ export class AuthService { storageLabel = null; } + const userName = profile.name ?? `${profile.given_name || ''} ${profile.family_name || ''}`; user = await this.userCore.createUser({ - firstName: profile.given_name || '', - lastName: profile.family_name || '', + name: userName, email: profile.email, oauthId: profile.sub, storageLabel, diff --git a/server/src/domain/partner/partner.service.spec.ts b/server/src/domain/partner/partner.service.spec.ts index 0bae95aa7..6eaa87ea0 100644 --- a/server/src/domain/partner/partner.service.spec.ts +++ b/server/src/domain/partner/partner.service.spec.ts @@ -7,10 +7,9 @@ import { PartnerService } from './partner.service'; const responseDto = { admin: { email: 'admin@test.com', - firstName: 'admin_first_name', + name: 'admin_name', id: 'admin_id', isAdmin: true, - lastName: 'admin_last_name', oauthId: '', profileImagePath: '', shouldChangePassword: false, @@ -24,10 +23,9 @@ const responseDto = { }, user1: { email: 'immich@test.com', - firstName: 'immich_first_name', + name: 'immich_name', id: 'user-id', isAdmin: false, - lastName: 'immich_last_name', oauthId: '', profileImagePath: '', shouldChangePassword: false, diff --git a/server/src/domain/repositories/user.repository.ts b/server/src/domain/repositories/user.repository.ts index c4647d571..76060c327 100644 --- a/server/src/domain/repositories/user.repository.ts +++ b/server/src/domain/repositories/user.repository.ts @@ -6,8 +6,7 @@ export interface UserListFilter { export interface UserStatsQueryResponse { userId: string; - userFirstName: string; - userLastName: string; + userName: string; photos: number; videos: number; usage: number; diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts index 907ce26ea..0223a58fa 100644 --- a/server/src/domain/server-info/server-info.dto.ts +++ b/server/src/domain/server-info/server-info.dto.ts @@ -38,9 +38,7 @@ export class UsageByUserDto { @ApiProperty({ type: 'string' }) userId!: string; @ApiProperty({ type: 'string' }) - userFirstName!: string; - @ApiProperty({ type: 'string' }) - userLastName!: string; + userName!: string; @ApiProperty({ type: 'integer' }) photos!: number; @ApiProperty({ type: 'integer' }) 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 204eb1bd1..74aab12c7 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -195,24 +195,21 @@ describe(ServerInfoService.name, () => { userMock.getUserStats.mockResolvedValue([ { userId: 'user1', - userFirstName: '1', - userLastName: 'User', + userName: '1 User', photos: 10, videos: 11, usage: 12345, }, { userId: 'user2', - userFirstName: '2', - userLastName: 'User', + userName: '2 User', photos: 10, videos: 20, usage: 123456, }, { userId: 'user3', - userFirstName: '3', - userLastName: 'User', + userName: '3 User', photos: 100, videos: 0, usage: 987654, @@ -227,25 +224,22 @@ describe(ServerInfoService.name, () => { { photos: 10, usage: 12345, - userFirstName: '1', + userName: '1 User', userId: 'user1', - userLastName: 'User', videos: 11, }, { photos: 10, usage: 123456, - userFirstName: '2', + userName: '2 User', userId: 'user2', - userLastName: 'User', videos: 20, }, { photos: 100, usage: 987654, - userFirstName: '3', + userName: '3 User', userId: 'user3', - userLastName: 'User', videos: 0, }, ], diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index 27005f176..c215709eb 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -98,8 +98,7 @@ export class ServerInfoService { for (const user of userStats) { const usage = new UsageByUserDto(); usage.userId = user.userId; - usage.userFirstName = user.userFirstName; - usage.userLastName = user.userLastName; + usage.userName = user.userName; usage.photos = user.photos; usage.videos = user.videos; usage.usage = user.usage; diff --git a/server/src/domain/user/dto/create-user.dto.spec.ts b/server/src/domain/user/dto/create-user.dto.spec.ts index 492d44c36..4e571d38a 100644 --- a/server/src/domain/user/dto/create-user.dto.spec.ts +++ b/server/src/domain/user/dto/create-user.dto.spec.ts @@ -7,8 +7,7 @@ describe('create user DTO', () => { const params: Partial = { email: undefined, password: 'password', - firstName: 'first name', - lastName: 'last name', + name: 'name', }; let dto: CreateUserDto = plainToInstance(CreateUserDto, params); let errors = await validate(dto); @@ -31,8 +30,7 @@ describe('create user DTO', () => { const dto = plainToInstance(CreateUserDto, { email: someEmail, password: 'some password', - firstName: 'some first name', - lastName: 'some last name', + name: 'some name', }); const errors = await validate(dto); expect(errors).toHaveLength(0); @@ -48,8 +46,7 @@ describe('create admin DTO', () => { isAdmin: true, email: someEmail, password: 'some password', - firstName: 'some first name', - lastName: 'some last name', + name: 'some name', }); const errors = await validate(dto); expect(errors).toHaveLength(0); @@ -64,8 +61,7 @@ describe('create user oauth DTO', () => { const dto = plainToInstance(CreateUserOAuthDto, { email: someEmail, oauthId: 'some oauth id', - firstName: 'some first name', - lastName: 'some last name', + name: 'some name', }); const errors = await validate(dto); expect(errors).toHaveLength(0); diff --git a/server/src/domain/user/dto/create-user.dto.ts b/server/src/domain/user/dto/create-user.dto.ts index 2a5f659ef..b1090d9c0 100644 --- a/server/src/domain/user/dto/create-user.dto.ts +++ b/server/src/domain/user/dto/create-user.dto.ts @@ -13,11 +13,7 @@ export class CreateUserDto { @IsNotEmpty() @IsString() - firstName!: string; - - @IsNotEmpty() - @IsString() - lastName!: string; + name!: string; @Optional({ nullable: true }) @IsString() @@ -45,10 +41,7 @@ export class CreateAdminDto { password!: string; @IsNotEmpty() - firstName!: string; - - @IsNotEmpty() - lastName!: string; + name!: string; } export class CreateUserOAuthDto { @@ -59,7 +52,5 @@ export class CreateUserOAuthDto { @IsNotEmpty() oauthId!: string; - firstName?: string; - - lastName?: string; + name?: string; } diff --git a/server/src/domain/user/dto/update-user.dto.ts b/server/src/domain/user/dto/update-user.dto.ts index e993ae80f..4f05498da 100644 --- a/server/src/domain/user/dto/update-user.dto.ts +++ b/server/src/domain/user/dto/update-user.dto.ts @@ -17,12 +17,7 @@ export class UpdateUserDto { @Optional() @IsString() @IsNotEmpty() - firstName?: string; - - @Optional() - @IsString() - @IsNotEmpty() - lastName?: string; + name?: string; @Optional() @IsString() diff --git a/server/src/domain/user/response-dto/user-response.dto.ts b/server/src/domain/user/response-dto/user-response.dto.ts index b9f990378..fd9a12167 100644 --- a/server/src/domain/user/response-dto/user-response.dto.ts +++ b/server/src/domain/user/response-dto/user-response.dto.ts @@ -2,8 +2,7 @@ import { UserEntity } from '@app/infra/entities'; export class UserDto { id!: string; - firstName!: string; - lastName!: string; + name!: string; email!: string; profileImagePath!: string; } @@ -24,8 +23,7 @@ export const mapSimpleUser = (entity: UserEntity): UserDto => { return { id: entity.id, email: entity.email, - firstName: entity.firstName, - lastName: entity.lastName, + name: entity.name, profileImagePath: entity.profileImagePath, }; }; diff --git a/server/src/domain/user/user.service.spec.ts b/server/src/domain/user/user.service.spec.ts index 1f9918fec..94f919178 100644 --- a/server/src/domain/user/user.service.spec.ts +++ b/server/src/domain/user/user.service.spec.ts @@ -289,8 +289,7 @@ describe(UserService.name, () => { await expect( sut.create({ email: 'john_smith@email.com', - firstName: 'John', - lastName: 'Smith', + name: 'John Smith', password: 'password', }), ).rejects.toBeInstanceOf(BadRequestException); @@ -303,8 +302,7 @@ describe(UserService.name, () => { await expect( sut.create({ email: userStub.user1.email, - firstName: userStub.user1.firstName, - lastName: userStub.user1.lastName, + name: userStub.user1.name, password: 'password', storageLabel: 'label', }), @@ -313,8 +311,7 @@ describe(UserService.name, () => { expect(userMock.getAdmin).toBeCalled(); expect(userMock.create).toBeCalledWith({ email: userStub.user1.email, - firstName: userStub.user1.firstName, - lastName: userStub.user1.lastName, + name: userStub.user1.name, storageLabel: 'label', password: expect.anything(), }); diff --git a/server/src/infra/entities/user.entity.ts b/server/src/infra/entities/user.entity.ts index e6555153a..83f23bef6 100644 --- a/server/src/infra/entities/user.entity.ts +++ b/server/src/infra/entities/user.entity.ts @@ -16,10 +16,7 @@ export class UserEntity { id!: string; @Column({ default: '' }) - firstName!: string; - - @Column({ default: '' }) - lastName!: string; + name!: string; @Column({ default: false }) isAdmin!: boolean; diff --git a/server/src/infra/migrations/1699322864544-UserNameConsolidation.ts b/server/src/infra/migrations/1699322864544-UserNameConsolidation.ts new file mode 100644 index 000000000..431a2a92f --- /dev/null +++ b/server/src/infra/migrations/1699322864544-UserNameConsolidation.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddUsername1699322864544 implements MigrationInterface { + name = 'AddUsername1699322864544' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" ADD "name" character varying NOT NULL DEFAULT ''`); + await queryRunner.query(`UPDATE "users" SET "name" = CONCAT(COALESCE("firstName", ''), ' ', COALESCE("lastName", ''))`); + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "firstName"`); + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "lastName"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "name"`); + await queryRunner.query(`ALTER TABLE "users" ADD "lastName" character varying NOT NULL DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "users" ADD "firstName" character varying NOT NULL DEFAULT ''`); + await queryRunner.query(`UPDATE "users" SET "lastName" = COALESCE("email", '')`); + await queryRunner.query(`UPDATE "users" SET "firstName" = COALESCE("email", '')`); + } + +} diff --git a/server/src/infra/repositories/user.repository.ts b/server/src/infra/repositories/user.repository.ts index 9809ec75d..b84adb2fd 100644 --- a/server/src/infra/repositories/user.repository.ts +++ b/server/src/infra/repositories/user.repository.ts @@ -80,8 +80,7 @@ export class UserRepository implements IUserRepository { const stats = await this.userRepository .createQueryBuilder('users') .select('users.id', 'userId') - .addSelect('users.firstName', 'userFirstName') - .addSelect('users.lastName', 'userLastName') + .addSelect('users.name', 'userName') .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos') .addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage') diff --git a/server/test/e2e/activity.e2e-spec.ts b/server/test/e2e/activity.e2e-spec.ts index 0bb8aa2c9..34799b163 100644 --- a/server/test/e2e/activity.e2e-spec.ts +++ b/server/test/e2e/activity.e2e-spec.ts @@ -350,8 +350,7 @@ describe(`${ActivityController.name} (e2e)`, () => { const { id: userId } = await api.userApi.create(server, admin.accessToken, { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }); await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); @@ -371,8 +370,7 @@ describe(`${ActivityController.name} (e2e)`, () => { const { id: userId } = await api.userApi.create(server, admin.accessToken, { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }); await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); @@ -393,8 +391,7 @@ describe(`${ActivityController.name} (e2e)`, () => { const { id: userId } = await api.userApi.create(server, admin.accessToken, { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }); await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); diff --git a/server/test/e2e/album.e2e-spec.ts b/server/test/e2e/album.e2e-spec.ts index 8348eff03..8c372a421 100644 --- a/server/test/e2e/album.e2e-spec.ts +++ b/server/test/e2e/album.e2e-spec.ts @@ -41,14 +41,12 @@ describe(`${AlbumController.name} (e2e)`, () => { api.userApi.create(server, admin.accessToken, { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }), api.userApi.create(server, admin.accessToken, { email: 'user2@immich.app', password: 'Password123', - firstName: 'User 2', - lastName: 'Test', + name: 'User 2', }), ]); diff --git a/server/test/e2e/asset.e2e-spec.ts b/server/test/e2e/asset.e2e-spec.ts index 70be784c7..e15e27d5a 100644 --- a/server/test/e2e/asset.e2e-spec.ts +++ b/server/test/e2e/asset.e2e-spec.ts @@ -22,15 +22,13 @@ import request from 'supertest'; const user1Dto = { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }; const user2Dto = { email: 'user2@immich.app', password: 'Password123', - firstName: 'User 2', - lastName: 'Test', + name: 'User 2', }; const makeUploadDto = (options?: { omit: string }): Record => { diff --git a/server/test/e2e/auth.e2e-spec.ts b/server/test/e2e/auth.e2e-spec.ts index 191add42f..eb47a8725 100644 --- a/server/test/e2e/auth.e2e-spec.ts +++ b/server/test/e2e/auth.e2e-spec.ts @@ -13,15 +13,13 @@ import { import { testApp } from '@test/test-utils'; import request from 'supertest'; -const firstName = 'Immich'; -const lastName = 'Admin'; +const name = 'Immich Admin'; const password = 'Password123'; const email = 'admin@immich.app'; const adminSignupResponse = { id: expect.any(String), - firstName: 'Immich', - lastName: 'Admin', + name: 'Immich Admin', email: 'admin@immich.app', storageLabel: 'admin', externalPath: null, @@ -64,23 +62,19 @@ describe(`${AuthController.name} (e2e)`, () => { const invalid = [ { should: 'require an email address', - data: { firstName, lastName, password }, + data: { name, password }, }, { should: 'require a password', - data: { firstName, lastName, email }, + data: { name, email }, }, { - should: 'require a first name ', - data: { lastName, email, password }, - }, - { - should: 'require a last name ', - data: { firstName, email, password }, + should: 'require a name', + data: { email, password }, }, { should: 'require a valid email', - data: { firstName, lastName, email: 'immich', password }, + data: { name, email: 'immich', password }, }, ]; diff --git a/server/test/e2e/library.e2e-spec.ts b/server/test/e2e/library.e2e-spec.ts index 9cfbe8961..d8905a8c1 100644 --- a/server/test/e2e/library.e2e-spec.ts +++ b/server/test/e2e/library.e2e-spec.ts @@ -15,15 +15,13 @@ describe(`${LibraryController.name} (e2e)`, () => { const user1Dto = { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }; const user2Dto = { email: 'user2@immich.app', password: 'Password123', - firstName: 'User 2', - lastName: 'Test', + name: 'User 2', }; beforeAll(async () => { diff --git a/server/test/e2e/partner.e2e-spec.ts b/server/test/e2e/partner.e2e-spec.ts index f6b8e144f..3eb48dd9e 100644 --- a/server/test/e2e/partner.e2e-spec.ts +++ b/server/test/e2e/partner.e2e-spec.ts @@ -10,15 +10,13 @@ import request from 'supertest'; const user1Dto = { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }; const user2Dto = { email: 'user2@immich.app', password: 'Password123', - firstName: 'User 2', - lastName: 'Test', + name: 'User 2', }; describe(`${PartnerController.name} (e2e)`, () => { diff --git a/server/test/e2e/server-info.e2e-spec.ts b/server/test/e2e/server-info.e2e-spec.ts index 0b508a2ef..be85e424f 100644 --- a/server/test/e2e/server-info.e2e-spec.ts +++ b/server/test/e2e/server-info.e2e-spec.ts @@ -111,7 +111,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => { it('should only work for admins', async () => { const loginDto = { email: 'test@immich.app', password: 'Immich123' }; - await api.userApi.create(server, accessToken, { ...loginDto, firstName: 'test', lastName: 'test' }); + await api.userApi.create(server, accessToken, { ...loginDto, name: 'test' }); const { accessToken: userAccessToken } = await api.authApi.login(server, loginDto); const { status, body } = await request(server) .get('/server-info/statistics') @@ -132,9 +132,8 @@ describe(`${ServerInfoController.name} (e2e)`, () => { { photos: 0, usage: 0, - userFirstName: 'Immich', + userName: 'Immich Admin', userId: loginResponse.userId, - userLastName: 'Admin', videos: 0, }, ], diff --git a/server/test/e2e/shared-link.e2e-spec.ts b/server/test/e2e/shared-link.e2e-spec.ts index 03eb9da7d..ededc0138 100644 --- a/server/test/e2e/shared-link.e2e-spec.ts +++ b/server/test/e2e/shared-link.e2e-spec.ts @@ -11,8 +11,7 @@ import request from 'supertest'; const user1Dto = { email: 'user1@immich.app', password: 'Password123', - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }; describe(`${PartnerController.name} (e2e)`, () => { diff --git a/server/test/e2e/system-config.e2e-spec.ts b/server/test/e2e/system-config.e2e-spec.ts index dd7a69439..0e505ca11 100644 --- a/server/test/e2e/system-config.e2e-spec.ts +++ b/server/test/e2e/system-config.e2e-spec.ts @@ -64,8 +64,7 @@ describe(`${SystemConfigController.name} (e2e)`, () => { const credentials = { email: 'user1@immich.app', password: 'Password123' }; await api.userApi.create(server, admin.accessToken, { ...credentials, - firstName: 'User 1', - lastName: 'Test', + name: 'User 1', }); const { accessToken } = await api.authApi.login(server, credentials); const { status, body } = await request(server) diff --git a/server/test/e2e/user.e2e-spec.ts b/server/test/e2e/user.e2e-spec.ts index 02b03e9ba..41ce7c116 100644 --- a/server/test/e2e/user.e2e-spec.ts +++ b/server/test/e2e/user.e2e-spec.ts @@ -59,8 +59,7 @@ describe(`${UserController.name}`, () => { const user1 = await api.userApi.create(server, accessToken, { email: `user1@immich.app`, password: 'Password123', - firstName: `User 1`, - lastName: 'Test', + name: `User 1`, }); await api.userApi.delete(server, accessToken, user1.id); @@ -78,8 +77,7 @@ describe(`${UserController.name}`, () => { const user1 = await api.userApi.create(server, accessToken, { email: `user1@immich.app`, password: 'Password123', - firstName: `User 1`, - lastName: 'Test', + name: `User 1`, }); await api.userApi.delete(server, accessToken, user1.id); @@ -149,8 +147,7 @@ describe(`${UserController.name}`, () => { isAdmin: true, email: 'user1@immich.app', password: 'Password123', - firstName: 'Immich', - lastName: 'User', + name: 'Immich', }) .set('Authorization', `Bearer ${accessToken}`); expect(body).toMatchObject({ @@ -167,8 +164,7 @@ describe(`${UserController.name}`, () => { .send({ email: 'no-memories@immich.app', password: 'Password123', - firstName: 'No Memories', - lastName: 'User', + name: 'No Memories', memoriesEnabled: false, }) .set('Authorization', `Bearer ${accessToken}`); @@ -186,8 +182,7 @@ describe(`${UserController.name}`, () => { beforeEach(async () => { userToDelete = await api.userApi.create(server, accessToken, { email: userStub.user1.email, - firstName: userStub.user1.firstName, - lastName: userStub.user1.lastName, + name: userStub.user1.name, password: 'superSecurePassword', }); }); @@ -246,8 +241,7 @@ describe(`${UserController.name}`, () => { const user = await api.userApi.create(server, accessToken, { email: 'user1@immich.app', password: 'Password123', - firstName: 'Immich', - lastName: 'User', + name: 'Immich User', }); const { status, body } = await request(server) @@ -284,15 +278,13 @@ describe(`${UserController.name}`, () => { const before = await api.userApi.get(server, accessToken, loginResponse.userId); const after = await api.userApi.update(server, accessToken, { id: before.id, - firstName: 'First Name', - lastName: 'Last Name', + name: 'Name', }); expect(after).toEqual({ ...before, updatedAt: expect.any(String), - firstName: 'First Name', - lastName: 'Last Name', + name: 'Name', }); expect(before.updatedAt).not.toEqual(after.updatedAt); }); diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts index 68ff9b717..d4b636896 100644 --- a/server/test/fixtures/auth.stub.ts +++ b/server/test/fixtures/auth.stub.ts @@ -1,8 +1,7 @@ import { AuthUserDto } from '@app/domain'; export const adminSignupStub = { - firstName: 'Immich', - lastName: 'Admin', + name: 'Immich Admin', email: 'admin@immich.app', password: 'Password123', }; @@ -103,9 +102,8 @@ export const loginResponseStub = { admin: { response: { accessToken: expect.any(String), - firstName: 'Immich', + name: 'Immich Admin', isAdmin: true, - lastName: 'Admin', profileImagePath: '', shouldChangePassword: true, userEmail: 'admin@immich.app', @@ -117,8 +115,7 @@ export const loginResponseStub = { accessToken: 'cmFuZG9tLWJ5dGVz', userId: 'user-id', userEmail: 'immich@test.com', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', profileImagePath: '', isAdmin: false, shouldChangePassword: false, @@ -133,8 +130,7 @@ export const loginResponseStub = { accessToken: 'cmFuZG9tLWJ5dGVz', userId: 'user-id', userEmail: 'immich@test.com', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', profileImagePath: '', isAdmin: false, shouldChangePassword: false, @@ -149,8 +145,7 @@ export const loginResponseStub = { accessToken: 'cmFuZG9tLWJ5dGVz', userId: 'user-id', userEmail: 'immich@test.com', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', profileImagePath: '', isAdmin: false, shouldChangePassword: false, diff --git a/server/test/fixtures/user.stub.ts b/server/test/fixtures/user.stub.ts index af5515baf..b528a107f 100644 --- a/server/test/fixtures/user.stub.ts +++ b/server/test/fixtures/user.stub.ts @@ -5,8 +5,7 @@ export const userStub = { admin: Object.freeze({ ...authStub.admin, password: 'admin_password', - firstName: 'admin_first_name', - lastName: 'admin_last_name', + name: 'admin_name', storageLabel: 'admin', externalPath: null, oauthId: '', @@ -22,8 +21,7 @@ export const userStub = { user1: Object.freeze({ ...authStub.user1, password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', storageLabel: null, externalPath: null, oauthId: '', @@ -39,8 +37,7 @@ export const userStub = { user2: Object.freeze({ ...authStub.user2, password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', storageLabel: null, externalPath: null, oauthId: '', @@ -56,8 +53,7 @@ export const userStub = { storageLabel: Object.freeze({ ...authStub.user1, password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', storageLabel: 'label-1', externalPath: null, oauthId: '', @@ -73,8 +69,7 @@ export const userStub = { externalPath1: Object.freeze({ ...authStub.user1, password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', storageLabel: 'label-1', externalPath: '/data/user1', oauthId: '', @@ -90,8 +85,7 @@ export const userStub = { externalPath2: Object.freeze({ ...authStub.user1, password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', storageLabel: 'label-1', externalPath: '/data/user2', oauthId: '', @@ -107,8 +101,7 @@ export const userStub = { profilePath: Object.freeze({ ...authStub.user1, password: 'immich_password', - firstName: 'immich_first_name', - lastName: 'immich_last_name', + name: 'immich_name', storageLabel: 'label-1', externalPath: null, oauthId: '', diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 496fa5441..8fb2c1b3d 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1341,24 +1341,18 @@ export interface CreateUserDto { * @memberof CreateUserDto */ 'externalPath'?: string | null; - /** - * - * @type {string} - * @memberof CreateUserDto - */ - 'firstName': string; - /** - * - * @type {string} - * @memberof CreateUserDto - */ - 'lastName': string; /** * * @type {boolean} * @memberof CreateUserDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof CreateUserDto + */ + 'name': string; /** * * @type {string} @@ -2137,12 +2131,6 @@ export interface LoginResponseDto { * @memberof LoginResponseDto */ 'accessToken': string; - /** - * - * @type {string} - * @memberof LoginResponseDto - */ - 'firstName': string; /** * * @type {boolean} @@ -2154,7 +2142,7 @@ export interface LoginResponseDto { * @type {string} * @memberof LoginResponseDto */ - 'lastName': string; + 'name': string; /** * * @type {string} @@ -2391,12 +2379,6 @@ export interface PartnerResponseDto { * @memberof PartnerResponseDto */ 'externalPath': string | null; - /** - * - * @type {string} - * @memberof PartnerResponseDto - */ - 'firstName': string; /** * * @type {string} @@ -2415,18 +2397,18 @@ export interface PartnerResponseDto { * @memberof PartnerResponseDto */ 'isAdmin': boolean; - /** - * - * @type {string} - * @memberof PartnerResponseDto - */ - 'lastName': string; /** * * @type {boolean} * @memberof PartnerResponseDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof PartnerResponseDto + */ + 'name': string; /** * * @type {string} @@ -3431,13 +3413,7 @@ export interface SignUpDto { * @type {string} * @memberof SignUpDto */ - 'firstName': string; - /** - * - * @type {string} - * @memberof SignUpDto - */ - 'lastName': string; + 'name': string; /** * * @type {string} @@ -4380,12 +4356,6 @@ export interface UpdateUserDto { * @memberof UpdateUserDto */ 'externalPath'?: string; - /** - * - * @type {string} - * @memberof UpdateUserDto - */ - 'firstName'?: string; /** * * @type {string} @@ -4398,18 +4368,18 @@ export interface UpdateUserDto { * @memberof UpdateUserDto */ 'isAdmin'?: boolean; - /** - * - * @type {string} - * @memberof UpdateUserDto - */ - 'lastName'?: string; /** * * @type {boolean} * @memberof UpdateUserDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof UpdateUserDto + */ + 'name'?: string; /** * * @type {string} @@ -4447,12 +4417,6 @@ export interface UsageByUserDto { * @memberof UsageByUserDto */ 'usage': number; - /** - * - * @type {string} - * @memberof UsageByUserDto - */ - 'userFirstName': string; /** * * @type {string} @@ -4464,7 +4428,7 @@ export interface UsageByUserDto { * @type {string} * @memberof UsageByUserDto */ - 'userLastName': string; + 'userName': string; /** * * @type {number} @@ -4484,12 +4448,6 @@ export interface UserDto { * @memberof UserDto */ 'email': string; - /** - * - * @type {string} - * @memberof UserDto - */ - 'firstName': string; /** * * @type {string} @@ -4501,7 +4459,7 @@ export interface UserDto { * @type {string} * @memberof UserDto */ - 'lastName': string; + 'name': string; /** * * @type {string} @@ -4539,12 +4497,6 @@ export interface UserResponseDto { * @memberof UserResponseDto */ 'externalPath': string | null; - /** - * - * @type {string} - * @memberof UserResponseDto - */ - 'firstName': string; /** * * @type {string} @@ -4557,18 +4509,18 @@ export interface UserResponseDto { * @memberof UserResponseDto */ 'isAdmin': boolean; - /** - * - * @type {string} - * @memberof UserResponseDto - */ - 'lastName': string; /** * * @type {boolean} * @memberof UserResponseDto */ 'memoriesEnabled'?: boolean; + /** + * + * @type {string} + * @memberof UserResponseDto + */ + 'name': string; /** * * @type {string} diff --git a/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte b/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte index 1b7261544..5bad6c940 100644 --- a/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte +++ b/web/src/lib/components/admin-page/delete-confirm-dialoge.svelte @@ -27,7 +27,7 @@

- {user.firstName} {user.lastName}'s account and assets will be permanently deleted after 7 days. + {user.name}'s account and assets will be permanently deleted after 7 days.

Are you sure you want to continue?

diff --git a/web/src/lib/components/admin-page/restore-dialoge.svelte b/web/src/lib/components/admin-page/restore-dialoge.svelte index 4e4517045..848dedaf6 100644 --- a/web/src/lib/components/admin-page/restore-dialoge.svelte +++ b/web/src/lib/components/admin-page/restore-dialoge.svelte @@ -16,6 +16,6 @@ -

{user.firstName} {user.lastName}'s account will be restored.

+

{user.name}'s account will be restored.

diff --git a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte index 0dcddfb3b..a4f527445 100644 --- a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte +++ b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte @@ -96,7 +96,7 @@ - {user.userFirstName} {user.userLastName} + {user.userName} {user.photos.toLocaleString($locale)} {user.videos.toLocaleString($locale)} {asByteUnitString(user.usage, $locale)} diff --git a/web/src/lib/components/album-page/album-card.svelte b/web/src/lib/components/album-page/album-card.svelte index fdaf59395..040d5faa4 100644 --- a/web/src/lib/components/album-page/album-card.svelte +++ b/web/src/lib/components/album-page/album-card.svelte @@ -123,8 +123,7 @@

Owned

{:else}

- Shared by {albumOwner.firstName} - {albumOwner.lastName} + Shared by {albumOwner.name}

{/if} {/await} diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte index 1a3e43892..5fecc6431 100644 --- a/web/src/lib/components/album-page/album-options.svelte +++ b/web/src/lib/components/album-page/album-options.svelte @@ -56,7 +56,7 @@
-
{`${user.firstName} ${user.lastName}`}
+
{user.name}
Owner
{#each album.sharedUsers as user (user.id)} @@ -64,7 +64,7 @@
-
{`${user.firstName} ${user.lastName}`}
+
{user.name}
{/each} diff --git a/web/src/lib/components/album-page/share-info-modal.svelte b/web/src/lib/components/album-page/share-info-modal.svelte index 53656de5a..668946d83 100644 --- a/web/src/lib/components/album-page/share-info-modal.svelte +++ b/web/src/lib/components/album-page/share-info-modal.svelte @@ -56,7 +56,7 @@ try { await api.albumApi.removeUserFromAlbum({ id: album.id, userId }); dispatch('remove', userId); - const message = userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.firstName}`; + const message = userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.name}`; notificationController.show({ type: NotificationType.Info, message }); } catch (e) { handleError(e, 'Unable to remove user'); @@ -78,7 +78,7 @@
-

{album.owner.firstName} {album.owner.lastName}

+

{album.owner.name}

@@ -91,7 +91,7 @@ >
-

{user.firstName} {user.lastName}

+

{user.name}

@@ -138,7 +138,7 @@ {#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id} (selectedRemoveUser = null)} diff --git a/web/src/lib/components/album-page/user-selection-modal.svelte b/web/src/lib/components/album-page/user-selection-modal.svelte index 7a774a3f0..0b4818ad5 100644 --- a/web/src/lib/components/album-page/user-selection-modal.svelte +++ b/web/src/lib/components/album-page/user-selection-modal.svelte @@ -72,7 +72,7 @@ class="flex place-items-center gap-1 rounded-full border border-gray-400 p-1 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" > -

{user.firstName} {user.lastName}

+

{user.name}

{/key} {/each} @@ -99,8 +99,7 @@

- {user.firstName} - {user.lastName} + {user.name}

{user.email} diff --git a/web/src/lib/components/asset-viewer/activity-viewer.svelte b/web/src/lib/components/asset-viewer/activity-viewer.svelte index f8663eceb..0f1e4ffcd 100644 --- a/web/src/lib/components/asset-viewer/activity-viewer.svelte +++ b/web/src/lib/components/asset-viewer/activity-viewer.svelte @@ -221,13 +221,8 @@

-
- {`${reaction.user.firstName} ${reaction.user.lastName} liked ${ - assetType ? `this ${getAssetType(assetType).toLowerCase()}` : 'it' - }`} +
+ {`${reaction.user.name} liked ${assetType ? `this ${getAssetType(assetType).toLowerCase()}` : 'it'}`}
{#if assetId === undefined && reaction.assetId}
diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index b64abdd5e..5fff954af 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -315,8 +315,7 @@

- {asset.owner.firstName} - {asset.owner.lastName} + {asset.owner.name}

diff --git a/web/src/lib/components/forms/admin-registration-form.svelte b/web/src/lib/components/forms/admin-registration-form.svelte index 9223f54e0..912c8f1da 100644 --- a/web/src/lib/components/forms/admin-registration-form.svelte +++ b/web/src/lib/components/forms/admin-registration-form.svelte @@ -27,15 +27,13 @@ const email = form.get('email'); const password = form.get('password'); - const firstName = form.get('firstName'); - const lastName = form.get('lastName'); + const name = form.get('name'); const { status } = await api.authenticationApi.signUpAdmin({ signUpDto: { email: String(email), password: String(password), - firstName: String(firstName), - lastName: String(lastName), + name: String(name), }, }); @@ -83,13 +81,8 @@
- - -
- -
- - + +
{#if error} diff --git a/web/src/lib/components/forms/create-user-form.svelte b/web/src/lib/components/forms/create-user-form.svelte index aee097a22..50c20790e 100644 --- a/web/src/lib/components/forms/create-user-form.svelte +++ b/web/src/lib/components/forms/create-user-form.svelte @@ -38,16 +38,14 @@ const email = form.get('email'); const password = form.get('password'); - const firstName = form.get('firstName'); - const lastName = form.get('lastName'); + const name = form.get('name'); try { const { status } = await api.userApi.createUser({ createUserDto: { email: String(email), password: String(password), - firstName: String(firstName), - lastName: String(lastName), + name: String(name), }, }); @@ -112,13 +110,8 @@
- - -
- -
- - + +
{#if error} diff --git a/web/src/lib/components/forms/edit-user-form.svelte b/web/src/lib/components/forms/edit-user-form.svelte index b4c426aca..2ad1a955c 100644 --- a/web/src/lib/components/forms/edit-user-form.svelte +++ b/web/src/lib/components/forms/edit-user-form.svelte @@ -20,13 +20,12 @@ const editUser = async () => { try { - const { id, email, firstName, lastName, storageLabel, externalPath } = user; + const { id, email, name, storageLabel, externalPath } = user; const { status } = await api.userApi.updateUser({ updateUserDto: { id, email, - firstName, - lastName, + name, storageLabel: storageLabel || '', externalPath: externalPath || '', }, @@ -84,20 +83,8 @@
- - -
- -
- - + +
@@ -161,7 +148,7 @@ >

- Are you sure you want to reset {user.firstName} {user.lastName}'s password? + Are you sure you want to reset {user.name}'s password?

diff --git a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte index 4fa41150b..6a135066f 100644 --- a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte @@ -26,8 +26,7 @@

- {user.firstName} - {user.lastName} + {user.name}

{user.email}

diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte index 36f9d2d65..c72803b54 100644 --- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte @@ -133,7 +133,7 @@ out:fade={{ delay: 200, duration: 150 }} class="absolute -bottom-12 right-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray" > -

{user.firstName} {user.lastName}

+

{user.name}

{user.email}

{/if} diff --git a/web/src/lib/components/shared-components/user-avatar.svelte b/web/src/lib/components/shared-components/user-avatar.svelte index c7dbdad38..07e65d94a 100644 --- a/web/src/lib/components/shared-components/user-avatar.svelte +++ b/web/src/lib/components/shared-components/user-avatar.svelte @@ -9,8 +9,7 @@ interface User { id: string; - firstName: string; - lastName: string; + name: string; email: string; profileImagePath: string; } @@ -51,7 +50,7 @@ $: colorClass = colorClasses[autoColor ? getUserColor() : color]; $: sizeClass = sizeClasses[size]; - $: title = `${user.firstName} ${user.lastName} (${user.email})`; + $: title = `${user.name} (${user.email})`; $: interactiveClass = interactive ? 'border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-colors' : ''; @@ -82,7 +81,7 @@ class:font-medium={!autoColor} class:font-semibold={autoColor} > - {((user.firstName[0] || '') + (user.lastName[0] || '')).toUpperCase()} + {(user.name[0] || '').toUpperCase()} {/if} diff --git a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte index 755b942d0..69b10ed6e 100644 --- a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte +++ b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte @@ -61,8 +61,7 @@

- {user.firstName} - {user.lastName} + {user.name}

{user.email} diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte index ee5fe8a09..374774aec 100644 --- a/web/src/lib/components/user-settings-page/partner-settings.svelte +++ b/web/src/lib/components/user-settings-page/partner-settings.svelte @@ -116,8 +116,7 @@

- {partner.user.firstName} - {partner.user.lastName} + {partner.user.name}

{partner.user.email} @@ -139,8 +138,8 @@ {#if partner.sharedByMe}


-

SHARED WITH {partner.user.firstName.toUpperCase()}

-

{partner.user.firstName} can access

+

SHARED WITH {partner.user.name.toUpperCase()}

+

{partner.user.name} can access

  • All your photos and videos except those in Archived and Deleted @@ -154,7 +153,7 @@ {#if partner.sharedWithMe}
    -

    PHOTOS FROM {partner.user.firstName.toUpperCase()}

    +

    PHOTOS FROM {partner.user.name.toUpperCase()}

    (removePartner = null)} on:confirm={() => handleRemovePartner()} /> diff --git a/web/src/lib/components/user-settings-page/user-profile-settings.svelte b/web/src/lib/components/user-settings-page/user-profile-settings.svelte index d571adbbf..89f4958ce 100644 --- a/web/src/lib/components/user-settings-page/user-profile-settings.svelte +++ b/web/src/lib/components/user-settings-page/user-profile-settings.svelte @@ -17,8 +17,7 @@ updateUserDto: { id: user.id, email: user.email, - firstName: user.firstName, - lastName: user.lastName, + name: user.name, }, }); @@ -47,19 +46,7 @@ - - - + goto(AppRoute.SHARING)}>

    - {data.partner.firstName} - {data.partner.lastName}'s photos + {data.partner.name}'s photos

    diff --git a/web/src/routes/(user)/sharing/+page.svelte b/web/src/routes/(user)/sharing/+page.svelte index fd9d1c58f..a8df23a0b 100644 --- a/web/src/routes/(user)/sharing/+page.svelte +++ b/web/src/routes/(user)/sharing/+page.svelte @@ -72,8 +72,7 @@

    - {partner.firstName} - {partner.lastName} + {partner.name}

    {partner.email} diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte index b800f5d94..09b3db7f7 100644 --- a/web/src/routes/admin/user-management/+page.svelte +++ b/web/src/routes/admin/user-management/+page.svelte @@ -168,8 +168,7 @@ > Email - First name - Last name + Name Can import Action @@ -187,8 +186,7 @@ }`} > {user.email} - {user.firstName} - {user.lastName} + {user.name}

    {#if user.externalPath} @@ -253,7 +251,7 @@ : 'bg-immich-bg dark:bg-immich-dark-gray/50' }`} > - {user.firstName} {user.lastName} + {user.name} {user.email} {#if !isDeleted(user)} diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte index f515e1d44..a4d0141d9 100644 --- a/web/src/routes/auth/change-password/+page.svelte +++ b/web/src/routes/auth/change-password/+page.svelte @@ -16,8 +16,7 @@

    - Hi {data.user.firstName} - {data.user.lastName} ({data.user.email}), + Hi {data.user.name} ({data.user.email}),

    This is either the first time you are signing into the system or a request has been made to change your password. Please diff --git a/web/src/test-data/factories/user-factory.ts b/web/src/test-data/factories/user-factory.ts index 507242fe8..21b4e3239 100644 --- a/web/src/test-data/factories/user-factory.ts +++ b/web/src/test-data/factories/user-factory.ts @@ -5,8 +5,7 @@ import { Sync } from 'factory.ts'; export const userFactory = Sync.makeFactory({ id: Sync.each(() => faker.datatype.uuid()), email: Sync.each(() => faker.internet.email()), - firstName: Sync.each(() => faker.name.firstName()), - lastName: Sync.each(() => faker.name.lastName()), + name: Sync.each(() => faker.name.fullName()), storageLabel: Sync.each(() => faker.random.alphaNumeric()), externalPath: Sync.each(() => faker.random.alphaNumeric()), profileImagePath: '', From 328a58ac0d51cfa78dd3243ce35255233aa12edc Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:04:49 -0500 Subject: [PATCH 08/88] feat(ml): add face models (#4952) added models to config dropdown fixed downloading updated tests use hf for face models formatting --- machine-learning/app/config.py | 10 ++- machine-learning/app/models/__init__.py | 10 +-- machine-learning/app/models/base.py | 11 +++- machine-learning/app/models/clip.py | 62 +------------------ machine-learning/app/models/constants.py | 57 +++++++++++++++++ .../app/models/facial_recognition.py | 37 +++++------ machine-learning/app/test_main.py | 4 +- .../machine-learning-settings.svelte | 4 +- 8 files changed, 101 insertions(+), 94 deletions(-) create mode 100644 machine-learning/app/models/constants.py diff --git a/machine-learning/app/config.py b/machine-learning/app/config.py index f3b41d22d..8870b8c0e 100644 --- a/machine-learning/app/config.py +++ b/machine-learning/app/config.py @@ -38,8 +38,16 @@ class LogSettings(BaseSettings): _clean_name = str.maketrans(":\\/", "___", ".") +def clean_name(model_name: str) -> str: + return model_name.split("/")[-1].translate(_clean_name) + + def get_cache_dir(model_name: str, model_type: ModelType) -> Path: - return Path(settings.cache_folder) / model_type.value / model_name.translate(_clean_name) + return Path(settings.cache_folder) / model_type.value / clean_name(model_name) + + +def get_hf_model_name(model_name: str) -> str: + return f"immich-app/{clean_name(model_name)}" LOG_LEVELS: dict[str, int] = { diff --git a/machine-learning/app/models/__init__.py b/machine-learning/app/models/__init__.py index a8df0050d..fa00a8614 100644 --- a/machine-learning/app/models/__init__.py +++ b/machine-learning/app/models/__init__.py @@ -3,7 +3,8 @@ from typing import Any from app.schemas import ModelType from .base import InferenceModel -from .clip import MCLIPEncoder, OpenCLIPEncoder, is_mclip, is_openclip +from .clip import MCLIPEncoder, OpenCLIPEncoder +from .constants import is_insightface, is_mclip, is_openclip from .facial_recognition import FaceRecognizer from .image_classification import ImageClassifier @@ -15,11 +16,12 @@ def from_model_type(model_type: ModelType, model_name: str, **model_kwargs: Any) return OpenCLIPEncoder(model_name, **model_kwargs) elif is_mclip(model_name): return MCLIPEncoder(model_name, **model_kwargs) - else: - raise ValueError(f"Unknown CLIP model {model_name}") case ModelType.FACIAL_RECOGNITION: - return FaceRecognizer(model_name, **model_kwargs) + if is_insightface(model_name): + return FaceRecognizer(model_name, **model_kwargs) case ModelType.IMAGE_CLASSIFICATION: return ImageClassifier(model_name, **model_kwargs) case _: raise ValueError(f"Unknown model type {model_type}") + + raise ValueError(f"Unknown ${model_type} model {model_name}") diff --git a/machine-learning/app/models/base.py b/machine-learning/app/models/base.py index 4f597d876..8149502ec 100644 --- a/machine-learning/app/models/base.py +++ b/machine-learning/app/models/base.py @@ -7,8 +7,9 @@ from shutil import rmtree from typing import Any import onnxruntime as ort +from huggingface_hub import snapshot_download -from ..config import get_cache_dir, log, settings +from ..config import get_cache_dir, get_hf_model_name, log, settings from ..schemas import ModelType @@ -78,9 +79,13 @@ class InferenceModel(ABC): def configure(self, **model_kwargs: Any) -> None: pass - @abstractmethod def _download(self) -> None: - ... + snapshot_download( + get_hf_model_name(self.model_name), + cache_dir=self.cache_dir, + local_dir=self.cache_dir, + local_dir_use_symlinks=False, + ) @abstractmethod def _load(self) -> None: diff --git a/machine-learning/app/models/clip.py b/machine-learning/app/models/clip.py index da0381d3a..296f790c3 100644 --- a/machine-learning/app/models/clip.py +++ b/machine-learning/app/models/clip.py @@ -7,11 +7,10 @@ from typing import Any, Literal import numpy as np import onnxruntime as ort -from huggingface_hub import snapshot_download from PIL import Image from transformers import AutoTokenizer -from app.config import log +from app.config import clean_name, log from app.models.transforms import crop, get_pil_resampling, normalize, resize, to_numpy from app.schemas import ModelType, ndarray_f32, ndarray_i32, ndarray_i64 @@ -117,15 +116,7 @@ class OpenCLIPEncoder(BaseCLIPEncoder): mode: Literal["text", "vision"] | None = None, **model_kwargs: Any, ) -> None: - super().__init__(_clean_model_name(model_name), cache_dir, mode, **model_kwargs) - - def _download(self) -> None: - snapshot_download( - f"immich-app/{self.model_name}", - cache_dir=self.cache_dir, - local_dir=self.cache_dir, - local_dir_use_symlinks=False, - ) + super().__init__(clean_name(model_name), cache_dir, mode, **model_kwargs) def _load(self) -> None: super()._load() @@ -171,52 +162,3 @@ class MCLIPEncoder(OpenCLIPEncoder): def tokenize(self, text: str) -> dict[str, ndarray_i32]: tokens: dict[str, ndarray_i64] = self.tokenizer(text, return_tensors="np") return {k: v.astype(np.int32) for k, v in tokens.items()} - - -_OPENCLIP_MODELS = { - "RN50__openai", - "RN50__yfcc15m", - "RN50__cc12m", - "RN101__openai", - "RN101__yfcc15m", - "RN50x4__openai", - "RN50x16__openai", - "RN50x64__openai", - "ViT-B-32__openai", - "ViT-B-32__laion2b_e16", - "ViT-B-32__laion400m_e31", - "ViT-B-32__laion400m_e32", - "ViT-B-32__laion2b-s34b-b79k", - "ViT-B-16__openai", - "ViT-B-16__laion400m_e31", - "ViT-B-16__laion400m_e32", - "ViT-B-16-plus-240__laion400m_e31", - "ViT-B-16-plus-240__laion400m_e32", - "ViT-L-14__openai", - "ViT-L-14__laion400m_e31", - "ViT-L-14__laion400m_e32", - "ViT-L-14__laion2b-s32b-b82k", - "ViT-L-14-336__openai", - "ViT-H-14__laion2b-s32b-b79k", - "ViT-g-14__laion2b-s12b-b42k", -} - - -_MCLIP_MODELS = { - "LABSE-Vit-L-14", - "XLM-Roberta-Large-Vit-B-32", - "XLM-Roberta-Large-Vit-B-16Plus", - "XLM-Roberta-Large-Vit-L-14", -} - - -def _clean_model_name(model_name: str) -> str: - return model_name.split("/")[-1].replace("::", "__") - - -def is_openclip(model_name: str) -> bool: - return _clean_model_name(model_name) in _OPENCLIP_MODELS - - -def is_mclip(model_name: str) -> bool: - return _clean_model_name(model_name) in _MCLIP_MODELS diff --git a/machine-learning/app/models/constants.py b/machine-learning/app/models/constants.py new file mode 100644 index 000000000..53f3f3381 --- /dev/null +++ b/machine-learning/app/models/constants.py @@ -0,0 +1,57 @@ +from app.config import clean_name + +_OPENCLIP_MODELS = { + "RN50__openai", + "RN50__yfcc15m", + "RN50__cc12m", + "RN101__openai", + "RN101__yfcc15m", + "RN50x4__openai", + "RN50x16__openai", + "RN50x64__openai", + "ViT-B-32__openai", + "ViT-B-32__laion2b_e16", + "ViT-B-32__laion400m_e31", + "ViT-B-32__laion400m_e32", + "ViT-B-32__laion2b-s34b-b79k", + "ViT-B-16__openai", + "ViT-B-16__laion400m_e31", + "ViT-B-16__laion400m_e32", + "ViT-B-16-plus-240__laion400m_e31", + "ViT-B-16-plus-240__laion400m_e32", + "ViT-L-14__openai", + "ViT-L-14__laion400m_e31", + "ViT-L-14__laion400m_e32", + "ViT-L-14__laion2b-s32b-b82k", + "ViT-L-14-336__openai", + "ViT-H-14__laion2b-s32b-b79k", + "ViT-g-14__laion2b-s12b-b42k", +} + + +_MCLIP_MODELS = { + "LABSE-Vit-L-14", + "XLM-Roberta-Large-Vit-B-32", + "XLM-Roberta-Large-Vit-B-16Plus", + "XLM-Roberta-Large-Vit-L-14", +} + + +_INSIGHTFACE_MODELS = { + "antelopev2", + "buffalo_l", + "buffalo_m", + "buffalo_s", +} + + +def is_openclip(model_name: str) -> bool: + return clean_name(model_name) in _OPENCLIP_MODELS + + +def is_mclip(model_name: str) -> bool: + return clean_name(model_name) in _MCLIP_MODELS + + +def is_insightface(model_name: str) -> bool: + return clean_name(model_name) in _INSIGHTFACE_MODELS diff --git a/machine-learning/app/models/facial_recognition.py b/machine-learning/app/models/facial_recognition.py index 2ea7fdf67..a8fa6484d 100644 --- a/machine-learning/app/models/facial_recognition.py +++ b/machine-learning/app/models/facial_recognition.py @@ -1,4 +1,3 @@ -import zipfile from pathlib import Path from typing import Any @@ -7,8 +6,8 @@ import numpy as np import onnxruntime as ort from insightface.model_zoo import ArcFaceONNX, RetinaFace from insightface.utils.face_align import norm_crop -from insightface.utils.storage import BASE_REPO_URL, download_file +from app.config import clean_name from app.schemas import ModelType, ndarray_f32 from .base import InferenceModel @@ -25,37 +24,21 @@ class FaceRecognizer(InferenceModel): **model_kwargs: Any, ) -> None: self.min_score = model_kwargs.pop("minScore", min_score) - super().__init__(model_name, cache_dir, **model_kwargs) - - def _download(self) -> None: - zip_file = self.cache_dir / f"{self.model_name}.zip" - download_file(f"{BASE_REPO_URL}/{self.model_name}.zip", zip_file) - with zipfile.ZipFile(zip_file, "r") as zip: - members = zip.namelist() - det_file = next(model for model in members if model.startswith("det_")) - rec_file = next(model for model in members if model.startswith("w600k_")) - zip.extractall(self.cache_dir, members=[det_file, rec_file]) - zip_file.unlink() + super().__init__(clean_name(model_name), cache_dir, **model_kwargs) def _load(self) -> None: - try: - det_file = next(self.cache_dir.glob("det_*.onnx")) - rec_file = next(self.cache_dir.glob("w600k_*.onnx")) - except StopIteration: - raise FileNotFoundError("Facial recognition models not found in cache directory") - self.det_model = RetinaFace( session=ort.InferenceSession( - det_file.as_posix(), + self.det_file.as_posix(), sess_options=self.sess_options, providers=self.providers, provider_options=self.provider_options, ), ) self.rec_model = ArcFaceONNX( - rec_file.as_posix(), + self.rec_file.as_posix(), session=ort.InferenceSession( - rec_file.as_posix(), + self.rec_file.as_posix(), sess_options=self.sess_options, providers=self.providers, provider_options=self.provider_options, @@ -103,7 +86,15 @@ class FaceRecognizer(InferenceModel): @property def cached(self) -> bool: - return self.cache_dir.is_dir() and any(self.cache_dir.glob("*.onnx")) + return self.det_file.is_file() and self.rec_file.is_file() + + @property + def det_file(self) -> Path: + return self.cache_dir / "detection" / "model.onnx" + + @property + def rec_file(self) -> Path: + return self.cache_dir / "recognition" / "model.onnx" def configure(self, **model_kwargs: Any) -> None: self.det_model.det_thresh = model_kwargs.pop("minScore", self.det_model.det_thresh) diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index 0b28f8234..e20a3e6c8 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -106,13 +106,13 @@ class TestCLIP: class TestFaceRecognition: def test_set_min_score(self, mocker: MockerFixture) -> None: mocker.patch.object(FaceRecognizer, "load") - face_recognizer = FaceRecognizer("test_model_name", cache_dir="test_cache", min_score=0.5) + face_recognizer = FaceRecognizer("buffalo_s", cache_dir="test_cache", min_score=0.5) assert face_recognizer.min_score == 0.5 def test_basic(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: mocker.patch.object(FaceRecognizer, "load") - face_recognizer = FaceRecognizer("test_model_name", min_score=0.0, cache_dir="test_cache") + face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache") det_model = mock.Mock() num_faces = 2 diff --git a/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte b/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte index e09157746..be6eb4135 100644 --- a/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte +++ b/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte @@ -160,11 +160,13 @@ Date: Sun, 12 Nov 2023 01:09:16 +0000 Subject: [PATCH 09/88] chore(deps): update dependency @types/byte-size to v8.1.2 (#4965) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index e99e0c179..6acd44cb9 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1489,9 +1489,9 @@ } }, "node_modules/@types/byte-size": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.0.tgz", - "integrity": "sha512-LCIlZh8vyx+I2fgRycE1D34c33QDppYY6quBYYoaOpQ1nGhJ/avSP2VlrAefVotjJxgSk6WkKo0rTcCJwGG7vA==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.2.tgz", + "integrity": "sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==", "dev": true }, "node_modules/@types/chai": { @@ -7270,9 +7270,9 @@ } }, "@types/byte-size": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.0.tgz", - "integrity": "sha512-LCIlZh8vyx+I2fgRycE1D34c33QDppYY6quBYYoaOpQ1nGhJ/avSP2VlrAefVotjJxgSk6WkKo0rTcCJwGG7vA==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/byte-size/-/byte-size-8.1.2.tgz", + "integrity": "sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==", "dev": true }, "@types/chai": { From 0bf55d8e32e7b3ce3359cfea56357f95f0120c74 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 01:10:37 +0000 Subject: [PATCH 10/88] chore(deps): update dependency @types/chai to v4.3.10 (#4966) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 6acd44cb9..3b41f2ac1 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1495,9 +1495,9 @@ "dev": true }, "node_modules/@types/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==", "dev": true }, "node_modules/@types/cli-progress": { @@ -7276,9 +7276,9 @@ "dev": true }, "@types/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==", "dev": true }, "@types/cli-progress": { From 55e3605ca475094fc30192096ae456f595469090 Mon Sep 17 00:00:00 2001 From: Sergey Kondrikov Date: Sun, 12 Nov 2023 04:23:15 +0300 Subject: [PATCH 11/88] feat(web): uniform random asset selection in slideshow mode (#4953) * Implement weighted asset selection * Rename totalAssets -> bucketCount * Remove redundant check --- web/src/lib/stores/assets.store.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index d7d8e0761..635d6e7aa 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -32,6 +32,7 @@ export class AssetBucket { */ bucketHeight!: number; bucketDate!: string; + bucketCount!: number; assets!: AssetResponseDto[]; cancelToken!: AbortController | null; position!: BucketPosition; @@ -158,6 +159,7 @@ export class AssetStore { return { bucketDate: bucket.timeBucket, bucketHeight: height, + bucketCount: bucket.count, assets: [], cancelToken: null, position: BucketPosition.Unknown, @@ -274,6 +276,7 @@ export class AssetStore { bucket = { bucketDate: timeBucket, bucketHeight: THUMBNAIL_HEIGHT, + bucketCount: 0, assets: [], cancelToken: null, position: BucketPosition.Unknown, @@ -313,16 +316,17 @@ export class AssetStore { } async getRandomAsset(): Promise { - const bucket = this.buckets[Math.floor(Math.random() * this.buckets.length)] || null; - if (!bucket) { - return null; + let index = Math.floor(Math.random() * this.buckets.reduce((acc, bucket) => acc + bucket.bucketCount, 0)); + for (const bucket of this.buckets) { + if (index < bucket.bucketCount) { + await this.loadBucket(bucket.bucketDate, BucketPosition.Unknown); + return bucket.assets[index] || null; + } + + index -= bucket.bucketCount; } - if (bucket.assets.length === 0) { - await this.loadBucket(bucket.bucketDate, BucketPosition.Unknown); - } - - return bucket.assets[Math.floor(Math.random() * bucket.assets.length)] || null; + return null; } updateAsset(_asset: AssetResponseDto, recalculate = false) { @@ -412,6 +416,9 @@ export class AssetStore { const assetToBucket: Record = {}; for (let i = 0; i < this.buckets.length; i++) { const bucket = this.buckets[i]; + if (bucket.assets.length !== 0) { + bucket.bucketCount = bucket.assets.length; + } for (let j = 0; j < bucket.assets.length; j++) { const asset = bucket.assets[j]; assetToBucket[asset.id] = { bucket, bucketIndex: i, assetIndex: j }; From 96f1a271efeccf9af24103b6f4322b2fd51509c1 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 11 Nov 2023 19:31:01 -0600 Subject: [PATCH 12/88] chore: update Makefile to use docker compose (#4967) --- Makefile | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index eaf3c17f3..7668d4f60 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,29 @@ dev: - docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans - -dev-new: docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans || make dev-down dev-down: docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans -dev-new-update: +dev-update: docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans -dev-update: - docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans - dev-scale: - docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans + docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans stage: - docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans + docker compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans pull-stage: - docker-compose -f ./docker/docker-compose.staging.yml pull + docker compose -f ./docker/docker-compose.staging.yml pull test-e2e: docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build prod: - docker-compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans + docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans prod-scale: - docker-compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans + docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans api: cd ./server && npm run api:generate From 86e04832a155318562a1406eeb6ededb2c2c9855 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 22:35:33 -0500 Subject: [PATCH 13/88] chore(deps): update dependency @types/jest to v29.5.8 (#4969) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 3b41f2ac1..528333bd0 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1543,9 +1543,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", + "version": "29.5.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", + "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -7324,9 +7324,9 @@ } }, "@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", + "version": "29.5.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", + "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", "dev": true, "requires": { "expect": "^29.0.0", From 2f462717aa632d7bcada64e2e9e863d5d5c7881f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 22:36:03 -0500 Subject: [PATCH 14/88] chore(deps): update dependency @types/cli-progress to v3.11.5 (#4968) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 528333bd0..df175a09d 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1501,9 +1501,9 @@ "dev": true }, "node_modules/@types/cli-progress": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.3.tgz", - "integrity": "sha512-/+C9xAdVtc+g5yHHkGBThgAA8rYpi5B+2ve3wLtybYj0JHEBs57ivR4x/zGfSsplRnV+psE91Nfin1soNKqz5Q==", + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz", + "integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==", "dev": true, "dependencies": { "@types/node": "*" @@ -7282,9 +7282,9 @@ "dev": true }, "@types/cli-progress": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.3.tgz", - "integrity": "sha512-/+C9xAdVtc+g5yHHkGBThgAA8rYpi5B+2ve3wLtybYj0JHEBs57ivR4x/zGfSsplRnV+psE91Nfin1soNKqz5Q==", + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz", + "integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==", "dev": true, "requires": { "@types/node": "*" From 2e82476cffc7a112fbccb3124098ecbfaa329d1f Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Sat, 11 Nov 2023 23:02:26 -0600 Subject: [PATCH 15/88] chore: styling for partner stylesheet --- mobile/lib/modules/partner/ui/partner_list.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/mobile/lib/modules/partner/ui/partner_list.dart b/mobile/lib/modules/partner/ui/partner_list.dart index 58810fec5..ffd2bdf86 100644 --- a/mobile/lib/modules/partner/ui/partner_list.dart +++ b/mobile/lib/modules/partner/ui/partner_list.dart @@ -21,13 +21,22 @@ class PartnerList extends HookConsumerWidget { Widget listEntry(BuildContext context, int index) { final User p = partner[index]; return ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), - leading: userAvatar(context, p, radius: 30), + contentPadding: const EdgeInsets.only( + left: 12.0, + right: 18.0, + ), + leading: userAvatar(context, p, radius: 24), title: Text( "${p.name}'s photos", - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, + ), + ), + trailing: Text( + "View all", + style: TextStyle( + fontWeight: FontWeight.bold, color: context.primaryColor, ), ), From d7d464570f2d676bded361ebd48646e408cae568 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Sun, 12 Nov 2023 14:46:17 +0100 Subject: [PATCH 16/88] fix maplibre latlng import (#4977) --- web/src/lib/components/shared-components/map/map.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/src/lib/components/shared-components/map/map.svelte b/web/src/lib/components/shared-components/map/map.svelte index b806c60ce..3687d117e 100644 --- a/web/src/lib/components/shared-components/map/map.svelte +++ b/web/src/lib/components/shared-components/map/map.svelte @@ -16,7 +16,8 @@ } from 'svelte-maplibre'; import { colorTheme, mapSettings } from '$lib/stores/preferences.store'; import { MapMarkerResponseDto, api } from '@api'; - import { LngLat, type GeoJSONSource, type LngLatLike, type StyleSpecification } from 'maplibre-gl'; + import maplibregl from 'maplibre-gl'; + import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl'; import type { Feature, Geometry, GeoJsonProperties, Point } from 'geojson'; import Icon from '$lib/components/elements/icon.svelte'; import { mdiCog } from '@mdi/js'; @@ -76,7 +77,7 @@ const asMarker = (feature: Feature): MapMarkerResponseDto => { const featurePoint = feature as FeaturePoint; - const coords = LngLat.convert(featurePoint.geometry.coordinates as [number, number]); + const coords = maplibregl.LngLat.convert(featurePoint.geometry.coordinates as [number, number]); return { lat: coords.lat, lon: coords.lng, From 2bfe5d1573c07802c31816417b8757ee51fb6092 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 09:48:17 -0500 Subject: [PATCH 17/88] chore(deps): update dependency @types/mock-fs to v4.13.4 (#4975) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index df175a09d..d4d51a2c4 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1571,9 +1571,9 @@ "dev": true }, "node_modules/@types/mock-fs": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.2.tgz", - "integrity": "sha512-mSIMAOjrNTVUFmZgJEigSIm+GlS4hbrk8U5+M8EB45uMrykKdN9TidjjSaOY1yFph2+TD7bsIfB4r+IrMYVyPQ==", + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz", + "integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==", "dev": true, "dependencies": { "@types/node": "*" @@ -7352,9 +7352,9 @@ "dev": true }, "@types/mock-fs": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.2.tgz", - "integrity": "sha512-mSIMAOjrNTVUFmZgJEigSIm+GlS4hbrk8U5+M8EB45uMrykKdN9TidjjSaOY1yFph2+TD7bsIfB4r+IrMYVyPQ==", + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz", + "integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==", "dev": true, "requires": { "@types/node": "*" From d2509c619e180973451552163e60a1c991ced9af Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 09:48:43 -0500 Subject: [PATCH 18/88] chore(deps): update dependency jest-extended to v4.0.2 (#4976) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index d4d51a2c4..e5e5bf925 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -3867,9 +3867,9 @@ } }, "node_modules/jest-extended": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.1.tgz", - "integrity": "sha512-KM6dwuBUAgy6QONuR19CGubZB9Hkjqvl/d5Yc/FXsdB8+gsGxB2VQ+NEdOrr95J4GMPeLnDoPOKyi6+mKCCnZQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz", + "integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==", "dev": true, "dependencies": { "jest-diff": "^29.0.0", @@ -8969,9 +8969,9 @@ } }, "jest-extended": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.1.tgz", - "integrity": "sha512-KM6dwuBUAgy6QONuR19CGubZB9Hkjqvl/d5Yc/FXsdB8+gsGxB2VQ+NEdOrr95J4GMPeLnDoPOKyi6+mKCCnZQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz", + "integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==", "dev": true, "requires": { "jest-diff": "^29.0.0", From 98f1e85c87a76e06682d972b3a83d9335b403c29 Mon Sep 17 00:00:00 2001 From: Ploonet <53194438+Ploonet@users.noreply.github.com> Date: Sun, 12 Nov 2023 15:49:47 +0100 Subject: [PATCH 19/88] fix(docs): Typo in libraries exclusion pattern (#4972) Co-authored-by: Antony Mota --- docs/docs/features/libraries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index f518a33a5..1437fb863 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -75,7 +75,7 @@ Some basic examples: - `*.tif` will exclude all files with the extension `.tif` - `hidden.jpg` will exclude all files named `hidden.jpg` - `**/Raw/**` will exclude all files in any directory named `Raw` -- `*.(tif,jpg)` will exclude all files with the extension `.tif` or `.jpg` +- `*.{tif,jpg}` will exclude all files with the extension `.tif` or `.jpg` ### Nightly job From 3ea0210c1d71f89fc9c3e78dd78ef2a707827ab3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 09:50:55 -0500 Subject: [PATCH 20/88] chore(deps): update dependency @types/mime-types to v2.1.4 (#4971) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index e5e5bf925..eaba37954 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1565,9 +1565,9 @@ "dev": true }, "node_modules/@types/mime-types": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.2.tgz", - "integrity": "sha512-q9QGHMGCiBJCHEvd4ZLdasdqXv570agPsUW0CeIm/B8DzhxsYMerD0l3IlI+EQ1A2RWHY2mmM9x1YIuuWxisCg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", "dev": true }, "node_modules/@types/mock-fs": { @@ -7346,9 +7346,9 @@ "dev": true }, "@types/mime-types": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.2.tgz", - "integrity": "sha512-q9QGHMGCiBJCHEvd4ZLdasdqXv570agPsUW0CeIm/B8DzhxsYMerD0l3IlI+EQ1A2RWHY2mmM9x1YIuuWxisCg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", "dev": true }, "@types/mock-fs": { From da33653b0ae45bf3bc8f5dfd4bbb28d7d66a6973 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 09:51:24 -0500 Subject: [PATCH 21/88] chore(deps): update dependency @types/js-yaml to v4.0.9 (#4970) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index eaba37954..f772de72b 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1553,9 +1553,9 @@ } }, "node_modules/@types/js-yaml": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz", - "integrity": "sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "node_modules/@types/json-schema": { @@ -7334,9 +7334,9 @@ } }, "@types/js-yaml": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz", - "integrity": "sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "@types/json-schema": { From 66120025b71f664cfae89c45c3ffe5ccbdf37408 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 10:04:24 -0500 Subject: [PATCH 22/88] fix(deps): update dependency tailwindcss to v3.3.5 (#4980) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/package-lock.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index df2ff7ebf..b2ddf2e1e 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -13232,19 +13232,19 @@ } }, "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", + "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.19.1", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -24517,19 +24517,19 @@ } }, "tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", + "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", "requires": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.19.1", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", From c23d84be39643a30910502c0b3cb75c08b1ee107 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 15:15:44 +0000 Subject: [PATCH 23/88] chore(deps): update dependency @types/node to v20.9.0 (#4983) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index f772de72b..61461d341 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1580,10 +1580,13 @@ } }, "node_modules/@types/node": { - "version": "20.8.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==", - "dev": true + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.2", @@ -5862,6 +5865,12 @@ "node": ">=4.2.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -7361,10 +7370,13 @@ } }, "@types/node": { - "version": "20.8.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==", - "dev": true + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } }, "@types/normalize-package-data": { "version": "2.4.2", @@ -10419,6 +10431,12 @@ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", From 388144823a8cf9af9decb50fc26899433d7ca9f4 Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Sun, 12 Nov 2023 17:07:55 +0100 Subject: [PATCH 24/88] chore(renovate): PR exiftool updates separately (#4985) --- renovate.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index f5ad25da7..315f01106 100644 --- a/renovate.json +++ b/renovate.json @@ -10,7 +10,12 @@ { "matchFileNames": ["server/**"], "groupName": "server", - "matchUpdateTypes": ["minor", "patch"] + "matchUpdateTypes": ["minor", "patch"], + "excludePackagePrefixes": ["exiftool"] + }, + { + "groupName": "exiftool", + "matchPackagePrefixes": ["exiftool"] }, { "matchFileNames": ["web/**"], From 069a32dcdba7ec73be1bbf0713788dfb40327973 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey <10599762+fyfrey@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:35:08 +0100 Subject: [PATCH 25/88] fix(mobile): run user sync operation with lock (#4984) --- mobile/lib/shared/services/sync.service.dart | 65 +++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index fb9888258..f195f5dbb 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/mobile/lib/shared/services/sync.service.dart @@ -34,36 +34,8 @@ class SyncService { /// Syncs users from the server to the local database /// Returns `true`if there were any changes - Future syncUsersFromServer(List users) async { - users.sortBy((u) => u.id); - final dbUsers = await _db.users.where().sortById().findAll(); - assert(dbUsers.isSortedBy((u) => u.id), "dbUsers not sorted!"); - final List toDelete = []; - final List toUpsert = []; - final changes = diffSortedListsSync( - users, - dbUsers, - compare: (User a, User b) => a.id.compareTo(b.id), - both: (User a, User b) { - if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) || - a.isPartnerSharedBy != b.isPartnerSharedBy || - a.isPartnerSharedWith != b.isPartnerSharedWith) { - toUpsert.add(a); - return true; - } - return false; - }, - onlyFirst: (User a) => toUpsert.add(a), - onlySecond: (User b) => toDelete.add(b.isarId), - ); - if (changes) { - await _db.writeTxn(() async { - await _db.users.deleteAll(toDelete); - await _db.users.putAll(toUpsert); - }); - } - return changes; - } + Future syncUsersFromServer(List users) => + _lock.run(() => _syncUsersFromServer(users)); /// Syncs remote assets owned by the logged-in user to the DB /// Returns `true` if there were any changes @@ -120,6 +92,39 @@ class SyncService { // private methods: + /// Syncs users from the server to the local database + /// Returns `true`if there were any changes + Future _syncUsersFromServer(List users) async { + users.sortBy((u) => u.id); + final dbUsers = await _db.users.where().sortById().findAll(); + assert(dbUsers.isSortedBy((u) => u.id), "dbUsers not sorted!"); + final List toDelete = []; + final List toUpsert = []; + final changes = diffSortedListsSync( + users, + dbUsers, + compare: (User a, User b) => a.id.compareTo(b.id), + both: (User a, User b) { + if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) || + a.isPartnerSharedBy != b.isPartnerSharedBy || + a.isPartnerSharedWith != b.isPartnerSharedWith) { + toUpsert.add(a); + return true; + } + return false; + }, + onlyFirst: (User a) => toUpsert.add(a), + onlySecond: (User b) => toDelete.add(b.isarId), + ); + if (changes) { + await _db.writeTxn(() async { + await _db.users.deleteAll(toDelete); + await _db.users.putAll(toUpsert); + }); + } + return changes; + } + /// Syncs a new asset to the db. Returns `true` if successful Future _syncNewAssetToDb(Asset a) async { final Asset? inDb = From 04a8bde7acab41b8429d336b7bff650915a4cca1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:38:43 -0600 Subject: [PATCH 26/88] chore(deps): update ghcr.io/nginxinc/nginx-unprivileged docker tag to v1.25.1 (#4978) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- nginx/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index a64dcfb2c..718e7f61c 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/nginxinc/nginx-unprivileged:1.25.0-alpine3.17@sha256:5ebb90a0dd5ce841d8527abcfee4081a48de86560cd26dc64a6b1212ef59bf36 +FROM ghcr.io/nginxinc/nginx-unprivileged:1.25.1-alpine3.17@sha256:c38e27fdba47f725f49177b88fdd1fd2feef11b13dc11dea3695c3feb2c6d96d COPY LICENSE /licenses/LICENSE.txt COPY LICENSE /LICENSE From 11880122799d4fdf9f429a5303baddad6a73eec3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:44:42 -0600 Subject: [PATCH 27/88] fix(deps): update server (#4982) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- server/package-lock.json | 1268 +++++++++++++++++--------------------- 1 file changed, 552 insertions(+), 716 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 218fc6edd..75db1d355 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -127,9 +127,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.3.tgz", - "integrity": "sha512-oZLdg2XTx7likYAXRj1CU0XmrsCfe5f2grj3iwuI3OB1LXwwpdbHBztruj03y3yHES+TnO+dIbkvRnvMXs7uAA==", + "version": "16.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.8.tgz", + "integrity": "sha512-PTGozYvh1Bin5lB15PwcXa26Ayd17bWGLS3H8Rs0s+04mUDvfNofmweaX1LgumWWy3nCUTDuwHxX10M3G0wE2g==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -154,12 +154,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.3.tgz", - "integrity": "sha512-+lBiHxi/C9HCfiCbtW25DldwvJDXXXv5oWw+Tg4s18BO/lYZLveGUEaZWu9ZJ5VIJ8GliUi2LohxhDxBkh4Oxg==", + "version": "16.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.8.tgz", + "integrity": "sha512-MBiKZOlR9/YMdflALr7/7w/BGAfo/BGTrlkqsIB6rDWV1dYiCgxI+033HsiNssLS6RQyCFx/e7JA2aBBzu9zEg==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.3", + "@angular-devkit/core": "16.2.8", "jsonc-parser": "3.2.0", "magic-string": "0.30.1", "ora": "5.4.1", @@ -172,13 +172,13 @@ } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.3.tgz", - "integrity": "sha512-5YQCbQmY9Kc03a9Io4XHOrxGXjnzcVveUuUO64R1m5x2aA5I+mVR8NVvxuoGRAeoI1FWusAKRe9hH8nRCLrelA==", + "version": "16.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.8.tgz", + "integrity": "sha512-EXURJCzWTVYCipiTT4vxQQOrF63asOUDbeOy3OtiSh7EwIUvxm3BPG6hquJqngEnI/N6bA75NJ1fBhU6Hrh7eA==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.3", - "@angular-devkit/schematics": "16.2.3", + "@angular-devkit/core": "16.2.8", + "@angular-devkit/schematics": "16.2.8", "ansi-colors": "4.1.3", "inquirer": "8.2.4", "symbol-observable": "4.0.0", @@ -805,9 +805,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", - "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -942,9 +942,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -987,9 +987,9 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1021,12 +1021,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -1048,9 +1048,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@ioredis/commands": { @@ -1710,20 +1710,21 @@ } }, "node_modules/@nestjs/cli": { - "version": "10.1.18", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.18.tgz", - "integrity": "sha512-jQtG47keLsACt7b4YwJbTBYRm90n82gJpMaiR1HGAyQ9pccbctjSYu592eT4bxqkUWxPgBE3mpNynXj7dWAfrw==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.2.1.tgz", + "integrity": "sha512-CAJAQwmxFZfB3RTvqz/eaXXWpyU+mZ4QSqfBYzjneTsPgF+uyOAW3yQpaLNn9Dfcv39R9UxSuAhayv6yuFd+Jg==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.3", - "@angular-devkit/schematics": "16.2.3", - "@angular-devkit/schematics-cli": "16.2.3", + "@angular-devkit/core": "16.2.8", + "@angular-devkit/schematics": "16.2.8", + "@angular-devkit/schematics-cli": "16.2.8", "@nestjs/schematics": "^10.0.1", "chalk": "4.1.2", "chokidar": "3.5.3", "cli-table3": "0.6.3", "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "8.0.0", + "fork-ts-checker-webpack-plugin": "9.0.2", + "glob": "10.3.10", "inquirer": "8.2.6", "node-emoji": "1.11.0", "ora": "5.4.1", @@ -1735,14 +1736,14 @@ "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.1.0", "typescript": "5.2.2", - "webpack": "5.88.2", + "webpack": "5.89.0", "webpack-node-externals": "3.0.0" }, "bin": { "nest": "bin/nest.js" }, "engines": { - "node": ">= 16" + "node": ">= 16.14" }, "peerDependencies": { "@swc/cli": "^0.1.62", @@ -1766,24 +1767,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@nestjs/cli/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@nestjs/cli/node_modules/minimatch": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", @@ -1826,10 +1809,28 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@nestjs/cli/node_modules/rimraf/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@nestjs/common": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.6.tgz", - "integrity": "sha512-ma8R7n+FXsWM4XF9QXjjrsRceyRzid/xKmNKVOa/sTJntkVG8lL71BHBEfjtFvO6EJUqjs/15LbDc0iaN5nCwA==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.8.tgz", + "integrity": "sha512-rmpwcdvq2IWMmsUVP8rsdKub6uDWk7dwCYo0aif50JTwcvcxzaP3iKVFKoSgvp0RKYu8h15+/AEOfaInmPpl0Q==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -1875,9 +1876,9 @@ } }, "node_modules/@nestjs/core": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.6.tgz", - "integrity": "sha512-oGQ2CoBeFRT7egG47MFqS89xlXBTIRZBkRpKRTPMftEfL1RMXhXIcIIaGfzp11wx6qxrBVxBXpVLM09oaqHpaQ==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.8.tgz", + "integrity": "sha512-9+MZ2s8ixfY9Bl/M9ofChiyYymcwdK9ZWNH4GDMF7Am7XRAQ1oqde6MYGG05rhQwiVXuTwaYLlXciJKfsrg5qg==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", @@ -1917,9 +1918,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@nestjs/mapped-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", - "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.3.tgz", + "integrity": "sha512-40Zdqg98lqoF0+7ThWIZFStxgzisK6GG22+1ABO4kZiGF/Tu2FE+DYLw+Q9D94vcFWizJ+MSjNN4ns9r6hIGxw==", "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", "class-transformer": "^0.4.0 || ^0.5.0", @@ -1936,9 +1937,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.6.tgz", - "integrity": "sha512-4U16D5ot2570CR8Qm5qu/SBXsA2l5KxN7AVSGvzoWoBxjEoOnnZOapC5Pler3yYa0tT1xLhji61RX1gceKW3dw==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.8.tgz", + "integrity": "sha512-WoSSVtwIRc5AdGMHWVzWZK4JZLT0f4o2xW8P9gQvcX+omL8W1kXCfY8GQYXNBG84XmBNYH8r0FtC8oMe/lH5NQ==", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -1961,9 +1962,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.6.tgz", - "integrity": "sha512-c4GbHeyd12hyrLngnHDouBui0fwPjaK4TopkZdvkskRCd4sOfph9EUX2CHny+ZL8UeY8IbW/yBZxQ746ahSYsQ==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.8.tgz", + "integrity": "sha512-P/Olw9alAaKD7Q1vS/ol7K81x1l7Bmi+AXthBNUPGMmG/W8kxO1krerW4rEhtF3BKJ0qJIa5bhDlb80p4lZcNA==", "dependencies": { "socket.io": "4.7.2", "tslib": "2.6.2" @@ -2010,13 +2011,13 @@ } }, "node_modules/@nestjs/schematics": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.2.tgz", - "integrity": "sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.3.tgz", + "integrity": "sha512-2BRujK0GqGQ7j1Zpz+obVfskDnnOeVKt5aXoSaVngKo8Oczy8uYCY+R547TQB+Kf35epdfFER2pVnQrX3/It5A==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.1.8", - "@angular-devkit/schematics": "16.1.8", + "@angular-devkit/core": "16.2.8", + "@angular-devkit/schematics": "16.2.8", "comment-json": "4.2.3", "jsonc-parser": "3.2.0", "pluralize": "8.0.0" @@ -2025,72 +2026,16 @@ "typescript": ">=4.8.2" } }, - "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.8.tgz", - "integrity": "sha512-dSRD/+bGanArIXkj+kaU1kDFleZeQMzmBiOXX+pK0Ah9/0Yn1VmY3RZh1zcX9vgIQXV+t7UPrTpOjaERMUtVGw==", - "dev": true, - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.1.8.tgz", - "integrity": "sha512-6LyzMdFJs337RTxxkI2U1Ndw0CW5mMX/aXWl8d7cW2odiSrAg8IdlMqpc+AM8+CPfsB0FtS1aWkEZqJLT0jHOg==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "16.1.8", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.0", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@nestjs/schematics/node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@nestjs/swagger": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.12.tgz", - "integrity": "sha512-Q1P/IE+cws0sJeNtbs+8uDalcVylpmAnaEUFenGOa3KSNnXF/8DOE84mET/uUhFXsiz9PLHK8Hy7o7B6fRpMhg==", + "version": "7.1.15", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.15.tgz", + "integrity": "sha512-ZaAO90R9MQXk4iLQLijIH6jrsllkUSYoh0Su6DECGgu8Y4Q/9LfdESwsZ9nmzr/48aLOu+wrv+cdI5Wr6fLKJw==", "dependencies": { - "@nestjs/mapped-types": "2.0.2", + "@nestjs/mapped-types": "2.0.3", "js-yaml": "4.1.0", "lodash": "4.17.21", "path-to-regexp": "3.2.0", - "swagger-ui-dist": "5.7.2" + "swagger-ui-dist": "5.9.1" }, "peerDependencies": { "@fastify/static": "^6.0.0", @@ -2113,9 +2058,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.6.tgz", - "integrity": "sha512-uxlxHhpSvG4yDTPmuPneoQL1/UnBkOkzE+Zaz6bwURg7lc3uS4ZsXl75OL3pYaJH37rHYXYT9bGcYSpxVbwIrg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz", + "integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==", "dev": true, "dependencies": { "tslib": "2.6.2" @@ -2161,9 +2106,9 @@ } }, "node_modules/@nestjs/websockets": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.6.tgz", - "integrity": "sha512-HwZADfixAMKMdMB/eBz0HJnPCs0r+W+5inpRwCazsQhwZniGUgXkfIhyRvNfHip/nb+DLS/M8BNBR2JGiJNTEg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.8.tgz", + "integrity": "sha512-oZN1VJFApN7d2eftr65a36QrV0IJNGba4znqyjFnyGvtDWTDcQwzDcnEfvJBTTYhOSBNS7KDfVhne0ythkl6tg==", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -2735,12 +2680,12 @@ "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, "node_modules/@testcontainers/postgresql": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.2.1.tgz", - "integrity": "sha512-snIB11wyHUYPzQNNgoHpiRcZO3sR0mDwU28cwOd0lAT4kzTvTihZTmz4gv8MYppcXlRsLAIE0QqzGauZsfFQiA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.2.2.tgz", + "integrity": "sha512-G1xJKe8omeNzngK0dj4R2cSYxWyOUdTXD/oBA03AqIwdReq/gi4WjT6CJqGbkqQy9opXZV6ug3gHMja+wM5BCA==", "dev": true, "dependencies": { - "testcontainers": "^10.2.1" + "testcontainers": "^10.2.2" } }, "node_modules/@tsconfig/node10": { @@ -2799,9 +2744,9 @@ } }, "node_modules/@types/archiver": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.3.tgz", - "integrity": "sha512-0ABdVcXL6jOwNGY+hjWPqrxUvKelBEwNLcuv/SV2vZ4YCH8w9NttFCt+/QqI5zgMX+iX/XqVy89/r7EmLJmMpQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.4.tgz", + "integrity": "sha512-Lj7fLBIMwYFgViVVZHEdExZC3lVYsl+QL0VmdNdIzGZH544jHveYWij6qdnBgJQDnR7pMKliN9z2cPZFEbhyPw==", "dev": true, "dependencies": { "@types/readdir-glob": "*" @@ -2849,9 +2794,9 @@ } }, "node_modules/@types/bcrypt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", - "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -2882,9 +2827,9 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "node_modules/@types/cookie-parser": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.4.tgz", - "integrity": "sha512-Var+aj5I6ZgIqsQ05N2V8q5OBrFfZXtIGWWDSrEYLIbMw758obagSwdGcLCjwh1Ga7M7+wj0SDIAaAC/WT7aaA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==", "dev": true, "dependencies": { "@types/express": "*" @@ -2941,9 +2886,9 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.18", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz", - "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -2965,9 +2910,9 @@ } }, "node_modules/@types/fluent-ffmpeg": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.22.tgz", - "integrity": "sha512-ZZPDDrDOb2Ahp5fxZzuw64f0rCcviv+SDuCyJ1PIF/UFn9wNHtb/bY8Dj/2nrbQ7SNsGI7gaO2wJVkkU2HBcMg==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.24.tgz", + "integrity": "sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==", "dev": true, "dependencies": { "@types/node": "*" @@ -2989,9 +2934,9 @@ "dev": true }, "node_modules/@types/imagemin": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.1.tgz", - "integrity": "sha512-DSpM//dRPzme7doePGkmR1uoquHi0h0ElaA5qFnxHECfFcB8z/jhMI8eqmxWNpHn9ZG18p4PC918sZLhR0cr5A==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.4.tgz", + "integrity": "sha512-t7vady38h/FTQAxFe6gJvaTxjgi/uw54ZrDbqyKx3yOMPu3NQjQexCoLxBR03FRv0HcKJMV2MqGLeY7BuPl6/A==", "dev": true, "dependencies": { "@types/node": "*" @@ -3042,9 +2987,9 @@ } }, "node_modules/@types/jest-when": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@types/jest-when/-/jest-when-3.5.3.tgz", - "integrity": "sha512-gJWLSCRWEkPHYXYjjrxEMfWopoA2bl9nUbwDomxIzRXfe0gLkmSDPD0lLcoAiO2RDpXDoHSeigy/rNa/ja0MfQ==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@types/jest-when/-/jest-when-3.5.5.tgz", + "integrity": "sha512-H9MDPIrz7NOu6IXP9OHExNN9LnJbGYAzRsGIDKxWr7Fth9vovemNV8yFbkUWLSEmuA8PREvAEvt9yK0PPLmFHA==", "dev": true, "dependencies": { "@types/jest": "*" @@ -3057,9 +3002,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.199", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", - "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", + "version": "4.14.201", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz", + "integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==", "dev": true }, "node_modules/@types/luxon": { @@ -3074,39 +3019,36 @@ "dev": true }, "node_modules/@types/mock-fs": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.2.tgz", - "integrity": "sha512-mSIMAOjrNTVUFmZgJEigSIm+GlS4hbrk8U5+M8EB45uMrykKdN9TidjjSaOY1yFph2+TD7bsIfB4r+IrMYVyPQ==", + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz", + "integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/multer": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.8.tgz", - "integrity": "sha512-VMZOW6mnmMMhA5m3fsCdXBwFwC+a+27/8gctNMuQC4f7UtWcF79KAFGoIfKZ4iqrElgWIa3j5vhMJDp0iikQ1g==", + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.10.tgz", + "integrity": "sha512-6l9mYMhUe8wbnz/67YIjc7ZJyQNZoKq7fRXVf7nMdgWgalD0KyzJ2ywI7hoATUSXSbTu9q2HBiEwzy0tNN1v2w==", "dev": true, "dependencies": { "@types/express": "*" } }, "node_modules/@types/mv": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/mv/-/mv-2.1.2.tgz", - "integrity": "sha512-IvAjPuiQ2exDicnTrMidt1m+tj3gZ60BM0PaoRsU0m9Cn+lrOyemuO9Tf8CvHFmXlxMjr1TVCfadi9sfwbSuKg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mv/-/mv-2.1.4.tgz", + "integrity": "sha512-MgEHBpXnQo44Q43j8G0Bvp/Yi8LYbC8hxKrRFMgDEDZMmzDKZLgiyMWtW49B37ko+QupgZ3G5rtPUnOGe5ixLw==", "dev": true }, "node_modules/@types/node": { - "version": "20.8.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/qs": { "version": "6.9.8", @@ -3130,9 +3072,9 @@ } }, "node_modules/@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "node_modules/@types/send": { @@ -3201,9 +3143,9 @@ } }, "node_modules/@types/supertest": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.13.tgz", - "integrity": "sha512-Vc/5/pRwSC055fU7Wu8erTj4gLpID9SdG2zRMuqaHLni3GTsrJ8gyB6MbFZZGLW6vQaGPhiUWRB6uWglv87MEg==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz", + "integrity": "sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==", "dev": true, "dependencies": { "@types/superagent": "*" @@ -3219,9 +3161,9 @@ } }, "node_modules/@types/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", "dev": true }, "node_modules/@types/validator": { @@ -3245,16 +3187,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz", + "integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/type-utils": "6.10.0", + "@typescript-eslint/utils": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3280,15 +3222,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", + "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4" }, "engines": { @@ -3308,13 +3250,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", + "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3325,13 +3267,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz", + "integrity": "sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/utils": "6.10.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3352,9 +3294,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", + "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3365,13 +3307,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", + "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3392,17 +3334,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz", + "integrity": "sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", "semver": "^7.5.4" }, "engines": { @@ -3417,12 +3359,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", + "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/types": "6.10.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3433,6 +3375,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -3933,9 +3881,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", + "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -4350,9 +4298,9 @@ } }, "node_modules/bullmq": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.12.0.tgz", - "integrity": "sha512-eAN7WnlR6MszikwQXE16oGMUWngfbYG0SqkxiwUmVm4muR3KYg+etIRZE3vRKNXWKw0WxJZTA+3oBfDMcCBh+w==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.13.2.tgz", + "integrity": "sha512-JhGfRk2ddBlZMWhQeg7vgYjfKKVsAbbEs9SWu5EMMOHIPrlJ+ZEScLDVz0Yl/N+3VP9mumCZmN7zfDzctSvquw==", "dependencies": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -5025,19 +4973,28 @@ } }, "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cpu-features": { @@ -5768,18 +5725,19 @@ } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -5834,9 +5792,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", + "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", @@ -6058,6 +6016,15 @@ "exiftool-vendored.pl": "12.67.0" } }, + "node_modules/exiftool-vendored.exe": { + "version": "12.67.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.67.0.tgz", + "integrity": "sha512-wzgMDoL/VWH34l38g22cVUn43mVFtTSVj0HRjfjR46+4fGwpSvSueeYbwLCZ5NvBAVINCS5Rz9Rl2DVmqoIjsw==", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/exiftool-vendored.pl": { "version": "12.67.0", "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.67.0.tgz", @@ -6541,15 +6508,15 @@ } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", - "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", + "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "cosmiconfig": "^7.0.1", + "cosmiconfig": "^8.2.0", "deepmerge": "^4.2.2", "fs-extra": "^10.0.0", "memfs": "^3.4.1", @@ -7293,7 +7260,6 @@ "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -8329,9 +8295,9 @@ } }, "node_modules/joi": { - "version": "17.10.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.10.2.tgz", - "integrity": "sha512-hcVhjBxRNW/is3nNLdGLIjkgXetkeGc2wyhydhz8KumG23Aerk4HPjU5zaPAMRqXQFc0xNqXTC7+zQjxr0GlKA==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -8669,9 +8635,9 @@ } }, "node_modules/luxon": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", - "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", "engines": { "node": ">=12" } @@ -9113,15 +9079,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/nest-commander": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.12.0.tgz", - "integrity": "sha512-6ncAT13l7lH9Hya3GKKOIG+ltRD7b4idTlbuNXaCsm2IJIuuVxnx35UxiogJPz+GarE437H3I+GJXzehBnDQqg==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.12.2.tgz", + "integrity": "sha512-iOIVzwepP21fFfU629lxGeWABhDRXfOmJeNsuqmdonx/TEYUpWuPJCPYMKRKOpv/UQj0vf/8hjbzq25VfU+0xQ==", "dependencies": { "@fig/complete-commander": "^2.0.1", "@golevelup/nestjs-discovery": "4.0.0", "commander": "11.0.0", - "cosmiconfig": "8.2.0", - "inquirer": "8.2.5" + "cosmiconfig": "8.3.6", + "inquirer": "8.2.6" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", @@ -9137,7 +9103,7 @@ "prettier": "^2.3.2" }, "peerDependencies": { - "commander": "^11.0.0" + "commander": "^8.0.0" } }, "node_modules/nest-commander/node_modules/commander": { @@ -9148,48 +9114,6 @@ "node": ">=16" } }, - "node_modules/nest-commander/node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", - "dependencies": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - } - }, - "node_modules/nest-commander/node_modules/inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/nest-commander/node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -9204,22 +9128,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/nest-commander/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/node-abi": { "version": "3.47.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", @@ -9444,11 +9352,11 @@ } }, "node_modules/openid-client": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.5.0.tgz", - "integrity": "sha512-Y7Xl8BgsrkzWLHkVDYuroM67hi96xITyEDSkmWaGUiNX6CkcXC3XyQGdv5aWZ6dukVKBFVQCADi9gCavOmU14w==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", + "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", "dependencies": { - "jose": "^4.14.4", + "jose": "^4.15.1", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" @@ -10013,9 +9921,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -10040,9 +9948,9 @@ } }, "node_modules/prettier-plugin-organize-imports": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz", - "integrity": "sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", + "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", "dev": true, "peerDependencies": { "@volar/vue-language-plugin-pug": "^1.0.4", @@ -11340,9 +11248,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.7.2.tgz", - "integrity": "sha512-mVZc9QVQ6pTCV5crli3+Ng+DoMPwdtMHK8QLk2oX8Mtamp4D/hV+uYdC3lV0JZrDgpNEcjs0RrWTqMwwosuLPQ==" + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.9.1.tgz", + "integrity": "sha512-5zAx+hUwJb9T3EAntc7TqYkV716CMqG6sZpNlAAMOMWkNXRYxGkN8ADIvD55dQZ10LxN90ZM/TQmN7y1gpICnw==" }, "node_modules/symbol-observable": { "version": "4.0.0", @@ -11585,22 +11493,22 @@ } }, "node_modules/testcontainers": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.2.1.tgz", - "integrity": "sha512-R9LUMUEkKGSL2M4cP466Jah+Vi+ZLFlvrT4BENjEKJKNzubATOmDk26RHe8DHeFT+hnMD6fvVii+McXr0UTO7g==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.2.2.tgz", + "integrity": "sha512-5GZ93rtoVXMx/s3xZjydftrKLnv1Yf+ETzGkXYRCm16LB60W48SGodxuiouYvNlVy0y0ogoQhdOw3DqsPActEA==", "dev": true, "dependencies": { "@balena/dockerignore": "^1.0.2", - "archiver": "^5.3.1", + "archiver": "^5.3.2", "async-lock": "^1.4.0", "byline": "^5.0.0", "debug": "^4.3.4", "docker-compose": "^0.24.2", "dockerode": "^3.3.5", "get-port": "^5.1.1", - "node-fetch": "^2.6.12", + "node-fetch": "^2.7.0", "proper-lockfile": "^4.1.2", - "properties-reader": "^2.2.0", + "properties-reader": "^2.3.0", "ssh-remote-port-forward": "^1.0.4", "tar-fs": "^3.0.4", "tmp": "^0.2.1" @@ -11986,15 +11894,16 @@ } }, "node_modules/ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", + "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" @@ -12395,13 +12304,16 @@ } }, "node_modules/typesense": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/typesense/-/typesense-1.7.1.tgz", - "integrity": "sha512-RVtwprXDyU8MfAtLZ3PyH9WWRpEFGwij5BTu9I3VBjPIWtIvM/hbi2ogL/UK9dPXFECaxY8HlHrZz9djhJDZtg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/typesense/-/typesense-1.7.2.tgz", + "integrity": "sha512-hgQESOiyNJq+w2mpRJa/a1UMhWtJ/+sb0p7NoeCDSkikm9sasisJdnc7uhQchM6vTWKw2sMLWUBNbAhItR6zUQ==", "dependencies": { "axios": "^0.26.0", "loglevel": "^1.8.0" }, + "engines": { + "node": ">=14" + }, "peerDependencies": { "@babel/runtime": "^7.17.2" } @@ -12415,9 +12327,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.36.tgz", - "integrity": "sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==", + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", "funding": [ { "type": "opencollective", @@ -12467,6 +12379,11 @@ "node": ">= 4.0.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -12673,9 +12590,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -12867,7 +12784,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -12960,15 +12876,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -13054,9 +12961,9 @@ } }, "@angular-devkit/core": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.3.tgz", - "integrity": "sha512-oZLdg2XTx7likYAXRj1CU0XmrsCfe5f2grj3iwuI3OB1LXwwpdbHBztruj03y3yHES+TnO+dIbkvRnvMXs7uAA==", + "version": "16.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.8.tgz", + "integrity": "sha512-PTGozYvh1Bin5lB15PwcXa26Ayd17bWGLS3H8Rs0s+04mUDvfNofmweaX1LgumWWy3nCUTDuwHxX10M3G0wE2g==", "dev": true, "requires": { "ajv": "8.12.0", @@ -13068,12 +12975,12 @@ } }, "@angular-devkit/schematics": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.3.tgz", - "integrity": "sha512-+lBiHxi/C9HCfiCbtW25DldwvJDXXXv5oWw+Tg4s18BO/lYZLveGUEaZWu9ZJ5VIJ8GliUi2LohxhDxBkh4Oxg==", + "version": "16.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.8.tgz", + "integrity": "sha512-MBiKZOlR9/YMdflALr7/7w/BGAfo/BGTrlkqsIB6rDWV1dYiCgxI+033HsiNssLS6RQyCFx/e7JA2aBBzu9zEg==", "dev": true, "requires": { - "@angular-devkit/core": "16.2.3", + "@angular-devkit/core": "16.2.8", "jsonc-parser": "3.2.0", "magic-string": "0.30.1", "ora": "5.4.1", @@ -13081,13 +12988,13 @@ } }, "@angular-devkit/schematics-cli": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.3.tgz", - "integrity": "sha512-5YQCbQmY9Kc03a9Io4XHOrxGXjnzcVveUuUO64R1m5x2aA5I+mVR8NVvxuoGRAeoI1FWusAKRe9hH8nRCLrelA==", + "version": "16.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.8.tgz", + "integrity": "sha512-EXURJCzWTVYCipiTT4vxQQOrF63asOUDbeOy3OtiSh7EwIUvxm3BPG6hquJqngEnI/N6bA75NJ1fBhU6Hrh7eA==", "dev": true, "requires": { - "@angular-devkit/core": "16.2.3", - "@angular-devkit/schematics": "16.2.3", + "@angular-devkit/core": "16.2.8", + "@angular-devkit/schematics": "16.2.8", "ansi-colors": "4.1.3", "inquirer": "8.2.4", "symbol-observable": "4.0.0", @@ -13553,9 +13460,9 @@ } }, "@babel/runtime": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", - "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -13664,9 +13571,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -13701,9 +13608,9 @@ } }, "@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true }, "@golevelup/nestjs-discovery": { @@ -13728,12 +13635,12 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" } @@ -13745,9 +13652,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@ioredis/commands": { @@ -14249,20 +14156,21 @@ } }, "@nestjs/cli": { - "version": "10.1.18", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.18.tgz", - "integrity": "sha512-jQtG47keLsACt7b4YwJbTBYRm90n82gJpMaiR1HGAyQ9pccbctjSYu592eT4bxqkUWxPgBE3mpNynXj7dWAfrw==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.2.1.tgz", + "integrity": "sha512-CAJAQwmxFZfB3RTvqz/eaXXWpyU+mZ4QSqfBYzjneTsPgF+uyOAW3yQpaLNn9Dfcv39R9UxSuAhayv6yuFd+Jg==", "dev": true, "requires": { - "@angular-devkit/core": "16.2.3", - "@angular-devkit/schematics": "16.2.3", - "@angular-devkit/schematics-cli": "16.2.3", + "@angular-devkit/core": "16.2.8", + "@angular-devkit/schematics": "16.2.8", + "@angular-devkit/schematics-cli": "16.2.8", "@nestjs/schematics": "^10.0.1", "chalk": "4.1.2", "chokidar": "3.5.3", "cli-table3": "0.6.3", "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "8.0.0", + "fork-ts-checker-webpack-plugin": "9.0.2", + "glob": "10.3.10", "inquirer": "8.2.6", "node-emoji": "1.11.0", "ora": "5.4.1", @@ -14274,7 +14182,7 @@ "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.1.0", "typescript": "5.2.2", - "webpack": "5.88.2", + "webpack": "5.89.0", "webpack-node-externals": "3.0.0" }, "dependencies": { @@ -14287,18 +14195,6 @@ "balanced-match": "^1.0.0" } }, - "glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - } - }, "minimatch": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", @@ -14321,14 +14217,28 @@ "dev": true, "requires": { "glob": "^9.2.0" + }, + "dependencies": { + "glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + } + } } } } }, "@nestjs/common": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.6.tgz", - "integrity": "sha512-ma8R7n+FXsWM4XF9QXjjrsRceyRzid/xKmNKVOa/sTJntkVG8lL71BHBEfjtFvO6EJUqjs/15LbDc0iaN5nCwA==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.8.tgz", + "integrity": "sha512-rmpwcdvq2IWMmsUVP8rsdKub6uDWk7dwCYo0aif50JTwcvcxzaP3iKVFKoSgvp0RKYu8h15+/AEOfaInmPpl0Q==", "requires": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -14354,9 +14264,9 @@ } }, "@nestjs/core": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.6.tgz", - "integrity": "sha512-oGQ2CoBeFRT7egG47MFqS89xlXBTIRZBkRpKRTPMftEfL1RMXhXIcIIaGfzp11wx6qxrBVxBXpVLM09oaqHpaQ==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.8.tgz", + "integrity": "sha512-9+MZ2s8ixfY9Bl/M9ofChiyYymcwdK9ZWNH4GDMF7Am7XRAQ1oqde6MYGG05rhQwiVXuTwaYLlXciJKfsrg5qg==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -14374,15 +14284,15 @@ } }, "@nestjs/mapped-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", - "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.3.tgz", + "integrity": "sha512-40Zdqg98lqoF0+7ThWIZFStxgzisK6GG22+1ABO4kZiGF/Tu2FE+DYLw+Q9D94vcFWizJ+MSjNN4ns9r6hIGxw==", "requires": {} }, "@nestjs/platform-express": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.6.tgz", - "integrity": "sha512-4U16D5ot2570CR8Qm5qu/SBXsA2l5KxN7AVSGvzoWoBxjEoOnnZOapC5Pler3yYa0tT1xLhji61RX1gceKW3dw==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.8.tgz", + "integrity": "sha512-WoSSVtwIRc5AdGMHWVzWZK4JZLT0f4o2xW8P9gQvcX+omL8W1kXCfY8GQYXNBG84XmBNYH8r0FtC8oMe/lH5NQ==", "requires": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -14399,9 +14309,9 @@ } }, "@nestjs/platform-socket.io": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.6.tgz", - "integrity": "sha512-c4GbHeyd12hyrLngnHDouBui0fwPjaK4TopkZdvkskRCd4sOfph9EUX2CHny+ZL8UeY8IbW/yBZxQ746ahSYsQ==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.8.tgz", + "integrity": "sha512-P/Olw9alAaKD7Q1vS/ol7K81x1l7Bmi+AXthBNUPGMmG/W8kxO1krerW4rEhtF3BKJ0qJIa5bhDlb80p4lZcNA==", "requires": { "socket.io": "4.7.2", "tslib": "2.6.2" @@ -14431,71 +14341,34 @@ } }, "@nestjs/schematics": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.2.tgz", - "integrity": "sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.3.tgz", + "integrity": "sha512-2BRujK0GqGQ7j1Zpz+obVfskDnnOeVKt5aXoSaVngKo8Oczy8uYCY+R547TQB+Kf35epdfFER2pVnQrX3/It5A==", "dev": true, "requires": { - "@angular-devkit/core": "16.1.8", - "@angular-devkit/schematics": "16.1.8", + "@angular-devkit/core": "16.2.8", + "@angular-devkit/schematics": "16.2.8", "comment-json": "4.2.3", "jsonc-parser": "3.2.0", "pluralize": "8.0.0" - }, - "dependencies": { - "@angular-devkit/core": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.8.tgz", - "integrity": "sha512-dSRD/+bGanArIXkj+kaU1kDFleZeQMzmBiOXX+pK0Ah9/0Yn1VmY3RZh1zcX9vgIQXV+t7UPrTpOjaERMUtVGw==", - "dev": true, - "requires": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "rxjs": "7.8.1", - "source-map": "0.7.4" - } - }, - "@angular-devkit/schematics": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.1.8.tgz", - "integrity": "sha512-6LyzMdFJs337RTxxkI2U1Ndw0CW5mMX/aXWl8d7cW2odiSrAg8IdlMqpc+AM8+CPfsB0FtS1aWkEZqJLT0jHOg==", - "dev": true, - "requires": { - "@angular-devkit/core": "16.1.8", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.0", - "ora": "5.4.1", - "rxjs": "7.8.1" - } - }, - "magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.13" - } - } } }, "@nestjs/swagger": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.12.tgz", - "integrity": "sha512-Q1P/IE+cws0sJeNtbs+8uDalcVylpmAnaEUFenGOa3KSNnXF/8DOE84mET/uUhFXsiz9PLHK8Hy7o7B6fRpMhg==", + "version": "7.1.15", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.15.tgz", + "integrity": "sha512-ZaAO90R9MQXk4iLQLijIH6jrsllkUSYoh0Su6DECGgu8Y4Q/9LfdESwsZ9nmzr/48aLOu+wrv+cdI5Wr6fLKJw==", "requires": { - "@nestjs/mapped-types": "2.0.2", + "@nestjs/mapped-types": "2.0.3", "js-yaml": "4.1.0", "lodash": "4.17.21", "path-to-regexp": "3.2.0", - "swagger-ui-dist": "5.7.2" + "swagger-ui-dist": "5.9.1" } }, "@nestjs/testing": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.6.tgz", - "integrity": "sha512-uxlxHhpSvG4yDTPmuPneoQL1/UnBkOkzE+Zaz6bwURg7lc3uS4ZsXl75OL3pYaJH37rHYXYT9bGcYSpxVbwIrg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz", + "integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==", "dev": true, "requires": { "tslib": "2.6.2" @@ -14518,9 +14391,9 @@ } }, "@nestjs/websockets": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.6.tgz", - "integrity": "sha512-HwZADfixAMKMdMB/eBz0HJnPCs0r+W+5inpRwCazsQhwZniGUgXkfIhyRvNfHip/nb+DLS/M8BNBR2JGiJNTEg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.8.tgz", + "integrity": "sha512-oZN1VJFApN7d2eftr65a36QrV0IJNGba4znqyjFnyGvtDWTDcQwzDcnEfvJBTTYhOSBNS7KDfVhne0ythkl6tg==", "requires": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -14944,12 +14817,12 @@ "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, "@testcontainers/postgresql": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.2.1.tgz", - "integrity": "sha512-snIB11wyHUYPzQNNgoHpiRcZO3sR0mDwU28cwOd0lAT4kzTvTihZTmz4gv8MYppcXlRsLAIE0QqzGauZsfFQiA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.2.2.tgz", + "integrity": "sha512-G1xJKe8omeNzngK0dj4R2cSYxWyOUdTXD/oBA03AqIwdReq/gi4WjT6CJqGbkqQy9opXZV6ug3gHMja+wM5BCA==", "dev": true, "requires": { - "testcontainers": "^10.2.1" + "testcontainers": "^10.2.2" } }, "@tsconfig/node10": { @@ -14999,9 +14872,9 @@ } }, "@types/archiver": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.3.tgz", - "integrity": "sha512-0ABdVcXL6jOwNGY+hjWPqrxUvKelBEwNLcuv/SV2vZ4YCH8w9NttFCt+/QqI5zgMX+iX/XqVy89/r7EmLJmMpQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.4.tgz", + "integrity": "sha512-Lj7fLBIMwYFgViVVZHEdExZC3lVYsl+QL0VmdNdIzGZH544jHveYWij6qdnBgJQDnR7pMKliN9z2cPZFEbhyPw==", "dev": true, "requires": { "@types/readdir-glob": "*" @@ -15049,9 +14922,9 @@ } }, "@types/bcrypt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", - "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", "dev": true, "requires": { "@types/node": "*" @@ -15082,9 +14955,9 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "@types/cookie-parser": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.4.tgz", - "integrity": "sha512-Var+aj5I6ZgIqsQ05N2V8q5OBrFfZXtIGWWDSrEYLIbMw758obagSwdGcLCjwh1Ga7M7+wj0SDIAaAC/WT7aaA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==", "dev": true, "requires": { "@types/express": "*" @@ -15140,9 +15013,9 @@ "dev": true }, "@types/express": { - "version": "4.17.18", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz", - "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "requires": { "@types/body-parser": "*", @@ -15164,9 +15037,9 @@ } }, "@types/fluent-ffmpeg": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.22.tgz", - "integrity": "sha512-ZZPDDrDOb2Ahp5fxZzuw64f0rCcviv+SDuCyJ1PIF/UFn9wNHtb/bY8Dj/2nrbQ7SNsGI7gaO2wJVkkU2HBcMg==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.24.tgz", + "integrity": "sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==", "dev": true, "requires": { "@types/node": "*" @@ -15188,9 +15061,9 @@ "dev": true }, "@types/imagemin": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.1.tgz", - "integrity": "sha512-DSpM//dRPzme7doePGkmR1uoquHi0h0ElaA5qFnxHECfFcB8z/jhMI8eqmxWNpHn9ZG18p4PC918sZLhR0cr5A==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.4.tgz", + "integrity": "sha512-t7vady38h/FTQAxFe6gJvaTxjgi/uw54ZrDbqyKx3yOMPu3NQjQexCoLxBR03FRv0HcKJMV2MqGLeY7BuPl6/A==", "dev": true, "requires": { "@types/node": "*" @@ -15241,9 +15114,9 @@ } }, "@types/jest-when": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@types/jest-when/-/jest-when-3.5.3.tgz", - "integrity": "sha512-gJWLSCRWEkPHYXYjjrxEMfWopoA2bl9nUbwDomxIzRXfe0gLkmSDPD0lLcoAiO2RDpXDoHSeigy/rNa/ja0MfQ==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@types/jest-when/-/jest-when-3.5.5.tgz", + "integrity": "sha512-H9MDPIrz7NOu6IXP9OHExNN9LnJbGYAzRsGIDKxWr7Fth9vovemNV8yFbkUWLSEmuA8PREvAEvt9yK0PPLmFHA==", "dev": true, "requires": { "@types/jest": "*" @@ -15256,9 +15129,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.199", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", - "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", + "version": "4.14.201", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz", + "integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==", "dev": true }, "@types/luxon": { @@ -15273,39 +15146,36 @@ "dev": true }, "@types/mock-fs": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.2.tgz", - "integrity": "sha512-mSIMAOjrNTVUFmZgJEigSIm+GlS4hbrk8U5+M8EB45uMrykKdN9TidjjSaOY1yFph2+TD7bsIfB4r+IrMYVyPQ==", + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz", + "integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==", "dev": true, "requires": { "@types/node": "*" } }, "@types/multer": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.8.tgz", - "integrity": "sha512-VMZOW6mnmMMhA5m3fsCdXBwFwC+a+27/8gctNMuQC4f7UtWcF79KAFGoIfKZ4iqrElgWIa3j5vhMJDp0iikQ1g==", + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.10.tgz", + "integrity": "sha512-6l9mYMhUe8wbnz/67YIjc7ZJyQNZoKq7fRXVf7nMdgWgalD0KyzJ2ywI7hoATUSXSbTu9q2HBiEwzy0tNN1v2w==", "dev": true, "requires": { "@types/express": "*" } }, "@types/mv": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/mv/-/mv-2.1.2.tgz", - "integrity": "sha512-IvAjPuiQ2exDicnTrMidt1m+tj3gZ60BM0PaoRsU0m9Cn+lrOyemuO9Tf8CvHFmXlxMjr1TVCfadi9sfwbSuKg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mv/-/mv-2.1.4.tgz", + "integrity": "sha512-MgEHBpXnQo44Q43j8G0Bvp/Yi8LYbC8hxKrRFMgDEDZMmzDKZLgiyMWtW49B37ko+QupgZ3G5rtPUnOGe5ixLw==", "dev": true }, "@types/node": { - "version": "20.8.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==" - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "requires": { + "undici-types": "~5.26.4" + } }, "@types/qs": { "version": "6.9.8", @@ -15329,9 +15199,9 @@ } }, "@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "@types/send": { @@ -15400,9 +15270,9 @@ } }, "@types/supertest": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.13.tgz", - "integrity": "sha512-Vc/5/pRwSC055fU7Wu8erTj4gLpID9SdG2zRMuqaHLni3GTsrJ8gyB6MbFZZGLW6vQaGPhiUWRB6uWglv87MEg==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz", + "integrity": "sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==", "dev": true, "requires": { "@types/superagent": "*" @@ -15418,9 +15288,9 @@ } }, "@types/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", "dev": true }, "@types/validator": { @@ -15444,16 +15314,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz", + "integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/type-utils": "6.10.0", + "@typescript-eslint/utils": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -15463,54 +15333,54 @@ } }, "@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", + "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", + "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0" } }, "@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz", + "integrity": "sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/utils": "6.10.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", + "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", + "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/visitor-keys": "6.10.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -15519,30 +15389,36 @@ } }, "@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz", + "integrity": "sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/scope-manager": "6.10.0", + "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/typescript-estree": "6.10.0", "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", + "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/types": "6.10.0", "eslint-visitor-keys": "^3.4.1" } }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -15965,9 +15841,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", + "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -16267,9 +16143,9 @@ "optional": true }, "bullmq": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.12.0.tgz", - "integrity": "sha512-eAN7WnlR6MszikwQXE16oGMUWngfbYG0SqkxiwUmVm4muR3KYg+etIRZE3vRKNXWKw0WxJZTA+3oBfDMcCBh+w==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.13.2.tgz", + "integrity": "sha512-JhGfRk2ddBlZMWhQeg7vgYjfKKVsAbbEs9SWu5EMMOHIPrlJ+ZEScLDVz0Yl/N+3VP9mumCZmN7zfDzctSvquw==", "requires": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -16763,16 +16639,14 @@ } }, "cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" } }, "cpu-features": { @@ -17298,18 +17172,19 @@ "dev": true }, "eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -17379,9 +17254,9 @@ "requires": {} }, "eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", + "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0", @@ -17509,6 +17384,12 @@ } } }, + "exiftool-vendored.exe": { + "version": "12.67.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.67.0.tgz", + "integrity": "sha512-wzgMDoL/VWH34l38g22cVUn43mVFtTSVj0HRjfjR46+4fGwpSvSueeYbwLCZ5NvBAVINCS5Rz9Rl2DVmqoIjsw==", + "optional": true + }, "exiftool-vendored.pl": { "version": "12.67.0", "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.67.0.tgz", @@ -17877,15 +17758,15 @@ } }, "fork-ts-checker-webpack-plugin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", - "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", + "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "cosmiconfig": "^7.0.1", + "cosmiconfig": "^8.2.0", "deepmerge": "^4.2.2", "fs-extra": "^10.0.0", "memfs": "^3.4.1", @@ -18423,7 +18304,6 @@ "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dev": true, "requires": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -19195,9 +19075,9 @@ } }, "joi": { - "version": "17.10.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.10.2.tgz", - "integrity": "sha512-hcVhjBxRNW/is3nNLdGLIjkgXetkeGc2wyhydhz8KumG23Aerk4HPjU5zaPAMRqXQFc0xNqXTC7+zQjxr0GlKA==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "requires": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -19473,9 +19353,9 @@ } }, "luxon": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", - "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==" + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==" }, "macos-release": { "version": "2.5.1", @@ -19820,15 +19700,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "nest-commander": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.12.0.tgz", - "integrity": "sha512-6ncAT13l7lH9Hya3GKKOIG+ltRD7b4idTlbuNXaCsm2IJIuuVxnx35UxiogJPz+GarE437H3I+GJXzehBnDQqg==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.12.2.tgz", + "integrity": "sha512-iOIVzwepP21fFfU629lxGeWABhDRXfOmJeNsuqmdonx/TEYUpWuPJCPYMKRKOpv/UQj0vf/8hjbzq25VfU+0xQ==", "requires": { "@fig/complete-commander": "^2.0.1", "@golevelup/nestjs-discovery": "4.0.0", "commander": "11.0.0", - "cosmiconfig": "8.2.0", - "inquirer": "8.2.5" + "cosmiconfig": "8.3.6", + "inquirer": "8.2.6" }, "dependencies": { "@fig/complete-commander": { @@ -19844,53 +19724,10 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==" }, - "cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", - "requires": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" - } - }, - "inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" - } - }, "prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==" - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } } } }, @@ -20047,11 +19884,11 @@ } }, "openid-client": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.5.0.tgz", - "integrity": "sha512-Y7Xl8BgsrkzWLHkVDYuroM67hi96xITyEDSkmWaGUiNX6CkcXC3XyQGdv5aWZ6dukVKBFVQCADi9gCavOmU14w==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", + "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", "requires": { - "jose": "^4.14.4", + "jose": "^4.15.1", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" @@ -20473,9 +20310,9 @@ "dev": true }, "prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", "dev": true }, "prettier-linter-helpers": { @@ -20488,9 +20325,9 @@ } }, "prettier-plugin-organize-imports": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz", - "integrity": "sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", + "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", "dev": true, "requires": {} }, @@ -21454,9 +21291,9 @@ "dev": true }, "swagger-ui-dist": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.7.2.tgz", - "integrity": "sha512-mVZc9QVQ6pTCV5crli3+Ng+DoMPwdtMHK8QLk2oX8Mtamp4D/hV+uYdC3lV0JZrDgpNEcjs0RrWTqMwwosuLPQ==" + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.9.1.tgz", + "integrity": "sha512-5zAx+hUwJb9T3EAntc7TqYkV716CMqG6sZpNlAAMOMWkNXRYxGkN8ADIvD55dQZ10LxN90ZM/TQmN7y1gpICnw==" }, "symbol-observable": { "version": "4.0.0", @@ -21618,22 +21455,22 @@ } }, "testcontainers": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.2.1.tgz", - "integrity": "sha512-R9LUMUEkKGSL2M4cP466Jah+Vi+ZLFlvrT4BENjEKJKNzubATOmDk26RHe8DHeFT+hnMD6fvVii+McXr0UTO7g==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.2.2.tgz", + "integrity": "sha512-5GZ93rtoVXMx/s3xZjydftrKLnv1Yf+ETzGkXYRCm16LB60W48SGodxuiouYvNlVy0y0ogoQhdOw3DqsPActEA==", "dev": true, "requires": { "@balena/dockerignore": "^1.0.2", - "archiver": "^5.3.1", + "archiver": "^5.3.2", "async-lock": "^1.4.0", "byline": "^5.0.0", "debug": "^4.3.4", "docker-compose": "^0.24.2", "dockerode": "^3.3.5", "get-port": "^5.1.1", - "node-fetch": "^2.6.12", + "node-fetch": "^2.7.0", "proper-lockfile": "^4.1.2", - "properties-reader": "^2.2.0", + "properties-reader": "^2.3.0", "ssh-remote-port-forward": "^1.0.4", "tar-fs": "^3.0.4", "tmp": "^0.2.1" @@ -21929,15 +21766,16 @@ } }, "ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", + "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", "dev": true, "requires": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" } }, "ts-node": { @@ -22152,9 +21990,9 @@ "devOptional": true }, "typesense": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/typesense/-/typesense-1.7.1.tgz", - "integrity": "sha512-RVtwprXDyU8MfAtLZ3PyH9WWRpEFGwij5BTu9I3VBjPIWtIvM/hbi2ogL/UK9dPXFECaxY8HlHrZz9djhJDZtg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/typesense/-/typesense-1.7.2.tgz", + "integrity": "sha512-hgQESOiyNJq+w2mpRJa/a1UMhWtJ/+sb0p7NoeCDSkikm9sasisJdnc7uhQchM6vTWKw2sMLWUBNbAhItR6zUQ==", "requires": { "axios": "^0.26.0", "loglevel": "^1.8.0" @@ -22171,9 +22009,9 @@ } }, "ua-parser-js": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.36.tgz", - "integrity": "sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==" + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==" }, "uglify-js": { "version": "3.17.4", @@ -22194,6 +22032,11 @@ "resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz", "integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==" }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -22350,9 +22193,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", @@ -22494,7 +22337,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -22556,12 +22398,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", From 8399130f05cb28d567b36265d0ee2790e6405ff6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:45:23 -0600 Subject: [PATCH 28/88] chore(deps): update dependency eslint-plugin-jest to v27.6.0 (#4987) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 61461d341..beee09c8a 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -2706,9 +2706,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.2.tgz", - "integrity": "sha512-3Nfvv3wbq2+PZlRTf2oaAWXWwbdBejFRBR2O8tAO67o+P8zno+QGbcDYaAXODlreXVg+9gvWhKKmG2rgfb8GEg==", + "version": "27.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz", + "integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^5.10.0" @@ -8174,9 +8174,9 @@ "requires": {} }, "eslint-plugin-jest": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.2.tgz", - "integrity": "sha512-3Nfvv3wbq2+PZlRTf2oaAWXWwbdBejFRBR2O8tAO67o+P8zno+QGbcDYaAXODlreXVg+9gvWhKKmG2rgfb8GEg==", + "version": "27.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz", + "integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==", "dev": true, "requires": { "@typescript-eslint/utils": "^5.10.0" From 14acee9090c3e7f1f0d31d562ee6a359dc3b7e88 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:45:52 -0600 Subject: [PATCH 29/88] fix(deps): update dependency systeminformation to v5.21.17 (#4979) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index beee09c8a..9b8dab1cd 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -5615,9 +5615,9 @@ "dev": true }, "node_modules/systeminformation": { - "version": "5.21.9", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.21.9.tgz", - "integrity": "sha512-7pI4mu9P/2MGDV0T49B52E7IULBGj+kRVk6JSYUj5qfAk7N7C7aNX15fXziqrbgZntc6/jjYzWeb/x41jhg/eA==", + "version": "5.21.17", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.21.17.tgz", + "integrity": "sha512-JZYRCbIjk3WuBV59A9/rTla2rROX+aAJ9uo2Z1dI+bjieORcukClN8rlM1zE9NYKpULSbaGc+KKct/870lO0DA==", "os": [ "darwin", "linux", @@ -10286,9 +10286,9 @@ "dev": true }, "systeminformation": { - "version": "5.21.9", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.21.9.tgz", - "integrity": "sha512-7pI4mu9P/2MGDV0T49B52E7IULBGj+kRVk6JSYUj5qfAk7N7C7aNX15fXziqrbgZntc6/jjYzWeb/x41jhg/eA==" + "version": "5.21.17", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.21.17.tgz", + "integrity": "sha512-JZYRCbIjk3WuBV59A9/rTla2rROX+aAJ9uo2Z1dI+bjieORcukClN8rlM1zE9NYKpULSbaGc+KKct/870lO0DA==" }, "test-exclude": { "version": "6.0.0", From 89255d088913679eaed7169a59afeb146a1d33c8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:46:06 -0600 Subject: [PATCH 30/88] fix(deps): update dependency yaml to v2.3.4 (#4981) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 9b8dab1cd..68ced6682 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -6100,9 +6100,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "engines": { "node": ">= 14" } @@ -10600,9 +10600,9 @@ "dev": true }, "yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==" + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==" }, "yargs": { "version": "17.7.2", From ab1d1ef4e7d5eb01c19b9f75b46ac7202719f621 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:46:22 -0600 Subject: [PATCH 31/88] chore(deps): update dependency eslint to v8.53.0 (#4986) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 90 +++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 68ced6682..bce1ae65f 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -775,9 +775,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -798,21 +798,21 @@ } }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -834,9 +834,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1809,6 +1809,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -2640,18 +2646,19 @@ } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -6711,9 +6718,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -6728,18 +6735,18 @@ } }, "@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" } @@ -6751,9 +6758,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@isaacs/cliui": { @@ -7510,6 +7517,12 @@ "eslint-visitor-keys": "^3.3.0" } }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -8104,18 +8117,19 @@ "dev": true }, "eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", From 880f4f61d288dd6f31f97837f21160fe2c7be413 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 03:54:25 +0000 Subject: [PATCH 32/88] chore(deps): update dependency @types/jest to v29.5.8 (#4995) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- server/package-lock.json | 14 +++++++------- server/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 75db1d355..45ba6ed58 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -71,7 +71,7 @@ "@types/express": "^4.17.17", "@types/fluent-ffmpeg": "^2.1.21", "@types/imagemin": "^8.0.1", - "@types/jest": "29.5.4", + "@types/jest": "29.5.8", "@types/jest-when": "^3.5.2", "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", @@ -2977,9 +2977,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.4", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.4.tgz", - "integrity": "sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A==", + "version": "29.5.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", + "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -15104,9 +15104,9 @@ } }, "@types/jest": { - "version": "29.5.4", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.4.tgz", - "integrity": "sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A==", + "version": "29.5.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", + "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", "dev": true, "requires": { "expect": "^29.0.0", diff --git a/server/package.json b/server/package.json index 62e9c960a..5ded6cee3 100644 --- a/server/package.json +++ b/server/package.json @@ -97,7 +97,7 @@ "@types/express": "^4.17.17", "@types/fluent-ffmpeg": "^2.1.21", "@types/imagemin": "^8.0.1", - "@types/jest": "29.5.4", + "@types/jest": "29.5.8", "@types/jest-when": "^3.5.2", "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", From a959f2a51d8cdbdf202a24b0246615175813ad94 Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Mon, 13 Nov 2023 10:46:32 +0100 Subject: [PATCH 33/88] chore(renovate): Ignore un-bumpable mobile deps (#5007) --- renovate.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/renovate.json b/renovate.json index 315f01106..551316f27 100644 --- a/renovate.json +++ b/renovate.json @@ -23,4 +23,11 @@ "matchUpdateTypes": ["minor", "patch"] } ], + "ignoreDeps": [ + "http", + "latlong2", + "vector_map_tiles", + "flutter_map", + "flutter_map_heatmap" + ], } From c3f8dc8c22237943a7d6adcffac0607282a9d971 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Nov 2023 09:24:00 -0600 Subject: [PATCH 34/88] chore(mobile): translation update (#5011) --- mobile/assets/i18n/ca.json | 18 ++ mobile/assets/i18n/cs-CZ.json | 120 +++++++------ mobile/assets/i18n/da-DK.json | 96 ++++++----- mobile/assets/i18n/de-DE.json | 238 ++++++++++++++------------ mobile/assets/i18n/en-US.json | 36 ++-- mobile/assets/i18n/es-ES.json | 170 +++++++++--------- mobile/assets/i18n/es-MX.json | 168 ++++++++++-------- mobile/assets/i18n/es-PE.json | 170 +++++++++--------- mobile/assets/i18n/fi-FI.json | 140 ++++++++------- mobile/assets/i18n/fr-FR.json | 96 ++++++----- mobile/assets/i18n/hi-IN.json | 18 ++ mobile/assets/i18n/hu-HU.json | 18 ++ mobile/assets/i18n/it-IT.json | 18 ++ mobile/assets/i18n/ja-JP.json | 40 +++-- mobile/assets/i18n/ko-KR.json | 294 +++++++++++++++++--------------- mobile/assets/i18n/lv-LV.json | 18 ++ mobile/assets/i18n/mn.json | 18 ++ mobile/assets/i18n/nb-NO.json | 98 ++++++----- mobile/assets/i18n/nl-NL.json | 18 ++ mobile/assets/i18n/pl-PL.json | 94 +++++----- mobile/assets/i18n/ru-RU.json | 100 ++++++----- mobile/assets/i18n/sk-SK.json | 102 ++++++----- mobile/assets/i18n/sr-Cyrl.json | 18 ++ mobile/assets/i18n/sr-Latn.json | 18 ++ mobile/assets/i18n/sv-FI.json | 18 ++ mobile/assets/i18n/sv-SE.json | 18 ++ mobile/assets/i18n/th-TH.json | 18 ++ mobile/assets/i18n/uk-UA.json | 18 ++ mobile/assets/i18n/vi-VN.json | 128 ++++++++------ mobile/assets/i18n/zh-CN.json | 72 +++++--- mobile/assets/i18n/zh-Hans.json | 72 +++++--- 31 files changed, 1504 insertions(+), 964 deletions(-) diff --git a/mobile/assets/i18n/ca.json b/mobile/assets/i18n/ca.json index 1ed97ed07..96dbead6a 100644 --- a/mobile/assets/i18n/ca.json +++ b/mobile/assets/i18n/ca.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Error al modificar el títol de l'àlbum", "album_viewer_appbar_share_leave": "Surt de l'àlbum", "album_viewer_appbar_share_remove": "Treu de l'àlbum", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Afegeix usuaris", "all_people_page_title": "Persones", "all_videos_page_title": "Vídeos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No s'ha trobat res arxivat", "archive_page_title": "Arxiu({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Ús de memòria cau", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Configuració de la memòria cau", "change_password_form_confirm_password": "Confirma la contrasenya", "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Tanca la sessió", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Error al crear l'àlbum", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "Versió de l'aplicació", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Versió del servidor", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "Afegeix fotografies", "share_add_title": "Afegeix un títol", "share_create_album": "Crea un àlbum", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparing...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/cs-CZ.json b/mobile/assets/i18n/cs-CZ.json index d7151e9cf..538462189 100644 --- a/mobile/assets/i18n/cs-CZ.json +++ b/mobile/assets/i18n/cs-CZ.json @@ -13,7 +13,7 @@ "album_info_card_backup_album_included": "ZAHRNUTO", "album_thumbnail_card_item": "1 položka", "album_thumbnail_card_items": "{} položek", - "album_thumbnail_card_shared": "Sdíleno", + "album_thumbnail_card_shared": " · Sdíleno", "album_thumbnail_owned": "Vlastní", "album_thumbnail_shared_by": "Sdílel(a) {}", "album_viewer_appbar_share_delete": "Odstranit album", @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Nepodařilo se změnit název alba", "album_viewer_appbar_share_leave": "Opustit album", "album_viewer_appbar_share_remove": "Odstranit z alba", + "album_viewer_appbar_share_to": "Sdílet na", "album_viewer_page_share_add_users": "Přidat uživatele", "all_people_page_title": "Lidé", "all_videos_page_title": "Videa", + "app_bar_signout_dialog_content": "Určitě se chcete odhlásit?", + "app_bar_signout_dialog_ok": "Ano", + "app_bar_signout_dialog_title": "Odhlásit se", "archive_page_no_archived_assets": "Nebyla nalezena žádná archivovaná média", "archive_page_title": "Archív ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamické rozložení", @@ -44,11 +48,11 @@ "backup_all": "Vše", "backup_background_service_backup_failed_message": "Zálohování médií selhalo. Zkouším to znovu...", "backup_background_service_connection_failed_message": "Nepodařilo se připojit k serveru. Zkouším to znovu...", - "backup_background_service_current_upload_notification": "Zálohování {}", + "backup_background_service_current_upload_notification": "Nahrávání {}", "backup_background_service_default_notification": "Kontrola nových médií…", "backup_background_service_error_title": "Chyba zálohování", "backup_background_service_in_progress_notification": "Zálohování vašich médií...", - "backup_background_service_upload_failure_notification": "Nepodařilo se zálohovat {}", + "backup_background_service_upload_failure_notification": "Nepodařilo se nahrát {}", "backup_controller_page_albums": "Zálohovaná alba", "backup_controller_page_background_app_refresh_disabled_content": "Povolte obnovení aplikace na pozadí v Nastavení > Obecné > Obnovení aplikace na pozadí, abyste mohli používat zálohování na pozadí.", "backup_controller_page_background_app_refresh_disabled_title": " Obnovování aplikací na pozadí je vypnuté", @@ -91,14 +95,14 @@ "backup_controller_page_total_sub": "Všechny jedinečné fotografie a videa z vybraných alb", "backup_controller_page_turn_off": "Vypnout zálohování na popředí", "backup_controller_page_turn_on": "Povolit zálohování na popředí", - "backup_controller_page_uploading_file_info": "Informace o zálohovaném souboru", + "backup_controller_page_uploading_file_info": "Informace o nahraném souboru", "backup_err_only_album": "Nelze odstranit jediné vybrané album", "backup_info_card_assets": "položek", "backup_manual_cancelled": "Zrušeno", "backup_manual_failed": "Selhalo", - "backup_manual_in_progress": "Zálohování již probíhá. Zkuste to znovu později", + "backup_manual_in_progress": "Nahrávání již probíhá. Zkuste to znovu později", "backup_manual_success": "Úspěch", - "backup_manual_title": "Stav zálohování", + "backup_manual_title": "Stav nahrávání", "cache_settings_album_thumbnails": "Náhledy stránek knihovny (položek {})", "cache_settings_clear_cache_button": "Vymazat vyrovnávací paměť", "cache_settings_clear_cache_button_title": "Vymaže vyrovnávací paměť aplikace. To výrazně ovlivní výkon aplikace, dokud se vyrovnávací paměť neobnoví.", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Použití vyrovnávací paměti", "cache_settings_subtitle": "Ovládání chování mobilní aplikace Immich v mezipaměti", "cache_settings_thumbnail_size": "Velikost vyrovnávací paměti náhledů (položek {})", + "cache_settings_tile_subtitle": "Ovládání chování místního úložiště", + "cache_settings_tile_title": "Místní úložiště", "cache_settings_title": "Nastavení vyrovnávací paměti", "change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_description": "Dobrý den, {firstName} {lastName},\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Vymazat", "control_bottom_app_bar_favorite": "Oblíbené", "control_bottom_app_bar_share": "Sdílet", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Sdílet v", + "control_bottom_app_bar_stack": "Zásobník", "control_bottom_app_bar_unarchive": "Odarchivovat", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Nahrát", "create_album_page_untitled": "Bez názvu", "create_shared_album_page_create": "Vytvořit", "create_shared_album_page_share": "Sdílet", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Zrušit", "delete_dialog_ok": "Vymazat", "delete_dialog_title": "Vymazat trvale", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Opravdu chcete tento odkaz ke sdílení odstranit?", + "delete_shared_link_dialog_title": "Odstranit sdílený odkaz", "description_input_hint_text": "Přidat popis...", "description_input_submit_error": "Chyba aktualizace popisu, další podrobnosti najdete v logu", "exif_bottom_sheet_description": "Přidat popis...", @@ -168,10 +174,10 @@ "home_page_building_timeline": "Vytváření časové osy", "home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuji", "home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných alb.", - "home_page_upload_err_limit": "Lze zálohovat nejvýše 30 položek najednou, přeskakuji", + "home_page_upload_err_limit": "Lze nahrát nejvýše 30 položek najednou, přeskakuji", "image_viewer_page_state_provider_download_error": "Chyba stahování", "image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Chyba sdílení", "library_page_albums": "Alba", "library_page_archive": "Archív", "library_page_device_albums": "Alba v zařízení", @@ -179,8 +185,8 @@ "library_page_new_album": "Nové album", "library_page_sharing": "Sdílení", "library_page_sort_created": "Naposledy vytvořené", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Naposledy upraveno", + "library_page_sort_most_recent_photo": "Nejnovější fotografie", "library_page_sort_title": "Podle názvu alba", "login_disabled": "Přihlášení bylo zakázáno", "login_form_api_exception": "Výjimka API. Zkontrolujte URL serveru a zkuste to znovu.", @@ -218,7 +224,7 @@ "map_settings_dialog_cancel": "Zrušit", "map_settings_dialog_save": "Uložit", "map_settings_dialog_title": "Nastavení map", - "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_archived": "Zahrnout archivované", "map_settings_only_relative_range": "Rozsah data", "map_settings_only_show_favorites": "Zobrazit pouze oblíbené", "map_zoom_to_see_photos": "Oddálit pro zobrazení fotografií", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich potřebuje přístup k zobrazení vašich fotek a videí.", "profile_drawer_app_logs": "Logy", "profile_drawer_client_server_up_to_date": "Klient a server jsou aktuální", + "profile_drawer_documentation": "Dokumentace", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Nastavení", "profile_drawer_sign_out": "Odhlásit se", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Vyhodit", "recently_added_page_title": "Nedávno přidané", "search_bar_hint": "Prohledejte své fotky", "search_page_categories": "Kategorie", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Nepodařilo se vytvořit album", "select_user_for_sharing_page_share_suggestions": "Návrhy", "server_info_box_app_version": "Verze aplikace", + "server_info_box_server_url": "URL serveru", "server_info_box_server_version": "Verze serveru", "setting_image_viewer_help": "V prohlížeči detailů se nejprve načte malá miniatura, poté se načte náhled střední velikosti (je-li povolen) a nakonec se načte originál (je-li povolen).", "setting_image_viewer_original_subtitle": "Umožňuje načíst původní obrázek v plném rozlišení (velký!). Zakázat pro snížení využití dat (v síti i v mezipaměti zařízení).", @@ -288,11 +297,11 @@ "setting_notifications_notify_minutes": "{} minut", "setting_notifications_notify_never": "nikdy", "setting_notifications_notify_seconds": "{} sekundy", - "setting_notifications_single_progress_subtitle": "Podrobné informace o průběhu zálohování položky", + "setting_notifications_single_progress_subtitle": "Podrobné informace o průběhu nahrávání položky", "setting_notifications_single_progress_title": "Zobrazit průběh detailů zálohování na pozadí", "setting_notifications_subtitle": "Přizpůsobení předvoleb oznámení", "setting_notifications_title": "Oznámení", - "setting_notifications_total_progress_subtitle": "Celkový průběh zálohování (hotovo/celkově)", + "setting_notifications_total_progress_subtitle": "Celkový průběh nahrání (hotovo/celkově)", "setting_notifications_total_progress_title": "Zobrazit celkový průběh zálohování na pozadí", "setting_pages_app_bar_settings": "Nastavení", "settings_require_restart": "Pro použití tohoto nastavení restartujte Immich", @@ -300,28 +309,37 @@ "share_add_photos": "Přidat fotografie", "share_add_title": "Přidat název", "share_create_album": "Vytvořit album", + "shared_album_activities_input_disable": "Komentář je vypnutý", + "shared_album_activities_input_hint": "Řekněte něco", + "shared_album_activity_remove_content": "Chcete odstranit tuto aktivitu?", + "shared_album_activity_remove_title": "Odstranit aktivitu", + "shared_album_activity_setting_subtitle": "Nechte ostatní reagovat", + "shared_album_activity_setting_title": "Komentáře a lajky", "share_dialog_preparing": "Připravuji...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Sdílené odkazy", + "shared_link_create_app_bar_title": "Vytvořit odkaz pro sdílení", + "shared_link_create_info": "Umožnit každému, kdo má odkaz, zobrazit vybrané fotografie", + "shared_link_create_submit_button": "Vytvořit odkaz", + "shared_link_edit_allow_download": "Povolit veřejným uživatelům stahovat", + "shared_link_edit_allow_upload": "Povolit veřejným uživatelům nahrávat", + "shared_link_edit_app_bar_title": "Upravit odkaz", + "shared_link_edit_change_expiry": "Změnit dobu platnosti", + "shared_link_edit_description": "Popis", + "shared_link_edit_description_hint": "Zadejte popis sdílení", + "shared_link_edit_expire_after": "Platnost vyprší po", + "shared_link_edit_password": "Heslo", + "shared_link_edit_password_hint": "Zadejte heslo pro sdílení", + "shared_link_edit_show_meta": "Zobrazit metadata", + "shared_link_edit_submit_button": "Aktualizovat odkaz", + "shared_link_empty": "Nemáte žádné sdílené odkazy", + "shared_link_manage_links": "Spravovat sdílené odkazy", + "share_done": "Hotovo", "share_invite": "Pozvat do alba", "sharing_page_album": "Sdílená alba", "sharing_page_description": "Vytvářejte sdílená alba a sdílejte fotografie a videa s lidmi ve vaší síti.", "sharing_page_empty_list": "PRÁZDNÝ SEZNAM", "sharing_silver_appbar_create_shared_album": "Vytvořit sdílené album", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Sdílené odkazy", "sharing_silver_appbar_share_partner": "Sdílet s partnerem", "tab_controller_nav_library": "Knihovna", "tab_controller_nav_photos": "Fotografie", @@ -338,29 +356,29 @@ "theme_setting_three_stage_loading_subtitle": "Třístupňové načítání může zvýšit výkonnost načítání, ale vede k výrazně vyššímu zatížení sítě.", "theme_setting_three_stage_loading_title": "Povolení třístupňového načítání", "translated_text_options": "Možnosti", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_delete": "Smazat", + "trash_page_delete_all": "Smazat všechny", + "trash_page_empty_trash_btn": "Vysypat koš", + "trash_page_empty_trash_dialog_content": "Chcete vyprázdnit svoje vyhozené položky? Tyto položky budou trvale odstraněny z aplikace", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_info": "Vyhozené položky budou trvale odstraněny po {} dnech", + "trash_page_no_assets": "Žádné vyhozené položky", + "trash_page_restore": "Obnovit", + "trash_page_restore_all": "Obnovit všechny", + "trash_page_select_assets_btn": "Vybrat položky", + "trash_page_select_btn": "Vybrat", + "trash_page_title": "Koš ({})", "upload_dialog_cancel": "Zrušit", "upload_dialog_info": "Chcete zálohovat vybrané položky na server?", - "upload_dialog_ok": "Zálohovat", - "upload_dialog_title": "Zálohovat položku", + "upload_dialog_ok": "Nahrát", + "upload_dialog_title": "Nahrát položku", "version_announcement_overlay_ack": "Potvrdit", "version_announcement_overlay_release_notes": "poznámky k vydání", "version_announcement_overlay_text_1": "Ahoj, k dispozici je nová verze", "version_announcement_overlay_text_2": "najděte si čas na návštěvu ", "version_announcement_overlay_text_3": " a ujistěte se, že vaše konfigurace docker-compose a .env je aktuální, abyste předešli nesprávné konfiguraci, zvláště pokud používáte WatchTower nebo jakýkoli mechanismus, který podporuje automatické aktualizace serverových aplikací.", "version_announcement_overlay_title": "K dispozici je nová verze serveru \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Odstranit ze zásobníku", + "viewer_stack_use_as_main_asset": "Použít jako hlavní položku", + "viewer_unstack": "Rozbalit zásobník" } \ No newline at end of file diff --git a/mobile/assets/i18n/da-DK.json b/mobile/assets/i18n/da-DK.json index 41e2cc340..0869a18be 100644 --- a/mobile/assets/i18n/da-DK.json +++ b/mobile/assets/i18n/da-DK.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Fejlede i at ændre albumtitel", "album_viewer_appbar_share_leave": "Forlad album", "album_viewer_appbar_share_remove": "Fjern fra album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Tilføj brugere", "all_people_page_title": "Personer", "all_videos_page_title": "Videoer", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet", "archive_page_title": "Arkivér ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cacheforbrug", "cache_settings_subtitle": "Håndter cache-adfærden for Immich-appen.", "cache_settings_thumbnail_size": "Størrelse af miniaturebillede cache ({} elementer)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Cache-indstillinger", "change_password_form_confirm_password": "Bekræft kodeord", "change_password_form_description": "Hej {firstName} {lastName},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.", @@ -130,8 +136,8 @@ "control_bottom_app_bar_delete": "Slet", "control_bottom_app_bar_favorite": "Favorit", "control_bottom_app_bar_share": "Del", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Del til", + "control_bottom_app_bar_stack": "Stak", "control_bottom_app_bar_unarchive": "Afakivér", "control_bottom_app_bar_upload": "Upload", "create_album_page_untitled": "Uden titel", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Annuller", "delete_dialog_ok": "Slet", "delete_dialog_title": "Slet permanent", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Er du sikker på, du vil slette dette delte link?", + "delete_shared_link_dialog_title": "Slet delt link", "description_input_hint_text": "Tilføj en beskrivelse...", "description_input_submit_error": "Fejl med at opdatere beskrivelsen. Tjek loggen for flere detaljer", "exif_bottom_sheet_description": "Tilføj beskrivelse...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. Springer over", "image_viewer_page_state_provider_download_error": "Fejl ved download", "image_viewer_page_state_provider_download_success": "Download succesfuld", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Delingsfejl", "library_page_albums": "Albummer", "library_page_archive": "Arkiv", "library_page_device_albums": "Albummer på enhed", @@ -179,8 +185,8 @@ "library_page_new_album": "Nyt album", "library_page_sharing": "Delte", "library_page_sort_created": "Senest oprettet", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Sidst redigeret", + "library_page_sort_most_recent_photo": "Seneste billede", "library_page_sort_title": "Albumtitel", "login_disabled": "Login er blevet deaktiveret", "login_form_api_exception": "API-undtagelse. Tjek serverens URL og prøv igen. ", @@ -218,7 +224,7 @@ "map_settings_dialog_cancel": "Annuller", "map_settings_dialog_save": "Gem", "map_settings_dialog_title": "Kortindstillinger", - "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_archived": "Inkluder arkiveret", "map_settings_only_relative_range": "Datointerval", "map_settings_only_show_favorites": "Vis kun favoritter", "map_zoom_to_see_photos": "Zoom ud for at vise billeder", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich kræver tilliadelse til at se dine billeder og videoer.", "profile_drawer_app_logs": "Log", "profile_drawer_client_server_up_to_date": "Klient og server er ajour", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Indstillinger", "profile_drawer_sign_out": "Log ud", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Papirkurv", "recently_added_page_title": "Nyligt tilføjet", "search_bar_hint": "Søg i dine billeder", "search_page_categories": "Kategorier", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Fejlede i at oprette et nyt album", "select_user_for_sharing_page_share_suggestions": "Anbefalinger", "server_info_box_app_version": "Applikationsversion", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Serverversion ", "setting_image_viewer_help": "Detaljeret visning indlæser miniaturebilleder først. Herefter indlæses mediumstørrelse forhåndsvisning af billedet (hvis dette er slået til), for til sidst at vise originalen (hvis dette er slået til).", "setting_image_viewer_original_subtitle": "Slå indlæsning af originalbillede i fuld størrelse til (stort!). Deaktiver for at reducere dataforbruget (både på netværket og for enhedscache).", @@ -300,28 +309,37 @@ "share_add_photos": "Tilføj billeder", "share_add_title": "Tilføj en titel", "share_create_album": "Opret album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Forbereder...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Delte links", + "shared_link_create_app_bar_title": "Opret link for at dele", + "shared_link_create_info": "Lad enhver med linket se de(t) valgte billede(r)", + "shared_link_create_submit_button": "Oprat link", + "shared_link_edit_allow_download": "Tillad at en offenlig bruger kan downloade", + "shared_link_edit_allow_upload": "Tillad at en offentlig bruger kan uploade", + "shared_link_edit_app_bar_title": "Rediger link", + "shared_link_edit_change_expiry": "Ændrer udløbstidspunkt", + "shared_link_edit_description": "Beskrivelse", + "shared_link_edit_description_hint": "Indtast beskrivelse", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", + "shared_link_edit_show_meta": "Vis metadata", + "shared_link_edit_submit_button": "Opdater link", + "shared_link_empty": "Du har endnu ingen delte links", + "shared_link_manage_links": "Håndter delte links", + "share_done": "Færdig", "share_invite": "Inviter til album", "sharing_page_album": "Delt albums", "sharing_page_description": "Opret delte albummer for at dele billeder og video med personer på dit netværk.", "sharing_page_empty_list": "TOM LISTE", "sharing_silver_appbar_create_shared_album": "Opret delt album", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Delte links", "sharing_silver_appbar_share_partner": "Del med partner", "tab_controller_nav_library": "Bibliotek", "tab_controller_nav_photos": "Billeder", @@ -338,18 +356,18 @@ "theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning", "theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til", "translated_text_options": "Handlinger", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_delete": "Slet", + "trash_page_delete_all": "Slet alt", + "trash_page_empty_trash_btn": "Tøm papirkurv", + "trash_page_empty_trash_dialog_content": "Vil du tømme papirkurven? Disse elementer vil blive permanent fjernet fra Immich", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_info": "Slettede elementer vil blive slettet permanent efter {} dage", + "trash_page_no_assets": "Ingen slettede elementer", + "trash_page_restore": "Gendan", + "trash_page_restore_all": "Gendan alt", + "trash_page_select_assets_btn": "Vælg elementer", + "trash_page_select_btn": "Vælg", + "trash_page_title": "Papirkurv ({})", "upload_dialog_cancel": "Annuller", "upload_dialog_info": "Vil du sikkerhedskopiere de(t) valgte element(er) til serveren?", "upload_dialog_ok": "Upload", @@ -360,7 +378,7 @@ "version_announcement_overlay_text_2": ". Besøg venligst ", "version_announcement_overlay_text_3": " for at sikre dig, at din dockercompose- og .env-fil er opdateret, så der undgås fejlkonfiguration, specielt hvis du bruger WatchTower eller lignede.", "version_announcement_overlay_title": "Ny serverversion er tilgængelig \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Fjern fra stak", + "viewer_stack_use_as_main_asset": "Brug som hovedelement", + "viewer_unstack": "Fjern fra stak" } \ No newline at end of file diff --git a/mobile/assets/i18n/de-DE.json b/mobile/assets/i18n/de-DE.json index 76085a61b..a43141e40 100644 --- a/mobile/assets/i18n/de-DE.json +++ b/mobile/assets/i18n/de-DE.json @@ -3,8 +3,8 @@ "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", "advanced_settings_prefer_remote_subtitle": "Manche Endgeräte laden Vorschaubilder lokaler Bilder sehr langsam. Durch diese Einstellung werden diese stattdessen direkt vom Server geladen.", "advanced_settings_prefer_remote_title": "Server-Bilder bevorzugen", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", + "advanced_settings_self_signed_ssl_subtitle": "Verifizierung von SSL-Zertifikaten vom Server überspringen. Notwendig bei selbstsignierten Zertifikaten.", + "advanced_settings_self_signed_ssl_title": "Selbstsignierte SSL-Zertifikate erlauben", "advanced_settings_tile_subtitle": "Erweiterte Benutzereinstellungen", "advanced_settings_tile_title": "Sonstige", "advanced_settings_troubleshooting_subtitle": "Aktiviere erweiterte Funktionen zur Fehlersuche", @@ -14,7 +14,7 @@ "album_thumbnail_card_item": "1 Element", "album_thumbnail_card_items": "{} Elemente", "album_thumbnail_card_shared": " · Geteilt", - "album_thumbnail_owned": "Owned", + "album_thumbnail_owned": "Eigene", "album_thumbnail_shared_by": "Geteilt von {}", "album_viewer_appbar_share_delete": "Album löschen", "album_viewer_appbar_share_err_delete": "Album konnte nicht gelöscht werden", @@ -22,12 +22,16 @@ "album_viewer_appbar_share_err_remove": "Beim Löschen von Elementen aus dem Album ist ein Problem aufgetreten", "album_viewer_appbar_share_err_title": "Der Titel konnte nicht geändert werden", "album_viewer_appbar_share_leave": "Album verlassen", - "album_viewer_appbar_share_remove": "Entferne vom Album", + "album_viewer_appbar_share_remove": "Vom Album entfernen", + "album_viewer_appbar_share_to": "Teile über", "album_viewer_page_share_add_users": "Nutzer hinzufügen", "all_people_page_title": "Personen", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Bist du sicher, dass du dich abmelden möchtest?", + "app_bar_signout_dialog_ok": "Ja", + "app_bar_signout_dialog_title": "Abmelden", "archive_page_no_archived_assets": "Keine archivierten Inhalte gefunden", - "archive_page_title": "Archive ({})", + "archive_page_title": "Archiv ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamisches Layout", "asset_list_layout_settings_group_automatically": "Automatisch", "asset_list_layout_settings_group_by": "Gruppiere Elemente nach", @@ -54,13 +58,13 @@ "backup_controller_page_background_app_refresh_disabled_title": "Hintergrundaktualisierungen sind deaktiviert.", "backup_controller_page_background_app_refresh_enable_button_text": "Gehe zu Einstellungen", "backup_controller_page_background_battery_info_link": "Zeige mir wie", - "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.", + "backup_controller_page_background_battery_info_message": "Für die besten Ergebnisse für Sicherungen im Hintergrund, deaktiviere alle Batterieoptimierungen und Einschränkungen für die Hintergrundaktivitäten von Immich.\n\nDa dies gerätespezifisch ist, schlage diese Informationen für deinen Gerätehersteller nach.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Batterieoptimierungen", "backup_controller_page_background_charging": "Nur während des Ladens", "backup_controller_page_background_configure_error": "Konnte Hintergrundservice nicht konfigurieren", - "backup_controller_page_background_delay": "Delay new assets backup: {}", - "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app", + "backup_controller_page_background_delay": "Sicherung neuer Elemente verzögern: {}", + "backup_controller_page_background_description": "Schalte den Hintergrundservice ein, um neue Elemente automatisch im Hintergrund zu sichern ohne die App zu öffnen", "backup_controller_page_background_is_off": "Automatische Sicherung im Hintergrund ist deaktiviert", "backup_controller_page_background_is_on": "Automatische Sicherung im Hintergrund ist aktiviert", "backup_controller_page_background_turn_off": "Hintergrundservice ausschalten", @@ -78,7 +82,7 @@ "backup_controller_page_id": "ID: {}", "backup_controller_page_info": "Informationen zur Sicherung", "backup_controller_page_none_selected": "Keine ausgewählt", - "backup_controller_page_remainder": "Übrig", + "backup_controller_page_remainder": "Verbleibend", "backup_controller_page_remainder_sub": "Noch zu sichernde Fotos und Videos", "backup_controller_page_select": "Auswählen", "backup_controller_page_server_storage": "Server Speicher", @@ -94,23 +98,25 @@ "backup_controller_page_uploading_file_info": "Informationen", "backup_err_only_album": "Das einzige Album kann nicht entfernt werden", "backup_info_card_assets": "Elemente", - "backup_manual_cancelled": "Cancelled", - "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", - "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", + "backup_manual_cancelled": "Abgebrochen", + "backup_manual_failed": "Fehlgeschlagen", + "backup_manual_in_progress": "Sicherung läuft bereits. Bitte später erneut versuchen", + "backup_manual_success": "Erfolgreich", + "backup_manual_title": "Sicherungsstatus", + "cache_settings_album_thumbnails": "Vorschaubilder der Bibliothek ({} Elemente)", "cache_settings_clear_cache_button": "Zwischenspeicher löschen", "cache_settings_clear_cache_button_title": "Löscht den Zwischenspeicher der App. Dies wird die Leistungsfähigkeit der App deutlich einschränken, bis der Zwischenspeicher wieder aufgebaut wurde.", "cache_settings_image_cache_size": "{} Bilder im Zwischenspeicher", - "cache_settings_statistics_album": "Library thumbnails", - "cache_settings_statistics_assets": "{} assets ({})", - "cache_settings_statistics_full": "Full images", - "cache_settings_statistics_shared": "Shared album thumbnails", + "cache_settings_statistics_album": "Vorschaubilder der Bibliothek", + "cache_settings_statistics_assets": "{} Elemente ({})", + "cache_settings_statistics_full": "Originalbilder", + "cache_settings_statistics_shared": "Vorschaubilder geteilter Alben", "cache_settings_statistics_thumbnail": "Vorschaubilder", "cache_settings_statistics_title": "Zwischenspeicher Nutzung", "cache_settings_subtitle": "Kontrolliere wie Immich den Zwischenspeicher nutzen soll", "cache_settings_thumbnail_size": "{} Vorschaubilder im Zwischenspeicher", + "cache_settings_tile_subtitle": "Lokalen Speicher verwalten", + "cache_settings_tile_title": "Lokaler Speicher", "cache_settings_title": "Zwischenspeicher Einstellungen", "change_password_form_confirm_password": "Passwort bestätigen", "change_password_form_description": "Hallo {firstName} {lastName}\n\nDas ist entweder das erste Mal dass du dich einloggst oder eine Anfrage zur Änderung deines Passwortes wurde gestellt. Bitte gebe das neue Passwort ein.", @@ -119,21 +125,21 @@ "change_password_form_reenter_new_password": "Passwort erneut eingeben", "common_add_to_album": "Zu Album hinzufügen", "common_change_password": "Passwort ändern", - "common_create_new_album": "Erstelle ein neues Album", + "common_create_new_album": "Neues Album erstellen", "common_server_error": "Bitte überprüfe Deine Netzwerkverbindung und stelle sicher, dass die App und Server Versionen kompatibel sind.", "common_shared": "Geteilt", "control_bottom_app_bar_add_to_album": "Zu Album hinzufügen", "control_bottom_app_bar_album_info": "{} Elemente", - "control_bottom_app_bar_album_info_shared": "{} Elemente · geteilt", + "control_bottom_app_bar_album_info_shared": "{} Elemente · Geteilt", "control_bottom_app_bar_archive": "Archiv", "control_bottom_app_bar_create_new_album": "Neues Album erstellen", "control_bottom_app_bar_delete": "Löschen", "control_bottom_app_bar_favorite": "Favorit", "control_bottom_app_bar_share": "Teilen", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Teilen mit", + "control_bottom_app_bar_stack": "Stapeln", "control_bottom_app_bar_unarchive": "Dearchivieren", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Hochladen", "create_album_page_untitled": "Unbenannt", "create_shared_album_page_create": "Erstellen", "create_shared_album_page_share": "Teilen", @@ -147,16 +153,16 @@ "delete_dialog_alert": "Diese Elemente werden unwiderruflich von Immich und dem Gerät entfernt", "delete_dialog_cancel": "Abbrechen", "delete_dialog_ok": "Löschen", - "delete_dialog_title": "Für immer löschen", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_dialog_title": "Endgültig löschen", + "delete_shared_link_dialog_content": "Bist du sicher, dass du diesen geteilten Link löschen möchtest?", + "delete_shared_link_dialog_title": "Geteilten Link löschen", "description_input_hint_text": "Beschreibung hinzufügen...", "description_input_submit_error": "Beschreibung konnte nicht geändert werden, bitte im Log für mehr Details nachsehen.", "exif_bottom_sheet_description": "Beschreibung hinzufügen...", "exif_bottom_sheet_details": "DETAILS", "exif_bottom_sheet_location": "STANDORT", "experimental_settings_new_asset_list_subtitle": "In Arbeit", - "experimental_settings_new_asset_list_title": "Experimentelle Fotogitter aktivieren", + "experimental_settings_new_asset_list_title": "Experimentelles Fotogitter aktivieren", "experimental_settings_subtitle": "Benutzung auf eigene Gefahr!", "experimental_settings_title": "Experimentell", "favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden", @@ -168,35 +174,35 @@ "home_page_building_timeline": "Zeitachse wird erstellt.", "home_page_favorite_err_local": "Kann lokale Elemente noch nicht favorisieren, überspringe", "home_page_first_time_notice": "Wenn dies das erste Mal ist dass Du Immich nutzt, stelle bitte sicher, dass mindestens ein Album zur Sicherung ausgewählt ist, sodass die Zeitachse mit Fotos und Videos gefüllt werden kann.", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "home_page_upload_err_limit": "Max. 30 Elemente können gleichzeitig hochgeladen werden, überspringe", "image_viewer_page_state_provider_download_error": "Fehler beim Herunterladen", "image_viewer_page_state_provider_download_success": "Erfolgreich heruntergeladen", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Fehler beim Teilen", "library_page_albums": "Alben", "library_page_archive": "Archiv", - "library_page_device_albums": "Alben auf dem Gerät.", + "library_page_device_albums": "Alben auf dem Gerät", "library_page_favorites": "Favoriten", "library_page_new_album": "Neues Album", "library_page_sharing": "Teilen", "library_page_sort_created": "Zuletzt erstellt", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", - "library_page_sort_title": "Albumtitel", - "login_disabled": "Login has been disabled", + "library_page_sort_last_modified": "Zuletzt bearbeitet", + "library_page_sort_most_recent_photo": "Neuestes Foto", + "library_page_sort_title": "Titel des Albums", + "login_disabled": "Login ist deaktiviert", "login_form_api_exception": "API Fehler. Bitte die Serveradresse überprüfen und erneut versuchen.", "login_form_button_text": "Anmelden", "login_form_email_hint": "deine@email.de", "login_form_endpoint_hint": "http://deine-server-ip:port/api", - "login_form_endpoint_url": "Server URL", + "login_form_endpoint_url": "Server-URL", "login_form_err_http": "Bitte gebe http:// oder https:// an", "login_form_err_invalid_email": "Ungültige E-Mail", "login_form_err_invalid_url": "Ungültige URL", - "login_form_err_leading_whitespace": "Leerzichen am Anfang", + "login_form_err_leading_whitespace": "Leerzeichen am Anfang", "login_form_err_trailing_whitespace": "Leerzeichen am Ende", "login_form_failed_get_oauth_server_config": "Fehler beim Login per OAuth, Server-URL überprüfen", "login_form_failed_get_oauth_server_disable": "OAuth-Funktion nicht verfügbar auf diesem Server.", - "login_form_failed_login": "Error logging you in, check server url, email and password", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_failed_login": "Fehler beim Login, prüfe Server-URL, E-Mail und Passwort", + "login_form_handshake_exception": "Fehler beim Verbindungsaufbau mit dem Server. Falls du ein selbstsigniertes Zertifikat verwendest, aktiviere die Unterstützung in den Einstellungen.", "login_form_label_email": "E-Mail", "login_form_label_password": "Passwort", "login_form_next_button": "Weiter", @@ -204,26 +210,26 @@ "login_form_save_login": "Angemeldet bleiben", "login_form_server_empty": "Serveradresse eingeben.", "login_form_server_error": "Konnte nicht mit Server verbinden.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_no_assets_in_bounds": "No photos in this area", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_only_relative_range": "Date range", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_zoom_to_see_photos": "Zoom out to see photos", + "login_password_changed_error": "Fehler beim Passwort ändern", + "login_password_changed_success": "Passwort erfolgreich geändert", + "map_cannot_get_user_location": "Standort konnte nicht ermittelt werden", + "map_location_dialog_cancel": "Abbrechen", + "map_location_dialog_yes": "Ja", + "map_location_service_disabled_content": "Ortungsdienste müssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste aktivieren?", + "map_location_service_disabled_title": "Ortungsdienste deaktiviert", + "map_no_assets_in_bounds": "Keine Fotos in dieser Gegend", + "map_no_location_permission_content": "Ortungsdienste müssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste aktivieren?", + "map_no_location_permission_title": "Kein Zugriff auf den Standort", + "map_settings_dark_mode": "Dunkler Modus", + "map_settings_dialog_cancel": "Abbrechen", + "map_settings_dialog_save": "Speichern", + "map_settings_dialog_title": "Karteneinstellungen", + "map_settings_include_show_archived": "Archivierte anzeigen", + "map_settings_only_relative_range": "Datumsbereich", + "map_settings_only_show_favorites": "Nur Favoriten anzeigen", + "map_zoom_to_see_photos": "Ansicht verkleinern um Fotos zu sehen", "monthly_title_text_date_format": "MMMM y", - "motion_photos_page_title": "Live Photos", + "motion_photos_page_title": "Live-Fotos", "notification_permission_dialog_cancel": "Abbrechen", "notification_permission_dialog_content": "Um Benachrichtigungen zu aktivieren, navigiere zu Einstellungen und klicke \"Erlauben\"", "notification_permission_dialog_settings": "Einstellungen", @@ -231,33 +237,35 @@ "notification_permission_list_tile_enable_button": "Aktiviere Benachrichtigungen", "notification_permission_list_tile_title": "Benachrichtigungs-Berechtigung", "partner_page_add_partner": "Partner hinzufügen", - "partner_page_empty_message": "Your photos are not yet shared with any partner.", - "partner_page_no_more_users": "No more users to add", - "partner_page_partner_add_failed": "Failed to add partner", + "partner_page_empty_message": "Deine Fotos sind noch nicht geteilt mit einem Partner", + "partner_page_no_more_users": "Keine weiteren Nutzer", + "partner_page_partner_add_failed": "Fehler beim Partner hinzufügen", "partner_page_select_partner": "Partner auswählen", "partner_page_shared_to_title": "Geteilt mit", "partner_page_stop_sharing_content": "{} wird nicht mehr auf deine Fotos zugreifen können.", - "partner_page_stop_sharing_title": "Stop sharing your photos?", + "partner_page_stop_sharing_title": "Deine Fotos nicht mehr teilen?", "partner_page_title": "Partner", "permission_onboarding_continue_anyway": "Trotzdem fortfahren", - "permission_onboarding_get_started": "Get started", + "permission_onboarding_get_started": "Jetzt starten", "permission_onboarding_go_to_settings": "Gehe zu Einstellungen", "permission_onboarding_grant_permission": "Berechtigung erteilen", "permission_onboarding_log_out": "Abmelden", - "permission_onboarding_permission_denied": "Berechtigungen verweigert. Um Immich zu benutzen, Zugriff auf Fotos und Videos in Einstellungen erlauben.", + "permission_onboarding_permission_denied": "Berechtigung verweigert. Um Immich zu benutzen, muss Zugriff auf Fotos und Videos in Einstellungen erlaubt werden.", "permission_onboarding_permission_granted": "Berechtigung erteilt! Du bist startklar.", "permission_onboarding_permission_limited": "Berechtigungen unzureichend. Um Immich das Sichern von ganzen Sammlungen zu ermöglichen, muss der Zugriff auf alle Fotos und Videos in den Einstellungen erlaubt werden.", "permission_onboarding_request": "Immich benötigt Berechtigung um auf deine Fotos und Videos zuzugreifen.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "App und Server sind aktuell", + "profile_drawer_documentation": "Dokumentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Einstellungen", "profile_drawer_sign_out": "Abmelden", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Papierkorb", "recently_added_page_title": "Zuletzt hinzugefügt", "search_bar_hint": "Durchsuche deine Fotos", "search_page_categories": "Kategorien", "search_page_favorites": "Favoriten", - "search_page_motion_photos": "Live Photos", + "search_page_motion_photos": "Live-Fotos", "search_page_no_objects": "Keine Objektinformationen verfügbar", "search_page_no_places": "Keine Informationen über Orte verfügbar", "search_page_people": "Personen", @@ -265,7 +273,7 @@ "search_page_recently_added": "Zuletzt hinzugefügt", "search_page_screenshots": "Bildschirmfotos", "search_page_selfies": "Selfies", - "search_page_things": "Dinge", + "search_page_things": "Gegenstände und Tiere", "search_page_videos": "Videos", "search_page_view_all_button": "Alle anzeigen", "search_page_your_activity": "Deine Aktivität", @@ -274,55 +282,65 @@ "search_suggestion_list_smart_search_hint_2": "m:dein-suchbegriff", "select_additional_user_for_sharing_page_suggestions": "Vorschläge", "select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden", - "select_user_for_sharing_page_share_suggestions": "Suggestions", + "select_user_for_sharing_page_share_suggestions": "Empfehlungen", "server_info_box_app_version": "App Version", + "server_info_box_server_url": "Server-URL", "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "Der Detailbildbetrachter lädt zuerst die kleine Miniaturansicht, dann die Vorschau in mittlerer Größe (falls aktiviert) und schließlich das Original (falls aktiviert).", "setting_image_viewer_original_subtitle": "Aktivieren, um das Originalbild in voller Auflösung (groß!) zu laden. Deaktivieren, um den Datenverbrauch zu reduzieren (sowohl im Netzwerk als auch im Gerätespeicher).", "setting_image_viewer_original_title": "Original laden", "setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.", "setting_image_viewer_preview_title": "Vorschaubild laden", - "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}", + "setting_notifications_notify_failures_grace_period": "Benachrichtigung über Fehler bei der Hintergrundsicherung: {}", "setting_notifications_notify_hours": "{} Stunden", "setting_notifications_notify_immediately": "sofort", "setting_notifications_notify_minutes": "{} Minuten", "setting_notifications_notify_never": "niemals", "setting_notifications_notify_seconds": "{} Sekunden", - "setting_notifications_single_progress_subtitle": "Detaillierte Upload Informationen für jedes Element.", - "setting_notifications_single_progress_title": "Zeige Hintergrund-Sicherungs Detailfortschritt", - "setting_notifications_subtitle": "Passe Deine Benachrichtigungen an", + "setting_notifications_single_progress_subtitle": "Detaillierter Upload-Fortschritt für jedes Element.", + "setting_notifications_single_progress_title": "Zeige detaillierten Fortschritt bei der Hintergrundsicherung", + "setting_notifications_subtitle": "Benachrichtigungen anpassen", "setting_notifications_title": "Benachrichtigungen", "setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)", - "setting_notifications_total_progress_title": "Zeige Hintergrundsicherungsfortschritt", + "setting_notifications_total_progress_title": "Zeige Gesamtfortschritt bei der Hintergrundsicherung", "setting_pages_app_bar_settings": "Einstellungen", "settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.", "share_add": "Hinzufügen", "share_add_photos": "Fotos hinzufügen", "share_add_title": "Titel hinzufügen", "share_create_album": "Album erstellen", + "shared_album_activities_input_disable": "Kommentare sind deaktiviert.", + "shared_album_activities_input_hint": "Sag etwas", + "shared_album_activity_remove_content": "Möchtest du diese Aktivität entfernen?", + "shared_album_activity_remove_title": "Aktivität entfernen", + "shared_album_activity_setting_subtitle": "Lass andere reagieren.", + "shared_album_activity_setting_title": "Kommentare & Likes", "share_dialog_preparing": "Vorbereiten...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Geteilte Links", + "shared_link_create_app_bar_title": "Link zum Teilen erstellen", + "shared_link_create_info": "Alle, die über den Link verfügen, können die Fotos sehen", + "shared_link_create_submit_button": "Link erstellen", + "shared_link_edit_allow_download": "Jeder darf herunterladen", + "shared_link_edit_allow_upload": "Jeder darf hochladen", + "shared_link_edit_app_bar_title": "Link bearbeiten", + "shared_link_edit_change_expiry": "Ablaufdatum bearbeiten", + "shared_link_edit_description": "Beschreibung", + "shared_link_edit_description_hint": "Beschreibung eingeben", + "shared_link_edit_expire_after": "Erlischt nach", + "shared_link_edit_password": "Passwort", + "shared_link_edit_password_hint": "Passwort eingeben", + "shared_link_edit_show_meta": "Metadaten anzeigen", + "shared_link_edit_submit_button": "Link aktualisieren", + "shared_link_empty": "Du hast keine geteilten Links", + "shared_link_manage_links": "Geteilte Links verwalten", + "share_done": "Fertig", "share_invite": "Zum Album einladen", "sharing_page_album": "Geteilte Alben", "sharing_page_description": "Erstelle ein geteiltes Album um Fotos und Videos mit Personen in deinem Netzwerk zu teilen.", "sharing_page_empty_list": "LEERE LISTE", "sharing_silver_appbar_create_shared_album": "Neues geteiltes Album", - "sharing_silver_appbar_shared_links": "Shared links", - "sharing_silver_appbar_share_partner": "Teile mit Partner", + "sharing_silver_appbar_shared_links": "Geteilte Links", + "sharing_silver_appbar_share_partner": "Mit Partner teilen", "tab_controller_nav_library": "Bibliothek", "tab_controller_nav_photos": "Fotos", "tab_controller_nav_search": "Suche", @@ -337,30 +355,30 @@ "theme_setting_theme_title": "Theme", "theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhöht allerdings den Datenverbrauch deutlich", "theme_setting_three_stage_loading_title": "Dreistufiges Laden aktivieren", - "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "translated_text_options": "Optionen", + "trash_page_delete": "Löschen", + "trash_page_delete_all": "Alle löschen", + "trash_page_empty_trash_btn": "Papierkorb leeren", + "trash_page_empty_trash_dialog_content": "Elemente im Papierkorb löschen? Diese Elemente werden dauerhaft von Immich entfernt", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", - "upload_dialog_cancel": "Cancel", - "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_ok": "Upload", - "upload_dialog_title": "Upload Asset", + "trash_page_info": "Elemente im Papierkorb werden nach {} Tagen endgültig gelöscht", + "trash_page_no_assets": "Keine Elemente im Papierkorb", + "trash_page_restore": "Wiederherstellen", + "trash_page_restore_all": "Alle wiederherstellen", + "trash_page_select_assets_btn": "Elemente auswählen", + "trash_page_select_btn": "Auswählen", + "trash_page_title": "Papierkorb ({})", + "upload_dialog_cancel": "Abbrechen", + "upload_dialog_info": "Willst du die ausgewählten Elemente auf dem Server sichern?", + "upload_dialog_ok": "Hochladen", + "upload_dialog_title": "Element hochladen", "version_announcement_overlay_ack": "Ich habe verstanden", "version_announcement_overlay_release_notes": "Änderungsprotokoll", "version_announcement_overlay_text_1": "Hallo mein Freund! Es gibt eine neue Version von", - "version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ", + "version_announcement_overlay_text_2": "Bitte nehme dir die Zeit und lies das ", "version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).", "version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Aus Stapel entfernen", + "viewer_stack_use_as_main_asset": "An Stapelanfang", + "viewer_unstack": "Stapel aufheben" } \ No newline at end of file diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 5a176ab96..672ab28fb 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -27,6 +27,9 @@ "album_viewer_page_share_add_users": "Add users", "all_people_page_title": "People", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -112,9 +115,11 @@ "cache_settings_statistics_title": "Cache usage", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "change_password_form_confirm_password": "Confirm Password", - "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", + "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_new_password": "New Password", "change_password_form_password_mismatch": "Passwords do not match", "change_password_form_reenter_new_password": "Re-enter New Password", @@ -251,11 +256,11 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Sign Out", "profile_drawer_trash": "Trash", - "profile_drawer_documentation": "Documentation", - "profile_drawer_github": "GitHub", "recently_added_page_title": "Recently Added", "search_bar_hint": "Search your photos", "search_page_categories": "Categories", @@ -279,8 +284,8 @@ "select_user_for_sharing_page_err_album": "Failed to create album", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "App Version", - "server_info_box_server_version": "Server Version", "server_info_box_server_url": "Server URL", + "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", "setting_image_viewer_original_title": "Load original image", @@ -304,6 +309,12 @@ "share_add_photos": "Add photos", "share_add_title": "Add a title", "share_create_album": "Create album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparing...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -315,8 +326,8 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_password": "Password", "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", @@ -368,17 +379,6 @@ "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", "viewer_remove_from_stack": "Remove from Stack", - "viewer_unstack": "Un-Stack", - "cache_settings_tile_title": "Local Storage", - "cache_settings_tile_subtitle": "Control the local storage behaviour", "viewer_stack_use_as_main_asset": "Use as Main Asset", - "app_bar_signout_dialog_title": "Sign out", - "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", - "app_bar_signout_dialog_ok": "Yes", - "shared_album_activities_input_hint": "Say something", - "shared_album_activity_remove_title": "Delete Activity", - "shared_album_activity_remove_content": "Do you want to delete this activity?", - "shared_album_activity_setting_title": "Comments & likes", - "shared_album_activity_setting_subtitle": "Let others respond", - "shared_album_activities_input_disable": "Comment is disabled" -} + "viewer_unstack": "Un-Stack" +} \ No newline at end of file diff --git a/mobile/assets/i18n/es-ES.json b/mobile/assets/i18n/es-ES.json index 7e68fe759..fcaa67174 100644 --- a/mobile/assets/i18n/es-ES.json +++ b/mobile/assets/i18n/es-ES.json @@ -3,8 +3,8 @@ "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", "advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas de recursos encontrados el dispositivo. Activa esta opción para cargar imágenes remotas en su lugar.", "advanced_settings_prefer_remote_title": "Preferir imágenes remotas", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", + "advanced_settings_self_signed_ssl_subtitle": "Omitir verificación del certificado SSL del servidor. Requerido para certificados autofirmados", + "advanced_settings_self_signed_ssl_title": "Permitir certificados autofirmados", "advanced_settings_tile_subtitle": "Configuraciones avanzadas del usuario", "advanced_settings_tile_title": "Avanzado", "advanced_settings_troubleshooting_subtitle": "Habilitar funciones adicionales para solución de problemas", @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Error al cambiar el título del álbum ", "album_viewer_appbar_share_leave": "Abandonar álbum ", "album_viewer_appbar_share_remove": "Eliminar del álbum ", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Agregar usuarios", "all_people_page_title": "Personas", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "¿Estás seguro que quieres cerrar sesión?", + "app_bar_signout_dialog_ok": "Sí", + "app_bar_signout_dialog_title": "Cerrar sesión", "archive_page_no_archived_assets": "No se encontraron recursos archivados", "archive_page_title": "Archivo ({})", "asset_list_layout_settings_dynamic_layout_title": "Diseño dinámico", @@ -59,7 +63,7 @@ "backup_controller_page_background_battery_info_title": "Optimizaciones de batería", "backup_controller_page_background_charging": "Solo mientras se carga", "backup_controller_page_background_configure_error": "Error al configurar el servicio en segundo plano", - "backup_controller_page_background_delay": "Retraso en la copia de seguridad de nuevos activos: {}", + "backup_controller_page_background_delay": "Retraso en la copia de seguridad de nuevos elementos: {}", "backup_controller_page_background_description": "Activa el servicio en segundo plano para copiar automáticamente cualquier nuevos archivos sin necesidad de abrir la aplicación.", "backup_controller_page_background_is_off": "La copia de seguridad en segundo plano automática está desactivada", "backup_controller_page_background_is_on": "La copia de seguridad en segundo plano automática está activada", @@ -71,7 +75,7 @@ "backup_controller_page_backup_sub": "Fotos y videos respaldados", "backup_controller_page_cancel": "Cancelar", "backup_controller_page_created": "Creado el: {}", - "backup_controller_page_desc_backup": "Active la copia de seguridad para cargar automáticamente los nuevos activos al servidor.", + "backup_controller_page_desc_backup": "Active la copia de seguridad para cargar automáticamente los nuevos elementos al servidor.", "backup_controller_page_excluded": "Excluido:", "backup_controller_page_failed": "Fallidos ({})", "backup_controller_page_filename": "Nombre del archivo: {} [{}]", @@ -94,11 +98,11 @@ "backup_controller_page_uploading_file_info": "Cargando información del archivo", "backup_err_only_album": "No se puede eliminar el único álbum", "backup_info_card_assets": "archivos", - "backup_manual_cancelled": "Cancelled", - "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", + "backup_manual_cancelled": "Cancelado", + "backup_manual_failed": "Fallido", + "backup_manual_in_progress": "Subida en progreso. Espere", + "backup_manual_success": "Éxito", + "backup_manual_title": "Estado de la subida", "cache_settings_album_thumbnails": "Miniaturas de la página de la biblioteca ({} archivos)", "cache_settings_clear_cache_button": "Borrar caché", "cache_settings_clear_cache_button_title": "Borra la caché de la aplicación. Esto afectará significativamente el rendimiento de la aplicación hasta que se reconstruya la caché.", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Uso de caché", "cache_settings_subtitle": "Controla el comportamiento del almacenamiento en caché de la aplicación móvil Immich", "cache_settings_thumbnail_size": "Tamaño de la caché de miniaturas ({} archivos)", + "cache_settings_tile_subtitle": "Controla el comportamiento del almacenamiento local", + "cache_settings_tile_title": "Almacenamiento local", "cache_settings_title": "Configuración de la caché", "change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Eliminar", "control_bottom_app_bar_favorite": "Favorito", "control_bottom_app_bar_share": "Compartir", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Enviar", + "control_bottom_app_bar_stack": "Apilar", "control_bottom_app_bar_unarchive": "Desarchivar", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Subir", "create_album_page_untitled": "Sin título", "create_shared_album_page_create": "Crear", "create_shared_album_page_share": "Compartir", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Cancelar", "delete_dialog_ok": "Eliminar", "delete_dialog_title": "Eliminar Permanentemente", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Estás seguro que quieres eliminar este enlace compartido", + "delete_shared_link_dialog_title": "Eliminar enlace compartido", "description_input_hint_text": "Agregar descripción...", "description_input_submit_error": "Error al actualizar la descripción, verifica el registro para obtener más detalles", "exif_bottom_sheet_description": "Agregar Descripción...", @@ -168,10 +174,10 @@ "home_page_building_timeline": "Construyendo la línea de tiempo", "home_page_favorite_err_local": "Aún no se pueden archivar recursos locales, omitiendo", "home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo", "image_viewer_page_state_provider_download_error": "Error de descarga", "image_viewer_page_state_provider_download_success": "Descarga exitosa", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Error al compartir", "library_page_albums": "Álbumes", "library_page_archive": "Archivo", "library_page_device_albums": "Álbumes en el dispositivo", @@ -179,10 +185,10 @@ "library_page_new_album": "Nuevo álbum", "library_page_sharing": "Compartiendo", "library_page_sort_created": "Creado más recientemente", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Última modificación", + "library_page_sort_most_recent_photo": "Foto más reciente", "library_page_sort_title": "Título del álbum", - "login_disabled": "Login has been disabled", + "login_disabled": "El inicio de sesión ha sido desactivado", "login_form_api_exception": "Excepción producida por API. Por favor, verifica el URL del servidor e inténtalo de nuevo.", "login_form_button_text": "Iniciar Sesión", "login_form_email_hint": "tucorreo@correo.com", @@ -196,7 +202,7 @@ "login_form_failed_get_oauth_server_config": "Error al iniciar sesión con OAuth, verifica la URL del servidor", "login_form_failed_get_oauth_server_disable": "La función de OAuth no está disponible en este servidor", "login_form_failed_login": "Error al iniciar sesión, comprueba la URL del servidor, el correo electrónico y la contraseña", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_handshake_exception": "Hubo un error de verificación del certificado del servidor. Activa el soporte para certificados autofirmados en las preferencias si estás usando un certificado autofirmado", "login_form_label_email": "Correo", "login_form_label_password": "Contraseña", "login_form_next_button": "Siguiente", @@ -204,24 +210,24 @@ "login_form_save_login": "Mantener la sesión iniciada", "login_form_server_empty": "Agrega la URL del servidor.", "login_form_server_error": "No se pudo conectar al servidor.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_no_assets_in_bounds": "No photos in this area", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_only_relative_range": "Date range", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_zoom_to_see_photos": "Zoom out to see photos", + "login_password_changed_error": "Hubo un error actualizando la contraseña", + "login_password_changed_success": "Contraseña cambiado con éxito", + "map_cannot_get_user_location": "No se pudo obtener la posición del usuario", + "map_location_dialog_cancel": "Cancelar", + "map_location_dialog_yes": "Sí", + "map_location_service_disabled_content": "Los servicios de ubicación deben estar activados para mostrar elementos de tu ubicación actual. Deseas activarlos ahora?", + "map_location_service_disabled_title": "Servicios de ubicación desactivados", + "map_no_assets_in_bounds": "No hay fotos en esta zona", + "map_no_location_permission_content": "Se necesitan permisos de ubicación para mostrar elementos de tu ubicación actual. Deseas activarlos ahora?", + "map_no_location_permission_title": "Permisos de ubicación denegados", + "map_settings_dark_mode": "Modo oscuro", + "map_settings_dialog_cancel": "Cancelar", + "map_settings_dialog_save": "Guardar", + "map_settings_dialog_title": "Ajustes mapa", + "map_settings_include_show_archived": "Incluir archivados", + "map_settings_only_relative_range": "Rango de fechas", + "map_settings_only_show_favorites": "Mostrar solo favoritas", + "map_zoom_to_see_photos": "Alejar para ver fotos", "monthly_title_text_date_format": "MMMM y", "motion_photos_page_title": "Foto en Movimiento", "notification_permission_dialog_cancel": "Cancelar", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.", "profile_drawer_app_logs": "Registros", "profile_drawer_client_server_up_to_date": "El Cliente y el Servidor están actualizados", + "profile_drawer_documentation": "Documentación", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Configuración", "profile_drawer_sign_out": "Cerrar Sesión", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Papelera", "recently_added_page_title": "Recién Agregadas", "search_bar_hint": "Busca tus fotos", "search_page_categories": "Categorías", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Fallo al crear el álbum", "select_user_for_sharing_page_share_suggestions": "Sugerencias", "server_info_box_app_version": "Versión de la Aplicación", + "server_info_box_server_url": "URL del servidor", "server_info_box_server_version": "Versión del Servidor", "setting_image_viewer_help": "El visor de detalles carga primero la miniatura pequeña, luego carga la vista previa de tamaño mediano (si está habilitada), finalmente carga la original (si está habilitada).", "setting_image_viewer_original_subtitle": "Activar para cargar la imagen en resolución original (¡muy grande!). Deshabilitar para reducir el consumo de datos (de red y caché).", @@ -300,35 +309,44 @@ "share_add_photos": "Agregar fotos", "share_add_title": "Agregar un título", "share_create_album": "Crear álbum", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparando...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Enlaces compartidos", + "shared_link_create_app_bar_title": "Crear enlace compartido", + "shared_link_create_info": "Cualquier persona con el enlace puede ver las fotos seleccionadas", + "shared_link_create_submit_button": "Crear enlace", + "shared_link_edit_allow_download": "Permitir descargar a usuarios públicos", + "shared_link_edit_allow_upload": "Permitir subir a usuarios públicos", + "shared_link_edit_app_bar_title": "Editar enlace", + "shared_link_edit_change_expiry": "Cambiar fecha de caducidad", + "shared_link_edit_description": "Descripción", + "shared_link_edit_description_hint": "Introduce la descripción del enlace", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Contraseña", + "shared_link_edit_password_hint": "Introduce la contraseña del enlace", + "shared_link_edit_show_meta": "Mostrar metadatos", + "shared_link_edit_submit_button": "Actualizar enlace", + "shared_link_empty": "No tienes enlaces compartidos", + "shared_link_manage_links": "Administrar enlaces compartidos", + "share_done": "Hecho", "share_invite": "Invitar al álbum", "sharing_page_album": "Álbumes compartidos", "sharing_page_description": "Crea álbumes compartidos para compartir fotos y vídeos con las personas de tu red.", "sharing_page_empty_list": "LISTA VACIA", "sharing_silver_appbar_create_shared_album": "Crear un álbum compartido", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Enlaces compartidos", "sharing_silver_appbar_share_partner": "Compartir con el compañero", "tab_controller_nav_library": "Biblioteca", "tab_controller_nav_photos": "Fotos", "tab_controller_nav_search": "Buscar", "tab_controller_nav_sharing": "Compartiendo", "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamiento en las miniaturas de los archivos", - "theme_setting_asset_list_tiles_per_row_title": "Número de activos por fila ({})", + "theme_setting_asset_list_tiles_per_row_title": "Número de elementos por fila ({})", "theme_setting_dark_mode_switch": "Modo oscuro", "theme_setting_image_viewer_quality_subtitle": "Ajustar la calidad del visor de detalles de imágenes", "theme_setting_image_viewer_quality_title": "Calidad del visor de imágenes", @@ -337,30 +355,30 @@ "theme_setting_theme_title": "Tema", "theme_setting_three_stage_loading_subtitle": "La carga en tres etapas puede aumentar el rendimiento de carga pero provoca un consumo de red significativamente mayor", "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", - "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", - "upload_dialog_cancel": "Cancel", - "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_ok": "Upload", - "upload_dialog_title": "Upload Asset", + "translated_text_options": "Opciones", + "trash_page_delete": "Eliminar", + "trash_page_delete_all": "Eliminar todos", + "trash_page_empty_trash_btn": "Vaciar papelera", + "trash_page_empty_trash_dialog_content": "Estás seguro que quieres eliminar los elementos? Estos elementos serán eliminados de Immich permanentemente", + "trash_page_empty_trash_dialog_ok": "Sí", + "trash_page_info": "Los archivos en la papelera serán eliminados automáticamente después de {} días", + "trash_page_no_assets": "No hay elementos en la papelera", + "trash_page_restore": "Restaurar", + "trash_page_restore_all": "Restaurar todos", + "trash_page_select_assets_btn": "Seleccionar elementos", + "trash_page_select_btn": "Seleccionar", + "trash_page_title": "Papelera ({})", + "upload_dialog_cancel": "Cancelar", + "upload_dialog_info": "Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?", + "upload_dialog_ok": "Subir", + "upload_dialog_title": "Subir elementos", "version_announcement_overlay_ack": "Aceptar", "version_announcement_overlay_release_notes": "notas de versión", "version_announcement_overlay_text_1": "Hola amigo, hay una nueva versión de", "version_announcement_overlay_text_2": "por favor, tómate tu tiempo para visitar las ", "version_announcement_overlay_text_3": " y asegúrate de que la configuración de docker-compose y .env estén actualizadas para evitar cualquier error de configuración, especialmente si utilizas WatchTower o cualquier mecanismo que actualice automáticamente la aplicación del servidor.", "version_announcement_overlay_title": "Nueva versión del servidor disponible \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Quitar de la pila", + "viewer_stack_use_as_main_asset": "Usar como elemento principal", + "viewer_unstack": "Desapilar" } \ No newline at end of file diff --git a/mobile/assets/i18n/es-MX.json b/mobile/assets/i18n/es-MX.json index 55299a262..cb908c55d 100644 --- a/mobile/assets/i18n/es-MX.json +++ b/mobile/assets/i18n/es-MX.json @@ -3,8 +3,8 @@ "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", "advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas de recursos encontrados el dispositivo. Activa esta opción para cargar imágenes remotas en su lugar.", "advanced_settings_prefer_remote_title": "Preferir imágenes remotas", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", + "advanced_settings_self_signed_ssl_subtitle": "Omitir verificación del certificado SSL del servidor. Requerido para certificados autofirmados", + "advanced_settings_self_signed_ssl_title": "Permitir certificados autofirmados", "advanced_settings_tile_subtitle": "Configuraciones avanzadas del usuario", "advanced_settings_tile_title": "Avanzado", "advanced_settings_troubleshooting_subtitle": "Habilitar funciones adicionales para solución de problemas", @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Error al cambiar el título del álbum", "album_viewer_appbar_share_leave": "Abandonar álbum ", "album_viewer_appbar_share_remove": "Eliminar del álbum", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Agregar usuarios", "all_people_page_title": "Personas", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "¿Estás seguro que quieres cerrar sesión?", + "app_bar_signout_dialog_ok": "Sí", + "app_bar_signout_dialog_title": "Cerrar sesión", "archive_page_no_archived_assets": "No se encontraron recursos archivados", "archive_page_title": "Archivo ({})", "asset_list_layout_settings_dynamic_layout_title": "Diseño dinámico", @@ -59,7 +63,7 @@ "backup_controller_page_background_battery_info_title": "Optimizaciones de batería", "backup_controller_page_background_charging": "Solo mientras se carga", "backup_controller_page_background_configure_error": "Error al configurar el servicio en segundo plano", - "backup_controller_page_background_delay": "Retraso en la copia de seguridad de nuevos activos: {}", + "backup_controller_page_background_delay": "Retraso en la copia de seguridad de nuevos elementos: {}", "backup_controller_page_background_description": "Activa el servicio en segundo plano para copiar automáticamente cualquier nuevos archivos sin necesidad de abrir la aplicación.", "backup_controller_page_background_is_off": "La copia de seguridad en segundo plano automática está desactivada", "backup_controller_page_background_is_on": "La copia de seguridad en segundo plano automática está desactivada", @@ -94,11 +98,11 @@ "backup_controller_page_uploading_file_info": "Cargando información del archivo", "backup_err_only_album": "No se puede eliminar el único álbum", "backup_info_card_assets": "archivos", - "backup_manual_cancelled": "Cancelled", - "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", + "backup_manual_cancelled": "Cancelado", + "backup_manual_failed": "Fallido", + "backup_manual_in_progress": "Subida en progreso. Espere", + "backup_manual_success": "Éxito", + "backup_manual_title": "Estado de la subida", "cache_settings_album_thumbnails": "Miniaturas de la página de la biblioteca ({} archivos)", "cache_settings_clear_cache_button": "Borrar caché", "cache_settings_clear_cache_button_title": "Borra la caché de la aplicación. Esto afectará significativamente el rendimiento de la aplicación hasta que se reconstruya la caché.", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Uso de caché", "cache_settings_subtitle": "Controla el comportamiento del almacenamiento en caché de la aplicación móvil Immich", "cache_settings_thumbnail_size": "Tamaño de la caché de miniaturas ({} archivos)", + "cache_settings_tile_subtitle": "Controla el comportamiento del almacenamiento local", + "cache_settings_tile_title": "Almacenamiento local", "cache_settings_title": "Configuración de la caché", "change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Eliminar", "control_bottom_app_bar_favorite": "Favorito", "control_bottom_app_bar_share": "Compartir", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Enviar", + "control_bottom_app_bar_stack": "Apilar", "control_bottom_app_bar_unarchive": "Desarchivar", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Subir", "create_album_page_untitled": "Sin título", "create_shared_album_page_create": "Crear", "create_shared_album_page_share": "Compartir", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Cancelar", "delete_dialog_ok": "Eliminar", "delete_dialog_title": "Eliminar permanentemente", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Estás seguro que quieres eliminar este enlace compartido", + "delete_shared_link_dialog_title": "Eliminar enlace compartido", "description_input_hint_text": "Agregar descripción...", "description_input_submit_error": "Error al actualizar la descripción, verifica el registro para obtener más detalles", "exif_bottom_sheet_description": "Agregar Descripción...", @@ -168,10 +174,10 @@ "home_page_building_timeline": "Construyendo la línea de tiempo", "home_page_favorite_err_local": "Aún no se pueden archivar recursos locales, omitiendo", "home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo", "image_viewer_page_state_provider_download_error": "Error de descarga", "image_viewer_page_state_provider_download_success": "Descarga exitosa", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Error al compartir", "library_page_albums": "Álbumes", "library_page_archive": "Archivo", "library_page_device_albums": "Álbumes en el dispositivo", @@ -179,10 +185,10 @@ "library_page_new_album": "Nuevo álbum", "library_page_sharing": "Compartiendo", "library_page_sort_created": "Creado más recientemente", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Última modificación", + "library_page_sort_most_recent_photo": "Foto más reciente", "library_page_sort_title": "Título del álbum", - "login_disabled": "Login has been disabled", + "login_disabled": "El inicio de sesión ha sido desactivado", "login_form_api_exception": "Excepción producida por API. Por favor, verifica el URL del servidor e inténtalo de nuevo.", "login_form_button_text": "Iniciar sesión", "login_form_email_hint": "tucorreo@correo.com", @@ -196,7 +202,7 @@ "login_form_failed_get_oauth_server_config": "Error al iniciar sesión con OAuth, verifica la URL del servidor", "login_form_failed_get_oauth_server_disable": "La función de OAuth no está disponible en este servidor", "login_form_failed_login": "Error al iniciar sesión, comprueba la URL del servidor, el correo electrónico y la contraseña", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_handshake_exception": "Hubo un error de verificación del certificado del servidor. Activa el soporte para certificados autofirmados en las preferencias si estás usando un certificado autofirmado", "login_form_label_email": "Correo electrónico", "login_form_label_password": "Contraseña", "login_form_next_button": "Siguiente", @@ -204,24 +210,24 @@ "login_form_save_login": "Permanecer conectado", "login_form_server_empty": "Agrega la URL del servidor.", "login_form_server_error": "No se pudo conectar al servidor.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_no_assets_in_bounds": "No photos in this area", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_only_relative_range": "Date range", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_zoom_to_see_photos": "Zoom out to see photos", + "login_password_changed_error": "Hubo un error actualizando la contraseña", + "login_password_changed_success": "Contraseña cambiado con éxito", + "map_cannot_get_user_location": "No se pudo obtener la posición del usuario", + "map_location_dialog_cancel": "Cancelar", + "map_location_dialog_yes": "Sí", + "map_location_service_disabled_content": "Los servicios de localización deben estar activados para mostrar elementos de tu ubicación actual. Deseas activarlos ahora?", + "map_location_service_disabled_title": "Servicios de localización desactivados", + "map_no_assets_in_bounds": "No hay fotos en esta zona", + "map_no_location_permission_content": "Se necesitan permisos de ubicación para mostrar elementos de tu ubicación actual. Deseas activarlos ahora?", + "map_no_location_permission_title": "Permisos de ubicación denegados", + "map_settings_dark_mode": "Modo oscuro", + "map_settings_dialog_cancel": "Cancelar", + "map_settings_dialog_save": "Guardar", + "map_settings_dialog_title": "Ajustes mapa", + "map_settings_include_show_archived": "Incluir archivados", + "map_settings_only_relative_range": "Rango de fechas", + "map_settings_only_show_favorites": "Mostrar solo favoritas", + "map_zoom_to_see_photos": "Alejar para ver fotos", "monthly_title_text_date_format": "MMMM y", "motion_photos_page_title": "Foto en Movimiento", "notification_permission_dialog_cancel": "Cancelar", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.", "profile_drawer_app_logs": "Registros", "profile_drawer_client_server_up_to_date": "El cliente y el servidor están actualizados", + "profile_drawer_documentation": "Documentación", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Configuración", "profile_drawer_sign_out": "Cerrar sesión", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Papelera", "recently_added_page_title": "Recién Agregadas", "search_bar_hint": "Busca tus fotos", "search_page_categories": "Categorías", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Error al crear álbum", "select_user_for_sharing_page_share_suggestions": "Sugerencias", "server_info_box_app_version": "Versión de la Aplicación", + "server_info_box_server_url": "URL del servidor", "server_info_box_server_version": "Versión del Servidor", "setting_image_viewer_help": "El visor de detalles carga primero la miniatura pequeña, luego carga la vista previa de tamaño mediano (si está habilitada), finalmente carga la original (si está habilitada).", "setting_image_viewer_original_subtitle": "Activar para cargar la imagen en resolución original (¡muy grande!). Deshabilitar para reducir el consumo de datos (de red y caché).", @@ -300,35 +309,44 @@ "share_add_photos": "Agregar fotos", "share_add_title": "Agregar un título", "share_create_album": "Crear álbum", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparando...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Enlaces compartidos", + "shared_link_create_app_bar_title": "Crear enlace compartido", + "shared_link_create_info": "Cualquier persona con el enlace puede ver las fotos seleccionadas", + "shared_link_create_submit_button": "Crear enlace", + "shared_link_edit_allow_download": "Permitir descargar a usuarios públicos", + "shared_link_edit_allow_upload": "Permitir subir a usuarios públicos", + "shared_link_edit_app_bar_title": "Editar enlace", + "shared_link_edit_change_expiry": "Cambiar fecha de caducidad", + "shared_link_edit_description": "Descripción", + "shared_link_edit_description_hint": "Introduce la descripción del enlace", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Contraseña", + "shared_link_edit_password_hint": "Introduce la contraseña del enlace", + "shared_link_edit_show_meta": "Mostrar metadatos", + "shared_link_edit_submit_button": "Actualizar enlace", + "shared_link_empty": "No tienes enlaces compartidos", + "shared_link_manage_links": "Administrar enlaces compartidos", + "share_done": "Hecho", "share_invite": "Invitar al álbum", "sharing_page_album": "Álbumes compartidos", "sharing_page_description": "Crea álbumes compartidos para compartir fotos y videos con personas de tu red.", "sharing_page_empty_list": "LISTA VACIA", "sharing_silver_appbar_create_shared_album": "Crear álbum compartido", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Enlaces compartidos", "sharing_silver_appbar_share_partner": "Compartir con compañero", "tab_controller_nav_library": "Biblioteca", "tab_controller_nav_photos": "Fotos", "tab_controller_nav_search": "Buscar", "tab_controller_nav_sharing": "Compartiendo", "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamiento en las miniaturas de los archivos", - "theme_setting_asset_list_tiles_per_row_title": "Número de activos por fila ({})", + "theme_setting_asset_list_tiles_per_row_title": "Número de elementos por fila ({})", "theme_setting_dark_mode_switch": "Modo oscuro", "theme_setting_image_viewer_quality_subtitle": "Ajustar la calidad del visor de detalles de imágenes", "theme_setting_image_viewer_quality_title": "Calidad del visor de imágenes", @@ -337,30 +355,30 @@ "theme_setting_theme_title": "Tema", "theme_setting_three_stage_loading_subtitle": "La carga en tres etapas puede aumentar el rendimiento de carga pero provoca un consumo de red significativamente mayor", "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", - "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", - "upload_dialog_cancel": "Cancel", - "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_ok": "Upload", - "upload_dialog_title": "Upload Asset", + "translated_text_options": "Opciones", + "trash_page_delete": "Eliminar", + "trash_page_delete_all": "Eliminar todos", + "trash_page_empty_trash_btn": "Vaciar papelera", + "trash_page_empty_trash_dialog_content": "Estás seguro que quieres eliminar los elementos? Estos elementos serán eliminados de Immich permanentemente", + "trash_page_empty_trash_dialog_ok": "Sí", + "trash_page_info": "Los archivos en la papelera serán eliminados automáticamente después de {} días", + "trash_page_no_assets": "No hay elementos en la papelera", + "trash_page_restore": "Restaurar", + "trash_page_restore_all": "Restaurar todos", + "trash_page_select_assets_btn": "Seleccionar elementos", + "trash_page_select_btn": "Seleccionar", + "trash_page_title": "Papelera ({})", + "upload_dialog_cancel": "Cancelar", + "upload_dialog_info": "Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?", + "upload_dialog_ok": "Subir", + "upload_dialog_title": "Subir elementos", "version_announcement_overlay_ack": "Aceptar", "version_announcement_overlay_release_notes": "notas de la versión", "version_announcement_overlay_text_1": "Hola, amigo, hay una nueva versión de", "version_announcement_overlay_text_2": "por favor, tómate tu tiempo para visitar las ", "version_announcement_overlay_text_3": " y asegúrate de que la configuración de docker-compose y .env estén actualizadas para evitar cualquier error de configuración, especialmente si utilizas WatchTower o cualquier mecanismo que actualice automáticamente la aplicación del servidor.", "version_announcement_overlay_title": "Nueva versión del servidor disponible \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Quitar de la pila", + "viewer_stack_use_as_main_asset": "Usar como elemento principal", + "viewer_unstack": "Desapilar" } \ No newline at end of file diff --git a/mobile/assets/i18n/es-PE.json b/mobile/assets/i18n/es-PE.json index 55299a262..354014e39 100644 --- a/mobile/assets/i18n/es-PE.json +++ b/mobile/assets/i18n/es-PE.json @@ -3,8 +3,8 @@ "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", "advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas de recursos encontrados el dispositivo. Activa esta opción para cargar imágenes remotas en su lugar.", "advanced_settings_prefer_remote_title": "Preferir imágenes remotas", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", + "advanced_settings_self_signed_ssl_subtitle": "Omitir verificación del certificado SSL del servidor. Requerido para certificados autofirmados", + "advanced_settings_self_signed_ssl_title": "Permitir certificados autofirmados", "advanced_settings_tile_subtitle": "Configuraciones avanzadas del usuario", "advanced_settings_tile_title": "Avanzado", "advanced_settings_troubleshooting_subtitle": "Habilitar funciones adicionales para solución de problemas", @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Error al cambiar el título del álbum", "album_viewer_appbar_share_leave": "Abandonar álbum ", "album_viewer_appbar_share_remove": "Eliminar del álbum", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Agregar usuarios", "all_people_page_title": "Personas", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "¿Estás seguro que quieres cerrar sesión?", + "app_bar_signout_dialog_ok": "Sí", + "app_bar_signout_dialog_title": "Cerrar sesión", "archive_page_no_archived_assets": "No se encontraron recursos archivados", "archive_page_title": "Archivo ({})", "asset_list_layout_settings_dynamic_layout_title": "Diseño dinámico", @@ -59,7 +63,7 @@ "backup_controller_page_background_battery_info_title": "Optimizaciones de batería", "backup_controller_page_background_charging": "Solo mientras se carga", "backup_controller_page_background_configure_error": "Error al configurar el servicio en segundo plano", - "backup_controller_page_background_delay": "Retraso en la copia de seguridad de nuevos activos: {}", + "backup_controller_page_background_delay": "Retraso en la copia de seguridad de nuevos elementos: {}", "backup_controller_page_background_description": "Activa el servicio en segundo plano para copiar automáticamente cualquier nuevos archivos sin necesidad de abrir la aplicación.", "backup_controller_page_background_is_off": "La copia de seguridad en segundo plano automática está desactivada", "backup_controller_page_background_is_on": "La copia de seguridad en segundo plano automática está desactivada", @@ -71,7 +75,7 @@ "backup_controller_page_backup_sub": "Fotos y videos respaldados", "backup_controller_page_cancel": "Cancelar", "backup_controller_page_created": "Creado el: {}", - "backup_controller_page_desc_backup": "Activa la copia de seguridad en primer plano para cargar automáticamente nuevos recursos al servidor al abrir la aplicación.", + "backup_controller_page_desc_backup": "Active la copia de seguridad para cargar automáticamente los nuevos elementos al servidor.", "backup_controller_page_excluded": "Excluido:", "backup_controller_page_failed": "Fallidos ({})", "backup_controller_page_filename": "Nombre del archivo: {} [{}]", @@ -94,11 +98,11 @@ "backup_controller_page_uploading_file_info": "Cargando información del archivo", "backup_err_only_album": "No se puede eliminar el único álbum", "backup_info_card_assets": "archivos", - "backup_manual_cancelled": "Cancelled", - "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", + "backup_manual_cancelled": "Cancelado", + "backup_manual_failed": "Fallido", + "backup_manual_in_progress": "Subida en progreso. Espere", + "backup_manual_success": "Éxito", + "backup_manual_title": "Estado de la subida", "cache_settings_album_thumbnails": "Miniaturas de la página de la biblioteca ({} archivos)", "cache_settings_clear_cache_button": "Borrar caché", "cache_settings_clear_cache_button_title": "Borra la caché de la aplicación. Esto afectará significativamente el rendimiento de la aplicación hasta que se reconstruya la caché.", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Uso de caché", "cache_settings_subtitle": "Controla el comportamiento del almacenamiento en caché de la aplicación móvil Immich", "cache_settings_thumbnail_size": "Tamaño de la caché de miniaturas ({} archivos)", + "cache_settings_tile_subtitle": "Controla el comportamiento del almacenamiento local", + "cache_settings_tile_title": "Almacenamiento local", "cache_settings_title": "Configuración de la caché", "change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Eliminar", "control_bottom_app_bar_favorite": "Favorito", "control_bottom_app_bar_share": "Compartir", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Enviar", + "control_bottom_app_bar_stack": "Apilar", "control_bottom_app_bar_unarchive": "Desarchivar", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Subir", "create_album_page_untitled": "Sin título", "create_shared_album_page_create": "Crear", "create_shared_album_page_share": "Compartir", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Cancelar", "delete_dialog_ok": "Eliminar", "delete_dialog_title": "Eliminar permanentemente", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Estás seguro que quieres eliminar este enlace compartido", + "delete_shared_link_dialog_title": "Eliminar enlace compartido", "description_input_hint_text": "Agregar descripción...", "description_input_submit_error": "Error al actualizar la descripción, verifica el registro para obtener más detalles", "exif_bottom_sheet_description": "Agregar Descripción...", @@ -168,10 +174,10 @@ "home_page_building_timeline": "Construyendo la línea de tiempo", "home_page_favorite_err_local": "Aún no se pueden archivar recursos locales, omitiendo", "home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo", "image_viewer_page_state_provider_download_error": "Error de descarga", "image_viewer_page_state_provider_download_success": "Descarga exitosa", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Error al compartir", "library_page_albums": "Álbumes", "library_page_archive": "Archivo", "library_page_device_albums": "Álbumes en el dispositivo", @@ -179,10 +185,10 @@ "library_page_new_album": "Nuevo álbum", "library_page_sharing": "Compartiendo", "library_page_sort_created": "Creado más recientemente", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Última modificación", + "library_page_sort_most_recent_photo": "Foto más reciente", "library_page_sort_title": "Título del álbum", - "login_disabled": "Login has been disabled", + "login_disabled": "El inicio de sesión ha sido desactivado", "login_form_api_exception": "Excepción producida por API. Por favor, verifica el URL del servidor e inténtalo de nuevo.", "login_form_button_text": "Iniciar sesión", "login_form_email_hint": "tucorreo@correo.com", @@ -196,7 +202,7 @@ "login_form_failed_get_oauth_server_config": "Error al iniciar sesión con OAuth, verifica la URL del servidor", "login_form_failed_get_oauth_server_disable": "La función de OAuth no está disponible en este servidor", "login_form_failed_login": "Error al iniciar sesión, comprueba la URL del servidor, el correo electrónico y la contraseña", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_handshake_exception": "Hubo un error de verificación del certificado del servidor. Activa el soporte para certificados autofirmados en las preferencias si estás usando un certificado autofirmado", "login_form_label_email": "Correo electrónico", "login_form_label_password": "Contraseña", "login_form_next_button": "Siguiente", @@ -204,24 +210,24 @@ "login_form_save_login": "Permanecer conectado", "login_form_server_empty": "Agrega la URL del servidor.", "login_form_server_error": "No se pudo conectar al servidor.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_no_assets_in_bounds": "No photos in this area", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_only_relative_range": "Date range", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_zoom_to_see_photos": "Zoom out to see photos", + "login_password_changed_error": "Hubo un error actualizando la contraseña", + "login_password_changed_success": "Contraseña cambiado con éxito", + "map_cannot_get_user_location": "No se pudo obtener la posición del usuario", + "map_location_dialog_cancel": "Cancelar", + "map_location_dialog_yes": "Sí", + "map_location_service_disabled_content": "Los servicios de localización deben estar activados para mostrar elementos de tu ubicación actual. Deseas activarlos ahora?", + "map_location_service_disabled_title": "Servicios de localización desactivados", + "map_no_assets_in_bounds": "No hay fotos en esta zona", + "map_no_location_permission_content": "Se necesitan permisos de ubicación para mostrar elementos de tu ubicación actual. Deseas activarlos ahora?", + "map_no_location_permission_title": "Permisos de ubicación denegados", + "map_settings_dark_mode": "Modo oscuro", + "map_settings_dialog_cancel": "Cancelar", + "map_settings_dialog_save": "Guardar", + "map_settings_dialog_title": "Ajustes mapa", + "map_settings_include_show_archived": "Incluir archivados", + "map_settings_only_relative_range": "Rango de fechas", + "map_settings_only_show_favorites": "Mostrar solo favoritas", + "map_zoom_to_see_photos": "Alejar para ver fotos", "monthly_title_text_date_format": "MMMM y", "motion_photos_page_title": "Foto en Movimiento", "notification_permission_dialog_cancel": "Cancelar", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.", "profile_drawer_app_logs": "Registros", "profile_drawer_client_server_up_to_date": "El cliente y el servidor están actualizados", + "profile_drawer_documentation": "Documentación", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Configuración", "profile_drawer_sign_out": "Cerrar sesión", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Papelera", "recently_added_page_title": "Recién Agregadas", "search_bar_hint": "Busca tus fotos", "search_page_categories": "Categorías", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Error al crear álbum", "select_user_for_sharing_page_share_suggestions": "Sugerencias", "server_info_box_app_version": "Versión de la Aplicación", + "server_info_box_server_url": "URL del servidor", "server_info_box_server_version": "Versión del Servidor", "setting_image_viewer_help": "El visor de detalles carga primero la miniatura pequeña, luego carga la vista previa de tamaño mediano (si está habilitada), finalmente carga la original (si está habilitada).", "setting_image_viewer_original_subtitle": "Activar para cargar la imagen en resolución original (¡muy grande!). Deshabilitar para reducir el consumo de datos (de red y caché).", @@ -300,35 +309,44 @@ "share_add_photos": "Agregar fotos", "share_add_title": "Agregar un título", "share_create_album": "Crear álbum", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparando...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Enlaces compartidos", + "shared_link_create_app_bar_title": "Crear enlace compartido", + "shared_link_create_info": "Cualquier persona con el enlace puede ver las fotos seleccionadas", + "shared_link_create_submit_button": "Crear enlace", + "shared_link_edit_allow_download": "Permitir descargar a usuarios públicos", + "shared_link_edit_allow_upload": "Permitir subir a usuarios públicos", + "shared_link_edit_app_bar_title": "Editar enlace", + "shared_link_edit_change_expiry": "Cambiar fecha de caducidad", + "shared_link_edit_description": "Descripción", + "shared_link_edit_description_hint": "Introduce la descripción del enlace", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Contraseña", + "shared_link_edit_password_hint": "Introduce la contraseña del enlace", + "shared_link_edit_show_meta": "Mostrar metadatos", + "shared_link_edit_submit_button": "Actualizar enlace", + "shared_link_empty": "No tienes enlaces compartidos", + "shared_link_manage_links": "Administrar enlaces compartidos", + "share_done": "Hecho", "share_invite": "Invitar al álbum", "sharing_page_album": "Álbumes compartidos", "sharing_page_description": "Crea álbumes compartidos para compartir fotos y videos con personas de tu red.", "sharing_page_empty_list": "LISTA VACIA", "sharing_silver_appbar_create_shared_album": "Crear álbum compartido", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Enlaces compartidos", "sharing_silver_appbar_share_partner": "Compartir con compañero", "tab_controller_nav_library": "Biblioteca", "tab_controller_nav_photos": "Fotos", "tab_controller_nav_search": "Buscar", "tab_controller_nav_sharing": "Compartiendo", "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamiento en las miniaturas de los archivos", - "theme_setting_asset_list_tiles_per_row_title": "Número de activos por fila ({})", + "theme_setting_asset_list_tiles_per_row_title": "Número de elementos por fila ({})", "theme_setting_dark_mode_switch": "Modo oscuro", "theme_setting_image_viewer_quality_subtitle": "Ajustar la calidad del visor de detalles de imágenes", "theme_setting_image_viewer_quality_title": "Calidad del visor de imágenes", @@ -337,30 +355,30 @@ "theme_setting_theme_title": "Tema", "theme_setting_three_stage_loading_subtitle": "La carga en tres etapas puede aumentar el rendimiento de carga pero provoca un consumo de red significativamente mayor", "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", - "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", - "upload_dialog_cancel": "Cancel", - "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_ok": "Upload", - "upload_dialog_title": "Upload Asset", + "translated_text_options": "Opciones", + "trash_page_delete": "Eliminar", + "trash_page_delete_all": "Eliminar todos", + "trash_page_empty_trash_btn": "Vaciar papelera", + "trash_page_empty_trash_dialog_content": "Estás seguro que quieres eliminar los elementos? Estos elementos serán eliminados de Immich permanentemente", + "trash_page_empty_trash_dialog_ok": "Sí", + "trash_page_info": "Los archivos en la papelera serán eliminados automáticamente después de {} días", + "trash_page_no_assets": "No hay elementos en la papelera", + "trash_page_restore": "Restaurar", + "trash_page_restore_all": "Restaurar todos", + "trash_page_select_assets_btn": "Seleccionar elementos", + "trash_page_select_btn": "Seleccionar", + "trash_page_title": "Papelera ({})", + "upload_dialog_cancel": "Cancelar", + "upload_dialog_info": "Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?", + "upload_dialog_ok": "Subir", + "upload_dialog_title": "Subir elementos", "version_announcement_overlay_ack": "Aceptar", "version_announcement_overlay_release_notes": "notas de la versión", "version_announcement_overlay_text_1": "Hola, amigo, hay una nueva versión de", "version_announcement_overlay_text_2": "por favor, tómate tu tiempo para visitar las ", "version_announcement_overlay_text_3": " y asegúrate de que la configuración de docker-compose y .env estén actualizadas para evitar cualquier error de configuración, especialmente si utilizas WatchTower o cualquier mecanismo que actualice automáticamente la aplicación del servidor.", "version_announcement_overlay_title": "Nueva versión del servidor disponible \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Quitar de la pila", + "viewer_stack_use_as_main_asset": "Usar como elemento principal", + "viewer_unstack": "Desapilar" } \ No newline at end of file diff --git a/mobile/assets/i18n/fi-FI.json b/mobile/assets/i18n/fi-FI.json index 1aa8a926a..0b7ec332a 100644 --- a/mobile/assets/i18n/fi-FI.json +++ b/mobile/assets/i18n/fi-FI.json @@ -3,8 +3,8 @@ "add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}", "advanced_settings_prefer_remote_subtitle": "Jotkut laitteet ovat erittäin hitaita lataamaan esikatselukuvia laitteen kohteista. Aktivoi tämä asetus käyttääksesi etäkuvia.", "advanced_settings_prefer_remote_title": "Suosi etäkuvia", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", + "advanced_settings_self_signed_ssl_subtitle": "Ohita SSL sertifikaattivarmennus palvelimen päätepisteellä. Vaaditaan self-signed -sertifikaateissa.", + "advanced_settings_self_signed_ssl_title": "Salli self-signed SSL -sertifikaatit", "advanced_settings_tile_subtitle": "Edistyneen käyttäjän asetukset", "advanced_settings_tile_title": "Edistyneet", "advanced_settings_troubleshooting_subtitle": "Kytke vianetsinnän lisäominaisuudet päälle", @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Albumin nimen muuttaminen epäonnistui", "album_viewer_appbar_share_leave": "Poistu albumista", "album_viewer_appbar_share_remove": "Poista albumista", + "album_viewer_appbar_share_to": "Jaa", "album_viewer_page_share_add_users": "Lisää käyttäjiä", "all_people_page_title": "Ihmiset", "all_videos_page_title": "Videot", + "app_bar_signout_dialog_content": "Haluatko varmasti kirjautua ulos?", + "app_bar_signout_dialog_ok": "Kyllä", + "app_bar_signout_dialog_title": "Kirjaudu ulos", "archive_page_no_archived_assets": "Arkistoituja kohteita ei löytynyt", "archive_page_title": "Arkisto ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynaaminen asetelma", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Välimuistin käyttö", "cache_settings_subtitle": "Hallitse Immich-mobiilisovelluksen välimuistin käyttöä", "cache_settings_thumbnail_size": "Esikatselukuvien välimuistin koko ({} kohdetta)", + "cache_settings_tile_subtitle": "Hallitse paikallista tallenustilaa", + "cache_settings_tile_title": "Paikallinen tallennustila", "cache_settings_title": "Välimuistin asetukset", "change_password_form_confirm_password": "Vahvista salasana", "change_password_form_description": "Hei {firstName} {lastName},\n\nTämä on joko ensimmäinen kirjautumisesi järjestelmään tai salasanan vaihtaminen vaihtaminen on pakotettu. Ole hyvä ja syötä uusi salasana alle.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Poista", "control_bottom_app_bar_favorite": "Suosikki", "control_bottom_app_bar_share": "Jaa", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Jaa", + "control_bottom_app_bar_stack": "Pinoa", "control_bottom_app_bar_unarchive": "Palauta arkistosta", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Siirrä palvelimelle", "create_album_page_untitled": "Nimetön", "create_shared_album_page_create": "Luo", "create_shared_album_page_share": "Jaa", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Peruuta", "delete_dialog_ok": "Poista", "delete_dialog_title": "Poista pysyvästi", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Oletko varma, että haluat poistaa jaetun linkin?", + "delete_shared_link_dialog_title": "Poista jaettu linkki", "description_input_hint_text": "Lisää kuvaus...", "description_input_submit_error": "Virhe kuvauksen päivittämisessä, tarkista lisätiedot lokista", "exif_bottom_sheet_description": "Lisää kuvaus…", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan", "image_viewer_page_state_provider_download_error": "Lataus epäonnistui", "image_viewer_page_state_provider_download_success": "Lataus onnistui", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Jakovirhe", "library_page_albums": "Albumit", "library_page_archive": "Arkisto", "library_page_device_albums": "Laitteen albumit", @@ -179,8 +185,8 @@ "library_page_new_album": "Uusi albumi", "library_page_sharing": "Jakaminen", "library_page_sort_created": "Viimeisin luotu", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Viimeksi muokattu", + "library_page_sort_most_recent_photo": "Viimeisin kuva", "library_page_sort_title": "Albumin otsikko", "login_disabled": "Kirjautuminen on poistettu käytöstä", "login_form_api_exception": "API-virhe. Tarkista palvelimen URL-osoite ja yritä uudelleen.", @@ -196,7 +202,7 @@ "login_form_failed_get_oauth_server_config": "Virhe kirjauduttaessa OAuth:lla, tarkista palvelimen URL", "login_form_failed_get_oauth_server_disable": "OAuth-ominaisuus ei ole käytössä tällä palvelimella", "login_form_failed_login": "Virhe kirjautumisessa. Tarkista palvelimen URL, sähköpostiosoite ja salasana.", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_handshake_exception": "Tapahtui poikkeus kättelyssä palvelimen kanssa. Kytke päälle self-signed -sertifikaattituki asetuksista, mikäli käytät self-signed -sertifikaatteja.", "login_form_label_email": "Sähköposti", "login_form_label_password": "Salasana", "login_form_next_button": "Seuraava", @@ -204,24 +210,24 @@ "login_form_save_login": "Pysy kirjautuneena", "login_form_server_empty": "Syötä palvelimen URL-osoite.", "login_form_server_error": "Palvelimeen ei saatu yhteyttä.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_no_assets_in_bounds": "No photos in this area", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_only_relative_range": "Date range", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_zoom_to_see_photos": "Zoom out to see photos", + "login_password_changed_error": "Salasanan päivityksessä tapahtui virhe", + "login_password_changed_success": "Salasan päivitetty onnistuneesti", + "map_cannot_get_user_location": "Käyttäjän sijaintia ei voitu määrittää", + "map_location_dialog_cancel": "Peruuta", + "map_location_dialog_yes": "Kyllä", + "map_location_service_disabled_content": "Paikannuspalvelun pitää olla päälle kytkettynä, jotta nykyisen sijaintisi kohteita voidaan näyttää. Haluatko kytkeä sen päälle?", + "map_location_service_disabled_title": "Paikannuspalvelu pois päältä", + "map_no_assets_in_bounds": "Ei kuvia tällä alueella", + "map_no_location_permission_content": "Paikannuslupa tarvitaan, jotta nykyisen sijainnin kohteita voidaan näyttää. Haluatko sallia pääsyn sijaintiin?", + "map_no_location_permission_title": "Paikannuslupa estetty", + "map_settings_dark_mode": "Tumma tila", + "map_settings_dialog_cancel": "Peruuta", + "map_settings_dialog_save": "Tallenna", + "map_settings_dialog_title": "Kartta-asetukset", + "map_settings_include_show_archived": "Sisällytä arkistoidut", + "map_settings_only_relative_range": "Päivämäärän rajaus", + "map_settings_only_show_favorites": "Näytä vain suosikit", + "map_zoom_to_see_photos": "Tarkenna nähdäksesi kuvat", "monthly_title_text_date_format": "MMMM y", "motion_photos_page_title": "Liikekuvat", "notification_permission_dialog_cancel": "Peruuta", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich vaatii käyttöoikeuden kuvien ja videoiden käyttämiseen.", "profile_drawer_app_logs": "Lokit", "profile_drawer_client_server_up_to_date": "Asiakassovellus ja palvelin ovat ajan tasalla", + "profile_drawer_documentation": "Dokumentaatio", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Asetukset", "profile_drawer_sign_out": "Kirjaudu ulos", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Roskakori", "recently_added_page_title": "Viimeksi lisätyt", "search_bar_hint": "Etsi kuvia", "search_page_categories": "Kategoriat", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui", "select_user_for_sharing_page_share_suggestions": "Ehdotukset", "server_info_box_app_version": "Sovelluksen versio", + "server_info_box_server_url": "Palvelimen URL-osoite", "server_info_box_server_version": "Palvelimen versio", "setting_image_viewer_help": "Sovellus lataa ensin pienen esikatselukuvan, toisena keskitarkkuuksisen kuvan (jos käytössä) ja kolmantena alkuperäisen täysitarkkuuksisen kuvan (jos käytössä)", "setting_image_viewer_original_subtitle": "Ota käyttöön ladataksesi alkuperäinen täysitarkkuuksinen kuva (suuri!). Poista käytöstä vähentääksesi datan käyttöä (sekä verkossa että laitteen välimuistissa).", @@ -300,28 +309,37 @@ "share_add_photos": "Lisää kuvia", "share_add_title": "Lisää nimi", "share_create_album": "Luo albumi", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Valmistellaan...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Jaetut linkit", + "shared_link_create_app_bar_title": "Luo linkki jaettavaksi", + "shared_link_create_info": "Salli kaikkien linkinhaltijoiden nähdä valitut kuvat", + "shared_link_create_submit_button": "Luo linkki", + "shared_link_edit_allow_download": "Salli julkisen käyttäjän ladata palvelimelta", + "shared_link_edit_allow_upload": "Salli julkisen käyttäjän siirtää palvelimelle", + "shared_link_edit_app_bar_title": "Muokkaa linkkiä", + "shared_link_edit_change_expiry": "Muuta erääntymisaikaa", + "shared_link_edit_description": "Kuvaus", + "shared_link_edit_description_hint": "Lisää jaon kuvaus", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Salasana", + "shared_link_edit_password_hint": "Syötä jaon salasana", + "shared_link_edit_show_meta": "Näytä metadata", + "shared_link_edit_submit_button": "Päivitä linkki", + "shared_link_empty": "Sinulla ei ole jaettuja linkkejä", + "shared_link_manage_links": "Hallitse jaettuja linkkejä", + "share_done": "Valmis", "share_invite": "Kutsu albumiin", "sharing_page_album": "Jaetut albumit", "sharing_page_description": "Luo jaettuja albumeja jakaaksesi kuvia ja videoita läheisillesi.", "sharing_page_empty_list": "TYHJÄ LISTA", "sharing_silver_appbar_create_shared_album": "Luo jaettu albumi", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Jaetut linkit", "sharing_silver_appbar_share_partner": "Jaa kumppanille", "tab_controller_nav_library": "Kirjasto", "tab_controller_nav_photos": "Kuvat", @@ -337,19 +355,19 @@ "theme_setting_theme_title": "Teema", "theme_setting_three_stage_loading_subtitle": "Kolmivaiheinen lataaminen saattaa parantaa latauksen suorituskykyä, mutta lisää kaistankäyttöä huomattavasti.", "theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttöön", - "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "translated_text_options": "Vaihtoehdot", + "trash_page_delete": "Poista", + "trash_page_delete_all": "Poista kaikki", + "trash_page_empty_trash_btn": "Tyhjennä roskakori", + "trash_page_empty_trash_dialog_content": "Haluatko poistaa roskakoriin siirretyt kohteet? Kohteet poistetaan lopullisesti Immich:sta.", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_info": "Roskakoriin siirretyt kohteet poistetaan lopullisesti {} päivän jälkeen", + "trash_page_no_assets": "Ei poistettuja kohteita", + "trash_page_restore": "Palauta", + "trash_page_restore_all": "Palauta kaikki", + "trash_page_select_assets_btn": "Valitse kohteet", + "trash_page_select_btn": "Valitse", + "trash_page_title": "Roskakori", "upload_dialog_cancel": "Peruuta", "upload_dialog_info": "Haluatko varmuuskopioida valitut kohteet palvelimelle?", "upload_dialog_ok": "Lähetä", @@ -360,7 +378,7 @@ "version_announcement_overlay_text_2": "Ota hetki aikaa vieraillaksesi", "version_announcement_overlay_text_3": "ja varmista, että käyttämäsi docker-compose ja .env-asetukset ovat ajantasalla välttyäksesi asetusongelmilta. Varsinkin jos käytät WatchToweria tai jotain muuta mekanismia päivittääksesi palvelinsovellusta automaattisesti.", "version_announcement_overlay_title": "Uusi palvelinversio saatavilla \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Poista pinosta", + "viewer_stack_use_as_main_asset": "Käytä pääkohteena", + "viewer_unstack": "Pura pino" } \ No newline at end of file diff --git a/mobile/assets/i18n/fr-FR.json b/mobile/assets/i18n/fr-FR.json index 5eacd5b56..93bfe14cd 100644 --- a/mobile/assets/i18n/fr-FR.json +++ b/mobile/assets/i18n/fr-FR.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Échec de la modification du titre de l'album", "album_viewer_appbar_share_leave": "Quitter l'album", "album_viewer_appbar_share_remove": "Retirer de l'album", + "album_viewer_appbar_share_to": "Partager à", "album_viewer_page_share_add_users": "Ajouter des utilisateurs", "all_people_page_title": "Personnes", "all_videos_page_title": "Vidéos", + "app_bar_signout_dialog_content": "Êtes-vous sûr de vouloir vous déconnecter ?", + "app_bar_signout_dialog_ok": "Oui", + "app_bar_signout_dialog_title": "Se déconnecter", "archive_page_no_archived_assets": "Aucun élément archivé n'a été trouvé", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Affichage dynamique", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Utilisation du cache", "cache_settings_subtitle": "Contrôler le comportement de mise en cache de l'application mobile Immich", "cache_settings_thumbnail_size": "Taille du cache des miniatures ({} éléments)", + "cache_settings_tile_subtitle": "Contrôler le comportement du stockage local", + "cache_settings_tile_title": "Stockage local", "cache_settings_title": "Paramètres de mise en cache", "change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_description": "Bonjour {firstName} {lastName},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé à changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Supprimer", "control_bottom_app_bar_favorite": "Favoris", "control_bottom_app_bar_share": "Partager", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Partager à", + "control_bottom_app_bar_stack": "Empiler", "control_bottom_app_bar_unarchive": "Désarchiver", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Téléverser", "create_album_page_untitled": "Sans titre", "create_shared_album_page_create": "Créer", "create_shared_album_page_share": "Partager", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Annuler", "delete_dialog_ok": "Supprimer", "delete_dialog_title": "Supprimer définitivement", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Êtes-vous sûr de vouloir supprimer ce lien partagé ?", + "delete_shared_link_dialog_title": "Supprimer le lien partagé", "description_input_hint_text": "Ajouter une description...", "description_input_submit_error": "Erreur de mise à jour de la description, vérifier le journal pour plus de détails", "exif_bottom_sheet_description": "Ajouter une description...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée", "image_viewer_page_state_provider_download_error": "Erreur de téléchargement", "image_viewer_page_state_provider_download_success": "Téléchargement réussi", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Erreur de partage", "library_page_albums": "Albums", "library_page_archive": "Archive", "library_page_device_albums": "Albums sur l'appareil", @@ -179,8 +185,8 @@ "library_page_new_album": "Nouvel album", "library_page_sharing": "Partage", "library_page_sort_created": "Créations les plus récentes", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Dernière modification", + "library_page_sort_most_recent_photo": "Photo la plus récente", "library_page_sort_title": "Titre de l'album", "login_disabled": "La connexion a été désactivée ", "login_form_api_exception": "Erreur de l'API. Veuillez vérifier l'URL du serveur et et réessayer.", @@ -218,7 +224,7 @@ "map_settings_dialog_cancel": "Annuler", "map_settings_dialog_save": "Sauvegarder", "map_settings_dialog_title": "Paramètres de la carte", - "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_archived": "Inclure les archives", "map_settings_only_relative_range": "Plage de dates", "map_settings_only_show_favorites": "Afficher uniquement les favoris", "map_zoom_to_see_photos": "Dézoomer pour voir les photos", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich demande l'autorisation de visionner vos photos et vidéo", "profile_drawer_app_logs": "Journaux", "profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Paramètres", "profile_drawer_sign_out": "Se déconnecter", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Corbeille", "recently_added_page_title": "Récemment ajouté", "search_bar_hint": "Rechercher vos photos", "search_page_categories": "Catégories", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Échec de la création de l'album", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "Version de l'application", + "server_info_box_server_url": "URL du serveur", "server_info_box_server_version": "Version du serveur", "setting_image_viewer_help": "Le visualiseur de détails charge d'abord la petite miniature, puis l'aperçu de taille moyenne (s'il est activé), enfin l'original (s'il est activé).", "setting_image_viewer_original_subtitle": "Activez cette option pour charger l'image en résolution originale (volumineux !). Désactiver pour réduire l'utilisation des données (réseau et cache de l'appareil).", @@ -300,28 +309,37 @@ "share_add_photos": "Ajouter des photos", "share_add_title": "Ajouter un titre", "share_create_album": "Créer un album", + "shared_album_activities_input_disable": "Les commentaires sont désactivés", + "shared_album_activities_input_hint": "Dire quelque chose", + "shared_album_activity_remove_content": "Souhaitez-vous supprimer cette activité ?", + "shared_album_activity_remove_title": "Supprimer l'activité", + "shared_album_activity_setting_subtitle": "Laisser les autres réagir", + "shared_album_activity_setting_title": "Commentaires et likes", "share_dialog_preparing": "Préparation...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", + "shared_link_app_bar_title": "Liens partagés", + "shared_link_create_app_bar_title": "Créer un lien pour partager", + "shared_link_create_info": "Permettre à toute personne ayant le lien de voir la ou les photos sélectionnées", + "shared_link_create_submit_button": "Créer le lien", + "shared_link_edit_allow_download": "Autoriser les utilisateurs publics à télécharger", + "shared_link_edit_allow_upload": "Autoriser les utilisateurs publics à téléverser", + "shared_link_edit_app_bar_title": "Modifier le lien", + "shared_link_edit_change_expiry": "Modifier le délai d'expiration", "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_edit_description_hint": "Saisir la description du partage", + "shared_link_edit_expire_after": "Expire après", + "shared_link_edit_password": "Mot de passe", + "shared_link_edit_password_hint": "Saisir le mot de passe de partage", + "shared_link_edit_show_meta": "Afficher les métadonnées", + "shared_link_edit_submit_button": "Mettre à jour le lien", + "shared_link_empty": "Vous n'avez pas de liens partagés", + "shared_link_manage_links": "Gérer les liens partagés", + "share_done": "Fait", "share_invite": "Inviter à l'album", "sharing_page_album": "Albums partagés", "sharing_page_description": "Créez des albums partagés pour partager des photos et des vidéos avec les personnes de votre réseau.", "sharing_page_empty_list": "LISTE VIDE", "sharing_silver_appbar_create_shared_album": "Créer un album partagé", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Liens partagés", "sharing_silver_appbar_share_partner": "Partager avec un partenaire", "tab_controller_nav_library": "Bibliothèque", "tab_controller_nav_photos": "Photos", @@ -338,18 +356,18 @@ "theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau.", "theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes", "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_delete": "Supprimer", + "trash_page_delete_all": "Tout supprimer", + "trash_page_empty_trash_btn": "Vider la corbeille", + "trash_page_empty_trash_dialog_content": "Voulez-vous vider les éléments de la corbeille? Ces objets seront définitivement retirés d'Immich", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_info": "Les éléments mis à la corbeille seront définitivement supprimés au bout de {} jours.", + "trash_page_no_assets": "Pas d'éléments dans la corbeille", + "trash_page_restore": "Restaurer", + "trash_page_restore_all": "Tout restaurer", + "trash_page_select_assets_btn": "Sélectionner les éléments", + "trash_page_select_btn": "Sélectionner", + "trash_page_title": "Corbeille ({})", "upload_dialog_cancel": "Annuler", "upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur ?", "upload_dialog_ok": "Télécharger ", @@ -360,7 +378,7 @@ "version_announcement_overlay_text_2": "veuillez prendre le temps de visiter le ", "version_announcement_overlay_text_3": " et assurez-vous que votre configuration docker-compose et .env est à jour pour éviter toute erreur de configuration, en particulier si vous utilisez WatchTower ou tout autre mécanisme qui gère la mise à jour automatique de votre application serveur.", "version_announcement_overlay_title": "Nouvelle version serveur disponible \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Retirer de la pile", + "viewer_stack_use_as_main_asset": "Utiliser comme élément principal", + "viewer_unstack": "Désempiler" } \ No newline at end of file diff --git a/mobile/assets/i18n/hi-IN.json b/mobile/assets/i18n/hi-IN.json index 31311535b..672ab28fb 100644 --- a/mobile/assets/i18n/hi-IN.json +++ b/mobile/assets/i18n/hi-IN.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Failed to change album title", "album_viewer_appbar_share_leave": "Leave album", "album_viewer_appbar_share_remove": "Remove from album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Add users", "all_people_page_title": "People", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cache usage", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Sign Out", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Failed to create album", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "App Version", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "Add photos", "share_add_title": "Add a title", "share_create_album": "Create album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparing...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/hu-HU.json b/mobile/assets/i18n/hu-HU.json index cc177bffb..d3b93d7fc 100644 --- a/mobile/assets/i18n/hu-HU.json +++ b/mobile/assets/i18n/hu-HU.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Hiba az album átnevezése közben", "album_viewer_appbar_share_leave": "Kilépés az albumból", "album_viewer_appbar_share_remove": "Törlés az albumból", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Felhasználók hozzáadása", "all_people_page_title": "Emberek", "all_videos_page_title": "Videók", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "Nem található archivált média", "archive_page_title": "Archívum ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Gyorsítótár által használt terület", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Gyorsítótár beállítások", "change_password_form_confirm_password": "Jelszó Megerősítése", "change_password_form_description": "Kedves {lastName} {firstName}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséfes a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Engedélyezni kell, hogy az Immich hozzáférjen a képekhez és videókhoz", "profile_drawer_app_logs": "Naplók", "profile_drawer_client_server_up_to_date": "Kliens és a szerver is naprakész", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Beállítások", "profile_drawer_sign_out": "Kijelentkezés", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Hiba az album létrehozása közben", "select_user_for_sharing_page_share_suggestions": "Javaslatok", "server_info_box_app_version": "Alkalmazás Verzió", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Szerver Verzió", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "Fotók hozzáadása", "share_add_title": "Cím hozzáadása", "share_create_album": "Album létrehozása", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Előkészítés...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/it-IT.json b/mobile/assets/i18n/it-IT.json index 479a46016..cce25a276 100644 --- a/mobile/assets/i18n/it-IT.json +++ b/mobile/assets/i18n/it-IT.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Impossibile cambiare il titolo dell'album ", "album_viewer_appbar_share_leave": "Lascia album", "album_viewer_appbar_share_remove": "Rimuovere dall'album ", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Aggiungi utenti", "all_people_page_title": "Persone", "all_videos_page_title": "Video", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "Nessuna oggetto archiviato", "archive_page_title": "Archivia ({})", "asset_list_layout_settings_dynamic_layout_title": "Layout dinamico", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Uso della cache", "cache_settings_subtitle": "Controlla il comportamento della cache dell'applicazione mobile immich", "cache_settings_thumbnail_size": "Dimensione cache dei thumbnail ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Impostazioni della Cache", "change_password_form_confirm_password": "Conferma Password ", "change_password_form_description": "Ciao {firstName} {lastName},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich richiede i permessi per vedere le tue foto e video", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client e server sono aggiornati", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Impostazioni ", "profile_drawer_sign_out": "Logout", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Impossibile nel creare l'album ", "select_user_for_sharing_page_share_suggestions": "Suggerimenti", "server_info_box_app_version": "Versione App", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Versione Server", "setting_image_viewer_help": "Il visualizzatore dettagliato carica una piccola thumbnail per prima, per poi caricare un immagine di media grandezza (se abilitato). Ed infine carica l'originale (se abilitato).", "setting_image_viewer_original_subtitle": "Abilita per caricare l'immagine originale a risoluzione massima (grande!). Disabilita per ridurre l'utilizzo di banda (sia sul network che nella cache del dispositivo).", @@ -300,6 +309,12 @@ "share_add_photos": "Aggiungi foto", "share_add_title": "Aggiungi un titolo ", "share_create_album": "Crea album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparo…", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/ja-JP.json b/mobile/assets/i18n/ja-JP.json index de2b23e06..2fba4b77c 100644 --- a/mobile/assets/i18n/ja-JP.json +++ b/mobile/assets/i18n/ja-JP.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "タイトル変更の失敗", "album_viewer_appbar_share_leave": "アルバムから脱退", "album_viewer_appbar_share_remove": "アルバムから削除", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "ユーザーを追加", "all_people_page_title": "People", "all_videos_page_title": "ビデオ", + "app_bar_signout_dialog_content": " サインアウトしますか?", + "app_bar_signout_dialog_ok": "はい", + "app_bar_signout_dialog_title": " サインアウト", "archive_page_no_archived_assets": "アーカイブ済みの写真またはビデオがありません", "archive_page_title": "アーカイブ({})", "asset_list_layout_settings_dynamic_layout_title": "ダイナミックレイアウト", @@ -111,9 +115,11 @@ "cache_settings_statistics_title": "キャッシュ", "cache_settings_subtitle": "キャッシュの動作を変更する", "cache_settings_thumbnail_size": "サムネイルのキャッシュのサイズ ({}枚)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "キャッシュの設定", "change_password_form_confirm_password": "確定", - "change_password_form_description": "{lastaName} {firstName}さん こんにちは\n\nサーバーにアクセスするのが初めてか、パスワードリセットのリクエストがされました。新しいパスワードを入力してください", + "change_password_form_description": "{lastName} {firstName}さん こんにちは\n\nサーバーにアクセスするのが初めてか、パスワードリセットのリクエストがされました。新しいパスワードを入力してください", "change_password_form_new_password": "新しいパスワード", "change_password_form_password_mismatch": "パスワードが一致しません", "change_password_form_reenter_new_password": "再度パスワードを入力してください", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "キャンセル", "delete_dialog_ok": "削除", "delete_dialog_title": "永久的に削除", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "本当にこの共有リンクを消しますか?", + "delete_shared_link_dialog_title": "共有リンクを消す", "description_input_hint_text": "説明を追加", "description_input_submit_error": "説明の編集に失敗、詳細の確認はログで行ってください", "exif_bottom_sheet_description": "説明を追加", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immichは写真へのアクセス許可が必要です", "profile_drawer_app_logs": "ログ", "profile_drawer_client_server_up_to_date": "すべて最新です", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "設定", "profile_drawer_sign_out": "サインアウト", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "アルバム作成に失敗", "select_user_for_sharing_page_share_suggestions": "ユーザ一覧", "server_info_box_app_version": "アプリVer.", + "server_info_box_server_url": " サーバのURL", "server_info_box_server_version": "サーバーVer.", "setting_image_viewer_help": "写真をタップするとサムネイル・中画質(要設定)・オリジナル(要設定)の順に読み込みます", "setting_image_viewer_original_subtitle": "オリジナルの画像を表示したい時にオンにしてください(最大画質で表示されるのでモバイルデータとストレージの消費量が増えます)。", @@ -300,20 +309,29 @@ "share_add_photos": "写真を追加", "share_add_title": "タイトルを追加", "share_create_album": "アルバムを作成", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "準備中", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", + "shared_link_app_bar_title": "共有リンク", + "shared_link_create_app_bar_title": "共有リンクを作る", "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", + "shared_link_create_submit_button": "リンクを作る", "shared_link_edit_allow_download": "Allow public user to download", "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", + "shared_link_edit_app_bar_title": " リンクを編集する", "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", + "shared_link_edit_description": " デスクリプション ", "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": " パスワード", + "shared_link_edit_password_hint": "共有パスワードを入力する", + "shared_link_edit_show_meta": " メタデータを見る", + "shared_link_edit_submit_button": "リンクをアップデートする", + "shared_link_empty": "共有リンクはありません ", "shared_link_manage_links": "Manage Shared links", "share_done": "Done", "share_invite": "アルバムに招待", diff --git a/mobile/assets/i18n/ko-KR.json b/mobile/assets/i18n/ko-KR.json index d2786e35c..6d6ae357f 100644 --- a/mobile/assets/i18n/ko-KR.json +++ b/mobile/assets/i18n/ko-KR.json @@ -1,21 +1,21 @@ { "add_to_album_bottom_sheet_added": "{album}에 추가", "add_to_album_bottom_sheet_already_exists": "{album}에 이미 포함되어 있습니다", - "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.", - "advanced_settings_prefer_remote_title": "Prefer remote images", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", - "advanced_settings_tile_subtitle": "Advanced user's settings", - "advanced_settings_tile_title": "Advanced", - "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", - "advanced_settings_troubleshooting_title": "Troubleshooting", + "advanced_settings_prefer_remote_subtitle": "일부 디바이스에서는 디바이스에 있는 미디어의 썸네일을 로드하는 속도가 매우 느립니다. 대신 원격 이미지를 로드하려면 이 설정을 활성화하세요", + "advanced_settings_prefer_remote_title": "원격 이미지 선호", + "advanced_settings_self_signed_ssl_subtitle": "서버 엔드포인트에 대한 SSL 인증서 확인을 건너뜁니다. 자체 서명 인증서에 필요합니다", + "advanced_settings_self_signed_ssl_title": "자체 서명된 SSL 인증서 허용", + "advanced_settings_tile_subtitle": "고급 사용자 설정", + "advanced_settings_tile_title": "고급", + "advanced_settings_troubleshooting_subtitle": "문제 해결을 위한 추가 기능 사용", + "advanced_settings_troubleshooting_title": "문제 해결", "album_info_card_backup_album_excluded": "제외됨", "album_info_card_backup_album_included": "포함됨", "album_thumbnail_card_item": "1개 항목", "album_thumbnail_card_items": "{}개 항목", "album_thumbnail_card_shared": " · 공유", - "album_thumbnail_owned": "Owned", - "album_thumbnail_shared_by": "Shared by {}", + "album_thumbnail_owned": "소유", + "album_thumbnail_shared_by": "공유자 {}", "album_viewer_appbar_share_delete": "앨범 삭제", "album_viewer_appbar_share_err_delete": "앨범 삭제 실패", "album_viewer_appbar_share_err_leave": "앨범에서 나가지 못했습니다", @@ -23,14 +23,18 @@ "album_viewer_appbar_share_err_title": "앨범 제목 변경 실패", "album_viewer_appbar_share_leave": "앨범 나가기", "album_viewer_appbar_share_remove": "앨범에서 제거", + "album_viewer_appbar_share_to": "공유 대상", "album_viewer_page_share_add_users": "사용자 추가", - "all_people_page_title": "People", - "all_videos_page_title": "Videos", - "archive_page_no_archived_assets": "No archived assets found", - "archive_page_title": "Archive ({})", + "all_people_page_title": "사람", + "all_videos_page_title": "동영상", + "app_bar_signout_dialog_content": "정말 로그아웃하시겠습니까?", + "app_bar_signout_dialog_ok": "네", + "app_bar_signout_dialog_title": "로그 아웃", + "archive_page_no_archived_assets": "보관된 미디어를 찾을 수 없습니다", + "archive_page_title": "보관 ({})", "asset_list_layout_settings_dynamic_layout_title": "다이나믹 레이아웃", - "asset_list_layout_settings_group_automatically": "Automatic", - "asset_list_layout_settings_group_by": "다음으로 그룹화", + "asset_list_layout_settings_group_automatically": "자동", + "asset_list_layout_settings_group_by": "다음으로 미디어 그룹화", "asset_list_layout_settings_group_by_month": "월", "asset_list_layout_settings_group_by_month_day": "월 + 일", "asset_list_settings_subtitle": "사진 배열 레이아웃 설정", @@ -50,9 +54,9 @@ "backup_background_service_in_progress_notification": "미디어파일 백업 중...", "backup_background_service_upload_failure_notification": "{} 업로드 실패", "backup_controller_page_albums": "백업대상", - "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.", - "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled", - "backup_controller_page_background_app_refresh_enable_button_text": "Go to settings", + "backup_controller_page_background_app_refresh_disabled_content": "백그라운드 백업을 사용하려면 설정 > 일반 > 백그라운드 앱 새로 고침에서 백그라운드 앱 새로 고침을 활성화합니다", + "backup_controller_page_background_app_refresh_disabled_title": "백그라운드 앱 새로 고침 비활성화", + "backup_controller_page_background_app_refresh_enable_button_text": "설정으로 이동", "backup_controller_page_background_battery_info_link": "사용 가이드", "backup_controller_page_background_battery_info_message": "최상의 백업 환경을 위해 Immich 앱의 백그라운드 활동을 제한하는 배터리 최적화기능을 꺼주세요.\n\n휴대폰마다 설정방법이 다르므로 제조업체별로 설정방법을 확인하세요.", "backup_controller_page_background_battery_info_ok": "확인", @@ -94,11 +98,11 @@ "backup_controller_page_uploading_file_info": "파일 정보 업로드 중", "backup_err_only_album": "유일한 앨범은 제거할 수 없습니다", "backup_info_card_assets": "미디어", - "backup_manual_cancelled": "Cancelled", - "backup_manual_failed": "Failed", - "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", + "backup_manual_cancelled": "취소됨", + "backup_manual_failed": "실패", + "backup_manual_in_progress": "업로드가 이미 진행 중입니다. 잠시 후 시도하세요", + "backup_manual_success": "성공", + "backup_manual_title": "업로드 상태", "cache_settings_album_thumbnails": "라이브러리 페이지 썸네일 ({} 미디어)", "cache_settings_clear_cache_button": "캐시 지우기", "cache_settings_clear_cache_button_title": "앱의 캐시를 지웁니다. 이 작업은 캐시가 다시 빌드될 때까지 앱의 성능에 상당한 영향을 미칩니다.", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "캐시 사용률", "cache_settings_subtitle": "Immich 앱의 캐싱 동작 제어", "cache_settings_thumbnail_size": "썸네일 캐시 크기 ({} 미디어)", + "cache_settings_tile_subtitle": "로컬 저장소 동작 제어", + "cache_settings_tile_title": "로컬 저장소", "cache_settings_title": "캐시 설정", "change_password_form_confirm_password": "비밀번호 확인", "change_password_form_description": "{firstName} {lastName} 님, 안녕하세요.\n\n시스템에 처음 로그인했거나 비밀번호 변경 요청이 있었습니다. 아래에 새 비밀번호를 입력하세요.", @@ -120,27 +126,27 @@ "common_add_to_album": "앨범에 추가", "common_change_password": "비밀번호 변경", "common_create_new_album": "새 앨범 만들기", - "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", + "common_server_error": "네트워크 연결을 확인하고 서버에 연결할 수 있는지, 앱/서버 버전이 호환되는지 확인하세요", "common_shared": "공유됨", "control_bottom_app_bar_add_to_album": "앨범에 추가", "control_bottom_app_bar_album_info": "{} 항목", "control_bottom_app_bar_album_info_shared": "{} 항목 · 공유됨", - "control_bottom_app_bar_archive": "Archive", + "control_bottom_app_bar_archive": "보관", "control_bottom_app_bar_create_new_album": "앨범 생성", "control_bottom_app_bar_delete": "삭제", "control_bottom_app_bar_favorite": "즐겨찾기", "control_bottom_app_bar_share": "공유", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", - "control_bottom_app_bar_unarchive": "Unarchive", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_share_to": "공유 대상", + "control_bottom_app_bar_stack": "스택", + "control_bottom_app_bar_unarchive": "보관 해제", + "control_bottom_app_bar_upload": "업로드", "create_album_page_untitled": "제목없음", "create_shared_album_page_create": "만들기", "create_shared_album_page_share": "공유", "create_shared_album_page_share_add_assets": "사진 추가", "create_shared_album_page_share_select_photos": "사진 선택", - "curated_location_page_title": "Places", - "curated_object_page_title": "Things", + "curated_location_page_title": "장소", + "curated_object_page_title": "사물", "daily_title_text_date": "E, M월 d일", "daily_title_text_date_year": "E, M월 d일, yyyy", "date_format": "yyyy년 M월 d일, EEEE • a h:mm", @@ -148,10 +154,10 @@ "delete_dialog_cancel": "취소", "delete_dialog_ok": "삭제", "delete_dialog_title": "영구적으로 삭제", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", - "description_input_hint_text": "Add description...", - "description_input_submit_error": "Error updating description, check the log for more details", + "delete_shared_link_dialog_content": "이 공유 링크를 삭제하시겠습니까?", + "delete_shared_link_dialog_title": "공유 링크 삭제", + "description_input_hint_text": "설명 추가", + "description_input_submit_error": "설명 업데이트 오류, 자세한 내용은 로그를 확인하세요", "exif_bottom_sheet_description": "설명 추가...", "exif_bottom_sheet_details": "상세정보", "exif_bottom_sheet_location": "위치", @@ -159,31 +165,31 @@ "experimental_settings_new_asset_list_title": "실험적 사진 그리드 적용", "experimental_settings_subtitle": "문제시 책임지지 않습니다!", "experimental_settings_title": "실험적기능", - "favorites_page_no_favorites": "No favorite assets found", + "favorites_page_no_favorites": "즐겨찾기된 미디어를 찾을 수 없습니다", "favorites_page_title": "즐겨찾기", "home_page_add_to_album_conflicts": "{album} 앨범에 {added} 미디어를 추가했습니다. {failed} 이미 앨범에 있는 항목입니다.", - "home_page_add_to_album_err_local": "앨범에 미디어파일을 추가할 수 없어, 건너뜁니다.", + "home_page_add_to_album_err_local": "아직 앨범에 로컬 미디어를 추가할 수 없으므로 건너뜁니다", "home_page_add_to_album_success": "{album} 앨범에 {added} 미디어를 추가했습니다. ", - "home_page_archive_err_local": "Can not archive local assets yet, skipping", + "home_page_archive_err_local": "아직 로컬 미디어를 보관할 수 없습니다", "home_page_building_timeline": "타임라인 생성", - "home_page_favorite_err_local": "미디어파일을 즐겨찾기에 추가할 수 없어, 건너뜁니다.", + "home_page_favorite_err_local": "아직 로컬 미디어를 즐겨찾기에 추가할 수 없으므로 건너뜁니다", "home_page_first_time_notice": "앱을 처음 사용하는 경우 타임라인이 앨범의 사진과 비디오를 채울 수 있도록 백업대상 앨범을 선택해야 합니다.", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "home_page_upload_err_limit": "한번에 최대 30개의 미디어만 업로드할 수 있습니다", "image_viewer_page_state_provider_download_error": "다운로드 에러", "image_viewer_page_state_provider_download_success": "다운로드 완료", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "공유 오류", "library_page_albums": "앨범", - "library_page_archive": "Archive", - "library_page_device_albums": "Albums on Device", + "library_page_archive": "보관", + "library_page_device_albums": "장치의 앨범", "library_page_favorites": "즐겨찾기", "library_page_new_album": "새 앨범", "library_page_sharing": "공유", "library_page_sort_created": "최근생성일", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "마지막 수정", + "library_page_sort_most_recent_photo": "가장 최근 사진", "library_page_sort_title": "앨범 제목", - "login_disabled": "Login has been disabled", - "login_form_api_exception": "API exception. Please check the server URL and try again.", + "login_disabled": "로그인이 비활성화되었습니다", + "login_form_api_exception": "API 예외입니다. 서버 URL을 확인한 후 다시 시도하세요", "login_form_button_text": "로그인", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "https://your-server-ip:port/api", @@ -196,86 +202,89 @@ "login_form_failed_get_oauth_server_config": "OAuth 로그인 오류, 서버 URL을 확인해주세요", "login_form_failed_get_oauth_server_disable": "이 서버에서는 OAuth 기능을 사용할 수 없습니다.", "login_form_failed_login": "로그인 오류, 서버 URL, 이메일 및 비밀번호를 확인하세요", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", + "login_form_handshake_exception": "서버에 핸드셰이크 예외가 발생했습니다. 자체 서명 인증서를 사용하는 경우 설정에서 자체 서명 인증서 지원을 사용 설정합니다", "login_form_label_email": "이메일", "login_form_label_password": "비밀번호", - "login_form_next_button": "Next", + "login_form_next_button": "다음", "login_form_password_hint": "비밀번호", "login_form_save_login": "로그인상태 유지", - "login_form_server_empty": "Enter a server URL.", - "login_form_server_error": "Could not connect to server.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_no_assets_in_bounds": "No photos in this area", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_only_relative_range": "Date range", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_zoom_to_see_photos": "Zoom out to see photos", + "login_form_server_empty": "서버 URL 입력", + "login_form_server_error": "서버에 연결할 수 없습니다", + "login_password_changed_error": "비밀번호를 업데이트하는 동안 오류가 발생했습니다", + "login_password_changed_success": "비밀번호 업데이트 성공", + "map_cannot_get_user_location": "사용자 위치를 가져올 수 없습니다.", + "map_location_dialog_cancel": "아니오", + "map_location_dialog_yes": "예", + "map_location_service_disabled_content": "현재 위치의 미디어를 표시하려면 위치 서비스를 활성화해야 합니다. 지금 활성화하시겠습니까?", + "map_location_service_disabled_title": "위치 서비스 비활성화", + "map_no_assets_in_bounds": "이 영역에 사진이 없습니다", + "map_no_location_permission_content": "현재 위치의 미디어를 표시하려면 위치 권한이 필요합니다. 지금 허용하시겠습니까?", + "map_no_location_permission_title": "위치 권한 거부됨", + "map_settings_dark_mode": "다크 모드", + "map_settings_dialog_cancel": "취소", + "map_settings_dialog_save": "저장", + "map_settings_dialog_title": "지도 설정", + "map_settings_include_show_archived": "아카이브 포함", + "map_settings_only_relative_range": "날짜 범위", + "map_settings_only_show_favorites": "즐겨찾기에만 표시", + "map_zoom_to_see_photos": "축소하여 사진 보기", "monthly_title_text_date_format": "y년 M월", - "motion_photos_page_title": "Motion Photos", + "motion_photos_page_title": "모션 사진", "notification_permission_dialog_cancel": "취소", "notification_permission_dialog_content": "알림을 활성화하려면 설정으로 이동하여 허용을 선택해주세요.", "notification_permission_dialog_settings": "설정", "notification_permission_list_tile_content": "알림 활성화 권한허용", "notification_permission_list_tile_enable_button": "알림 활성화", "notification_permission_list_tile_title": "알림 권한", - "partner_page_add_partner": "Add partner", - "partner_page_empty_message": "Your photos are not yet shared with any partner.", - "partner_page_no_more_users": "No more users to add", - "partner_page_partner_add_failed": "Failed to add partner", - "partner_page_select_partner": "Select partner", - "partner_page_shared_to_title": "Shared to", - "partner_page_stop_sharing_content": "{} will no longer be able to access your photos.", - "partner_page_stop_sharing_title": "Stop sharing your photos?", - "partner_page_title": "Partner", - "permission_onboarding_continue_anyway": "Continue anyway", - "permission_onboarding_get_started": "Get started", - "permission_onboarding_go_to_settings": "Go to settings", - "permission_onboarding_grant_permission": "Grant permission", - "permission_onboarding_log_out": "Log out", - "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", - "permission_onboarding_permission_granted": "Permission granted! You are all set.", - "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", - "permission_onboarding_request": "Immich requires permission to view your photos and videos.", + "partner_page_add_partner": "파트너 추가", + "partner_page_empty_message": "사진이 아직 어떤 파트너와도 공유되지 않았습니다", + "partner_page_no_more_users": "더 이상 추가할 사용자 없음", + "partner_page_partner_add_failed": "파트너 추가에 실패했습니다", + "partner_page_select_partner": "파트너 선택", + "partner_page_shared_to_title": "공유 대상", + "partner_page_stop_sharing_content": "더 이상 {}에서 사진에 액세스할 수 없습니다.", + "partner_page_stop_sharing_title": "사진 공유를 중단하시겠습니까?", + "partner_page_title": "파트너", + "permission_onboarding_continue_anyway": "어쨌든 계속하기", + "permission_onboarding_get_started": "시작하기", + "permission_onboarding_go_to_settings": "설정으로 이동", + "permission_onboarding_grant_permission": "권한 부여", + "permission_onboarding_log_out": "로그 아웃", + "permission_onboarding_permission_denied": "권한이 거부되었습니다. Immich를 사용하려면 설정에서 사진 및 동영상 권한을 부여하세요", + "permission_onboarding_permission_granted": "승인되었습니다! 모든 준비가 완료되었습니다", + "permission_onboarding_permission_limited": "권한 제한. Immich가 전체 갤러리 컬렉션을 백업하고 관리하도록 하려면 설정에서 사진 및 동영상 권한을 부여하세요", + "permission_onboarding_request": "Immich는 사진과 동영상을 볼 수 있는 권한을 요구합니다", "profile_drawer_app_logs": "로그", "profile_drawer_client_server_up_to_date": "클라이언트와 서버가 최신 상태입니다", + "profile_drawer_documentation": "문서", + "profile_drawer_github": "깃허브", "profile_drawer_settings": "설정", "profile_drawer_sign_out": "로그아웃", - "profile_drawer_trash": "Trash", - "recently_added_page_title": "Recently Added", + "profile_drawer_trash": "휴지통", + "recently_added_page_title": "최근 추가", "search_bar_hint": "사진 검색", - "search_page_categories": "Categories", - "search_page_favorites": "Favorites", - "search_page_motion_photos": "Motion Photos", + "search_page_categories": "카테고리", + "search_page_favorites": "즐겨찾기", + "search_page_motion_photos": "모션 사진", "search_page_no_objects": "발견된 사물이\n없습니다", "search_page_no_places": "발견된 장소가\n없습니다", - "search_page_people": "People", + "search_page_people": "사람", "search_page_places": "장소", - "search_page_recently_added": "Recently added", - "search_page_screenshots": "Screenshots", - "search_page_selfies": "Selfies", + "search_page_recently_added": "최근 추가", + "search_page_screenshots": "스크린샷", + "search_page_selfies": "셀카", "search_page_things": "사물", - "search_page_videos": "Videos", - "search_page_view_all_button": "View all", - "search_page_your_activity": "Your activity", + "search_page_videos": "동영상", + "search_page_view_all_button": "모두 보기", + "search_page_your_activity": "내 활동", "search_result_page_new_search_hint": "새 검색", - "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", + "search_suggestion_list_smart_search_hint_1": "스마트 검색은 기본적으로 활성화되어 있으며, 메타데이터를 검색하려면 다음 구문을 사용합니다", "search_suggestion_list_smart_search_hint_2": "m:your-search-term", "select_additional_user_for_sharing_page_suggestions": "초대 가능한 사용자 제안", "select_user_for_sharing_page_err_album": "앨범 생성 실패", "select_user_for_sharing_page_share_suggestions": "초대 가능한 사용자 제안", "server_info_box_app_version": "앱 버전", + "server_info_box_server_url": "서버 URL", "server_info_box_server_version": "서버 버전", "setting_image_viewer_help": "상세뷰어는 먼저 작은 썸네일을 불러온 다음 중간크기 미리보기를 불러오고(활성화된 경우) 마지막으로 원본을 불러옵니다(활성화된 경우).", "setting_image_viewer_original_subtitle": "원본 해상도 이미지(고화질)를 로드하려면 활성화합니다. 데이터 사용량을 줄이려면 비활성화합니다.", @@ -300,28 +309,37 @@ "share_add_photos": "사진 추가", "share_add_title": "새 앨범제목", "share_create_album": "앨범 만들기", + "shared_album_activities_input_disable": "댓글이 비활성화되었습니다.", + "shared_album_activities_input_hint": "말하기", + "shared_album_activity_remove_content": "이 활동을 삭제하시겠습니까?", + "shared_album_activity_remove_title": "활동 삭제", + "shared_album_activity_setting_subtitle": "다른 사람이 응답하도록 허용", + "shared_album_activity_setting_title": "댓글 및 좋아요", "share_dialog_preparing": "준비중...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "공유 링크", + "shared_link_create_app_bar_title": "공유할 링크 만들기", + "shared_link_create_info": "링크를 가진 모든 사람이 선택한 사진을 볼 수 있도록 합니다", + "shared_link_create_submit_button": "링크 만들기", + "shared_link_edit_allow_download": "공용 사용자의 다운로드 허용", + "shared_link_edit_allow_upload": "공용 사용자의 업로드 허용", + "shared_link_edit_app_bar_title": "링크 수정", + "shared_link_edit_change_expiry": "만료 시간 변경", + "shared_link_edit_description": "설명", + "shared_link_edit_description_hint": "공유 설명 입력", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "비밀번호", + "shared_link_edit_password_hint": "공유 비밀번호 입력", + "shared_link_edit_show_meta": "메타데이터 표시", + "shared_link_edit_submit_button": "링크 업데이트", + "shared_link_empty": "공유 링크가 없습니다", + "shared_link_manage_links": "공유 링크 관리", + "share_done": "완료", "share_invite": "앨범에 초대", "sharing_page_album": "공유앨범", "sharing_page_description": "공유앨범을 만들어 다른 사용자들과 사진 및 비디오를 공유합니다.", "sharing_page_empty_list": "공유앨범 없음", "sharing_silver_appbar_create_shared_album": "공유앨범 만들기", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "공유 링크", "sharing_silver_appbar_share_partner": "파트너와 공유", "tab_controller_nav_library": "라이브러리", "tab_controller_nav_photos": "사진", @@ -337,30 +355,30 @@ "theme_setting_theme_title": "테마", "theme_setting_three_stage_loading_subtitle": "이 기능은 로딩 성능을 향상시킬 수 있지만 훨씬 더 많은 데이터를 사용합니다.", "theme_setting_three_stage_loading_title": "3단계 로딩 활성화", - "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", - "upload_dialog_cancel": "Cancel", - "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_ok": "Upload", - "upload_dialog_title": "Upload Asset", + "translated_text_options": "옵션", + "trash_page_delete": "삭제", + "trash_page_delete_all": "모두 삭제", + "trash_page_empty_trash_btn": "휴지통 비우기", + "trash_page_empty_trash_dialog_content": "휴지통에 버린 미디어를 비우고 싶으신가요? 이 항목들은 Immich에서 영구적으로 삭제됩니다", + "trash_page_empty_trash_dialog_ok": "확인", + "trash_page_info": "휴지통에 버린 항목은 {}일 후에 영구 삭제됩니다", + "trash_page_no_assets": "휴지통에 버려진 미디어 없음", + "trash_page_restore": "복원", + "trash_page_restore_all": "모두 복원", + "trash_page_select_assets_btn": "미디어 선택", + "trash_page_select_btn": "선택", + "trash_page_title": "휴지통 ({})", + "upload_dialog_cancel": "취소", + "upload_dialog_info": "선택한 미디어를 서버에 백업하시겠습니까?", + "upload_dialog_ok": "업로드", + "upload_dialog_title": "미디어 업로드", "version_announcement_overlay_ack": "승인", "version_announcement_overlay_release_notes": "릴리스 정보", "version_announcement_overlay_text_1": "안녕하세요!", "version_announcement_overlay_text_2": "앱에 새로운 업데이트가 있습니다!", "version_announcement_overlay_text_3": "특히 WatchTower 또는 서버 응용 프로그램 자동 업데이트를 처리하는 메커니즘을 사용하는 경우 잘못된 구성을 방지하기 위해 docker-compose 및 .env 설정이 최신 상태인지 확인하세요.", "version_announcement_overlay_title": "새 서버 버전 사용 가능 \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "스택에서 제거", + "viewer_stack_use_as_main_asset": "메인 미디어로 사용", + "viewer_unstack": "스택 해제" } \ No newline at end of file diff --git a/mobile/assets/i18n/lv-LV.json b/mobile/assets/i18n/lv-LV.json index 966bc8db8..00d11c440 100644 --- a/mobile/assets/i18n/lv-LV.json +++ b/mobile/assets/i18n/lv-LV.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Neizdevās mainīt albuma nosaukumu", "album_viewer_appbar_share_leave": "Pamest albumu", "album_viewer_appbar_share_remove": "Noņemt no albuma", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Pievienot lietotājus", "all_people_page_title": "Cilvēki", "all_videos_page_title": "Videoklipi", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs", "archive_page_title": "Arhīvs ({})", "asset_list_layout_settings_dynamic_layout_title": "Dinamiskais izkārtojums", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Kešatmiņas lietojums", "cache_settings_subtitle": "Kontrolēt Immich mobilās lietotnes kešdarbi", "cache_settings_thumbnail_size": "Sīktēlu keša lielums ({} aktīvi)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Kešdarbes iestatījumi", "change_password_form_confirm_password": "Apstiprināt Paroli", "change_password_form_description": "Sveiki {FirstName} {LastName},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich nepieciešama atļauja skatīt jūsu fotoattēlus un videoklipus.", "profile_drawer_app_logs": "Žurnāli", "profile_drawer_client_server_up_to_date": "Klients un serveris ir atjaunināti", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Iestatījumi", "profile_drawer_sign_out": "Izrakstīties", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Neizdevās izveidot albumu", "select_user_for_sharing_page_share_suggestions": "Ieteikumi", "server_info_box_app_version": "Aplikācijas Versija", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Servera Versija", "setting_image_viewer_help": "Detaļu skatītājs vispirms ielādē mazo sīktēlu, pēc tam ielādē vidēja lieluma priekšskatījumu (ja iespējots), visbeidzot ielādē oriģinālu (ja iespējots).", "setting_image_viewer_original_subtitle": "Iespējojiet sākotnējā pilnas izšķirtspējas attēla (liels!) ielādi. Atspējot lai samazinātu datu lietojumu (gan tīklā, gan ierīces kešatmiņā).", @@ -300,6 +309,12 @@ "share_add_photos": "Pievienot fotoattēlus", "share_add_title": "Pievienot virsrakstu", "share_create_album": "Izveidot albumu", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Notiek sagatavošana...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/mn.json b/mobile/assets/i18n/mn.json index b20d35a7d..6ccea3eff 100644 --- a/mobile/assets/i18n/mn.json +++ b/mobile/assets/i18n/mn.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Failed to change album title", "album_viewer_appbar_share_leave": "Leave album", "album_viewer_appbar_share_remove": "Remove from album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Add users", "all_people_page_title": "People", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cache usage", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Sign Out", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Failed to create album", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "App Version", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "Add photos", "share_add_title": "Add a title", "share_create_album": "Create album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparing...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/nb-NO.json b/mobile/assets/i18n/nb-NO.json index 18cf4475b..20b387c54 100644 --- a/mobile/assets/i18n/nb-NO.json +++ b/mobile/assets/i18n/nb-NO.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Feilet ved endring av albumtittel", "album_viewer_appbar_share_leave": "Forlat album", "album_viewer_appbar_share_remove": "Fjern fra album", + "album_viewer_appbar_share_to": "Del til", "album_viewer_page_share_add_users": "Legg til brukere", "all_people_page_title": "Folk", "all_videos_page_title": "Videoer", + "app_bar_signout_dialog_content": "Er du sikker på at du vil logge ut?", + "app_bar_signout_dialog_ok": "Ja", + "app_bar_signout_dialog_title": "Logg ut", "archive_page_no_archived_assets": "Ingen arkiverte objekter funnet", "archive_page_title": "Arkiv ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamisk bildeorganisering", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Bufferbruk", "cache_settings_subtitle": "Kontroller bufringsadferden til Immich-appen", "cache_settings_thumbnail_size": "Størrelse på miniatyrbildebuffer ({} objekter)", + "cache_settings_tile_subtitle": "Kontroller lokal lagring", + "cache_settings_tile_title": "Lokal lagring", "cache_settings_title": "Bufringsinnstillinger", "change_password_form_confirm_password": "Bekreft passord", "change_password_form_description": "Hei {firstName} {lastName}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Slett", "control_bottom_app_bar_favorite": "Favoritt", "control_bottom_app_bar_share": "Del", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Del til", + "control_bottom_app_bar_stack": "Stable", "control_bottom_app_bar_unarchive": "Fjern fra arkiv", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Last opp", "create_album_page_untitled": "Uten navn", "create_shared_album_page_create": "Opprett", "create_shared_album_page_share": "Del", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Avbryt", "delete_dialog_ok": "Slett", "delete_dialog_title": "Slett permanent", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Er du sikker på at du vil slette denne delte linken?", + "delete_shared_link_dialog_title": "Slett delt link", "description_input_hint_text": "Legg til beskrivelse ...", "description_input_submit_error": "Feil ved oppdatering av beskrivelse, sjekk loggen for flere detaljer", "exif_bottom_sheet_description": "Legg til beskrivelse ...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "Maksimalt 30 objekter kan lastes opp om gangen, hopper over", "image_viewer_page_state_provider_download_error": "Nedlasting feilet", "image_viewer_page_state_provider_download_success": "Nedlasting vellykket", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Delingsfeil", "library_page_albums": "Albumer", "library_page_archive": "Arkiv", "library_page_device_albums": "Albumer på enheten", @@ -179,8 +185,8 @@ "library_page_new_album": "Nytt album", "library_page_sharing": "Deling", "library_page_sort_created": "Nylig opplastet", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Sist endret", + "library_page_sort_most_recent_photo": "Siste bilde", "library_page_sort_title": "Albumtittel", "login_disabled": "Innlogging har blitt deaktivert", "login_form_api_exception": "API-feil. Sjekk URL-en til serveren og prøv igjen.", @@ -218,7 +224,7 @@ "map_settings_dialog_cancel": "Avbryt", "map_settings_dialog_save": "Lagre", "map_settings_dialog_title": "Kartinnstillinger", - "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_archived": "Inkluder arkiverte", "map_settings_only_relative_range": "Datoområde", "map_settings_only_show_favorites": "Vis kun favoritter", "map_zoom_to_see_photos": "Zoom ut for å se bilder", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich trenger tilgang til å se dine bilder og videoer", "profile_drawer_app_logs": "Logg", "profile_drawer_client_server_up_to_date": "Klient og server er oppdatert", + "profile_drawer_documentation": "Dokumentasjon", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Innstillinger", "profile_drawer_sign_out": "Logg ut", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Søppelbøtte", "recently_added_page_title": "Nylig lagt til", "search_bar_hint": "Søk i dine bilder", "search_page_categories": "Kategorier", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Feilet ved oppretting av album", "select_user_for_sharing_page_share_suggestions": "Forslag", "server_info_box_app_version": "App-versjon", + "server_info_box_server_url": "Server-adresse", "server_info_box_server_version": "Server-versjon", "setting_image_viewer_help": "Detaljvisningen laster først miniatyrbildet, deretter forhåndsvisningsbildet (hvis aktivert), og til slutt originalen (hvis aktivert).", "setting_image_viewer_original_subtitle": "Aktiver for å laste originalbildet i full oppløsning (stort!). Deaktiver for å spare databruk (både nettverksbruk og bufferdata på enheten).", @@ -300,28 +309,37 @@ "share_add_photos": "Legg til bilder", "share_add_title": "Legg til tittel", "share_create_album": "Opprett album", + "shared_album_activities_input_disable": "Kommenterer er deaktivert", + "shared_album_activities_input_hint": "Si noe", + "shared_album_activity_remove_content": "Vil du slette denne aktiviteten?", + "shared_album_activity_remove_title": "Slett aktivitet", + "shared_album_activity_setting_subtitle": "La andre respondere", + "shared_album_activity_setting_title": "Kommentarer og likes", "share_dialog_preparing": "Forbereder ...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Delte linker", + "shared_link_create_app_bar_title": "Opprett delelink", + "shared_link_create_info": "La alle med linken se de(t) valgte bilde(ne)", + "shared_link_create_submit_button": "Opprett link", + "shared_link_edit_allow_download": "Tillat offentlig bruker å laste ned", + "shared_link_edit_allow_upload": "Tillat offentlig bruker å laste opp", + "shared_link_edit_app_bar_title": "Endre link", + "shared_link_edit_change_expiry": "Endre utløpstid", + "shared_link_edit_description": "Beskrivelse", + "shared_link_edit_description_hint": "Endre delebeskrivelse", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Passord", + "shared_link_edit_password_hint": "Skriv inn dele-passord", + "shared_link_edit_show_meta": "Vis metadata", + "shared_link_edit_submit_button": "Oppdater link", + "shared_link_empty": "Du har ingen delte linker", + "shared_link_manage_links": "Håndter delte linker", + "share_done": "Ferdig", "share_invite": "Inviter til album", "sharing_page_album": "Delte album", "sharing_page_description": "Lag delte albumer for å dele bilder og videoer med folk i nettverket ditt.", "sharing_page_empty_list": "TOM LISTE", "sharing_silver_appbar_create_shared_album": "Lag delt album", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Delte linker", "sharing_silver_appbar_share_partner": "Del med partner", "tab_controller_nav_library": "Bibliotek", "tab_controller_nav_photos": "Bilder", @@ -338,18 +356,18 @@ "theme_setting_three_stage_loading_subtitle": "Tre-trinns innlasting kan øke lasteytelsen, men forårsaker betydelig høyere nettverksbelastning", "theme_setting_three_stage_loading_title": "Aktiver tre-trinns innlasting", "translated_text_options": "Valg", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_delete": "Slett", + "trash_page_delete_all": "Slett alt", + "trash_page_empty_trash_btn": "Tøm søppelbøtte", + "trash_page_empty_trash_dialog_content": "Vil du tømme søppelbøtten? Objektene vil bli permanent fjernet fra Immich", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_info": "Objekter i søppelbøtten blir permanent fjernet etter {} dager", + "trash_page_no_assets": "Ingen forkastede objekter", + "trash_page_restore": "Gjenopprett", + "trash_page_restore_all": "Gjenopprett alt", + "trash_page_select_assets_btn": "Velg objekter", + "trash_page_select_btn": "Velg", + "trash_page_title": "Søppelbøtte ({})", "upload_dialog_cancel": "Avbryt", "upload_dialog_info": "Vil du utføre backup av valgte objekt(er) til serveren?", "upload_dialog_ok": "Last opp", @@ -360,7 +378,7 @@ "version_announcement_overlay_text_2": "vennligst ta deg tid til å besøke ", "version_announcement_overlay_text_3": " og verifiser at docker-compose og .env-oppsettet ditt er oppdatert for å forhindre en eventuell feilkonfigurasjon, spesielt hvis du benytter WatchTower eller en annen tjeneste som håndterer oppdatering av server-applikasjonen automatisk.", "version_announcement_overlay_title": "Ny serverversjon tilgjengelig", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Fjern fra stabling", + "viewer_stack_use_as_main_asset": "Bruk som hovedobjekt", + "viewer_unstack": "avstable" } \ No newline at end of file diff --git a/mobile/assets/i18n/nl-NL.json b/mobile/assets/i18n/nl-NL.json index 724b650f9..b6cb84c08 100644 --- a/mobile/assets/i18n/nl-NL.json +++ b/mobile/assets/i18n/nl-NL.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Albumtitel wijzigen mislukt", "album_viewer_appbar_share_leave": "Verlaat album", "album_viewer_appbar_share_remove": "Verwijder uit album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Gebruikers toevoegen", "all_people_page_title": "Personen", "all_videos_page_title": "Video's", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "Geen gearchiveerde items gevonden", "archive_page_title": "Archief ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamische layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cachegebruik", "cache_settings_subtitle": "Beheer het cachegedrag van de Immich app", "cache_settings_thumbnail_size": "Thumbnail-cachegrootte ({} items)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Cache-instellingen", "change_password_form_confirm_password": "Bevestig wachtwoord", "change_password_form_description": "Hallo {firstName} {lastName},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich heeft toestemming nodig om je foto's en video's te bekijken.", "profile_drawer_app_logs": "Logboek", "profile_drawer_client_server_up_to_date": "App en server zijn up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Instellingen", "profile_drawer_sign_out": "Uitloggen", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Album aanmaken mislukt", "select_user_for_sharing_page_share_suggestions": "Suggesties", "server_info_box_app_version": "Appversie", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Serverversie", "setting_image_viewer_help": "De gedetailleerde weergave laadt eerst de kleine thumbnail, vervolgens het middelgrote voorbeeld (indien ingeschakeld) en ten slotte het origineel (indien ingeschakeld).", "setting_image_viewer_original_subtitle": "Schakel in om de originele afbeelding met volledige resolutie (groot!) te laden. Schakel uit om datagebruik te verminderen (zowel netwerk als apparaatcache).", @@ -300,6 +309,12 @@ "share_add_photos": "Foto's toevoegen", "share_add_title": "Titel toevoegen", "share_create_album": "Album aanmaken", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Voorbereiden...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/pl-PL.json b/mobile/assets/i18n/pl-PL.json index e9a8e305f..222617b4e 100644 --- a/mobile/assets/i18n/pl-PL.json +++ b/mobile/assets/i18n/pl-PL.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Nie udało się zmienić tytułu albumu", "album_viewer_appbar_share_leave": "Opuść album", "album_viewer_appbar_share_remove": "Usuń z albumu", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Dodaj użytkowników", "all_people_page_title": "Ludzie", "all_videos_page_title": "Filmy", + "app_bar_signout_dialog_content": "Czy na pewno chcesz się wylogować?", + "app_bar_signout_dialog_ok": "Tak", + "app_bar_signout_dialog_title": "Wyloguj się", "archive_page_no_archived_assets": "Nie znaleziono zarchiwizowanych zasobów", "archive_page_title": "Archiwum ({})", "asset_list_layout_settings_dynamic_layout_title": "Układ dynamiczny", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Użycie Cache", "cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich", "cache_settings_thumbnail_size": "Rozmiar pamięci podręcznej miniatur ({} zasobów)", + "cache_settings_tile_subtitle": "Kontroluj zachowanie lokalnego magazynu", + "cache_settings_tile_title": "Lokalny magazyn", "cache_settings_title": "Ustawienia Buforowania", "change_password_form_confirm_password": "Potwierdź Hasło", "change_password_form_description": "Cześć {firstName} {lastName},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.", @@ -131,9 +137,9 @@ "control_bottom_app_bar_favorite": "Ulubione", "control_bottom_app_bar_share": "Udostępnij", "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_stack": "Stos", "control_bottom_app_bar_unarchive": "Cofnij archiwizację", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Wgraj", "create_album_page_untitled": "Bez tytułu", "create_shared_album_page_create": "Utwórz", "create_shared_album_page_share": "Udostępnij", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Anuluj", "delete_dialog_ok": "Usuń", "delete_dialog_title": "Usuń trwale", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Czy na pewno chcesz usunąć ten udostępniony link?", + "delete_shared_link_dialog_title": "Usuń udostępniony link", "description_input_hint_text": "Dodaj opis...", "description_input_submit_error": "Błąd aktualizacji opisu, sprawdź dziennik, aby uzyskać więcej szczegółów", "exif_bottom_sheet_description": "Dodaj Opis...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "Można przesłać maksymalnie 30 zasobów jednocześnie, pomijanie", "image_viewer_page_state_provider_download_error": "Błąd pobierania", "image_viewer_page_state_provider_download_success": "Pobieranie zakończone", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Udostępnij błąd", "library_page_albums": "Albumy", "library_page_archive": "Archiwum", "library_page_device_albums": "Albumy na Urządzeniu", @@ -179,8 +185,8 @@ "library_page_new_album": "Nowy album", "library_page_sharing": "Udostępnianie", "library_page_sort_created": "Ostatnio utworzone", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Ostatnio zmodyfikowany", + "library_page_sort_most_recent_photo": "Najnowsze zdjęcie", "library_page_sort_title": "Tytuł albumu", "login_disabled": "Logowanie zostało wyłączone", "login_form_api_exception": "Wyjątek API. Sprawdź adres URL serwera i spróbuj ponownie.", @@ -218,7 +224,7 @@ "map_settings_dialog_cancel": "Anuluj", "map_settings_dialog_save": "Zapisz", "map_settings_dialog_title": "Ustawienia mapy", - "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_archived": "Uwzględnij zarchiwizowane", "map_settings_only_relative_range": "Zakres dat", "map_settings_only_show_favorites": "Pokaż tylko ulubione", "map_zoom_to_see_photos": "Pomniejsz, aby zobaczyć zdjęcia", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich potrzebuje pozwolenia na przeglądanie Twoich zdjęć i filmów.", "profile_drawer_app_logs": "Logi", "profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne", + "profile_drawer_documentation": "Dokumentacja", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Ustawienia", "profile_drawer_sign_out": "Wyloguj się", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Kosz", "recently_added_page_title": "Ostatnio Dodane", "search_bar_hint": "Szukaj swoich zdjęć", "search_page_categories": "Kategorie", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu", "select_user_for_sharing_page_share_suggestions": "Propozycje", "server_info_box_app_version": "Wersja Aplikacji", + "server_info_box_server_url": "Adres URL", "server_info_box_server_version": "Wersja Serwera", "setting_image_viewer_help": "Przeglądarka szczegółów najpierw ładuje małą miniaturę, następnie ładuje podgląd średniej wielkości (jeśli jest włączony), a na koniec ładuje oryginał (jeśli jest włączony).", "setting_image_viewer_original_subtitle": "Włącz ładowanie oryginalnego obrazu w pełnej rozdzielczości (dużego!). Wyłącz, aby zmniejszyć zużycie danych (zarówno w sieci, jak i w pamięci podręcznej urządzenia).", @@ -300,28 +309,37 @@ "share_add_photos": "Dodaj zdjęcia", "share_add_title": "Dodaj tytuł", "share_create_album": "Utwórz album", + "shared_album_activities_input_disable": "Komentarz jest wyłączony", + "shared_album_activities_input_hint": "Powiedz coś", + "shared_album_activity_remove_content": "Czy chcesz usunąć tę aktywność?", + "shared_album_activity_remove_title": "Usuń aktywność", + "shared_album_activity_setting_subtitle": "Pozwól innym odpowiedzieć", + "shared_album_activity_setting_title": "Komentarze i polubienia", "share_dialog_preparing": "Przygotowywanie...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Udostępnione linki", + "shared_link_create_app_bar_title": "Utwórz link do udostępnienia", + "shared_link_create_info": "Pozwól każdemu, kto ma link, zobaczyć wybrane zdjęcia", + "shared_link_create_submit_button": "Utwórz link", + "shared_link_edit_allow_download": "Zezwalaj użytkownikowi publicznemu na pobieranie", + "shared_link_edit_allow_upload": "Zezwalaj użytkownikowi publicznemu na przesyłanie", + "shared_link_edit_app_bar_title": "Edytuj link", + "shared_link_edit_change_expiry": "Zmień czas ważności", + "shared_link_edit_description": "Opis", + "shared_link_edit_description_hint": "Wprowadź opis udostępnienia", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Hasło", + "shared_link_edit_password_hint": "Wprowadź hasło udostępniania", + "shared_link_edit_show_meta": "Pokaż metadane", + "shared_link_edit_submit_button": "Aktualizuj link", + "shared_link_empty": "Nie masz żadnych udostępnionych linków", + "shared_link_manage_links": "Zarządzaj udostępnionymi linkami", + "share_done": "Zrobione", "share_invite": "Zaproś do albumu", "sharing_page_album": "Udostępnione albumy", "sharing_page_description": "Twórz wspóldzielone albumy, aby udostępniać zdjęcia i filmy osobom w sieci.", "sharing_page_empty_list": "PUSTA LISTA", "sharing_silver_appbar_create_shared_album": "Utwórz współdzielony album", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Udostępnione linki", "sharing_silver_appbar_share_partner": "Udostępnij partnerce/partnerowi", "tab_controller_nav_library": "Biblioteka", "tab_controller_nav_photos": "Zdjęcia", @@ -338,18 +356,18 @@ "theme_setting_three_stage_loading_subtitle": "Trójstopniowe ładowanie może zwiększyć wydajność ładowania, ale powoduje znacznie większe obciążenie sieci", "theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania", "translated_text_options": "Opcje", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", + "trash_page_delete": "Usuń", + "trash_page_delete_all": "Usuń wszystko", "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_empty_trash_dialog_content": "Czy chcesz opróżnić swoje usunięte zasoby? Przedmioty te zostaną trwale usunięte z Immich", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_info": "Elementy przeniesione do kosza zostaną trwale usunięte po {} dniach", + "trash_page_no_assets": "Brak usuniętych zasobów", + "trash_page_restore": "Przywrócić", + "trash_page_restore_all": "Przywrócić wszystkie", + "trash_page_select_assets_btn": "Wybierz zasoby", + "trash_page_select_btn": "Wybierz", + "trash_page_title": "Kosz({})", "upload_dialog_cancel": "Anuluj", "upload_dialog_info": "Czy chcesz wykonać kopię zapasową wybranych zasobów na serwerze?", "upload_dialog_ok": "Prześlij", @@ -360,7 +378,7 @@ "version_announcement_overlay_text_2": "prosimy o poświęcenie czasu na odwiedzenie ", "version_announcement_overlay_text_3": " i upewnij się, że twoja konfiguracja docker-compose i .env jest aktualna, aby zapobiec błędnym konfiguracjom, zwłaszcza jeśli używasz WatchTower lub dowolnego mechanizmu, który obsługuje automatyczną aktualizację aplikacji serwera.", "version_announcement_overlay_title": "Nowa wersja serwera dostępna \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Usuń ze stosu", + "viewer_stack_use_as_main_asset": "Użyj jako głównego zasobu", + "viewer_unstack": "Usuń stos" } \ No newline at end of file diff --git a/mobile/assets/i18n/ru-RU.json b/mobile/assets/i18n/ru-RU.json index 542eab218..9c524a773 100644 --- a/mobile/assets/i18n/ru-RU.json +++ b/mobile/assets/i18n/ru-RU.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Ошибка переименования альбома", "album_viewer_appbar_share_leave": "Покинуть альбом", "album_viewer_appbar_share_remove": "Удалить из альбома", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Добавить пользователей", "all_people_page_title": "Люди", "all_videos_page_title": "Видео", + "app_bar_signout_dialog_content": "Вы уверены, что хотите выйти из системы?", + "app_bar_signout_dialog_ok": "Да", + "app_bar_signout_dialog_title": "Выйти из системы", "archive_page_no_archived_assets": "В архиве сейчас пусто", "archive_page_title": "Архив ({})", "asset_list_layout_settings_dynamic_layout_title": "Динамическое расположение", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Размер кэша", "cache_settings_subtitle": "Управление кэшированием мобильного приложения Immich", "cache_settings_thumbnail_size": "Размер кэша эскизов ({} объектов)", + "cache_settings_tile_subtitle": "Управление поведением локального хранилища", + "cache_settings_tile_title": "Локальное хранилище", "cache_settings_title": "Настройки кэширования", "change_password_form_confirm_password": "Подтвердите пароль", "change_password_form_description": "Привет {firstName} {lastName},\n\nЭто либо ваш первый вход в систему, либо был сделан запрос на смену пароля. Пожалуйста, введите новый пароль ниже.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Удалить", "control_bottom_app_bar_favorite": "Избранное", "control_bottom_app_bar_share": "Поделиться", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_share_to": "Поделиться", + "control_bottom_app_bar_stack": "Стек", "control_bottom_app_bar_unarchive": "Восстановить", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Загрузить", "create_album_page_untitled": "Без названия", "create_shared_album_page_create": "Создать", "create_shared_album_page_share": "Поделиться", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Отменить", "delete_dialog_ok": "Удалить", "delete_dialog_title": "Удалить навсегда", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Вы уверены, что хотите удалить эту общую ссылку?", + "delete_shared_link_dialog_title": "Удалить общую ссылку", "description_input_hint_text": "Добавить описание...", "description_input_submit_error": "Не удалось обновить описание, проверьте логи, чтобы узнать причину", "exif_bottom_sheet_description": "Добавить описание...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "Вы можете выгрузить максимум 30 файлов за раз", "image_viewer_page_state_provider_download_error": "Ошибка загрузки", "image_viewer_page_state_provider_download_success": "Успешно загружено", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Ошибка при публикации", "library_page_albums": "Альбомы", "library_page_archive": "Архив", "library_page_device_albums": "Альбомы на устройстве", @@ -179,8 +185,8 @@ "library_page_new_album": "Новый альбом", "library_page_sharing": "Общие", "library_page_sort_created": "По новизне", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Последнее изменение", + "library_page_sort_most_recent_photo": "Последняя фотография", "library_page_sort_title": "По названию альбома", "login_disabled": "Вход отключен", "login_form_api_exception": "Ошибка при попытке взаимодействия с сервером. Проверьте URL-адрес до него и попробуйте еще раз.", @@ -218,7 +224,7 @@ "map_settings_dialog_cancel": "Отмена", "map_settings_dialog_save": "Сохранить", "map_settings_dialog_title": "Настройки карты", - "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_archived": "Включить архивные данные", "map_settings_only_relative_range": "Период времени", "map_settings_only_show_favorites": "Показать только избранное", "map_zoom_to_see_photos": "Уменьшение масштаба для просмотра фотографий", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich просит вас предоставить разрешение на доступ к вашим фото и видео", "profile_drawer_app_logs": "Журнал", "profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены", + "profile_drawer_documentation": "Документация", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Настройки", "profile_drawer_sign_out": "Выйти", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Корзина", "recently_added_page_title": "Недавно добавленные", "search_bar_hint": "Поиск фотографий", "search_page_categories": "Категории", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "\nНе удалось создать альбом", "select_user_for_sharing_page_share_suggestions": "Предложения", "server_info_box_app_version": "Версия приложения", + "server_info_box_server_url": "URL сервера", "server_info_box_server_version": "Версия сервера", "setting_image_viewer_help": "Средство просмотра деталей сначала загружает маленькую миниатюру, затем загружает предварительный просмотр среднего размера (если включено) и, наконец, загружает оригинал (если включено).", "setting_image_viewer_original_subtitle": "Включите загрузку оригинального изображения в полном разрешении (большое!). Отключите, чтобы уменьшить объем данных (как в сети, так и в кеше устройства).", @@ -300,28 +309,37 @@ "share_add_photos": "Добавить фото", "share_add_title": "Добавить название", "share_create_album": "Создать альбом", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Подготовка...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Общие ссылки", + "shared_link_create_app_bar_title": "Создать ссылку для совместного использования", + "shared_link_create_info": "Позволить любому человеку, имеющему ссылку, увидеть выбранную фотографию (фотографии)", + "shared_link_create_submit_button": "Создать ссылку", + "shared_link_edit_allow_download": "Разрешить публичному пользователю скачивать", + "shared_link_edit_allow_upload": "Разрешить публичному пользователю загружать файлы", + "shared_link_edit_app_bar_title": "Редактировать ссылку", + "shared_link_edit_change_expiry": "Изменить срок действия доступа", + "shared_link_edit_description": "Описание", + "shared_link_edit_description_hint": "Введите описание совместного доступа", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Пароль", + "shared_link_edit_password_hint": "Введите пароль общего доступа", + "shared_link_edit_show_meta": "Показать метаданные", + "shared_link_edit_submit_button": "Обновить ссылку", + "shared_link_empty": "У вас нет общих ссылок", + "shared_link_manage_links": "Управление общими ссылками", + "share_done": "Выполнено", "share_invite": "\nПригласить в альбом", "sharing_page_album": "Общие альбомы", "sharing_page_description": "Создавайте общие альбомы, чтобы делиться фотографиями и видео с людьми в вашей сети.", "sharing_page_empty_list": "ПУСТОЙ СПИСОК", "sharing_silver_appbar_create_shared_album": "Создать общий альбом", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Общие ссылки", "sharing_silver_appbar_share_partner": "Поделиться с партнёром", "tab_controller_nav_library": "Библиотека", "tab_controller_nav_photos": "Фото", @@ -338,18 +356,18 @@ "theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность загрузки, но вызывает значительно более высокую нагрузку на сеть", "theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку", "translated_text_options": "Опции", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_delete": "Удалить", + "trash_page_delete_all": "Удалить все", + "trash_page_empty_trash_btn": "Очистить корзину", + "trash_page_empty_trash_dialog_content": "Вы хотите очистить свою корзину? Эти объекты будут навсегда удалены из Immich", + "trash_page_empty_trash_dialog_ok": "ОК", + "trash_page_info": "Удаленные элементы будут окончательно удалены через {} дней", + "trash_page_no_assets": "Отсутствие удаленных объектов", + "trash_page_restore": "Восстановить", + "trash_page_restore_all": "Восстановить все", + "trash_page_select_assets_btn": "Выбранные объекты", + "trash_page_select_btn": "Выбрать", + "trash_page_title": "Корзина ({})", "upload_dialog_cancel": "Отмена", "upload_dialog_info": "Вы хотите загрузить выбранный объект(ы) на ваш сервер?", "upload_dialog_ok": "Загрузить", @@ -360,7 +378,7 @@ "version_announcement_overlay_text_2": "пожалуйста, найдите время, чтобы посетить", "version_announcement_overlay_text_3": " и убедитесь, что ваши настройки docker-compose и .env обновлены, чтобы предотвратить любые неправильные настройки, особенно если вы используете WatchTower или любой другой механизм, который обрабатывает обновление вашего серверного приложения автоматически.", "version_announcement_overlay_title": "Доступна новая версия сервера \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Удалить из стека", + "viewer_stack_use_as_main_asset": "Использование в качестве основного объекта", + "viewer_unstack": "Разобрать стек" } \ No newline at end of file diff --git a/mobile/assets/i18n/sk-SK.json b/mobile/assets/i18n/sk-SK.json index 42ff34680..2c6e6e2be 100644 --- a/mobile/assets/i18n/sk-SK.json +++ b/mobile/assets/i18n/sk-SK.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Nepodarilo sa zmeniť názov albumu", "album_viewer_appbar_share_leave": "Opustiť album", "album_viewer_appbar_share_remove": "Odstrániť z albumu", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Pridať používateľov", "all_people_page_title": "Ľudia", "all_videos_page_title": "Videá", + "app_bar_signout_dialog_content": "Skutočne sa chcete odhlásiť?", + "app_bar_signout_dialog_ok": "Áno", + "app_bar_signout_dialog_title": "Odhlásiť sa", "archive_page_no_archived_assets": "Žiadne archivované médiá", "archive_page_title": "Archív ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamické rozloženie", @@ -65,7 +69,7 @@ "backup_controller_page_background_is_on": "Automatické zálohovanie na pozadí je zapnuté", "backup_controller_page_background_turn_off": "Vypnúť zálohovanie na pozadí", "backup_controller_page_background_turn_on": "Povoliť zálohovanie na pozadí", - "backup_controller_page_background_wifi": "Len na WiFi", + "backup_controller_page_background_wifi": "Len cez WiFi", "backup_controller_page_backup": "Zálohovanie", "backup_controller_page_backup_selected": "Vybrané: ", "backup_controller_page_backup_sub": "Zálohované fotografie a videa", @@ -86,7 +90,7 @@ "backup_controller_page_status_off": "Automatické zálohovanie na popredí je vypnuté", "backup_controller_page_status_on": "Automatické zálohovanie na popredí je zapnuté", "backup_controller_page_storage_format": "{} z {} použitých", - "backup_controller_page_to_backup": "Albumy, ktoré sa majú zálohovať", + "backup_controller_page_to_backup": "Albumy ktoré budú zálohované", "backup_controller_page_total": "Celkom", "backup_controller_page_total_sub": "Všetky jedinečné fotografie a videá z vybraných albumov", "backup_controller_page_turn_off": "Vypnúť zálohovanie na popredí", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Použitie vyrovnávacej pamäte", "cache_settings_subtitle": "Ovládanie správania mobilnej aplikácie Immich v medzipamäti", "cache_settings_thumbnail_size": "Veľkosť vyrovnávacej pamäte náhľadov (položiek {})", + "cache_settings_tile_subtitle": "Ovládanie správania lokálneho úložiska", + "cache_settings_tile_title": "Lokálne úložisko", "cache_settings_title": "Nastavenia vyrovnávacej pamäte", "change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_description": "Dobrý deň, {firstName} {lastName},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.", @@ -125,15 +131,15 @@ "control_bottom_app_bar_add_to_album": "Pridať do albumu", "control_bottom_app_bar_album_info": "{} položiek", "control_bottom_app_bar_album_info_shared": "{} položiek - zdieľané", - "control_bottom_app_bar_archive": "Archív", + "control_bottom_app_bar_archive": "Archivovať", "control_bottom_app_bar_create_new_album": "Vytvoriť nový album", "control_bottom_app_bar_delete": "Vymazať", "control_bottom_app_bar_favorite": "Obľúbené", "control_bottom_app_bar_share": "Zdieľať", "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_stack": "Stack", + "control_bottom_app_bar_stack": "Zoskupenie", "control_bottom_app_bar_unarchive": "Odarchivovať", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Nahrať", "create_album_page_untitled": "Bez názvu", "create_shared_album_page_create": "Vytvoriť", "create_shared_album_page_share": "Zdieľať", @@ -148,8 +154,8 @@ "delete_dialog_cancel": "Zrušiť", "delete_dialog_ok": "Vymazať", "delete_dialog_title": "Vymazať natrvalo", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Ste si istí že chcete odstrániť tento zdieľaný odkaz?", + "delete_shared_link_dialog_title": "Odstrániť zdieľaný odkaz", "description_input_hint_text": "Pridať popis...", "description_input_submit_error": "Chyba pri aktualizovaní popisu, zobrazte log pre viac detailov", "exif_bottom_sheet_description": "Pridať popis...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "Naraz môžete nahrať len 30 médií, preskakujem...", "image_viewer_page_state_provider_download_error": "Chyba sťahovania", "image_viewer_page_state_provider_download_success": "Sťahovanie bolo úspešné", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "Chyba zdieľania", "library_page_albums": "Albumy", "library_page_archive": "Archív", "library_page_device_albums": "Albumy v zariadení", @@ -179,8 +185,8 @@ "library_page_new_album": "Nový album", "library_page_sharing": "Zdieľanie", "library_page_sort_created": "Najnovšie vytvorené", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Naposledy upravené", + "library_page_sort_most_recent_photo": "Najnovšia fotka", "library_page_sort_title": "Podľa názvu albumu", "login_disabled": "Prihlasovanie bolo vypnuté", "login_form_api_exception": "Chyba API. Skontrolujte adresu URL servera a skúste to znova.", @@ -218,7 +224,7 @@ "map_settings_dialog_cancel": "Zrušiť", "map_settings_dialog_save": "Uložiť", "map_settings_dialog_title": "Nastavenia máp", - "map_settings_include_show_archived": "Include Archived", + "map_settings_include_show_archived": "Zahrnúť archivované", "map_settings_only_relative_range": "Rozsah dátumu", "map_settings_only_show_favorites": "Zobraziť iba obľúbené", "map_zoom_to_see_photos": "Oddiaľte priblíženie aby ste videli fotky", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich vyžaduje povolenie na prezeranie vašich fotografií a videí.", "profile_drawer_app_logs": "Logy", "profile_drawer_client_server_up_to_date": "Klient a server sú aktuálne", + "profile_drawer_documentation": "Dokumentácia", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Nastavenia", "profile_drawer_sign_out": "Odhlásiť sa", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Kôš", "recently_added_page_title": "Nedávno pridané", "search_bar_hint": "Prehľadajte svoje obrázky", "search_page_categories": "Kategórie", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Nepodarilo sa vytvoriť album", "select_user_for_sharing_page_share_suggestions": "Návrhy", "server_info_box_app_version": "Verzia aplikácie", + "server_info_box_server_url": "URL Serveru", "server_info_box_server_version": "Verzia servera", "setting_image_viewer_help": "Prehliadač detailov najprv načíta malú miniatúru, potom načíta náhľad strednej veľkosti (ak je povolený) a nakoniec načíta originál (ak je povolený).", "setting_image_viewer_original_subtitle": "Povolením umožníte načítať pôvodný obrázok v plnom rozlíšení (veľký!). Zakázaním znížite používania dát (v sieti, aj v dočasnej pamäte zariadenia).", @@ -300,28 +309,37 @@ "share_add_photos": "Pridať fotografie", "share_add_title": "Pridať názov", "share_create_album": "Vytvoriť album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Pripravujem...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Zdieľané odkazy", + "shared_link_create_app_bar_title": "Vytvoriť odkaz na zdieľanie", + "shared_link_create_info": "Umožniť komukoľvek s odkazom zobraziť označené médiá", + "shared_link_create_submit_button": "Vytvoriť odkaz", + "shared_link_edit_allow_download": "Povoliť návštevníkom sťahovať médiá", + "shared_link_edit_allow_upload": "Povoliť návštevníkom pridávať médiá", + "shared_link_edit_app_bar_title": "Upraviť odkaz", + "shared_link_edit_change_expiry": "Zmeniť čas vypršania", + "shared_link_edit_description": "Popis", + "shared_link_edit_description_hint": "Zadajte popis zdieľania", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Heslo", + "shared_link_edit_password_hint": "Zadajte heslo zdieľania", + "shared_link_edit_show_meta": "Zobraziť metadáta", + "shared_link_edit_submit_button": "Aktualizovať odkaz", + "shared_link_empty": "Zatiaľ nemáte žiadne zdieľané odkazy", + "shared_link_manage_links": "Spravovať zdieľané odkazy", + "share_done": "Hotovo", "share_invite": "Pozvať do albumu", "sharing_page_album": "Zdieľané albumy", "sharing_page_description": "Vytvárajte zdieľané albumy a zdieľajte fotografie a videá s ľuďmi vo vašej sieti.", "sharing_page_empty_list": "Prázdny list", "sharing_silver_appbar_create_shared_album": "Vytvoriť zdieľaný album", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Zdieľané odkazy", "sharing_silver_appbar_share_partner": "Zdieľať s partnerom", "tab_controller_nav_library": "Knižnica", "tab_controller_nav_photos": "Fotografie", @@ -338,18 +356,18 @@ "theme_setting_three_stage_loading_subtitle": "Trojstupňové načítanie môže zvýšiť výkonnosť načítania, ale vedie k výrazne vyššiemu zaťaženiu siete.", "theme_setting_three_stage_loading_title": "Povolenie trojstupňového načítavania", "translated_text_options": "Nastavenia", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", + "trash_page_delete": "Vymazať", + "trash_page_delete_all": "Vymazať všetky", + "trash_page_empty_trash_btn": "Vyprázdniť kôš", + "trash_page_empty_trash_dialog_content": "Skutočne chcete vyprázdniť kôš? Tieto položky budú permanentne odstránené z Immichu", "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", + "trash_page_info": "Médiá v koši sa permanentne odstránia po {} dňoch", + "trash_page_no_assets": "Žiadne médiá v koši", + "trash_page_restore": "Obnoviť", + "trash_page_restore_all": "Obnoviť všetky", + "trash_page_select_assets_btn": "Označiť médiá", + "trash_page_select_btn": "Označiť", + "trash_page_title": "Kôš ({})", "upload_dialog_cancel": "Zrušiť", "upload_dialog_info": "Chcete zálohovať zvolené médiá na server?", "upload_dialog_ok": "Nahrať", @@ -360,7 +378,7 @@ "version_announcement_overlay_text_2": "nájdite si čas na návštevu ", "version_announcement_overlay_text_3": " a uistite sa, že vaša konfigurácia docker-compose a .env je aktuálna, aby ste predišli nesprávnej konfigurácii, najmä ak používate WatchTower alebo akýkoľvek mechanizmus, ktorý podporuje automatické aktualizácie serverových aplikácií.", "version_announcement_overlay_title": "K dispozícii je nová verzia servera \uD83C\uDF89", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" + "viewer_remove_from_stack": "Odstrániť zo zoskupenia", + "viewer_stack_use_as_main_asset": "Použiť ako hlavnú fotku", + "viewer_unstack": "Odskupiť" } \ No newline at end of file diff --git a/mobile/assets/i18n/sr-Cyrl.json b/mobile/assets/i18n/sr-Cyrl.json index 31311535b..672ab28fb 100644 --- a/mobile/assets/i18n/sr-Cyrl.json +++ b/mobile/assets/i18n/sr-Cyrl.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Failed to change album title", "album_viewer_appbar_share_leave": "Leave album", "album_viewer_appbar_share_remove": "Remove from album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Add users", "all_people_page_title": "People", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cache usage", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Sign Out", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Failed to create album", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "App Version", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "Add photos", "share_add_title": "Add a title", "share_create_album": "Create album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparing...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/sr-Latn.json b/mobile/assets/i18n/sr-Latn.json index 6273f33fc..6d3fb153b 100644 --- a/mobile/assets/i18n/sr-Latn.json +++ b/mobile/assets/i18n/sr-Latn.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Neuspešno menjanje naziva albuma", "album_viewer_appbar_share_leave": "Izađi iz albuma", "album_viewer_appbar_share_remove": "Obriši iz albuma", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Dodaj korisnike", "all_people_page_title": "People", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Dinamični raspored", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Iskorišćena keš memorija", "cache_settings_subtitle": "Kontrole za keš memoriju mobilne aplikacije Immich", "cache_settings_thumbnail_size": "Keš memorija koju zauzimaju minijature ({} stavki)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Opcije za keširanje", "change_password_form_confirm_password": "Ponovo unesite šifru", "change_password_form_description": "Ćao, {firstName}, {lastName}\n\nOvo je verovatno Vaše prvo pristupanje sistemu, ili je podnešen zahtev za promenu šifre. Molimo Vas, unesite novu šifru ispod", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Evidencija", "profile_drawer_client_server_up_to_date": "Klijent i server su najnovije verzije", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Opcije", "profile_drawer_sign_out": "Odjavi se", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Neuspešno kreiranje albuma", "select_user_for_sharing_page_share_suggestions": "Sugestije", "server_info_box_app_version": "Verzija Aplikacije", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Verzija Servera", "setting_image_viewer_help": "Detaljno pregledanje prvo učitava minijaturu, pa srednju, pa original. (Ako te opcije uključene)", "setting_image_viewer_original_subtitle": "Aktiviraj učitavanje slika u punoj rezoluciji (Velika!). Deaktivacijom ove stavke možeš da smanjiš potrošnju interneta i zauzetog prostora na uređaju.", @@ -300,6 +309,12 @@ "share_add_photos": "Dodaj fotografije", "share_add_title": "Dodaj naslov", "share_create_album": "Napravi album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Pripremanje...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/sv-FI.json b/mobile/assets/i18n/sv-FI.json index 31311535b..672ab28fb 100644 --- a/mobile/assets/i18n/sv-FI.json +++ b/mobile/assets/i18n/sv-FI.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Failed to change album title", "album_viewer_appbar_share_leave": "Leave album", "album_viewer_appbar_share_remove": "Remove from album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Add users", "all_people_page_title": "People", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cache usage", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Caching Settings", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Sign Out", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Failed to create album", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "App Version", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "Add photos", "share_add_title": "Add a title", "share_create_album": "Create album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Preparing...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/sv-SE.json b/mobile/assets/i18n/sv-SE.json index 04ae04992..e356e63a1 100644 --- a/mobile/assets/i18n/sv-SE.json +++ b/mobile/assets/i18n/sv-SE.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Kunde inte ändra albumtitel", "album_viewer_appbar_share_leave": "Lämna album", "album_viewer_appbar_share_remove": "Ta bort från album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Lägg till användare", "all_people_page_title": "People", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Arkivera ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cacheförbrukning", "cache_settings_subtitle": "Hantera cachebeteendet för Immich-appen.", "cache_settings_thumbnail_size": "Storlek på cacheminnet ({} bilder och videor)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Cache Inställningar", "change_password_form_confirm_password": "Bekräfta lösenord", "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich kräver tillstånd för att se dina foton och videor.", "profile_drawer_app_logs": "Loggar", "profile_drawer_client_server_up_to_date": "Klient och server är uppdaterade", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Inställningar", "profile_drawer_sign_out": "Logga ut", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Kunde inte skapa nytt album", "select_user_for_sharing_page_share_suggestions": "Förslag", "server_info_box_app_version": "App version", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Server version", "setting_image_viewer_help": "Detaljerad vy laddar miniatyrer först. Efter detta laddas den medelstora förhandsgranskningen av bilden (om detta är aktiverat), och visar slutligen originalet (om detta är aktiverat).", "setting_image_viewer_original_subtitle": "Aktivera för att ladda originalbilden i full storlek (stor!). Inaktivera för att minska dataanvändningen (både i nätverket och för enhetscache).", @@ -300,6 +309,12 @@ "share_add_photos": "Lägg till foton", "share_add_title": "Lägg till en titel", "share_create_album": "Skapa album", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Förbereder...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/th-TH.json b/mobile/assets/i18n/th-TH.json index 6f89ce173..c60ceadce 100644 --- a/mobile/assets/i18n/th-TH.json +++ b/mobile/assets/i18n/th-TH.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "เปลี่ยนชื่ออัลบั้มไม่สำเร็จ", "album_viewer_appbar_share_leave": "ออกจากอัลบั้ม", "album_viewer_appbar_share_remove": "ลบออกจากอัลบั้ม", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "เพิ่มผู้ใช้งาน", "all_people_page_title": "ผู้คน", "all_videos_page_title": "วิดีโอ", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "ไม่พบทรัพยากรในที่เก็บถาวร", "archive_page_title": "เก็บถาวร ({})", "asset_list_layout_settings_dynamic_layout_title": "แผนผังปรับตัว", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "การใช้งานแคช", "cache_settings_subtitle": "ควบคุมพฤติกรรมการแคชของแอปพลิเคชัน Immich", "cache_settings_thumbnail_size": "ขนาดแคชรูปย่อ ({} ทรัพยากร)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "ตั้งค่าแคช", "change_password_form_confirm_password": "ยืนยันรหัสผ่าน", "change_password_form_description": "สวัสดี {firstName} {lastName},\n\nครั้งนี้อาจจะเป็นครั้งแรกที่คุณเข้าสู่ระบบ หรือมีคำขอเพื่อที่จะเปลี่ยนรหัสผ่านของคุI กรุณาเพิ่มรหัสผ่านใหม่ข้างล่าง", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich จำเป็นจะต้องได้รับสิทธิ์ดูรูปภาพและวิดีโอ", "profile_drawer_app_logs": "Log", "profile_drawer_client_server_up_to_date": "ไคลเอนต์และเซิร์ฟเวอร์เป็นปัจจุบัน", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "ออกจากระบบ", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "สร้างอัลบั้มล้มเหลว", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "เวอร์ชั่นแอพ", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "เวอร์ชั้นเซิร์ฟเวอร์", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "เพิ่มรูปภาพ", "share_add_title": "เพิ่มชื่อ", "share_create_album": "สร้างอัลบั้ม", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "กำลังเตรียม...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/uk-UA.json b/mobile/assets/i18n/uk-UA.json index 6a733c4f1..3ea6fac43 100644 --- a/mobile/assets/i18n/uk-UA.json +++ b/mobile/assets/i18n/uk-UA.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Не вдалося змінити назву альбому", "album_viewer_appbar_share_leave": "Вийти з альбому", "album_viewer_appbar_share_remove": "Видалити з альбому", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Додати користувачів", "all_people_page_title": "Люди", "all_videos_page_title": "Відео", + "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", + "app_bar_signout_dialog_ok": "Yes", + "app_bar_signout_dialog_title": "Sign out", "archive_page_no_archived_assets": "Немає архівних елементів", "archive_page_title": "Архів ({})", "asset_list_layout_settings_dynamic_layout_title": "Динамічне компонування", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Використання кешу", "cache_settings_subtitle": "Контролює кешування у мобільному застосунку", "cache_settings_thumbnail_size": "Розмір кешованих мініатюр ({} елементи)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", "cache_settings_title": "Налаштування Кешування", "change_password_form_confirm_password": "Підтвердити пароль", "change_password_form_description": "Привіт {firstName} {lastName},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.", @@ -250,6 +256,8 @@ "permission_onboarding_request": "Immich потребує доступу до ваших знімків та відео.", "profile_drawer_app_logs": "Журнал", "profile_drawer_client_server_up_to_date": "Клієнт та Сервер — актуальні", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Налаштування", "profile_drawer_sign_out": "Вийти", "profile_drawer_trash": "Trash", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Не вдалося створити альбом", "select_user_for_sharing_page_share_suggestions": "Suggestions", "server_info_box_app_version": "App Version", + "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,6 +309,12 @@ "share_add_photos": "Додати знімки", "share_add_title": "Додати назву", "share_create_album": "Створити альбом", + "shared_album_activities_input_disable": "Comment is disabled", + "shared_album_activities_input_hint": "Say something", + "shared_album_activity_remove_content": "Do you want to delete this activity?", + "shared_album_activity_remove_title": "Delete Activity", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "Comments & likes", "share_dialog_preparing": "Підготовка...", "shared_link_app_bar_title": "Shared Links", "shared_link_create_app_bar_title": "Create link to share", @@ -311,6 +326,9 @@ "shared_link_edit_change_expiry": "Change expiration time", "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "Password", + "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", "shared_link_edit_submit_button": "Update link", "shared_link_empty": "You don't have any shared links", diff --git a/mobile/assets/i18n/vi-VN.json b/mobile/assets/i18n/vi-VN.json index 340efef7d..21a8e2da0 100644 --- a/mobile/assets/i18n/vi-VN.json +++ b/mobile/assets/i18n/vi-VN.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "Failed to change album title", "album_viewer_appbar_share_leave": "Leave album", "album_viewer_appbar_share_remove": "Remove from album", + "album_viewer_appbar_share_to": "Chia sẻ với", "album_viewer_page_share_add_users": "Add users", - "all_people_page_title": "People", + "all_people_page_title": "Mọi người", "all_videos_page_title": "Videos", + "app_bar_signout_dialog_content": "Bạn có muốn đăng xuất?", + "app_bar_signout_dialog_ok": "Có", + "app_bar_signout_dialog_title": "Đăng xuất", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({})", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", @@ -69,7 +73,7 @@ "backup_controller_page_backup": "Backup", "backup_controller_page_backup_selected": "Selected: ", "backup_controller_page_backup_sub": "Backed up photos and videos", - "backup_controller_page_cancel": "Cancel", + "backup_controller_page_cancel": "Từ chối", "backup_controller_page_created": "Created on: {}", "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.", "backup_controller_page_excluded": "Excluded: ", @@ -95,10 +99,10 @@ "backup_err_only_album": "Cannot remove the only album", "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", - "backup_manual_failed": "Failed", + "backup_manual_failed": "Thất bại", "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", + "backup_manual_success": "Thành công", + "backup_manual_title": "Trạng thái tải lên", "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", "cache_settings_clear_cache_button": "Clear cache", "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "Cache usage", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", + "cache_settings_tile_subtitle": "Kiểm soát cách xử lý lưu trữ cục bộ", + "cache_settings_tile_title": "Lưu trữ cục bộ", "cache_settings_title": "Caching Settings", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "Delete", "control_bottom_app_bar_favorite": "Favorite", "control_bottom_app_bar_share": "Share", - "control_bottom_app_bar_share_to": "Share To", + "control_bottom_app_bar_share_to": "Chia sẻ với", "control_bottom_app_bar_stack": "Stack", "control_bottom_app_bar_unarchive": "Unarchive", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "Tải lên", "create_album_page_untitled": "Untitled", "create_shared_album_page_create": "Create", "create_shared_album_page_share": "Share", @@ -145,11 +151,11 @@ "daily_title_text_date_year": "E, MMM dd, yyyy", "date_format": "E, LLL d, y • h:mm a", "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", - "delete_dialog_cancel": "Cancel", + "delete_dialog_cancel": "Từ chối", "delete_dialog_ok": "Delete", "delete_dialog_title": "Delete Permanently", - "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_content": "Bạn có muốn xóa liên kết đã chia sẻ này không?", + "delete_shared_link_dialog_title": "Xoá liên kết đã chia sẻ", "description_input_hint_text": "Add description...", "description_input_submit_error": "Error updating description, check the log for more details", "exif_bottom_sheet_description": "Add Description...", @@ -179,8 +185,8 @@ "library_page_new_album": "New album", "library_page_sharing": "Sharing", "library_page_sort_created": "Most recently created", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "Sửa đổi lần cuối", + "library_page_sort_most_recent_photo": "Ảnh gần đây nhất", "library_page_sort_title": "Album title", "login_disabled": "Login has been disabled", "login_form_api_exception": "API exception. Please check the server URL and try again.", @@ -199,32 +205,32 @@ "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", "login_form_label_email": "Email", "login_form_label_password": "Password", - "login_form_next_button": "Next", + "login_form_next_button": "Tiếp tục", "login_form_password_hint": "password", "login_form_save_login": "Stay logged in", "login_form_server_empty": "Enter a server URL.", "login_form_server_error": "Could not connect to server.", "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", + "login_password_changed_success": "Cập nhật mật khẩu thành công", "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_cancel": "Cancel", - "map_location_dialog_yes": "Yes", + "map_location_dialog_cancel": "Từ chối", + "map_location_dialog_yes": "Có", "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", "map_location_service_disabled_title": "Location Service disabled", "map_no_assets_in_bounds": "No photos in this area", "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", "map_no_location_permission_title": "Location Permission denied", - "map_settings_dark_mode": "Dark mode", - "map_settings_dialog_cancel": "Cancel", - "map_settings_dialog_save": "Save", - "map_settings_dialog_title": "Map Settings", + "map_settings_dark_mode": "Chế độ tối", + "map_settings_dialog_cancel": "Từ chối", + "map_settings_dialog_save": "Lưu", + "map_settings_dialog_title": "Cài đặt bản đồ", "map_settings_include_show_archived": "Include Archived", "map_settings_only_relative_range": "Date range", "map_settings_only_show_favorites": "Show Favorite Only", "map_zoom_to_see_photos": "Zoom out to see photos", "monthly_title_text_date_format": "MMMM y", "motion_photos_page_title": "Motion Photos", - "notification_permission_dialog_cancel": "Cancel", + "notification_permission_dialog_cancel": "Từ chối", "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", "notification_permission_dialog_settings": "Settings", "notification_permission_list_tile_content": "Grant permission to enable notifications.", @@ -235,7 +241,7 @@ "partner_page_no_more_users": "No more users to add", "partner_page_partner_add_failed": "Failed to add partner", "partner_page_select_partner": "Select partner", - "partner_page_shared_to_title": "Shared to", + "partner_page_shared_to_title": "Chia sẻ với", "partner_page_stop_sharing_content": "{} will no longer be able to access your photos.", "partner_page_stop_sharing_title": "Stop sharing your photos?", "partner_page_title": "Partner", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", + "profile_drawer_documentation": "Tài liệu", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Sign Out", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "Thùng rác", "recently_added_page_title": "Recently Added", "search_bar_hint": "Search your photos", "search_page_categories": "Categories", @@ -260,7 +268,7 @@ "search_page_motion_photos": "Motion Photos", "search_page_no_objects": "Không có thông tin vật thể", "search_page_no_places": "No Places Info Available", - "search_page_people": "People", + "search_page_people": "Mọi người", "search_page_places": "Places", "search_page_recently_added": "Recently added", "search_page_screenshots": "Screenshots", @@ -276,6 +284,7 @@ "select_user_for_sharing_page_err_album": "Failed to create album", "select_user_for_sharing_page_share_suggestions": "Gợi ý", "server_info_box_app_version": "App Version", + "server_info_box_server_url": "Địa chỉ máy chủ", "server_info_box_server_version": "Server Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", @@ -300,28 +309,37 @@ "share_add_photos": "Add photos", "share_add_title": "Add a title", "share_create_album": "Create album", + "shared_album_activities_input_disable": "Nhận xét hiện đã tắt", + "shared_album_activities_input_hint": "Nói điều gì đó", + "shared_album_activity_remove_content": "Bạn có muốn xoá hoạt động này?", + "shared_album_activity_remove_title": "Xoá hoạt động", + "shared_album_activity_setting_subtitle": "Cho phép người khác phản hồi", + "shared_album_activity_setting_title": "Bình luận và lượt thích", "share_dialog_preparing": "Preparing...", - "shared_link_app_bar_title": "Shared Links", - "shared_link_create_app_bar_title": "Create link to share", - "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", - "shared_link_edit_allow_download": "Allow public user to download", - "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", - "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_app_bar_title": "Đường liên kết chia sẻ", + "shared_link_create_app_bar_title": "Tạo liên kết để chia sẻ", + "shared_link_create_info": "Cho phép bất cứ ai có liên kết xem (các) ảnh đã chọn", + "shared_link_create_submit_button": "Tạo liên kết", + "shared_link_edit_allow_download": "Cho phép bất cứ ai đều có thể tải xuống", + "shared_link_edit_allow_upload": "Cho phép bất cứ ai đều có thể tải lên", + "shared_link_edit_app_bar_title": "Chỉnh sửa liên kết", + "shared_link_edit_change_expiry": "Thay đổi thời gian hết hạn", + "shared_link_edit_description": "Mô tả", + "shared_link_edit_description_hint": "Nhập mô tả chia sẻ", + "shared_link_edit_expire_after": "Hết hạn sau", + "shared_link_edit_password": "Mật khẩu", + "shared_link_edit_password_hint": "Nhập mật khẩu chia sẻ", + "shared_link_edit_show_meta": "Hiện thị siêu dữ liệu", + "shared_link_edit_submit_button": "Cập nhật liên kết", + "shared_link_empty": "Bạn không có liên kết được chia sẻ nào", + "shared_link_manage_links": "Quản lý liên kết được chia sẻ", + "share_done": "Hoàn tất", "share_invite": "Invite to album", "sharing_page_album": "Shared albums", "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", "sharing_page_empty_list": "EMPTY LIST", "sharing_silver_appbar_create_shared_album": "Create shared album", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "Các liên kết chia sẻ", "sharing_silver_appbar_share_partner": "Share with partner", "tab_controller_nav_library": "Library", "tab_controller_nav_photos": "Photos", @@ -337,22 +355,22 @@ "theme_setting_theme_title": "Theme", "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", "theme_setting_three_stage_loading_title": "Enable three-stage loading", - "translated_text_options": "Options", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", - "trash_page_info": "Trashed items will be permanently deleted after {} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", - "trash_page_title": "Trash ({})", - "upload_dialog_cancel": "Cancel", + "translated_text_options": "Tuỳ chỉnh", + "trash_page_delete": "Xoá", + "trash_page_delete_all": "Xoá tất cả", + "trash_page_empty_trash_btn": "Dọn sạch thùng rác", + "trash_page_empty_trash_dialog_content": "Bạn có muốn dọn sạch thùng rác của mình không? Những mục này sẽ bị xoá vĩnh viễn khỏi Immich", + "trash_page_empty_trash_dialog_ok": "Đồng ý", + "trash_page_info": "Những mục này sẽ bị xoá sau {} ngày", + "trash_page_no_assets": "Không có ảnh hoặc video", + "trash_page_restore": "Khôi phục", + "trash_page_restore_all": "Khôi phục tất cả", + "trash_page_select_assets_btn": "Chọn mục", + "trash_page_select_btn": "Chọn", + "trash_page_title": "Thùng rác ({})", + "upload_dialog_cancel": "Từ chối", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_ok": "Upload", + "upload_dialog_ok": "Tải lên", "upload_dialog_title": "Upload Asset", "version_announcement_overlay_ack": "Acknowledge", "version_announcement_overlay_release_notes": "release notes", diff --git a/mobile/assets/i18n/zh-CN.json b/mobile/assets/i18n/zh-CN.json index 6ddf38545..7e9e4757f 100644 --- a/mobile/assets/i18n/zh-CN.json +++ b/mobile/assets/i18n/zh-CN.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "修改相册标题失败", "album_viewer_appbar_share_leave": "退出共享", "album_viewer_appbar_share_remove": "从相册中移除", + "album_viewer_appbar_share_to": "共享给", "album_viewer_page_share_add_users": "创建用户", "all_people_page_title": "人物", "all_videos_page_title": "视频", + "app_bar_signout_dialog_content": "您确定要注消吗?", + "app_bar_signout_dialog_ok": "是", + "app_bar_signout_dialog_title": "注消", "archive_page_no_archived_assets": "未找到归档项目", "archive_page_title": "归档({})", "asset_list_layout_settings_dynamic_layout_title": "动态布局", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "缓存使用情况", "cache_settings_subtitle": "控制 Immich 的缓存行为", "cache_settings_thumbnail_size": "缩略图缓存大小({} 项)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "本地存储", "cache_settings_title": "缓存设置", "change_password_form_confirm_password": "确认密码", "change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "删除", "control_bottom_app_bar_favorite": "收藏", "control_bottom_app_bar_share": "共享", - "control_bottom_app_bar_share_to": "Share To", + "control_bottom_app_bar_share_to": "共享给", "control_bottom_app_bar_stack": "Stack", "control_bottom_app_bar_unarchive": "取消归档", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "上传", "create_album_page_untitled": "未命名", "create_shared_album_page_create": "创建", "create_shared_album_page_share": "共享", @@ -149,7 +155,7 @@ "delete_dialog_ok": "删除", "delete_dialog_title": "永久删除", "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_title": "删除共享链接", "description_input_hint_text": "添加描述...", "description_input_submit_error": "更新描述时出错,请检查日志以获取更多详细信息", "exif_bottom_sheet_description": "添加描述...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "一次最多只能上传 30 个项目,跳过", "image_viewer_page_state_provider_download_error": "下载出现错误", "image_viewer_page_state_provider_download_success": "下载成功", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "共享错误", "library_page_albums": "相册", "library_page_archive": "归档", "library_page_device_albums": "设备上的相册", @@ -179,14 +185,14 @@ "library_page_new_album": "新建相册", "library_page_sharing": "共享", "library_page_sort_created": "最近创建的", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "上次修改", + "library_page_sort_most_recent_photo": "最近的项目", "library_page_sort_title": "相册标题", "login_disabled": "登录已被禁用", "login_form_api_exception": "API 异常,请检查服务器地址并重试。", "login_form_button_text": "登录", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api", + "login_form_endpoint_hint": "http(s)://您的服务器地址:端口/api", "login_form_endpoint_url": "服务器链接地址", "login_form_err_http": "请注明 http:// 或 https://", "login_form_err_invalid_email": "无效的电子邮件", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich 需要权限才能查看您的照片和视频。", "profile_drawer_app_logs": "日志", "profile_drawer_client_server_up_to_date": "客户端和服务端都是最新的", + "profile_drawer_documentation": "文档", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "设置", "profile_drawer_sign_out": "退出登录", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "回收站", "recently_added_page_title": "最近添加", "search_bar_hint": "搜索照片", "search_page_categories": "类别", @@ -271,11 +279,12 @@ "search_page_your_activity": "您的活动", "search_result_page_new_search_hint": "搜索新的", "search_suggestion_list_smart_search_hint_1": "默认情况下启用智能搜索;要搜索元数据,请使用相关语法", - "search_suggestion_list_smart_search_hint_2": "m:你的搜索关键词", + "search_suggestion_list_smart_search_hint_2": "m:您的搜索关键词", "select_additional_user_for_sharing_page_suggestions": "建议", "select_user_for_sharing_page_err_album": "创建相册失败", "select_user_for_sharing_page_share_suggestions": "建议", "server_info_box_app_version": "App 版本", + "server_info_box_server_url": "服务器地址", "server_info_box_server_version": "服务器版本", "setting_image_viewer_help": "详细信息查看器首先加载小缩略图,然后加载中等大小的预览图(若启用),最后加载原始图像。", "setting_image_viewer_original_subtitle": "启用以加载原图,禁用以减少数据使用量(网络和设备缓存)。", @@ -300,28 +309,37 @@ "share_add_photos": "添加项目", "share_add_title": "添加标题", "share_create_album": "创建相册", + "shared_album_activities_input_disable": "评论已禁用", + "shared_album_activities_input_hint": "说些什么", + "shared_album_activity_remove_content": "您确定要删除此活动吗?", + "shared_album_activity_remove_title": "删除活动", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "评论与喜欢", "share_dialog_preparing": "正在准备...", - "shared_link_app_bar_title": "Shared Links", + "shared_link_app_bar_title": "共享链接", "shared_link_create_app_bar_title": "Create link to share", "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", + "shared_link_create_submit_button": "创建链接", "shared_link_edit_allow_download": "Allow public user to download", "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", + "shared_link_edit_app_bar_title": "编辑链接", + "shared_link_edit_change_expiry": "修改过期时间", + "shared_link_edit_description": "描述", "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "密码", + "shared_link_edit_password_hint": "输入共享密码", + "shared_link_edit_show_meta": "显示元数据", + "shared_link_edit_submit_button": "更新链接", "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_manage_links": "管理共享链接", + "share_done": "完成", "share_invite": "邀请相册共享", "sharing_page_album": "共享相册", "sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。", "sharing_page_empty_list": "空", "sharing_silver_appbar_create_shared_album": "创建共享相册", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "共享链接", "sharing_silver_appbar_share_partner": "共享给同伴", "tab_controller_nav_library": "图库", "tab_controller_nav_photos": "照片", @@ -338,17 +356,17 @@ "theme_setting_three_stage_loading_subtitle": "三段式加载可能会提升加载性能,但可能会导致更高的网络负载", "theme_setting_three_stage_loading_title": "启用三段式加载", "translated_text_options": "选项", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", + "trash_page_delete": "删除", + "trash_page_delete_all": "删除全部", + "trash_page_empty_trash_btn": "清空回收站", "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", + "trash_page_empty_trash_dialog_ok": "好的", "trash_page_info": "Trashed items will be permanently deleted after {} days", "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", + "trash_page_restore": "恢复", + "trash_page_restore_all": "恢复全部", + "trash_page_select_assets_btn": "选择项目", + "trash_page_select_btn": "选择", "trash_page_title": "Trash ({})", "upload_dialog_cancel": "取消", "upload_dialog_info": "是否要将所选项目备份到服务器?", diff --git a/mobile/assets/i18n/zh-Hans.json b/mobile/assets/i18n/zh-Hans.json index 59d67bd66..1ab59e54e 100644 --- a/mobile/assets/i18n/zh-Hans.json +++ b/mobile/assets/i18n/zh-Hans.json @@ -23,9 +23,13 @@ "album_viewer_appbar_share_err_title": "修改相册标题失败", "album_viewer_appbar_share_leave": "退出共享", "album_viewer_appbar_share_remove": "从相册中移除", + "album_viewer_appbar_share_to": "共享给", "album_viewer_page_share_add_users": "创建用户", "all_people_page_title": "人物", "all_videos_page_title": "视频", + "app_bar_signout_dialog_content": "您确定要注消吗?", + "app_bar_signout_dialog_ok": "是", + "app_bar_signout_dialog_title": "注消", "archive_page_no_archived_assets": "未找到归档项目", "archive_page_title": "归档({})", "asset_list_layout_settings_dynamic_layout_title": "动态布局", @@ -111,6 +115,8 @@ "cache_settings_statistics_title": "缓存使用情况", "cache_settings_subtitle": "控制 Immich 的缓存行为", "cache_settings_thumbnail_size": "缩略图缓存大小({} 项)", + "cache_settings_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "本地存储", "cache_settings_title": "缓存设置", "change_password_form_confirm_password": "确认密码", "change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。", @@ -130,10 +136,10 @@ "control_bottom_app_bar_delete": "删除", "control_bottom_app_bar_favorite": "收藏", "control_bottom_app_bar_share": "共享", - "control_bottom_app_bar_share_to": "Share To", + "control_bottom_app_bar_share_to": "共享给", "control_bottom_app_bar_stack": "Stack", "control_bottom_app_bar_unarchive": "取消归档", - "control_bottom_app_bar_upload": "Upload", + "control_bottom_app_bar_upload": "上传", "create_album_page_untitled": "未命名", "create_shared_album_page_create": "创建", "create_shared_album_page_share": "共享", @@ -149,7 +155,7 @@ "delete_dialog_ok": "删除", "delete_dialog_title": "永久删除", "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?", - "delete_shared_link_dialog_title": "Delete Shared Link", + "delete_shared_link_dialog_title": "删除共享链接", "description_input_hint_text": "添加描述...", "description_input_submit_error": "更新描述时出错,请检查日志以获取更多详细信息", "exif_bottom_sheet_description": "添加描述...", @@ -171,7 +177,7 @@ "home_page_upload_err_limit": "一次最多只能上传 30 个项目,跳过", "image_viewer_page_state_provider_download_error": "下载出现错误", "image_viewer_page_state_provider_download_success": "下载成功", - "image_viewer_page_state_provider_share_error": "Share Error", + "image_viewer_page_state_provider_share_error": "共享错误", "library_page_albums": "相册", "library_page_archive": "归档", "library_page_device_albums": "设备上的相册", @@ -179,14 +185,14 @@ "library_page_new_album": "新建相册", "library_page_sharing": "共享", "library_page_sort_created": "最近创建的", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_most_recent_photo": "Most recent photo", + "library_page_sort_last_modified": "上次修改", + "library_page_sort_most_recent_photo": "最近的项目", "library_page_sort_title": "相册标题", "login_disabled": "登录已被禁用", "login_form_api_exception": "API 异常,请检查服务器地址并重试。", "login_form_button_text": "登录", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http(s)://你的服务器地址:端口/api", + "login_form_endpoint_hint": "http(s)://您的服务器地址:端口/api", "login_form_endpoint_url": "服务器链接地址", "login_form_err_http": "请注明 http:// 或 https://", "login_form_err_invalid_email": "无效的电子邮件", @@ -250,9 +256,11 @@ "permission_onboarding_request": "Immich 需要权限才能查看您的照片和视频。", "profile_drawer_app_logs": "日志", "profile_drawer_client_server_up_to_date": "客户端和服务端都是最新的", + "profile_drawer_documentation": "文档", + "profile_drawer_github": "GitHub", "profile_drawer_settings": "设置", "profile_drawer_sign_out": "退出登录", - "profile_drawer_trash": "Trash", + "profile_drawer_trash": "回收站", "recently_added_page_title": "最近添加", "search_bar_hint": "搜索照片", "search_page_categories": "类别", @@ -271,11 +279,12 @@ "search_page_your_activity": "您的活动", "search_result_page_new_search_hint": "搜索新的", "search_suggestion_list_smart_search_hint_1": "默认情况下启用智能搜索;要搜索元数据,请使用相关语法", - "search_suggestion_list_smart_search_hint_2": "m:你的搜索关键词", + "search_suggestion_list_smart_search_hint_2": "m:您的搜索关键词", "select_additional_user_for_sharing_page_suggestions": "建议", "select_user_for_sharing_page_err_album": "创建相册失败", "select_user_for_sharing_page_share_suggestions": "建议", "server_info_box_app_version": "App 版本", + "server_info_box_server_url": "服务器地址", "server_info_box_server_version": "服务器版本", "setting_image_viewer_help": "详细信息查看器首先加载小缩略图,然后加载中等大小的预览图(若启用),最后加载原始图像。", "setting_image_viewer_original_subtitle": "启用以加载原图,禁用以减少数据使用量(网络和设备缓存)。", @@ -300,28 +309,37 @@ "share_add_photos": "添加项目", "share_add_title": "添加标题", "share_create_album": "创建相册", + "shared_album_activities_input_disable": "评论已禁用", + "shared_album_activities_input_hint": "说些什么", + "shared_album_activity_remove_content": "您确定要删除此活动吗?", + "shared_album_activity_remove_title": "删除活动", + "shared_album_activity_setting_subtitle": "Let others respond", + "shared_album_activity_setting_title": "评论与喜欢", "share_dialog_preparing": "正在准备...", - "shared_link_app_bar_title": "Shared Links", + "shared_link_app_bar_title": "共享链接", "shared_link_create_app_bar_title": "Create link to share", "shared_link_create_info": "Let anyone with the link see the selected photo(s)", - "shared_link_create_submit_button": "Create link", + "shared_link_create_submit_button": "创建链接", "shared_link_edit_allow_download": "Allow public user to download", "shared_link_edit_allow_upload": "Allow public user to upload", - "shared_link_edit_app_bar_title": "Edit link", - "shared_link_edit_change_expiry": "Change expiration time", - "shared_link_edit_description": "Description", + "shared_link_edit_app_bar_title": "编辑链接", + "shared_link_edit_change_expiry": "修改过期时间", + "shared_link_edit_description": "描述", "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_show_meta": "Show metadata", - "shared_link_edit_submit_button": "Update link", + "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_password": "密码", + "shared_link_edit_password_hint": "输入共享密码", + "shared_link_edit_show_meta": "显示元数据", + "shared_link_edit_submit_button": "更新链接", "shared_link_empty": "You don't have any shared links", - "shared_link_manage_links": "Manage Shared links", - "share_done": "Done", + "shared_link_manage_links": "管理共享链接", + "share_done": "完成", "share_invite": "邀请相册共享", "sharing_page_album": "共享相册", "sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。", "sharing_page_empty_list": "空", "sharing_silver_appbar_create_shared_album": "创建共享相册", - "sharing_silver_appbar_shared_links": "Shared links", + "sharing_silver_appbar_shared_links": "共享链接", "sharing_silver_appbar_share_partner": "共享给同伴", "tab_controller_nav_library": "图库", "tab_controller_nav_photos": "照片", @@ -338,17 +356,17 @@ "theme_setting_three_stage_loading_subtitle": "三段式加载可能会提升加载性能,但可能会导致更高的网络负载", "theme_setting_three_stage_loading_title": "启用三段式加载", "translated_text_options": "选项", - "trash_page_delete": "Delete", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_btn": "Empty trash", + "trash_page_delete": "删除", + "trash_page_delete_all": "删除全部", + "trash_page_empty_trash_btn": "清空回收站", "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_empty_trash_dialog_ok": "Ok", + "trash_page_empty_trash_dialog_ok": "好的", "trash_page_info": "Trashed items will be permanently deleted after {} days", "trash_page_no_assets": "No trashed assets", - "trash_page_restore": "Restore", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_select_btn": "Select", + "trash_page_restore": "恢复", + "trash_page_restore_all": "恢复全部", + "trash_page_select_assets_btn": "选择项目", + "trash_page_select_btn": "选择", "trash_page_title": "Trash ({})", "upload_dialog_cancel": "取消", "upload_dialog_info": "是否要将所选项目备份到服务器?", From 0c482960ce840698dcb758746830933bff574207 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 13 Nov 2023 16:37:50 +0100 Subject: [PATCH 35/88] fix(web): better year labels (#5009) * better year labels * fixes * lint fixes --- .../scrollbar/scrollbar.svelte | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte index 580277fba..7dfd34e8f 100644 --- a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte +++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte @@ -1,5 +1,6 @@ @@ -125,11 +120,8 @@ /> {/if} - {#each segments as segment, index (segment.timeGroup)} - {@const date = fromLocalDateTime(segment.timeGroup)} - {@const year = date.year} - {@const label = `${date.toLocaleString({ month: 'short' })} ${year}`} - {@const lastGroupYear = fromLocalDateTime(segments[index - 1]?.timeGroup).year} + {#each segments as segment} + {@const label = `${segment.date.toLocaleString({ month: 'short' })} ${segment.date.year}`}

    (hoverLabel = label)} > - {#if lastGroupYear !== year && (index == 0 || prevYearSegmentHeight(segments, index - 1) > 16)} + {#if segment.hasLabel}
    - {year} + {segment.date.year}
    {:else if segment.height > 5}
    Date: Mon, 13 Nov 2023 16:54:41 +0100 Subject: [PATCH 36/88] feat(mobile): unify partner assets on timeline (#4974) * feat(mobile): unify partner assets on timeline * skip non-owned assets in bulk actions * add message when trying to delete partner assets --- mobile/assets/i18n/en-US.json | 4 + .../providers/archive_asset_provider.dart | 15 +-- .../providers/render_list.provider.dart | 15 +-- .../favorite/providers/favorite_provider.dart | 15 +-- mobile/lib/modules/home/views/home_page.dart | 101 +++++++++++++----- .../partner/providers/partner.provider.dart | 14 ++- .../partner/services/partner.service.dart | 16 +++ .../partner/views/partner_detail_page.dart | 42 ++++++++ .../providers/trashed_asset.provider.dart | 11 +- mobile/lib/shared/models/user.dart | 11 +- mobile/lib/shared/models/user.g.dart | 48 ++------- .../shared/providers/app_state.provider.dart | 1 + .../lib/shared/providers/asset.provider.dart | 54 ++++++---- .../lib/shared/providers/user.provider.dart | 31 ++++++ mobile/lib/shared/services/user.service.dart | 6 +- mobile/lib/utils/migration.dart | 2 + mobile/lib/utils/renderlist_generator.dart | 26 +++++ 17 files changed, 274 insertions(+), 138 deletions(-) create mode 100644 mobile/lib/utils/renderlist_generator.dart diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 672ab28fb..ff4309547 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -175,6 +175,10 @@ "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", + "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", + "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping", + "home_page_archive_err_partner": "Can not archive partner assets, skipping", + "home_page_delete_err_partner": "Can not delete partner assets, skipping", "image_viewer_page_state_provider_download_error": "Download Error", "image_viewer_page_state_provider_download_success": "Download Success", "image_viewer_page_state_provider_share_error": "Share Error", diff --git a/mobile/lib/modules/archive/providers/archive_asset_provider.dart b/mobile/lib/modules/archive/providers/archive_asset_provider.dart index 328b3c7b5..579ace7c5 100644 --- a/mobile/lib/modules/archive/providers/archive_asset_provider.dart +++ b/mobile/lib/modules/archive/providers/archive_asset_provider.dart @@ -1,15 +1,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; -import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; -import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; +import 'package:immich_mobile/utils/renderlist_generator.dart'; import 'package:isar/isar.dart'; -final archiveProvider = StreamProvider((ref) async* { +final archiveProvider = StreamProvider((ref) { final user = ref.watch(currentUserProvider); - if (user == null) return; + if (user == null) return const Stream.empty(); final query = ref .watch(dbProvider) .assets @@ -19,11 +18,5 @@ final archiveProvider = StreamProvider((ref) async* { .isArchivedEqualTo(true) .isTrashedEqualTo(false) .sortByFileCreatedAt(); - final settings = ref.watch(appSettingsServiceProvider); - final groupBy = - GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)]; - yield await RenderList.fromQuery(query, groupBy); - await for (final _ in query.watchLazy()) { - yield await RenderList.fromQuery(query, groupBy); - } + return renderListGenerator(query, ref); }); diff --git a/mobile/lib/modules/asset_viewer/providers/render_list.provider.dart b/mobile/lib/modules/asset_viewer/providers/render_list.provider.dart index 04532ce1b..c2e8782bb 100644 --- a/mobile/lib/modules/asset_viewer/providers/render_list.provider.dart +++ b/mobile/lib/modules/asset_viewer/providers/render_list.provider.dart @@ -3,6 +3,7 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structu import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/utils/renderlist_generator.dart'; import 'package:isar/isar.dart'; final renderListProvider = @@ -17,16 +18,6 @@ final renderListProvider = final renderListQueryProvider = StreamProvider.family?>( - (ref, query) async* { - if (query == null) { - return; - } - final settings = ref.watch(appSettingsServiceProvider); - final groupBy = GroupAssetsBy - .values[settings.getSetting(AppSettingsEnum.groupAssetsBy)]; - yield await RenderList.fromQuery(query, groupBy); - await for (final _ in query.watchLazy()) { - yield await RenderList.fromQuery(query, groupBy); - } - }, + (ref, query) => + query == null ? const Stream.empty() : renderListGenerator(query, ref), ); diff --git a/mobile/lib/modules/favorite/providers/favorite_provider.dart b/mobile/lib/modules/favorite/providers/favorite_provider.dart index 427d2c88b..0da6b3f8a 100644 --- a/mobile/lib/modules/favorite/providers/favorite_provider.dart +++ b/mobile/lib/modules/favorite/providers/favorite_provider.dart @@ -1,15 +1,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; -import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; -import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; +import 'package:immich_mobile/utils/renderlist_generator.dart'; import 'package:isar/isar.dart'; -final favoriteAssetsProvider = StreamProvider((ref) async* { +final favoriteAssetsProvider = StreamProvider((ref) { final user = ref.watch(currentUserProvider); - if (user == null) return; + if (user == null) return const Stream.empty(); final query = ref .watch(dbProvider) .assets @@ -19,11 +18,5 @@ final favoriteAssetsProvider = StreamProvider((ref) async* { .isFavoriteEqualTo(true) .isTrashedEqualTo(false) .sortByFileCreatedAt(); - final settings = ref.watch(appSettingsServiceProvider); - final groupBy = - GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)]; - yield await RenderList.fromQuery(query, groupBy); - await for (final _ in query.watchLazy()) { - yield await RenderList.fromQuery(query, groupBy); - } + return renderListGenerator(query, ref); }); diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index eb26ea077..07397ecd1 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -44,6 +44,7 @@ class HomePage extends HookConsumerWidget { final sharedAlbums = ref.watch(sharedAlbumProvider); final albumService = ref.watch(albumServiceProvider); final currentUser = ref.watch(currentUserProvider); + final timelineUsers = ref.watch(timelineUsersIdsProvider); final trashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); @@ -55,6 +56,7 @@ class HomePage extends HookConsumerWidget { () { ref.read(websocketProvider.notifier).connect(); Future(() => ref.read(assetProvider.notifier).getAllAsset()); + ref.read(assetProvider.notifier).getPartnerAssets(); ref.read(albumProvider.notifier).getAllAlbums(); ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums(); ref.read(serverInfoProvider.notifier).getServerInfo(); @@ -84,20 +86,49 @@ class HomePage extends HookConsumerWidget { SelectionAssetState.fromSelection(selectedAssets); } - List remoteOnlySelection({String? localErrorMessage}) { - final Set assets = selection.value; + errorBuilder(String? msg) => msg != null && msg.isNotEmpty + ? () => ImmichToast.show( + context: context, + msg: msg, + gravity: ToastGravity.BOTTOM, + ) + : null; + + Iterable remoteOnly( + Iterable assets, { + void Function()? errorCallback, + }) { final bool onlyRemote = assets.every((e) => e.isRemote); if (!onlyRemote) { - if (localErrorMessage != null && localErrorMessage.isNotEmpty) { - ImmichToast.show( - context: context, - msg: localErrorMessage, - gravity: ToastGravity.BOTTOM, - ); - } - return assets.where((a) => a.isRemote).toList(); + if (errorCallback != null) errorCallback(); + return assets.where((a) => a.isRemote); } - return assets.toList(); + return assets; + } + + Iterable ownedOnly( + Iterable assets, { + void Function()? errorCallback, + }) { + if (currentUser == null) return []; + final userId = currentUser.isarId; + final bool onlyOwned = assets.every((e) => e.ownerId == userId); + if (!onlyOwned) { + if (errorCallback != null) errorCallback(); + return assets.where((a) => a.ownerId == userId); + } + return assets; + } + + Iterable ownedRemoteSelection({ + String? localErrorMessage, + String? ownerErrorMessage, + }) { + final assets = selection.value; + return remoteOnly( + ownedOnly(assets, errorCallback: errorBuilder(ownerErrorMessage)), + errorCallback: errorBuilder(localErrorMessage), + ); } void onShareAssets(bool shareLocal) { @@ -105,7 +136,7 @@ class HomePage extends HookConsumerWidget { if (shareLocal) { handleShareAssets(ref, context, selection.value.toList()); } else { - final ids = remoteOnlySelection().map((e) => e.remoteId!); + final ids = ownedRemoteSelection().map((e) => e.remoteId!); context.autoPush(SharedLinkEditRoute(assetsList: ids.toList())); } processing.value = false; @@ -115,11 +146,12 @@ class HomePage extends HookConsumerWidget { void onFavoriteAssets() async { processing.value = true; try { - final remoteAssets = remoteOnlySelection( + final remoteAssets = ownedRemoteSelection( localErrorMessage: 'home_page_favorite_err_local'.tr(), + ownerErrorMessage: 'home_page_favorite_err_partner'.tr(), ); if (remoteAssets.isNotEmpty) { - await handleFavoriteAssets(ref, context, remoteAssets); + await handleFavoriteAssets(ref, context, remoteAssets.toList()); } } finally { processing.value = false; @@ -130,10 +162,11 @@ class HomePage extends HookConsumerWidget { void onArchiveAsset() async { processing.value = true; try { - final remoteAssets = remoteOnlySelection( + final remoteAssets = ownedRemoteSelection( localErrorMessage: 'home_page_archive_err_local'.tr(), + ownerErrorMessage: 'home_page_archive_err_partner'.tr(), ); - await handleArchiveAssets(ref, context, remoteAssets); + await handleArchiveAssets(ref, context, remoteAssets.toList()); } finally { processing.value = false; selectionEnabledHook.value = false; @@ -143,12 +176,16 @@ class HomePage extends HookConsumerWidget { void onDelete() async { processing.value = true; try { + final toDelete = ownedOnly( + selection.value, + errorCallback: errorBuilder('home_page_delete_err_partner'.tr()), + ).toList(); await ref .read(assetProvider.notifier) - .deleteAssets(selection.value, force: !trashEnabled); + .deleteAssets(toDelete, force: !trashEnabled); - final hasRemote = selection.value.any((a) => a.isRemote); - final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset'; + final hasRemote = toDelete.any((a) => a.isRemote); + final assetOrAssets = toDelete.length > 1 ? 'assets' : 'asset'; final trashOrRemoved = !trashEnabled ? 'deleted permanently' : 'trashed'; if (hasRemote) { @@ -180,8 +217,9 @@ class HomePage extends HookConsumerWidget { void onAddToAlbum(Album album) async { processing.value = true; try { - final Iterable assets = remoteOnlySelection( + final Iterable assets = ownedRemoteSelection( localErrorMessage: "home_page_add_to_album_err_local".tr(), + ownerErrorMessage: "home_page_album_err_partner".tr(), ); if (assets.isEmpty) { return; @@ -228,8 +266,9 @@ class HomePage extends HookConsumerWidget { void onCreateNewAlbum() async { processing.value = true; try { - final Iterable assets = remoteOnlySelection( + final Iterable assets = ownedRemoteSelection( localErrorMessage: "home_page_add_to_album_err_local".tr(), + ownerErrorMessage: "home_page_album_err_partner".tr(), ); if (assets.isEmpty) { return; @@ -270,6 +309,9 @@ class HomePage extends HookConsumerWidget { Future refreshAssets() async { final fullRefresh = refreshCount.value > 0; await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh); + if (timelineUsers.length > 1) { + await ref.read(assetProvider.notifier).getPartnerAssets(); + } if (fullRefresh) { // refresh was forced: user requested another refresh within 2 seconds refreshCount.value = 0; @@ -330,7 +372,13 @@ class HomePage extends HookConsumerWidget { bottom: false, child: Stack( children: [ - ref.watch(assetsProvider(currentUser?.isarId)).when( + ref + .watch( + timelineUsers.length > 1 + ? multiUserAssetsProvider(timelineUsers) + : assetsProvider(currentUser?.isarId), + ) + .when( data: (data) => data.isEmpty ? buildLoadingIndicator() : ImmichAssetGrid( @@ -338,11 +386,10 @@ class HomePage extends HookConsumerWidget { listener: selectionListener, selectionActive: selectionEnabledHook.value, onRefresh: refreshAssets, - topWidget: (currentUser != null && - currentUser.memoryEnabled != null && - currentUser.memoryEnabled!) - ? const MemoryLane() - : const SizedBox(), + topWidget: + (currentUser != null && currentUser.memoryEnabled) + ? const MemoryLane() + : const SizedBox(), showStack: true, ), error: (error, _) => Center(child: Text(error.toString())), diff --git a/mobile/lib/modules/partner/providers/partner.provider.dart b/mobile/lib/modules/partner/providers/partner.provider.dart index d48435616..3123382bb 100644 --- a/mobile/lib/modules/partner/providers/partner.provider.dart +++ b/mobile/lib/modules/partner/providers/partner.provider.dart @@ -2,21 +2,31 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart'; +import 'package:immich_mobile/modules/partner/services/partner.service.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:isar/isar.dart'; class PartnerSharedWithNotifier extends StateNotifier> { - PartnerSharedWithNotifier(Isar db) : super([]) { + PartnerSharedWithNotifier(Isar db, this._ps) : super([]) { final query = db.users.filter().isPartnerSharedWithEqualTo(true); query.findAll().then((partners) => state = partners); query.watch().listen((partners) => state = partners); } + + Future updatePartner(User partner, {required bool inTimeline}) { + return _ps.updatePartner(partner, inTimeline: inTimeline); + } + + final PartnerService _ps; } final partnerSharedWithProvider = StateNotifierProvider>((ref) { - return PartnerSharedWithNotifier(ref.watch(dbProvider)); + return PartnerSharedWithNotifier( + ref.watch(dbProvider), + ref.watch(partnerServiceProvider), + ); }); class PartnerSharedByNotifier extends StateNotifier> { diff --git a/mobile/lib/modules/partner/services/partner.service.dart b/mobile/lib/modules/partner/services/partner.service.dart index 0605e56ef..32e500353 100644 --- a/mobile/lib/modules/partner/services/partner.service.dart +++ b/mobile/lib/modules/partner/services/partner.service.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/services/api.service.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; +import 'package:openapi/api.dart'; final partnerServiceProvider = Provider( (ref) => PartnerService( @@ -69,4 +70,19 @@ class PartnerService { } return false; } + + Future updatePartner(User partner, {required bool inTimeline}) async { + try { + final dto = await _apiService.partnerApi + .updatePartner(partner.id, UpdatePartnerDto(inTimeline: inTimeline)); + if (dto != null) { + partner.inTimeline = dto.inTimeline ?? partner.inTimeline; + await _db.writeTxn(() => _db.users.put(partner)); + return true; + } + } catch (e) { + _log.warning("failed to update partner ${partner.id}:\n$e"); + } + return false; + } } diff --git a/mobile/lib/modules/partner/views/partner_detail_page.dart b/mobile/lib/modules/partner/views/partner_detail_page.dart index 2aecdb35c..f7b1580b2 100644 --- a/mobile/lib/modules/partner/views/partner_detail_page.dart +++ b/mobile/lib/modules/partner/views/partner_detail_page.dart @@ -2,9 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/modules/partner/providers/partner.provider.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/shared/ui/immich_toast.dart'; class PartnerDetailPage extends HookConsumerWidget { const PartnerDetailPage({Key? key, required this.partner}) : super(key: key); @@ -14,6 +16,8 @@ class PartnerDetailPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final assets = ref.watch(assetsProvider(partner.isarId)); + final inTimeline = useState(partner.inTimeline); + bool toggleInProcess = false; useEffect( () { @@ -23,11 +27,49 @@ class PartnerDetailPage extends HookConsumerWidget { [], ); + void toggleInTimeline() async { + if (toggleInProcess) return; + toggleInProcess = true; + try { + final ok = await ref + .read(partnerSharedWithProvider.notifier) + .updatePartner(partner, inTimeline: !inTimeline.value); + if (ok) { + inTimeline.value = !inTimeline.value; + final action = inTimeline.value ? "shown on" : "hidden from"; + ImmichToast.show( + context: context, + toastType: ToastType.success, + durationInSecond: 1, + msg: "${partner.name}'s assets $action your timeline", + ); + } else { + ImmichToast.show( + context: context, + toastType: ToastType.error, + durationInSecond: 1, + msg: "Failed to toggle the timeline setting", + ); + } + } finally { + toggleInProcess = false; + } + } + return Scaffold( appBar: AppBar( title: Text(partner.name), elevation: 0, centerTitle: false, + actions: [ + IconButton( + onPressed: toggleInTimeline, + icon: Icon( + inTimeline.value ? Icons.collections : Icons.collections_outlined, + ), + tooltip: "Show/hide photos on your main timeline", + ), + ], ), body: assets.when( data: (renderList) => renderList.isEmpty diff --git a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart b/mobile/lib/modules/trash/providers/trashed_asset.provider.dart index d0ad41b5f..04f8d5f16 100644 --- a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart +++ b/mobile/lib/modules/trash/providers/trashed_asset.provider.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; import 'package:immich_mobile/shared/services/sync.service.dart'; +import 'package:immich_mobile/utils/renderlist_generator.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; @@ -107,9 +108,9 @@ final trashProvider = StateNotifierProvider((ref) { ); }); -final trashedAssetsProvider = StreamProvider((ref) async* { +final trashedAssetsProvider = StreamProvider((ref) { final user = ref.read(currentUserProvider); - if (user == null) return; + if (user == null) return const Stream.empty(); final query = ref .watch(dbProvider) .assets @@ -117,9 +118,5 @@ final trashedAssetsProvider = StreamProvider((ref) async* { .ownerIdEqualTo(user.isarId) .isTrashedEqualTo(true) .sortByFileCreatedAt(); - const groupBy = GroupAssetsBy.none; - yield await RenderList.fromQuery(query, groupBy); - await for (final _ in query.watchLazy()) { - yield await RenderList.fromQuery(query, groupBy); - } + return renderListGeneratorWithGroupBy(query, GroupAssetsBy.none); }); diff --git a/mobile/lib/shared/models/user.dart b/mobile/lib/shared/models/user.dart index a247fb21e..e9e5004da 100644 --- a/mobile/lib/shared/models/user.dart +++ b/mobile/lib/shared/models/user.dart @@ -31,7 +31,8 @@ class User { isPartnerSharedWith = false, profileImagePath = dto.profileImagePath, isAdmin = dto.isAdmin, - memoryEnabled = dto.memoriesEnabled; + memoryEnabled = dto.memoriesEnabled ?? false, + inTimeline = false; User.fromPartnerDto(PartnerResponseDto dto) : id = dto.id, @@ -42,8 +43,8 @@ class User { isPartnerSharedWith = false, profileImagePath = dto.profileImagePath, isAdmin = dto.isAdmin, - memoryEnabled = dto.memoriesEnabled, - inTimeline = dto.inTimeline; + memoryEnabled = dto.memoriesEnabled ?? false, + inTimeline = dto.inTimeline ?? false; @Index(unique: true, replace: false, type: IndexType.hash) String id; @@ -54,8 +55,8 @@ class User { bool isPartnerSharedWith; bool isAdmin; String profileImagePath; - bool? memoryEnabled; - bool? inTimeline; + bool memoryEnabled; + bool inTimeline; @Backlink(to: 'owner') final IsarLinks albums = IsarLinks(); diff --git a/mobile/lib/shared/models/user.g.dart b/mobile/lib/shared/models/user.g.dart index f5167c2a8..82420a175 100644 --- a/mobile/lib/shared/models/user.g.dart +++ b/mobile/lib/shared/models/user.g.dart @@ -151,11 +151,11 @@ User _userDeserialize( final object = User( email: reader.readString(offsets[0]), id: reader.readString(offsets[1]), - inTimeline: reader.readBoolOrNull(offsets[2]), + inTimeline: reader.readBoolOrNull(offsets[2]) ?? false, isAdmin: reader.readBool(offsets[3]), isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false, isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false, - memoryEnabled: reader.readBoolOrNull(offsets[6]), + memoryEnabled: reader.readBoolOrNull(offsets[6]) ?? true, name: reader.readString(offsets[7]), profileImagePath: reader.readStringOrNull(offsets[8]) ?? '', updatedAt: reader.readDateTime(offsets[9]), @@ -175,7 +175,7 @@ P _userDeserializeProp

    ( case 1: return (reader.readString(offset)) as P; case 2: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readBoolOrNull(offset) ?? false) as P; case 3: return (reader.readBool(offset)) as P; case 4: @@ -183,7 +183,7 @@ P _userDeserializeProp

    ( case 5: return (reader.readBoolOrNull(offset) ?? false) as P; case 6: - return (reader.readBoolOrNull(offset)) as P; + return (reader.readBoolOrNull(offset) ?? true) as P; case 7: return (reader.readString(offset)) as P; case 8: @@ -638,24 +638,8 @@ extension UserQueryFilter on QueryBuilder { }); } - QueryBuilder inTimelineIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'inTimeline', - )); - }); - } - - QueryBuilder inTimelineIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'inTimeline', - )); - }); - } - QueryBuilder inTimelineEqualTo( - bool? value) { + bool value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( property: r'inTimeline', @@ -745,24 +729,8 @@ extension UserQueryFilter on QueryBuilder { }); } - QueryBuilder memoryEnabledIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'memoryEnabled', - )); - }); - } - - QueryBuilder memoryEnabledIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'memoryEnabled', - )); - }); - } - QueryBuilder memoryEnabledEqualTo( - bool? value) { + bool value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( property: r'memoryEnabled', @@ -1540,7 +1508,7 @@ extension UserQueryProperty on QueryBuilder { }); } - QueryBuilder inTimelineProperty() { + QueryBuilder inTimelineProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'inTimeline'); }); @@ -1564,7 +1532,7 @@ extension UserQueryProperty on QueryBuilder { }); } - QueryBuilder memoryEnabledProperty() { + QueryBuilder memoryEnabledProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'memoryEnabled'); }); diff --git a/mobile/lib/shared/providers/app_state.provider.dart b/mobile/lib/shared/providers/app_state.provider.dart index e2813d86d..d2d3f1e1e 100644 --- a/mobile/lib/shared/providers/app_state.provider.dart +++ b/mobile/lib/shared/providers/app_state.provider.dart @@ -54,6 +54,7 @@ class AppStateNotiifer extends StateNotifier { switch (ref.read(tabProvider)) { case TabEnum.home: ref.read(assetProvider.notifier).getAllAsset(); + ref.read(assetProvider.notifier).getPartnerAssets(); case TabEnum.search: // nothing to do case TabEnum.sharing: diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 1694e1f26..50c601801 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -8,12 +8,11 @@ import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/user.provider.dart'; import 'package:immich_mobile/shared/services/asset.service.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; -import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; -import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/services/sync.service.dart'; import 'package:immich_mobile/shared/services/user.service.dart'; import 'package:immich_mobile/utils/db.dart'; +import 'package:immich_mobile/utils/renderlist_generator.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -251,26 +250,23 @@ final assetWatcher = return db.assets.watchObject(asset.id, fireImmediately: true); }); -final assetsProvider = - StreamProvider.family((ref, userId) async* { - if (userId == null) return; - final query = ref - .watch(dbProvider) - .assets - .where() - .ownerIdEqualToAnyChecksum(userId) - .filter() - .isArchivedEqualTo(false) - .isTrashedEqualTo(false) - .stackParentIdIsNull() - .sortByFileCreatedAtDesc(); - final settings = ref.watch(appSettingsServiceProvider); - final groupBy = - GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)]; - yield await RenderList.fromQuery(query, groupBy); - await for (final _ in query.watchLazy()) { - yield await RenderList.fromQuery(query, groupBy); - } +final assetsProvider = StreamProvider.family((ref, userId) { + if (userId == null) return const Stream.empty(); + final query = _commonFilterAndSort( + _assets(ref).where().ownerIdEqualToAnyChecksum(userId), + ); + return renderListGenerator(query, ref); +}); + +final multiUserAssetsProvider = + StreamProvider.family>((ref, userIds) { + if (userIds.isEmpty) return const Stream.empty(); + final query = _commonFilterAndSort( + _assets(ref) + .where() + .anyOf(userIds, (q, u) => q.ownerIdEqualToAnyChecksum(u)), + ); + return renderListGenerator(query, ref); }); QueryBuilder? getRemoteAssetQuery(WidgetRef ref) { @@ -289,3 +285,17 @@ QueryBuilder? getRemoteAssetQuery(WidgetRef ref) { .stackParentIdIsNull() .sortByFileCreatedAtDesc(); } + +IsarCollection _assets(StreamProviderRef ref) => + ref.watch(dbProvider).assets; + +QueryBuilder _commonFilterAndSort( + QueryBuilder query, +) { + return query + .filter() + .isArchivedEqualTo(false) + .isTrashedEqualTo(false) + .stackParentIdIsNull() + .sortByFileCreatedAtDesc(); +} diff --git a/mobile/lib/shared/providers/user.provider.dart b/mobile/lib/shared/providers/user.provider.dart index df8ff328d..61c77b24b 100644 --- a/mobile/lib/shared/providers/user.provider.dart +++ b/mobile/lib/shared/providers/user.provider.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; +import 'package:immich_mobile/shared/providers/db.provider.dart'; +import 'package:isar/isar.dart'; class CurrentUserProvider extends StateNotifier { CurrentUserProvider() : super(null) { @@ -24,3 +26,32 @@ final currentUserProvider = StateNotifierProvider((ref) { return CurrentUserProvider(); }); + +class TimelineUserIdsProvider extends StateNotifier> { + TimelineUserIdsProvider(Isar db, User? currentUser) : super([]) { + final query = db.users + .filter() + .inTimelineEqualTo(true) + .or() + .isarIdEqualTo(currentUser?.isarId ?? Isar.autoIncrement) + .isarIdProperty(); + query.findAll().then((users) => state = users); + streamSub = query.watch().listen((users) => state = users); + } + + late final StreamSubscription> streamSub; + + @override + void dispose() { + streamSub.cancel(); + super.dispose(); + } +} + +final timelineUsersIdsProvider = + StateNotifierProvider>((ref) { + return TimelineUserIdsProvider( + ref.watch(dbProvider), + ref.watch(currentUserProvider), + ); +}); diff --git a/mobile/lib/shared/services/user.service.dart b/mobile/lib/shared/services/user.service.dart index 16f7b7b0e..4d398c3a8 100644 --- a/mobile/lib/shared/services/user.service.dart +++ b/mobile/lib/shared/services/user.service.dart @@ -99,7 +99,11 @@ class UserService { users, sharedWith, compare: (User a, User b) => a.id.compareTo(b.id), - both: (User a, User b) => a.isPartnerSharedWith = true, + both: (User a, User b) { + a.isPartnerSharedWith = true; + a.inTimeline = b.inTimeline; + return true; + }, onlyFirst: (_) {}, onlySecond: (_) {}, ); diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index 724f3a872..2356c7314 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -11,6 +11,8 @@ Future migrateDatabaseIfNeeded(Isar db) async { await _migrateTo(db, 2); case 2: await _migrateTo(db, 3); + case 3: + await _migrateTo(db, 4); } } diff --git a/mobile/lib/utils/renderlist_generator.dart b/mobile/lib/utils/renderlist_generator.dart new file mode 100644 index 000000000..c68957a2a --- /dev/null +++ b/mobile/lib/utils/renderlist_generator.dart @@ -0,0 +1,26 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; +import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:isar/isar.dart'; + +Stream renderListGenerator( + QueryBuilder query, + StreamProviderRef ref, +) { + final settings = ref.watch(appSettingsServiceProvider); + final groupBy = + GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)]; + return renderListGeneratorWithGroupBy(query, groupBy); +} + +Stream renderListGeneratorWithGroupBy( + QueryBuilder query, + GroupAssetsBy groupBy, +) async* { + yield await RenderList.fromQuery(query, groupBy); + await for (final _ in query.watchLazy()) { + yield await RenderList.fromQuery(query, groupBy); + } +} From 935f471ccb26ebd804526a75caa9c6655c444736 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:18:46 -0500 Subject: [PATCH 37/88] chore(ml): use strict mypy (#5001) * improved typing * improved export typing * strict mypy & check export folder * formatting * add formatting checks for export folder * re-added init call --- .github/workflows/test.yml | 6 ++-- machine-learning/app/conftest.py | 3 +- machine-learning/app/main.py | 2 +- machine-learning/app/models/base.py | 10 +++--- machine-learning/app/models/cache.py | 10 +++--- machine-learning/app/models/clip.py | 14 ++++---- .../app/models/facial_recognition.py | 33 +++++++++---------- .../app/models/image_classification.py | 2 +- machine-learning/app/schemas.py | 32 ++++++++++-------- machine-learning/export/models/openclip.py | 13 +++++--- 10 files changed, 70 insertions(+), 55 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 58efec377..07e9f16d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -168,13 +168,13 @@ jobs: poetry install --with dev - name: Lint with ruff run: | - poetry run ruff check --format=github app + poetry run ruff check --format=github app export - name: Check black formatting run: | - poetry run black --check app + poetry run black --check app export - name: Run mypy type checking run: | - poetry run mypy --install-types --non-interactive app/ + poetry run mypy --install-types --non-interactive --strict app/ export/ - name: Run tests and coverage run: | poetry run pytest --cov app diff --git a/machine-learning/app/conftest.py b/machine-learning/app/conftest.py index 3bbb89c52..5e2dc1e84 100644 --- a/machine-learning/app/conftest.py +++ b/machine-learning/app/conftest.py @@ -36,7 +36,8 @@ def deployed_app() -> TestClient: @pytest.fixture(scope="session") def responses() -> dict[str, Any]: - return json.load(open("responses.json", "r")) + responses: dict[str, Any] = json.load(open("responses.json", "r")) + return responses @pytest.fixture(scope="session") diff --git a/machine-learning/app/main.py b/machine-learning/app/main.py index 375c14a9e..e1d71e9fa 100644 --- a/machine-learning/app/main.py +++ b/machine-learning/app/main.py @@ -7,7 +7,7 @@ from zipfile import BadZipFile import orjson from fastapi import FastAPI, Form, HTTPException, UploadFile from fastapi.responses import ORJSONResponse -from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile # type: ignore +from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile from starlette.formparsers import MultiPartParser from app.models.base import InferenceModel diff --git a/machine-learning/app/models/base.py b/machine-learning/app/models/base.py index 8149502ec..d3252d000 100644 --- a/machine-learning/app/models/base.py +++ b/machine-learning/app/models/base.py @@ -8,6 +8,7 @@ from typing import Any import onnxruntime as ort from huggingface_hub import snapshot_download +from typing_extensions import Buffer from ..config import get_cache_dir, get_hf_model_name, log, settings from ..schemas import ModelType @@ -139,11 +140,12 @@ class InferenceModel(ABC): # HF deep copies configs, so we need to make session options picklable -class PicklableSessionOptions(ort.SessionOptions): +class PicklableSessionOptions(ort.SessionOptions): # type: ignore[misc] def __getstate__(self) -> bytes: return pickle.dumps([(attr, getattr(self, attr)) for attr in dir(self) if not callable(getattr(self, attr))]) - def __setstate__(self, state: Any) -> None: - self.__init__() # type: ignore - for attr, val in pickle.loads(state): + def __setstate__(self, state: Buffer) -> None: + self.__init__() # type: ignore[misc] + attrs: list[tuple[str, Any]] = pickle.loads(state) + for attr, val in attrs: setattr(self, attr, val) diff --git a/machine-learning/app/models/cache.py b/machine-learning/app/models/cache.py index bd8b59b3e..1d6a0fc76 100644 --- a/machine-learning/app/models/cache.py +++ b/machine-learning/app/models/cache.py @@ -6,7 +6,7 @@ from aiocache.plugins import BasePlugin, TimingPlugin from app.models import from_model_type -from ..schemas import ModelType +from ..schemas import ModelType, has_profiling from .base import InferenceModel @@ -50,20 +50,20 @@ class ModelCache: key = f"{model_name}{model_type.value}{model_kwargs.get('mode', '')}" async with OptimisticLock(self.cache, key) as lock: - model = await self.cache.get(key) + model: InferenceModel | None = await self.cache.get(key) if model is None: model = from_model_type(model_type, model_name, **model_kwargs) await lock.cas(model, ttl=self.ttl) return model async def get_profiling(self) -> dict[str, float] | None: - if not hasattr(self.cache, "profiling"): + if not has_profiling(self.cache): return None - return self.cache.profiling # type: ignore + return self.cache.profiling -class RevalidationPlugin(BasePlugin): +class RevalidationPlugin(BasePlugin): # type: ignore[misc] """Revalidates cache item's TTL after cache hit.""" async def post_get( diff --git a/machine-learning/app/models/clip.py b/machine-learning/app/models/clip.py index 296f790c3..1dee967de 100644 --- a/machine-learning/app/models/clip.py +++ b/machine-learning/app/models/clip.py @@ -51,7 +51,7 @@ class BaseCLIPEncoder(InferenceModel): provider_options=self.provider_options, ) - def _predict(self, image_or_text: Image.Image | str) -> list[float]: + def _predict(self, image_or_text: Image.Image | str) -> ndarray_f32: if isinstance(image_or_text, bytes): image_or_text = Image.open(BytesIO(image_or_text)) @@ -60,16 +60,16 @@ class BaseCLIPEncoder(InferenceModel): if self.mode == "text": raise TypeError("Cannot encode image as text-only model") - outputs = self.vision_model.run(None, self.transform(image_or_text)) + outputs: ndarray_f32 = self.vision_model.run(None, self.transform(image_or_text))[0][0] case str(): if self.mode == "vision": raise TypeError("Cannot encode text as vision-only model") - outputs = self.text_model.run(None, self.tokenize(image_or_text)) + outputs = self.text_model.run(None, self.tokenize(image_or_text))[0][0] case _: raise TypeError(f"Expected Image or str, but got: {type(image_or_text)}") - return outputs[0][0].tolist() + return outputs @abstractmethod def tokenize(self, text: str) -> dict[str, ndarray_i32]: @@ -151,11 +151,13 @@ class OpenCLIPEncoder(BaseCLIPEncoder): @cached_property def model_cfg(self) -> dict[str, Any]: - return json.load(self.model_cfg_path.open()) + model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open()) + return model_cfg @cached_property def preprocess_cfg(self) -> dict[str, Any]: - return json.load(self.preprocess_cfg_path.open()) + preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open()) + return preprocess_cfg class MCLIPEncoder(OpenCLIPEncoder): diff --git a/machine-learning/app/models/facial_recognition.py b/machine-learning/app/models/facial_recognition.py index a8fa6484d..24719eb83 100644 --- a/machine-learning/app/models/facial_recognition.py +++ b/machine-learning/app/models/facial_recognition.py @@ -8,7 +8,7 @@ from insightface.model_zoo import ArcFaceONNX, RetinaFace from insightface.utils.face_align import norm_crop from app.config import clean_name -from app.schemas import ModelType, ndarray_f32 +from app.schemas import BoundingBox, Face, ModelType, ndarray_f32 from .base import InferenceModel @@ -52,7 +52,7 @@ class FaceRecognizer(InferenceModel): ) self.rec_model.prepare(ctx_id=0) - def _predict(self, image: ndarray_f32 | bytes) -> list[dict[str, Any]]: + def _predict(self, image: ndarray_f32 | bytes) -> list[Face]: if isinstance(image, bytes): image = cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR) bboxes, kpss = self.det_model.detect(image) @@ -67,21 +67,20 @@ class FaceRecognizer(InferenceModel): height, width, _ = image.shape for (x1, y1, x2, y2), score, kps in zip(bboxes, scores, kpss): cropped_img = norm_crop(image, kps) - embedding = self.rec_model.get_feat(cropped_img)[0].tolist() - results.append( - { - "imageWidth": width, - "imageHeight": height, - "boundingBox": { - "x1": x1, - "y1": y1, - "x2": x2, - "y2": y2, - }, - "score": score, - "embedding": embedding, - } - ) + embedding: ndarray_f32 = self.rec_model.get_feat(cropped_img)[0] + face: Face = { + "imageWidth": width, + "imageHeight": height, + "boundingBox": { + "x1": x1, + "y1": y1, + "x2": x2, + "y2": y2, + }, + "score": score, + "embedding": embedding, + } + results.append(face) return results @property diff --git a/machine-learning/app/models/image_classification.py b/machine-learning/app/models/image_classification.py index cbf784e5a..b8c38327c 100644 --- a/machine-learning/app/models/image_classification.py +++ b/machine-learning/app/models/image_classification.py @@ -66,7 +66,7 @@ class ImageClassifier(InferenceModel): def _predict(self, image: Image.Image | bytes) -> list[str]: if isinstance(image, bytes): image = Image.open(BytesIO(image)) - predictions: list[dict[str, Any]] = self.model(image) # type: ignore + predictions: list[dict[str, Any]] = self.model(image) tags = [tag for pred in predictions for tag in pred["label"].split(", ") if pred["score"] >= self.min_score] return tags diff --git a/machine-learning/app/schemas.py b/machine-learning/app/schemas.py index ad1faac8c..9e7f62fc8 100644 --- a/machine-learning/app/schemas.py +++ b/machine-learning/app/schemas.py @@ -1,17 +1,12 @@ from enum import StrEnum -from typing import TypeAlias +from typing import Any, Protocol, TypeAlias, TypedDict, TypeGuard import numpy as np from pydantic import BaseModel - -def to_lower_camel(string: str) -> str: - tokens = [token.capitalize() if i > 0 else token for i, token in enumerate(string.split("_"))] - return "".join(tokens) - - -class TextModelRequest(BaseModel): - text: str +ndarray_f32: TypeAlias = np.ndarray[int, np.dtype[np.float32]] +ndarray_i64: TypeAlias = np.ndarray[int, np.dtype[np.int64]] +ndarray_i32: TypeAlias = np.ndarray[int, np.dtype[np.int32]] class TextResponse(BaseModel): @@ -22,7 +17,7 @@ class MessageResponse(BaseModel): message: str -class BoundingBox(BaseModel): +class BoundingBox(TypedDict): x1: int y1: int x2: int @@ -35,6 +30,17 @@ class ModelType(StrEnum): FACIAL_RECOGNITION = "facial-recognition" -ndarray_f32: TypeAlias = np.ndarray[int, np.dtype[np.float32]] -ndarray_i64: TypeAlias = np.ndarray[int, np.dtype[np.int64]] -ndarray_i32: TypeAlias = np.ndarray[int, np.dtype[np.int32]] +class HasProfiling(Protocol): + profiling: dict[str, float] + + +class Face(TypedDict): + boundingBox: BoundingBox + embedding: ndarray_f32 + imageWidth: int + imageHeight: int + score: float + + +def has_profiling(obj: Any) -> TypeGuard[HasProfiling]: + return hasattr(obj, "profiling") and type(obj.profiling) == dict diff --git a/machine-learning/export/models/openclip.py b/machine-learning/export/models/openclip.py index c29dafce7..46c11cb4e 100644 --- a/machine-learning/export/models/openclip.py +++ b/machine-learning/export/models/openclip.py @@ -1,6 +1,7 @@ import tempfile import warnings from dataclasses import dataclass, field +from math import e from pathlib import Path import open_clip @@ -69,10 +70,12 @@ def export_image_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig, output_path = Path(output_path) def encode_image(image: torch.Tensor) -> torch.Tensor: - return model.encode_image(image, normalize=True) + output = model.encode_image(image, normalize=True) + assert isinstance(output, torch.Tensor) + return output args = (torch.randn(1, 3, model_cfg.image_size, model_cfg.image_size),) - traced = torch.jit.trace(encode_image, args) + traced = torch.jit.trace(encode_image, args) # type: ignore[no-untyped-call] with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) @@ -91,10 +94,12 @@ def export_text_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig, o output_path = Path(output_path) def encode_text(text: torch.Tensor) -> torch.Tensor: - return model.encode_text(text, normalize=True) + output = model.encode_text(text, normalize=True) + assert isinstance(output, torch.Tensor) + return output args = (torch.ones(1, model_cfg.sequence_length, dtype=torch.int32),) - traced = torch.jit.trace(encode_text, args) + traced = torch.jit.trace(encode_text, args) # type: ignore[no-untyped-call] with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) From 464cf903f42c56a9f9087151ec72ca93de407394 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey <10599762+fyfrey@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:19:51 +0100 Subject: [PATCH 38/88] fix(mobile): better icon spacing in bottom bar (#5010) * fix(mobile): better icon spacing in bottom bar * wider buttons --- mobile/lib/modules/home/ui/control_bottom_app_bar.dart | 1 + mobile/lib/shared/ui/drag_sheet.dart | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart index e9283218c..8ae7f98cd 100644 --- a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart +++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart @@ -150,6 +150,7 @@ class ControlBottomAppBar extends ConsumerWidget { SizedBox( height: 70, child: ListView( + shrinkWrap: true, scrollDirection: Axis.horizontal, children: renderActionButtons(), ), diff --git a/mobile/lib/shared/ui/drag_sheet.dart b/mobile/lib/shared/ui/drag_sheet.dart index 574962cc0..31ed8f482 100644 --- a/mobile/lib/shared/ui/drag_sheet.dart +++ b/mobile/lib/shared/ui/drag_sheet.dart @@ -34,12 +34,13 @@ class ControlBoxButton extends StatelessWidget { padding: const EdgeInsets.all(10), shape: const CircleBorder(), onPressed: onPressed, + minWidth: 75.0, child: Column( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon(iconData, size: 24), - const SizedBox(height: 6), + const SizedBox(height: 4), Text( label, style: const TextStyle(fontSize: 12.0), From 291159e7fc3894a9753cdd80d583571b5603a698 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:37:39 -0500 Subject: [PATCH 39/88] fixed tests (#5017) --- machine-learning/app/test_main.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index e20a3e6c8..318c3b045 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -75,9 +75,9 @@ class TestCLIP: embedding = clip_encoder.predict(pil_image) assert clip_encoder.mode == "vision" - assert isinstance(embedding, list) - assert len(embedding) == clip_model_cfg["embed_dim"] - assert all([isinstance(num, float) for num in embedding]) + assert isinstance(embedding, np.ndarray) + assert embedding.shape[0] == clip_model_cfg["embed_dim"] + assert embedding.dtype == np.float32 clip_encoder.vision_model.run.assert_called_once() def test_basic_text( @@ -97,9 +97,9 @@ class TestCLIP: embedding = clip_encoder.predict("test search query") assert clip_encoder.mode == "text" - assert isinstance(embedding, list) - assert len(embedding) == clip_model_cfg["embed_dim"] - assert all([isinstance(num, float) for num in embedding]) + assert isinstance(embedding, np.ndarray) + assert embedding.shape[0] == clip_model_cfg["embed_dim"] + assert embedding.dtype == np.float32 clip_encoder.text_model.run.assert_called_once() @@ -133,9 +133,9 @@ class TestFaceRecognition: for face in faces: assert face["imageHeight"] == 800 assert face["imageWidth"] == 600 - assert isinstance(face["embedding"], list) - assert len(face["embedding"]) == 512 - assert all([isinstance(num, float) for num in face["embedding"]]) + assert isinstance(face["embedding"], np.ndarray) + assert face["embedding"].shape[0] == 512 + assert face["embedding"].dtype == np.float32 det_model.detect.assert_called_once() assert rec_model.get_feat.call_count == num_faces From 38983838fde44e8bc4c85736c3d7da7042c15f47 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey <10599762+fyfrey@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:51:16 +0100 Subject: [PATCH 40/88] fix(mobile): better app state handling (#4989) * fix(mobile): better app state handling * watch -> read --------- Co-authored-by: Alex Tran --- .../providers/manual_upload.provider.dart | 2 +- .../shared/providers/app_state.provider.dart | 70 ++++++++++--------- .../shared/providers/websocket.provider.dart | 52 +++++++------- 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/mobile/lib/modules/backup/providers/manual_upload.provider.dart b/mobile/lib/modules/backup/providers/manual_upload.provider.dart index 6d585e289..4d643b9a1 100644 --- a/mobile/lib/modules/backup/providers/manual_upload.provider.dart +++ b/mobile/lib/modules/backup/providers/manual_upload.provider.dart @@ -274,7 +274,7 @@ class ManualUploadNotifier extends StateNotifier { // The app is currently in background. Perform the necessary cleanups which // are on-hold for upload completion if (appState != AppStateEnum.active && appState != AppStateEnum.resumed) { - ref.read(appStateProvider.notifier).handleAppInactivity(); + ref.read(backupProvider.notifier).cancelBackup(); } } diff --git a/mobile/lib/shared/providers/app_state.provider.dart b/mobile/lib/shared/providers/app_state.provider.dart index d2d3f1e1e..44fff2c07 100644 --- a/mobile/lib/shared/providers/app_state.provider.dart +++ b/mobile/lib/shared/providers/app_state.provider.dart @@ -28,13 +28,10 @@ enum AppStateEnum { } class AppStateNotiifer extends StateNotifier { - final Ref ref; + final Ref _ref; + bool _wasPaused = false; - AppStateNotiifer(this.ref) : super(AppStateEnum.active); - - void updateAppState(AppStateEnum appState) { - state = appState; - } + AppStateNotiifer(this._ref) : super(AppStateEnum.active); AppStateEnum getAppState() { return state; @@ -43,65 +40,72 @@ class AppStateNotiifer extends StateNotifier { void handleAppResume() { state = AppStateEnum.resumed; - var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated; - final permission = ref.watch(galleryPermissionNotifier); + // no need to resume because app was never really paused + if (!_wasPaused) return; + _wasPaused = false; + + final isAuthenticated = _ref.read(authenticationProvider).isAuthenticated; + final permission = _ref.read(galleryPermissionNotifier); // Needs to be logged in and have gallery permissions if (isAuthenticated && (permission.isGranted || permission.isLimited)) { - ref.read(backupProvider.notifier).resumeBackup(); - ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); - ref.read(serverInfoProvider.notifier).getServerVersion(); - switch (ref.read(tabProvider)) { + _ref.read(backupProvider.notifier).resumeBackup(); + _ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); + _ref.read(serverInfoProvider.notifier).getServerVersion(); + switch (_ref.read(tabProvider)) { case TabEnum.home: - ref.read(assetProvider.notifier).getAllAsset(); - ref.read(assetProvider.notifier).getPartnerAssets(); + _ref.read(assetProvider.notifier).getAllAsset(); + _ref.read(assetProvider.notifier).getPartnerAssets(); case TabEnum.search: // nothing to do case TabEnum.sharing: - ref.read(assetProvider.notifier).getPartnerAssets(); - ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums(); + _ref.read(assetProvider.notifier).getPartnerAssets(); + _ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums(); case TabEnum.library: - ref.read(albumProvider.notifier).getAllAlbums(); + _ref.read(albumProvider.notifier).getAllAlbums(); } } - ref.watch(websocketProvider.notifier).connect(); + _ref.read(websocketProvider.notifier).connect(); - ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo(); + _ref.read(releaseInfoProvider.notifier).checkGithubReleaseInfo(); - ref - .watch(notificationPermissionProvider.notifier) + _ref + .read(notificationPermissionProvider.notifier) .getNotificationPermission(); - ref.watch(galleryPermissionNotifier.notifier).getGalleryPermissionStatus(); + _ref.read(galleryPermissionNotifier.notifier).getGalleryPermissionStatus(); - ref.read(iOSBackgroundSettingsProvider.notifier).refresh(); + _ref.read(iOSBackgroundSettingsProvider.notifier).refresh(); - ref.invalidate(memoryFutureProvider); + _ref.invalidate(memoryFutureProvider); } void handleAppInactivity() { state = AppStateEnum.inactive; - - // Do not handle inactivity if manual upload is in progress - if (ref.watch(backupProvider.notifier).backupProgress != - BackUpProgressEnum.manualInProgress) { - ImmichLogger().flush(); - ref.read(websocketProvider.notifier).disconnect(); - ref.read(backupProvider.notifier).cancelBackup(); - } + // do not stop/clean up anything on inactivity: issued on every orientation change } void handleAppPause() { state = AppStateEnum.paused; + _wasPaused = true; + // Do not cancel backup if manual upload is in progress + if (_ref.read(backupProvider.notifier).backupProgress != + BackUpProgressEnum.manualInProgress) { + _ref.read(backupProvider.notifier).cancelBackup(); + } + _ref.read(websocketProvider.notifier).disconnect(); + ImmichLogger().flush(); } void handleAppDetached() { state = AppStateEnum.detached; - ref.watch(manualUploadProvider.notifier).cancelBackup(); + // no guarantee this is called at all + _ref.read(manualUploadProvider.notifier).cancelBackup(); } void handleAppHidden() { state = AppStateEnum.hidden; + // do not stop/clean up anything on inactivity: issued on every orientation change } } diff --git a/mobile/lib/shared/providers/websocket.provider.dart b/mobile/lib/shared/providers/websocket.provider.dart index 83fcb000f..f49bc6686 100644 --- a/mobile/lib/shared/providers/websocket.provider.dart +++ b/mobile/lib/shared/providers/websocket.provider.dart @@ -63,21 +63,19 @@ class WebsocketState { } class WebsocketNotifier extends StateNotifier { - WebsocketNotifier(this.ref) + WebsocketNotifier(this._ref) : super( WebsocketState(socket: null, isConnected: false, pendingChanges: []), - ) { - debounce = Debounce( - const Duration(milliseconds: 500), - ); - } + ); - final log = Logger('WebsocketNotifier'); - final Ref ref; - late final Debounce debounce; + final _log = Logger('WebsocketNotifier'); + final Ref _ref; + final Debounce _debounce = Debounce(const Duration(milliseconds: 500)); - connect() { - var authenticationState = ref.read(authenticationProvider); + /// Connects websocket to server unless already connected + void connect() { + if (state.isConnected) return; + final authenticationState = _ref.read(authenticationProvider); if (authenticationState.isAuthenticated) { final accessToken = Store.get(StoreKey.accessToken); @@ -118,7 +116,7 @@ class WebsocketNotifier extends StateNotifier { }); socket.on('error', (errorMessage) { - log.severe("Websocket Error - $errorMessage"); + _log.severe("Websocket Error - $errorMessage"); state = WebsocketState( isConnected: false, socket: null, @@ -138,7 +136,7 @@ class WebsocketNotifier extends StateNotifier { } } - disconnect() { + void disconnect() { debugPrint("Attempting to disconnect from websocket"); var socket = state.socket?.disconnect(); @@ -152,30 +150,30 @@ class WebsocketNotifier extends StateNotifier { } } - stopListenToEvent(String eventName) { + void stopListenToEvent(String eventName) { debugPrint("Stop listening to event $eventName"); state.socket?.off(eventName); } - listenUploadEvent() { + void listenUploadEvent() { debugPrint("Start listening to event on_upload_success"); state.socket?.on('on_upload_success', _handleOnUploadSuccess); } - addPendingChange(PendingAction action, dynamic value) { + void addPendingChange(PendingAction action, dynamic value) { state = state.copyWith( pendingChanges: [...state.pendingChanges, PendingChange(action, value)], ); } - handlePendingChanges() { + void handlePendingChanges() { final deleteChanges = state.pendingChanges .where((c) => c.action == PendingAction.assetDelete) .toList(); if (deleteChanges.isNotEmpty) { List remoteIds = deleteChanges.map((a) => a.value.toString()).toList(); - ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds); + _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds); state = state.copyWith( pendingChanges: state.pendingChanges .where((c) => c.action != PendingAction.assetDelete) @@ -184,27 +182,27 @@ class WebsocketNotifier extends StateNotifier { } } - _handleOnUploadSuccess(dynamic data) { + void _handleOnUploadSuccess(dynamic data) { final dto = AssetResponseDto.fromJson(data); if (dto != null) { final newAsset = Asset.remote(dto); - ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset); + _ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset); } } - _handleOnConfigUpdate(dynamic _) { - ref.read(serverInfoProvider.notifier).getServerFeatures(); - ref.read(serverInfoProvider.notifier).getServerConfig(); + void _handleOnConfigUpdate(dynamic _) { + _ref.read(serverInfoProvider.notifier).getServerFeatures(); + _ref.read(serverInfoProvider.notifier).getServerConfig(); } // Refresh updated assets - _handleServerUpdates(dynamic _) { - ref.read(assetProvider.notifier).getAllAsset(); + void _handleServerUpdates(dynamic _) { + _ref.read(assetProvider.notifier).getAllAsset(); } - _handleOnAssetDelete(dynamic data) { + void _handleOnAssetDelete(dynamic data) { addPendingChange(PendingAction.assetDelete, data); - debounce(handlePendingChanges); + _debounce(handlePendingChanges); } } From ac7e8bcdf469bfbe9810c8ccfc39ebb1e6b37b45 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Nov 2023 14:15:36 -0600 Subject: [PATCH 41/88] fix(mobile): freeze at splash screen when updating from 1.85 to the new version (#5012) * fix(mobile): Cannot return to logged in screen due to name changes * fix(mobile): Cannot return to logged in screen due to name changes * remove deadcode * test deprecate * Add deprecated decorator * revert api change --- mobile/ios/Podfile.lock | 2 +- mobile/lib/shared/views/splash_screen.dart | 25 ++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index c6c23d942..75168ce1c 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -169,4 +169,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382 -COCOAPODS: 1.12.1 +COCOAPODS: 1.11.3 diff --git a/mobile/lib/shared/views/splash_screen.dart b/mobile/lib/shared/views/splash_screen.dart index 2f493d16a..1298e0efe 100644 --- a/mobile/lib/shared/views/splash_screen.dart +++ b/mobile/lib/shared/views/splash_screen.dart @@ -40,12 +40,25 @@ class SplashScreenPage extends HookConsumerWidget { log.severe(e); } - isSuccess = - await ref.read(authenticationProvider.notifier).setSuccessLoginInfo( - accessToken: accessToken, - serverUrl: serverUrl, - offlineLogin: deviceIsOffline, - ); + try { + isSuccess = await ref + .read(authenticationProvider.notifier) + .setSuccessLoginInfo( + accessToken: accessToken, + serverUrl: serverUrl, + offlineLogin: deviceIsOffline, + ); + } catch (error, stackTrace) { + ref.read(authenticationProvider.notifier).logout(); + + log.severe( + 'Cannot set success login info: $error', + error, + stackTrace, + ); + + context.autoPush(const LoginRoute()); + } } // If the device is offline and there is a currentUser stored locallly From 72fb421f547f05c18fc34297fceddfb25021cfcd Mon Sep 17 00:00:00 2001 From: Dmitriy P Date: Mon, 13 Nov 2023 15:57:58 -0600 Subject: [PATCH 42/88] feat(web): Add file path info for owned assets (#4943) * feat(web): Add file path info for external assets Add file path information to the asset details panel for External assets. This will allow a user to easily locate said asset in the filesystem, to perform any desired tasks on that asset. Styling was chosen to be as unobtrusive as possible. * feat(web): toggleable file path info for external assets If the user is the owner of the current asset and it's an external asset, then add a button next to the filename to reveal the asset's file path. * show path on owned asset and styling --------- Co-authored-by: Alex Tran --- .../asset-viewer/detail-panel.svelte | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 5fff954af..70d76ca1a 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -6,10 +6,18 @@ import { AlbumResponseDto, AssetResponseDto, ThumbnailFormat, api } from '@api'; import { DateTime } from 'luxon'; import { createEventDispatcher } from 'svelte'; + import { slide } from 'svelte/transition'; import { asByteUnitString } from '../../utils/byte-units'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import UserAvatar from '../shared-components/user-avatar.svelte'; - import { mdiCalendar, mdiCameraIris, mdiClose, mdiImageOutline, mdiMapMarkerOutline } from '@mdi/js'; + import { + mdiCalendar, + mdiCameraIris, + mdiClose, + mdiImageOutline, + mdiMapMarkerOutline, + mdiInformationOutline, + } from '@mdi/js'; import Icon from '$lib/components/elements/icon.svelte'; import Map from '../shared-components/map/map.svelte'; @@ -77,6 +85,9 @@ console.error(error); } }; + + let showAssetPath = false; + const toggleAssetPath = () => (showAssetPath = !showAssetPath);

    @@ -215,8 +226,15 @@
    -

    - {getAssetFilename(asset)} +

    + {#if isOwner} + {asset.originalFileName} + + {:else} + {getAssetFilename(asset)} + {/if}

    {#if asset.exifInfo.exifImageHeight && asset.exifInfo.exifImageWidth} @@ -230,6 +248,11 @@ {/if}

    {asByteUnitString(asset.exifInfo.fileSizeInByte, $locale)}

    + {#if showAssetPath} +

    + {asset.originalPath} +

    + {/if}
    {/if} From 24670178dc85f8822f888b7cb88a611f4e6c0e58 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 13 Nov 2023 17:16:26 -0500 Subject: [PATCH 43/88] feat(web): use css vraiables (#5022) --- web/src/app.css | 22 ++++++++++++++++++++++ web/tailwind.config.cjs | 28 ++++++++++++++-------------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/web/src/app.css b/web/src/app.css index c38700ba9..746ba810a 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -2,6 +2,28 @@ @tailwind components; @tailwind utilities; +@layer base { + :root { + /* light */ + --immich-primary: 66 80 175; + --immich-bg: 255 255 255; + --immich-fg: 0 0 0; + --immich-gray: 246 246 244; + --immich-error: 229 115 115; + --immich-success: 129 199 132; + --immich-warning: 255 183 77; + + /* dark */ + --immich-dark-primary: 172 203 250; + --immich-dark-bg: 0 0 0; + --immich-dark-fg: 229 231 235; + --immich-dark-gray: 33 33 33; + --immich-dark-error: 211 47 47; + --immich-dark-success: 56 142 60; + --immich-dark-warning: 245 124 0; + } +} + @font-face { font-family: 'Work Sans'; src: url('$lib/assets/fonts/WorkSans-VariableFont_wght.ttf') format('truetype-variations'); diff --git a/web/tailwind.config.cjs b/web/tailwind.config.cjs index cc212d258..3691e15dc 100644 --- a/web/tailwind.config.cjs +++ b/web/tailwind.config.cjs @@ -6,22 +6,22 @@ module.exports = { extend: { colors: { // Light Theme - 'immich-primary': '#4250af', - 'immich-bg': 'white', - 'immich-fg': 'black', - 'immich-gray': '#F6F6F4', - 'immich-error': '#e57373', - 'immich-success': '#81c784', - 'immich-warning': '#ffb74d', + 'immich-primary': 'rgb(var(--immich-primary) / )', + 'immich-bg': 'rgb(var(--immich-bg) / )', + 'immich-fg': 'rgb(var(--immich-fg) / )', + 'immich-gray': 'rgb(var(--immich-gray) / )', + 'immich-error': 'rgb(var(--immich-error) / )', + 'immich-success': 'rgb(var(--immich-success) / )', + 'immich-warning': 'rgb(var(--immich-warning) / )', // Dark Theme - 'immich-dark-primary': '#adcbfa', - 'immich-dark-bg': 'black', - 'immich-dark-fg': '#e5e7eb', - 'immich-dark-gray': '#212121', - 'immich-dark-error': '#d32f2f', - 'immich-dark-success': '#388e3c', - 'immich-dark-warning': '#f57c00', + 'immich-dark-primary': 'rgb(var(--immich-dark-primary) / )', + 'immich-dark-bg': 'rgb(var(--immich-dark-bg) / )', + 'immich-dark-fg': 'rgb(var(--immich-dark-fg) / )', + 'immich-dark-gray': 'rgb(var(--immich-dark-gray) / )', + 'immich-dark-error': 'rgb(var(--immich-dark-error) / )', + 'immich-dark-success': 'rgb(var(--immich-dark-success) / )', + 'immich-dark-warning': 'rgb(var(--immich-dark-warning) / )', }, fontFamily: { 'immich-title': ['Snowburst One', 'cursive'], From 14c71875393d898b091527a22132ea99153eeda1 Mon Sep 17 00:00:00 2001 From: opdelta <1195214+opdelta@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:20:54 -0500 Subject: [PATCH 44/88] feat(mobile): Added fr-CA translations (#5023) Co-authored-by: Ziad Lteif --- mobile/assets/i18n/fr-CA.json | 384 ++++++++++++++++++++++++++++++ mobile/ios/Runner/Info.plist | 1 + mobile/lib/constants/locales.dart | 1 + 3 files changed, 386 insertions(+) create mode 100644 mobile/assets/i18n/fr-CA.json diff --git a/mobile/assets/i18n/fr-CA.json b/mobile/assets/i18n/fr-CA.json new file mode 100644 index 000000000..ee06e82ca --- /dev/null +++ b/mobile/assets/i18n/fr-CA.json @@ -0,0 +1,384 @@ +{ + "add_to_album_bottom_sheet_added": "Ajouté à {album}", + "add_to_album_bottom_sheet_already_exists": "Déjà dans {album}", + "advanced_settings_prefer_remote_subtitle": "Certains appareils sont très lents à charger des vignettes à partir de ressources présentes sur l'appareil. Activez ce paramètre pour charger des images externes à la place.", + "advanced_settings_prefer_remote_title": "Préférer les images externes", + "advanced_settings_self_signed_ssl_subtitle": "Permet d'ignorer la vérification du certificat SSL pour le point d'accès du serveur. Requis pour les certificats auto-signés.", + "advanced_settings_self_signed_ssl_title": "Autoriser les certificats SSL auto-signés", + "advanced_settings_tile_subtitle": "Paramètres d'utilisateur avancés", + "advanced_settings_tile_title": "Avancé", + "advanced_settings_troubleshooting_subtitle": "Activer des fonctions supplémentaires pour le dépannage", + "advanced_settings_troubleshooting_title": "Dépannage", + "album_info_card_backup_album_excluded": "EXCLUS", + "album_info_card_backup_album_included": "INCLUS", + "album_thumbnail_card_item": "1 élément", + "album_thumbnail_card_items": "{} éléments", + "album_thumbnail_card_shared": " · Partagé", + "album_thumbnail_owned": "Possédé", + "album_thumbnail_shared_by": "Partagé par {}", + "album_viewer_appbar_share_delete": "Supprimer l'album", + "album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album", + "album_viewer_appbar_share_err_leave": "Impossible de quitter l'album", + "album_viewer_appbar_share_err_remove": "Il y a des problèmes lors de la suppression des éléments de l'album", + "album_viewer_appbar_share_err_title": "Échec de la modification du titre de l'album", + "album_viewer_appbar_share_leave": "Quitter l'album", + "album_viewer_appbar_share_remove": "Retirer de l'album", + "album_viewer_appbar_share_to": "Partager à", + "album_viewer_page_share_add_users": "Ajouter des utilisateurs", + "all_people_page_title": "Personnes", + "all_videos_page_title": "Vidéos", + "app_bar_signout_dialog_content": "Êtes-vous sûr de vouloir vous déconnecter?", + "app_bar_signout_dialog_ok": "Oui", + "app_bar_signout_dialog_title": "Se déconnecter", + "archive_page_no_archived_assets": "Aucun élément archivé n'a été trouvé", + "archive_page_title": "Archive ({})", + "asset_list_layout_settings_dynamic_layout_title": "Affichage dynamique", + "asset_list_layout_settings_group_automatically": "Automatique", + "asset_list_layout_settings_group_by": "Grouper les éléments par", + "asset_list_layout_settings_group_by_month": "Mois", + "asset_list_layout_settings_group_by_month_day": "Mois + jour", + "asset_list_settings_subtitle": "Paramètres de disposition de la grille de photos", + "asset_list_settings_title": "Grille de photos", + "backup_album_selection_page_albums_device": "Albums sur l'appareil ({})", + "backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure", + "backup_album_selection_page_assets_scatter": "Les éléments peuvent être répartis sur plusieurs albums. De ce fait, les albums peuvent être inclus ou exclus pendant le processus de sauvegarde.", + "backup_album_selection_page_select_albums": "Sélectionner les albums", + "backup_album_selection_page_selection_info": "Informations sur la sélection", + "backup_album_selection_page_total_assets": "Total des éléments uniques", + "backup_all": "Tout", + "backup_background_service_backup_failed_message": "Échec de la sauvegarde des éléments. Nouvelle tentative...", + "backup_background_service_connection_failed_message": "Impossible de se connecter au serveur. Nouvelle tentative...", + "backup_background_service_current_upload_notification": "Transfert {}", + "backup_background_service_default_notification": "Recherche de nouveaux éléments...", + "backup_background_service_error_title": "Erreur de sauvegarde", + "backup_background_service_in_progress_notification": "Sauvegarde de vos éléments...", + "backup_background_service_upload_failure_notification": "Impossible de transférer {}", + "backup_controller_page_albums": "Sauvegarder les albums", + "backup_controller_page_background_app_refresh_disabled_content": "Activez le rafraîchissement de l'application en arrière-plan dans Paramètres > Général > Rafraîchissement de l'application en arrière-plan afin d'utiliser la sauvegarde en arrière-plan.", + "backup_controller_page_background_app_refresh_disabled_title": "Rafraîchissement de l'application en arrière-plan désactivé", + "backup_controller_page_background_app_refresh_enable_button_text": "Aller aux paramètres", + "backup_controller_page_background_battery_info_link": "Montrez-moi comment", + "backup_controller_page_background_battery_info_message": "Pour une expérience optimale de la sauvegarde en arrière-plan, veuillez désactiver toute optimisation de la batterie limitant l'activité en arrière-plan pour Immich.\n\nÉtant donné que cela est spécifique à chaque appareil, veuillez consulter les informations requises pour le fabricant de votre appareil.", + "backup_controller_page_background_battery_info_ok": "OK", + "backup_controller_page_background_battery_info_title": "Optimisation de la batterie", + "backup_controller_page_background_charging": "Seulement pendant la charge", + "backup_controller_page_background_configure_error": "Échec de la configuration du service d'arrière-plan", + "backup_controller_page_background_delay": "Retarder la sauvegarde des nouveaux éléments d'actif: {}", + "backup_controller_page_background_description": "Activez le service d'arrière-plan pour sauvegarder automatiquement tous les nouveaux éléments sans avoir à ouvrir l'application.", + "backup_controller_page_background_is_off": "La sauvegarde automatique en arrière-plan est désactivée", + "backup_controller_page_background_is_on": "La sauvegarde automatique en arrière-plan est activée", + "backup_controller_page_background_turn_off": "Désactiver le service d'arrière-plan", + "backup_controller_page_background_turn_on": "Activer le service d'arrière-plan", + "backup_controller_page_background_wifi": "Uniquement sur WiFi", + "backup_controller_page_backup": "Sauvegardé", + "backup_controller_page_backup_selected": "Sélectionné: ", + "backup_controller_page_backup_sub": "Photos et vidéos sauvegardées", + "backup_controller_page_cancel": "Annuler", + "backup_controller_page_created": "Créé le: {}", + "backup_controller_page_desc_backup": "Activez la sauvegarde pour envoyer automatiquement les nouveaux éléments sur le serveur.", + "backup_controller_page_excluded": "Exclus: ", + "backup_controller_page_failed": "Échec de l'opération ({})", + "backup_controller_page_filename": "Nom du fichier: {} [{}]", + "backup_controller_page_id": "ID: {}", + "backup_controller_page_info": "Informations de sauvegarde", + "backup_controller_page_none_selected": "Aucune sélection", + "backup_controller_page_remainder": "Restant", + "backup_controller_page_remainder_sub": "Photos et albums restants à sauvegarder à partir de la sélection", + "backup_controller_page_select": "Sélectionner", + "backup_controller_page_server_storage": "Stockage du serveur", + "backup_controller_page_start_backup": "Démarrer la sauvegarde", + "backup_controller_page_status_off": "La sauvegarde est désactivée", + "backup_controller_page_status_on": "La sauvegarde est activée", + "backup_controller_page_storage_format": "{} de {} utilisé", + "backup_controller_page_to_backup": "Albums à sauvegarder", + "backup_controller_page_total": "Total", + "backup_controller_page_total_sub": "Toutes les photos et vidéos uniques des albums sélectionnés", + "backup_controller_page_turn_off": "Désactiver la sauvegarde", + "backup_controller_page_turn_on": "Activer la sauvegarde", + "backup_controller_page_uploading_file_info": "Transfert des informations du fichier", + "backup_err_only_album": "Impossible de retirer le seul album", + "backup_info_card_assets": "éléments", + "backup_manual_cancelled": "Annulé", + "backup_manual_failed": "Echec", + "backup_manual_in_progress": "Téléchargement déjà en cours. Essayez après un instant", + "backup_manual_success": "Succès ", + "backup_manual_title": "Statut du téléchargement ", + "cache_settings_album_thumbnails": "vignettes de la page bibliothèque ({} éléments)", + "cache_settings_clear_cache_button": "Effacer le cache", + "cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.", + "cache_settings_image_cache_size": "Taille du cache des images ({} éléments)", + "cache_settings_statistics_album": "vignettes de la bibliothèque", + "cache_settings_statistics_assets": "{} éléments ({})", + "cache_settings_statistics_full": "Images complètes", + "cache_settings_statistics_shared": "vignettes d'albums partagés", + "cache_settings_statistics_thumbnail": "vignettes", + "cache_settings_statistics_title": "Utilisation du cache", + "cache_settings_subtitle": "Contrôler le comportement de mise en cache de l'application mobile Immich", + "cache_settings_thumbnail_size": "Taille du cache des vignettes ({} éléments)", + "cache_settings_tile_subtitle": "Contrôler le comportement du stockage local", + "cache_settings_tile_title": "Stockage local", + "cache_settings_title": "Paramètres de mise en cache", + "change_password_form_confirm_password": "Confirmez le mot de passe", + "change_password_form_description": "Bonjour {firstName} {lastName},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", + "change_password_form_new_password": "Nouveau mot de passe", + "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", + "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", + "common_add_to_album": "Ajouter à l'album", + "common_change_password": "Modifier le mot de passe", + "common_create_new_album": "Créer un nouvel album", + "common_server_error": "Veuillez vérifier votre connexion réseau, vous assurer que le serveur est accessible et que les versions de l'application et du serveur sont compatibles.", + "common_shared": "Partagé", + "control_bottom_app_bar_add_to_album": "Ajouter à l'album", + "control_bottom_app_bar_album_info": "{} éléments", + "control_bottom_app_bar_album_info_shared": "{} éléments - Partagés", + "control_bottom_app_bar_archive": "Archive", + "control_bottom_app_bar_create_new_album": "Créer un nouvel album", + "control_bottom_app_bar_delete": "Supprimer", + "control_bottom_app_bar_favorite": "Favoris", + "control_bottom_app_bar_share": "Partager", + "control_bottom_app_bar_share_to": "Partager à", + "control_bottom_app_bar_stack": "Empiler", + "control_bottom_app_bar_unarchive": "Désarchiver", + "control_bottom_app_bar_upload": "Téléverser", + "create_album_page_untitled": "Sans titre", + "create_shared_album_page_create": "Créer", + "create_shared_album_page_share": "Partager", + "create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS", + "create_shared_album_page_share_select_photos": "Sélectionner les photos", + "curated_location_page_title": "Places", + "curated_object_page_title": "Objets", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", + "date_format": "E, LLL d, y • h:mm a", + "delete_dialog_alert": "Ces éléments seront définitivement supprimés de Immich et de votre appareil.", + "delete_dialog_cancel": "Annuler", + "delete_dialog_ok": "Supprimer", + "delete_dialog_title": "Supprimer définitivement", + "delete_shared_link_dialog_content": "Êtes-vous sûr de vouloir supprimer ce lien partagé?", + "delete_shared_link_dialog_title": "Supprimer le lien partagé", + "description_input_hint_text": "Ajouter une description...", + "description_input_submit_error": "Erreur de mise à jour de la description, vérifier le journal pour plus de détails", + "exif_bottom_sheet_description": "Ajouter une description...", + "exif_bottom_sheet_details": "DÉTAILS", + "exif_bottom_sheet_location": "LOCALISATION", + "experimental_settings_new_asset_list_subtitle": "En cours de développement", + "experimental_settings_new_asset_list_title": "Activer la grille de photos expérimentale", + "experimental_settings_subtitle": "Utilisez à vos dépends!", + "experimental_settings_title": "Expérimental", + "favorites_page_no_favorites": "Aucun élément favori n'a été trouvé", + "favorites_page_title": "Favoris", + "home_page_add_to_album_conflicts": "{added} éléments ajoutés à l'album {album}. Les éléments {failed} sont déjà dans l'album.", + "home_page_add_to_album_err_local": "Impossible d'ajouter des éléments locaux aux albums pour le moment, étape ignorée", + "home_page_add_to_album_success": "{added} éléments ajoutés à l'album {album}.", + "home_page_archive_err_local": "Impossible d'archiver les ressources locales pour l'instant, étape ignorée", + "home_page_building_timeline": "Construction de la chronologie", + "home_page_favorite_err_local": "Impossible d'ajouter des éléments locaux aux favoris pour le moment, étape ignorée", + "home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidéos de cet ou ces albums.", + "home_page_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée", + "image_viewer_page_state_provider_download_error": "Erreur de téléchargement", + "image_viewer_page_state_provider_download_success": "Téléchargement réussi", + "image_viewer_page_state_provider_share_error": "Erreur de partage", + "library_page_albums": "Albums", + "library_page_archive": "Archive", + "library_page_device_albums": "Albums sur l'appareil", + "library_page_favorites": "Favoris", + "library_page_new_album": "Nouvel album", + "library_page_sharing": "Partage", + "library_page_sort_created": "Créations les plus récentes", + "library_page_sort_last_modified": "Dernière modification", + "library_page_sort_most_recent_photo": "Photo la plus récente", + "library_page_sort_title": "Titre de l'album", + "login_disabled": "La connexion a été désactivée ", + "login_form_api_exception": "Erreur de l'API. Veuillez vérifier l'URL du serveur et et réessayer.", + "login_form_button_text": "Connexion", + "login_form_email_hint": "votreemail@email.com", + "login_form_endpoint_hint": "http://adresse-ip-serveur:port/api", + "login_form_endpoint_url": "URL du point d'accès au serveur", + "login_form_err_http": "Veuillez préciser http:// ou https://", + "login_form_err_invalid_email": "E-mail invalide", + "login_form_err_invalid_url": "URL invalide", + "login_form_err_leading_whitespace": "Espace en début de ligne", + "login_form_err_trailing_whitespace": "Espace de fin de ligne", + "login_form_failed_get_oauth_server_config": "Erreur de connexion par OAuth, vérifiez l\"URL du serveur", + "login_form_failed_get_oauth_server_disable": "La fonctionnalité OAuth n'est pas disponible sur ce serveur", + "login_form_failed_login": "Erreur de connexion, vérifiez l'url du serveur, l'email et le mot de passe", + "login_form_handshake_exception": "Il y a eu une exception de liaison avec le serveur. Activez la prise en charge des certificats auto-signés dans les paramètres si vous utilisez un certificat auto-signé.", + "login_form_label_email": "E-mail", + "login_form_label_password": "Mot de passe", + "login_form_next_button": "Suivant", + "login_form_password_hint": "mot de passe", + "login_form_save_login": "Rester connecté", + "login_form_server_empty": "Saisissez l'URL du serveur.", + "login_form_server_error": "Impossible de se connecter au serveur.", + "login_password_changed_error": "Une erreur s'est produite lors de la mise à jour de votre mot de passe", + "login_password_changed_success": "Mot de passe mis à jour avec succès", + "map_cannot_get_user_location": "Impossible d'obtenir la localisation de l'utilisateur", + "map_location_dialog_cancel": "Annuler", + "map_location_dialog_yes": "Oui", + "map_location_service_disabled_content": "Le service de localisation doit être activé pour afficher les éléments de votre emplacement actuel. Souhaitez-vous l'activer maintenant?", + "map_location_service_disabled_title": "Service de localisation désactivé", + "map_no_assets_in_bounds": "Pas de photos dans cette zone", + "map_no_location_permission_content": "L'autorisation de localisation est nécessaire pour afficher les éléments de votre emplacement actuel. Souhaitez-vous l'autoriser maintenant?", + "map_no_location_permission_title": "Permission de localisation refusée", + "map_settings_dark_mode": "Mode sombre", + "map_settings_dialog_cancel": "Annuler", + "map_settings_dialog_save": "Sauvegarder", + "map_settings_dialog_title": "Paramètres de la carte", + "map_settings_include_show_archived": "Inclure les archives", + "map_settings_only_relative_range": "Plage de dates", + "map_settings_only_show_favorites": "Afficher uniquement les favoris", + "map_zoom_to_see_photos": "Dézoomer pour voir les photos", + "monthly_title_text_date_format": "MMMM y", + "motion_photos_page_title": "Photos avec mouvement", + "notification_permission_dialog_cancel": "Annuler", + "notification_permission_dialog_content": "Pour activer les notifications, allez dans Paramètres et sélectionnez Autoriser.", + "notification_permission_dialog_settings": "Paramètres", + "notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.", + "notification_permission_list_tile_enable_button": "Activer les notifications", + "notification_permission_list_tile_title": "Permission de notification", + "partner_page_add_partner": "Ajouter un partenaire", + "partner_page_empty_message": "Vos photos ne sont pas encore partagées avec un partenaire.", + "partner_page_no_more_users": "Plus d'utilisateurs à ajouter", + "partner_page_partner_add_failed": "Échec de l'ajout d'un partenaire", + "partner_page_select_partner": "Sélectionner un partenaire", + "partner_page_shared_to_title": "Partagé avec", + "partner_page_stop_sharing_content": "{} ne pourra plus accéder à vos photos.", + "partner_page_stop_sharing_title": "Arrêter de partager vos photos?", + "partner_page_title": "Partenaire", + "permission_onboarding_continue_anyway": "Continuer quand même", + "permission_onboarding_get_started": "Commencer", + "permission_onboarding_go_to_settings": "Accéder aux paramètres", + "permission_onboarding_grant_permission": "Accorder l'autorisation", + "permission_onboarding_log_out": "Se déconnecter", + "permission_onboarding_permission_denied": "Permission refusée. Pour utiliser Immich, accordez lautorisation pour les photos et vidéos dans les Paramètres.", + "permission_onboarding_permission_granted": "Permission accordée! Vous êtes prêts.", + "permission_onboarding_permission_limited": "Permission limitée. Pour permettre à Immich de sauvegarder et de gérer l'ensemble de votre bibliothèque, accordez l'autorisation pour les photos et vidéos dans les Paramètres.", + "permission_onboarding_request": "Immich demande l'autorisation de visionner vos photos et vidéo", + "profile_drawer_app_logs": "Journaux", + "profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour", + "profile_drawer_documentation": "Documentation", + "profile_drawer_github": "GitHub", + "profile_drawer_settings": "Paramètres", + "profile_drawer_sign_out": "Se déconnecter", + "profile_drawer_trash": "Corbeille", + "recently_added_page_title": "Récemment ajouté", + "search_bar_hint": "Rechercher vos photos", + "search_page_categories": "Catégories", + "search_page_favorites": "Favoris", + "search_page_motion_photos": "Photos avec mouvement", + "search_page_no_objects": "Aucune information disponible sur les objets", + "search_page_no_places": "Aucune information disponible sur la localisation", + "search_page_people": "Personnes", + "search_page_places": "Lieux", + "search_page_recently_added": "Récemment ajouté", + "search_page_screenshots": "Captures d'écran", + "search_page_selfies": "Selfies", + "search_page_things": "Objets", + "search_page_videos": "Vidéos", + "search_page_view_all_button": "Voir tout", + "search_page_your_activity": "Votre activité", + "search_result_page_new_search_hint": "Nouvelle recherche", + "search_suggestion_list_smart_search_hint_1": "La recherche intelligente est activée par défaut. Pour rechercher des métadonnées, utilisez la syntaxe suivante", + "search_suggestion_list_smart_search_hint_2": "m:votre-terme-de-recherche", + "select_additional_user_for_sharing_page_suggestions": "Suggestions", + "select_user_for_sharing_page_err_album": "Échec de la création de l'album", + "select_user_for_sharing_page_share_suggestions": "Suggestions", + "server_info_box_app_version": "Version de l'application", + "server_info_box_server_url": "URL du serveur", + "server_info_box_server_version": "Version du serveur", + "setting_image_viewer_help": "Le visualiseur de détails charge d'abord la petite vignette, puis l'aperçu de taille moyenne (s'il est activé), enfin l'original (s'il est activé).", + "setting_image_viewer_original_subtitle": "Activez cette option pour charger l'image en résolution originale (volumineux!). Désactiver pour réduire l'utilisation des données (réseau et cache de l'appareil).", + "setting_image_viewer_original_title": "Charger l'image originale", + "setting_image_viewer_preview_subtitle": "Activer pour charger une image de résolution moyenne. Désactiver pour charger directement l'original ou utiliser uniquement la vignette.", + "setting_image_viewer_preview_title": "Charger l'image d'aperçu", + "setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan: {}", + "setting_notifications_notify_hours": "{} heures", + "setting_notifications_notify_immediately": "immédiatement", + "setting_notifications_notify_minutes": "{} minutes", + "setting_notifications_notify_never": "jamais", + "setting_notifications_notify_seconds": "{} secondes", + "setting_notifications_single_progress_subtitle": "Informations détaillées sur la progression du transfert par élément", + "setting_notifications_single_progress_title": "Afficher la progression du détail de la sauvegarde en arrière-plan", + "setting_notifications_subtitle": "Ajustez vos préférences de notification", + "setting_notifications_title": "Notifications", + "setting_notifications_total_progress_subtitle": "Progression globale du transfert (effectué/total des éléments)", + "setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan", + "setting_pages_app_bar_settings": "Paramètres", + "settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre", + "share_add": "Ajouter", + "share_add_photos": "Ajouter des photos", + "share_add_title": "Ajouter un titre", + "share_create_album": "Créer un album", + "shared_album_activities_input_disable": "Les commentaires sont désactivés", + "shared_album_activities_input_hint": "Dire quelque chose", + "shared_album_activity_remove_content": "Souhaitez-vous supprimer cette activité?", + "shared_album_activity_remove_title": "Supprimer l'activité", + "shared_album_activity_setting_subtitle": "Laisser les autres réagir", + "shared_album_activity_setting_title": "Commentaires et likes", + "share_dialog_preparing": "Préparation...", + "shared_link_app_bar_title": "Liens partagés", + "shared_link_create_app_bar_title": "Créer un lien pour partager", + "shared_link_create_info": "Permettre à toute personne ayant le lien de voir la ou les photos sélectionnées", + "shared_link_create_submit_button": "Créer le lien", + "shared_link_edit_allow_download": "Autoriser les utilisateurs publics à télécharger", + "shared_link_edit_allow_upload": "Autoriser les utilisateurs publics à téléverser", + "shared_link_edit_app_bar_title": "Modifier le lien", + "shared_link_edit_change_expiry": "Modifier le délai d'expiration", + "shared_link_edit_description": "Description", + "shared_link_edit_description_hint": "Saisir la description du partage", + "shared_link_edit_expire_after": "Expire après", + "shared_link_edit_password": "Mot de passe", + "shared_link_edit_password_hint": "Saisir le mot de passe de partage", + "shared_link_edit_show_meta": "Afficher les métadonnées", + "shared_link_edit_submit_button": "Mettre à jour le lien", + "shared_link_empty": "Vous n'avez pas de liens partagés", + "shared_link_manage_links": "Gérer les liens partagés", + "share_done": "Fait", + "share_invite": "Inviter à l'album", + "sharing_page_album": "Albums partagés", + "sharing_page_description": "Créez des albums partagés pour partager des photos et des vidéos avec les personnes de votre réseau.", + "sharing_page_empty_list": "LISTE VIDE", + "sharing_silver_appbar_create_shared_album": "Créer un album partagé", + "sharing_silver_appbar_shared_links": "Liens partagés", + "sharing_silver_appbar_share_partner": "Partager avec un partenaire", + "tab_controller_nav_library": "Bibliothèque", + "tab_controller_nav_photos": "Photos", + "tab_controller_nav_search": "Recherche", + "tab_controller_nav_sharing": "Partage", + "theme_setting_asset_list_storage_indicator_title": "Afficher l'indicateur de stockage sur les tuiles des éléments", + "theme_setting_asset_list_tiles_per_row_title": "Nombre d'éléments par ligne ({})", + "theme_setting_dark_mode_switch": "Mode sombre", + "theme_setting_image_viewer_quality_subtitle": "Ajustez la qualité de la visionneuse d'images détaillées", + "theme_setting_image_viewer_quality_title": "Qualité de la visualisation des images", + "theme_setting_system_theme_switch": "Automatique (suivre les paramètres du système)", + "theme_setting_theme_subtitle": "Choisissez le thème de l'application", + "theme_setting_theme_title": "Thème", + "theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau.", + "theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes", + "translated_text_options": "Options", + "trash_page_delete": "Supprimer", + "trash_page_delete_all": "Tout supprimer", + "trash_page_empty_trash_btn": "Vider la corbeille", + "trash_page_empty_trash_dialog_content": "Voulez-vous vider les éléments de la corbeille? Ces objets seront définitivement retirés d'Immich", + "trash_page_empty_trash_dialog_ok": "Ok", + "trash_page_info": "Les éléments mis à la corbeille seront définitivement supprimés au bout de {} jours.", + "trash_page_no_assets": "Pas d'éléments dans la corbeille", + "trash_page_restore": "Restaurer", + "trash_page_restore_all": "Tout restaurer", + "trash_page_select_assets_btn": "Sélectionner les éléments", + "trash_page_select_btn": "Sélectionner", + "trash_page_title": "Corbeille ({})", + "upload_dialog_cancel": "Annuler", + "upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur?", + "upload_dialog_ok": "Télécharger ", + "upload_dialog_title": "Télécharger cet élément ", + "version_announcement_overlay_ack": "Confirmer", + "version_announcement_overlay_release_notes": "notes de mise à jour", + "version_announcement_overlay_text_1": "Bonjour, une nouvelle version de", + "version_announcement_overlay_text_2": "veuillez prendre le temps de visiter le ", + "version_announcement_overlay_text_3": " et assurez-vous que votre configuration docker-compose et .env est à jour pour éviter toute erreur de configuration, en particulier si vous utilisez WatchTower ou tout autre mécanisme qui gère la mise à jour automatique de votre application serveur.", + "version_announcement_overlay_title": "Nouvelle version serveur disponible \uD83C\uDF89", + "viewer_remove_from_stack": "Retirer de la pile", + "viewer_stack_use_as_main_asset": "Utiliser comme élément principal", + "viewer_unstack": "Désempiler" + } \ No newline at end of file diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index c1ceb76cb..9cfb210f1 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -28,6 +28,7 @@ es vi fr + fr ja pl fi diff --git a/mobile/lib/constants/locales.dart b/mobile/lib/constants/locales.dart index 452203d51..8d1a7bd30 100644 --- a/mobile/lib/constants/locales.dart +++ b/mobile/lib/constants/locales.dart @@ -9,6 +9,7 @@ const List locales = [ Locale('it', 'IT'), Locale('es', 'ES'), Locale('vi', 'VN'), + Locale('fr', 'CA'), Locale('fr', 'FR'), Locale('ja', 'JP'), Locale('pl', 'PL'), From d25a245049bd0612b4ba6f3c86b706c98c72c19a Mon Sep 17 00:00:00 2001 From: martin <74269598+martabal@users.noreply.github.com> Date: Tue, 14 Nov 2023 04:10:35 +0100 Subject: [PATCH 45/88] feat(web,server): user avatar color (#4779) --- cli/src/api/open-api/api.ts | 119 +++++++++++ .../album/views/album_options_part.dart | 9 +- .../album/views/album_viewer_page.dart | 1 - mobile/lib/shared/models/user.dart | 78 +++++++ mobile/lib/shared/models/user.g.dart | 201 ++++++++++++++---- .../app_bar_dialog/app_bar_profile_info.dart | 28 +-- mobile/lib/shared/ui/immich_app_bar.dart | 5 +- mobile/lib/shared/ui/user_circle_avatar.dart | 29 +-- mobile/openapi/.openapi-generator/FILES | 3 + mobile/openapi/README.md | 2 + mobile/openapi/doc/PartnerResponseDto.md | 1 + mobile/openapi/doc/UpdateUserDto.md | 1 + mobile/openapi/doc/UserApi.md | 51 +++++ mobile/openapi/doc/UserAvatarColor.md | 14 ++ mobile/openapi/doc/UserDto.md | 1 + mobile/openapi/doc/UserResponseDto.md | 1 + mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api/user_api.dart | 33 +++ mobile/openapi/lib/api_client.dart | 2 + mobile/openapi/lib/api_helper.dart | 3 + .../lib/model/partner_response_dto.dart | 10 +- mobile/openapi/lib/model/update_user_dto.dart | 19 +- .../openapi/lib/model/user_avatar_color.dart | 109 ++++++++++ mobile/openapi/lib/model/user_dto.dart | 10 +- .../openapi/lib/model/user_response_dto.dart | 10 +- .../test/partner_response_dto_test.dart | 5 + mobile/openapi/test/update_user_dto_test.dart | 5 + mobile/openapi/test/user_api_test.dart | 5 + .../openapi/test/user_avatar_color_test.dart | 21 ++ mobile/openapi/test/user_dto_test.dart | 5 + .../openapi/test/user_response_dto_test.dart | 5 + server/immich-openapi-specs.json | 53 +++++ server/src/domain/auth/auth.service.spec.ts | 1 + .../domain/partner/partner.service.spec.ts | 3 + server/src/domain/user/dto/update-user.dto.ts | 8 +- .../user/response-dto/user-response.dto.ts | 19 +- server/src/domain/user/user.core.ts | 1 - server/src/domain/user/user.service.spec.ts | 41 +++- server/src/domain/user/user.service.ts | 15 +- .../src/immich/controllers/auth.controller.ts | 3 +- .../src/immich/controllers/user.controller.ts | 8 + server/src/infra/entities/user.entity.ts | 16 ++ .../1699889987493-AddAvatarColor.ts | 14 ++ server/test/e2e/auth.e2e-spec.ts | 1 + server/test/fixtures/user.stub.ts | 9 +- web/src/api/open-api/api.ts | 119 +++++++++++ .../album-page/share-info-modal.svelte | 4 +- .../album-page/user-selection-modal.svelte | 4 +- .../asset-viewer/detail-panel.svelte | 2 +- .../navigation-bar/account-info-panel.svelte | 59 ++++- .../navigation-bar/avatar-selector.svelte | 39 ++++ .../navigation-bar/navigation-bar.svelte | 6 +- .../shared-components/user-avatar.svelte | 39 ++-- .../partner-selection-modal.svelte | 2 +- .../partner-settings.svelte | 2 +- .../(user)/albums/[albumId]/+page.svelte | 4 +- web/src/routes/(user)/sharing/+page.svelte | 2 +- web/src/test-data/factories/user-factory.ts | 3 +- 58 files changed, 1123 insertions(+), 141 deletions(-) create mode 100644 mobile/openapi/doc/UserAvatarColor.md create mode 100644 mobile/openapi/lib/model/user_avatar_color.dart create mode 100644 mobile/openapi/test/user_avatar_color_test.dart create mode 100644 server/src/infra/migrations/1699889987493-AddAvatarColor.ts create mode 100644 web/src/lib/components/shared-components/navigation-bar/avatar-selector.svelte diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 8fb2c1b3d..4eb5e1228 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2355,6 +2355,12 @@ export interface OAuthConfigResponseDto { * @interface PartnerResponseDto */ export interface PartnerResponseDto { + /** + * + * @type {UserAvatarColor} + * @memberof PartnerResponseDto + */ + 'avatarColor': UserAvatarColor; /** * * @type {string} @@ -2440,6 +2446,8 @@ export interface PartnerResponseDto { */ 'updatedAt': string; } + + /** * * @export @@ -4344,6 +4352,12 @@ export interface UpdateTagDto { * @interface UpdateUserDto */ export interface UpdateUserDto { + /** + * + * @type {UserAvatarColor} + * @memberof UpdateUserDto + */ + 'avatarColor'?: UserAvatarColor; /** * * @type {string} @@ -4399,6 +4413,8 @@ export interface UpdateUserDto { */ 'storageLabel'?: string; } + + /** * * @export @@ -4436,12 +4452,40 @@ export interface UsageByUserDto { */ 'videos': number; } +/** + * + * @export + * @enum {string} + */ + +export const UserAvatarColor = { + Primary: 'primary', + Pink: 'pink', + Red: 'red', + Yellow: 'yellow', + Blue: 'blue', + Green: 'green', + Purple: 'purple', + Orange: 'orange', + Gray: 'gray', + Amber: 'amber' +} as const; + +export type UserAvatarColor = typeof UserAvatarColor[keyof typeof UserAvatarColor]; + + /** * * @export * @interface UserDto */ export interface UserDto { + /** + * + * @type {UserAvatarColor} + * @memberof UserDto + */ + 'avatarColor': UserAvatarColor; /** * * @type {string} @@ -4467,12 +4511,20 @@ export interface UserDto { */ 'profileImagePath': string; } + + /** * * @export * @interface UserResponseDto */ export interface UserResponseDto { + /** + * + * @type {UserAvatarColor} + * @memberof UserResponseDto + */ + 'avatarColor': UserAvatarColor; /** * * @type {string} @@ -4552,6 +4604,8 @@ export interface UserResponseDto { */ 'updatedAt': string; } + + /** * * @export @@ -16477,6 +16531,44 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteProfileImage: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/user/profile-image`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {string} id @@ -16802,6 +16894,15 @@ export const UserApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.createUser(createUserDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteProfileImage(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteProfileImage(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id @@ -16899,6 +17000,14 @@ export const UserApiFactory = function (configuration?: Configuration, basePath? createUser(requestParameters: UserApiCreateUserRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.createUser(requestParameters.createUserDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteProfileImage(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.deleteProfileImage(options).then((request) => request(axios, basePath)); + }, /** * * @param {UserApiDeleteUserRequest} requestParameters Request parameters. @@ -17105,6 +17214,16 @@ export class UserApi extends BaseAPI { return UserApiFp(this.configuration).createUser(requestParameters.createUserDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UserApi + */ + public deleteProfileImage(options?: AxiosRequestConfig) { + return UserApiFp(this.configuration).deleteProfileImage(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {UserApiDeleteUserRequest} requestParameters Request parameters. diff --git a/mobile/lib/modules/album/views/album_options_part.dart b/mobile/lib/modules/album/views/album_options_part.dart index 8d45d36b4..5e5a3a6b8 100644 --- a/mobile/lib/modules/album/views/album_options_part.dart +++ b/mobile/lib/modules/album/views/album_options_part.dart @@ -117,12 +117,8 @@ class AlbumOptionsPage extends HookConsumerWidget { buildOwnerInfo() { return ListTile( - leading: owner != null - ? UserCircleAvatar( - user: owner, - useRandomBackgroundColor: true, - ) - : const SizedBox(), + leading: + owner != null ? UserCircleAvatar(user: owner) : const SizedBox(), title: Text( album.owner.value?.name ?? "", style: const TextStyle( @@ -151,7 +147,6 @@ class AlbumOptionsPage extends HookConsumerWidget { return ListTile( leading: UserCircleAvatar( user: user, - useRandomBackgroundColor: true, radius: 22, ), title: Text( diff --git a/mobile/lib/modules/album/views/album_viewer_page.dart b/mobile/lib/modules/album/views/album_viewer_page.dart index b12336328..bcb32b835 100644 --- a/mobile/lib/modules/album/views/album_viewer_page.dart +++ b/mobile/lib/modules/album/views/album_viewer_page.dart @@ -217,7 +217,6 @@ class AlbumViewerPage extends HookConsumerWidget { user: album.sharedUsers.toList()[index], radius: 18, size: 36, - useRandomBackgroundColor: true, ), ); }), diff --git a/mobile/lib/shared/models/user.dart b/mobile/lib/shared/models/user.dart index e9e5004da..094509c89 100644 --- a/mobile/lib/shared/models/user.dart +++ b/mobile/lib/shared/models/user.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/utils/hash.dart'; import 'package:isar/isar.dart'; @@ -16,6 +18,7 @@ class User { this.isPartnerSharedBy = false, this.isPartnerSharedWith = false, this.profileImagePath = '', + this.avatarColor = AvatarColorEnum.primary, this.memoryEnabled = true, this.inTimeline = false, }); @@ -32,6 +35,7 @@ class User { profileImagePath = dto.profileImagePath, isAdmin = dto.isAdmin, memoryEnabled = dto.memoriesEnabled ?? false, + avatarColor = dto.avatarColor.toAvatarColor(), inTimeline = false; User.fromPartnerDto(PartnerResponseDto dto) @@ -44,6 +48,7 @@ class User { profileImagePath = dto.profileImagePath, isAdmin = dto.isAdmin, memoryEnabled = dto.memoriesEnabled ?? false, + avatarColor = dto.avatarColor.toAvatarColor(), inTimeline = dto.inTimeline ?? false; @Index(unique: true, replace: false, type: IndexType.hash) @@ -55,6 +60,8 @@ class User { bool isPartnerSharedWith; bool isAdmin; String profileImagePath; + @Enumerated(EnumType.ordinal) + AvatarColorEnum avatarColor; bool memoryEnabled; bool inTimeline; @@ -68,6 +75,7 @@ class User { if (other is! User) return false; return id == other.id && updatedAt.isAtSameMomentAs(other.updatedAt) && + avatarColor == other.avatarColor && email == other.email && name == other.name && isPartnerSharedBy == other.isPartnerSharedBy && @@ -88,7 +96,77 @@ class User { isPartnerSharedBy.hashCode ^ isPartnerSharedWith.hashCode ^ profileImagePath.hashCode ^ + avatarColor.hashCode ^ isAdmin.hashCode ^ memoryEnabled.hashCode ^ inTimeline.hashCode; } + +enum AvatarColorEnum { + // do not change this order or reuse indices for other purposes, adding is OK + primary, + pink, + red, + yellow, + blue, + green, + purple, + orange, + gray, + amber, +} + +extension AvatarColorEnumHelper on UserAvatarColor { + AvatarColorEnum toAvatarColor() { + switch (this) { + case UserAvatarColor.primary: + return AvatarColorEnum.primary; + case UserAvatarColor.pink: + return AvatarColorEnum.pink; + case UserAvatarColor.red: + return AvatarColorEnum.red; + case UserAvatarColor.yellow: + return AvatarColorEnum.yellow; + case UserAvatarColor.blue: + return AvatarColorEnum.blue; + case UserAvatarColor.green: + return AvatarColorEnum.green; + case UserAvatarColor.purple: + return AvatarColorEnum.purple; + case UserAvatarColor.orange: + return AvatarColorEnum.orange; + case UserAvatarColor.gray: + return AvatarColorEnum.gray; + case UserAvatarColor.amber: + return AvatarColorEnum.amber; + } + return AvatarColorEnum.primary; + } +} + +extension AvatarColorToColorHelper on AvatarColorEnum { + Color toColor([bool isDarkTheme = false]) { + switch (this) { + case AvatarColorEnum.primary: + return isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF); + case AvatarColorEnum.pink: + return const Color.fromARGB(255, 244, 114, 182); + case AvatarColorEnum.red: + return const Color.fromARGB(255, 239, 68, 68); + case AvatarColorEnum.yellow: + return const Color.fromARGB(255, 234, 179, 8); + case AvatarColorEnum.blue: + return const Color.fromARGB(255, 59, 130, 246); + case AvatarColorEnum.green: + return const Color.fromARGB(255, 22, 163, 74); + case AvatarColorEnum.purple: + return const Color.fromARGB(255, 147, 51, 234); + case AvatarColorEnum.orange: + return const Color.fromARGB(255, 234, 88, 12); + case AvatarColorEnum.gray: + return const Color.fromARGB(255, 75, 85, 99); + case AvatarColorEnum.amber: + return const Color.fromARGB(255, 217, 119, 6); + } + } +} diff --git a/mobile/lib/shared/models/user.g.dart b/mobile/lib/shared/models/user.g.dart index 82420a175..0b2605b94 100644 --- a/mobile/lib/shared/models/user.g.dart +++ b/mobile/lib/shared/models/user.g.dart @@ -17,53 +17,59 @@ const UserSchema = CollectionSchema( name: r'User', id: -7838171048429979076, properties: { - r'email': PropertySchema( + r'avatarColor': PropertySchema( id: 0, + name: r'avatarColor', + type: IsarType.byte, + enumMap: _UseravatarColorEnumValueMap, + ), + r'email': PropertySchema( + id: 1, name: r'email', type: IsarType.string, ), r'id': PropertySchema( - id: 1, + id: 2, name: r'id', type: IsarType.string, ), r'inTimeline': PropertySchema( - id: 2, + id: 3, name: r'inTimeline', type: IsarType.bool, ), r'isAdmin': PropertySchema( - id: 3, + id: 4, name: r'isAdmin', type: IsarType.bool, ), r'isPartnerSharedBy': PropertySchema( - id: 4, + id: 5, name: r'isPartnerSharedBy', type: IsarType.bool, ), r'isPartnerSharedWith': PropertySchema( - id: 5, + id: 6, name: r'isPartnerSharedWith', type: IsarType.bool, ), r'memoryEnabled': PropertySchema( - id: 6, + id: 7, name: r'memoryEnabled', type: IsarType.bool, ), r'name': PropertySchema( - id: 7, + id: 8, name: r'name', type: IsarType.string, ), r'profileImagePath': PropertySchema( - id: 8, + id: 9, name: r'profileImagePath', type: IsarType.string, ), r'updatedAt': PropertySchema( - id: 9, + id: 10, name: r'updatedAt', type: IsarType.dateTime, ) @@ -130,16 +136,17 @@ void _userSerialize( List offsets, Map> allOffsets, ) { - writer.writeString(offsets[0], object.email); - writer.writeString(offsets[1], object.id); - writer.writeBool(offsets[2], object.inTimeline); - writer.writeBool(offsets[3], object.isAdmin); - writer.writeBool(offsets[4], object.isPartnerSharedBy); - writer.writeBool(offsets[5], object.isPartnerSharedWith); - writer.writeBool(offsets[6], object.memoryEnabled); - writer.writeString(offsets[7], object.name); - writer.writeString(offsets[8], object.profileImagePath); - writer.writeDateTime(offsets[9], object.updatedAt); + writer.writeByte(offsets[0], object.avatarColor.index); + writer.writeString(offsets[1], object.email); + writer.writeString(offsets[2], object.id); + writer.writeBool(offsets[3], object.inTimeline); + writer.writeBool(offsets[4], object.isAdmin); + writer.writeBool(offsets[5], object.isPartnerSharedBy); + writer.writeBool(offsets[6], object.isPartnerSharedWith); + writer.writeBool(offsets[7], object.memoryEnabled); + writer.writeString(offsets[8], object.name); + writer.writeString(offsets[9], object.profileImagePath); + writer.writeDateTime(offsets[10], object.updatedAt); } User _userDeserialize( @@ -149,16 +156,19 @@ User _userDeserialize( Map> allOffsets, ) { final object = User( - email: reader.readString(offsets[0]), - id: reader.readString(offsets[1]), - inTimeline: reader.readBoolOrNull(offsets[2]) ?? false, - isAdmin: reader.readBool(offsets[3]), - isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false, - isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false, - memoryEnabled: reader.readBoolOrNull(offsets[6]) ?? true, - name: reader.readString(offsets[7]), - profileImagePath: reader.readStringOrNull(offsets[8]) ?? '', - updatedAt: reader.readDateTime(offsets[9]), + avatarColor: + _UseravatarColorValueEnumMap[reader.readByteOrNull(offsets[0])] ?? + AvatarColorEnum.primary, + email: reader.readString(offsets[1]), + id: reader.readString(offsets[2]), + inTimeline: reader.readBoolOrNull(offsets[3]) ?? false, + isAdmin: reader.readBool(offsets[4]), + isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false, + isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false, + memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true, + name: reader.readString(offsets[8]), + profileImagePath: reader.readStringOrNull(offsets[9]) ?? '', + updatedAt: reader.readDateTime(offsets[10]), ); return object; } @@ -171,30 +181,58 @@ P _userDeserializeProp

    ( ) { switch (propertyId) { case 0: - return (reader.readString(offset)) as P; + return (_UseravatarColorValueEnumMap[reader.readByteOrNull(offset)] ?? + AvatarColorEnum.primary) as P; case 1: return (reader.readString(offset)) as P; case 2: - return (reader.readBoolOrNull(offset) ?? false) as P; + return (reader.readString(offset)) as P; case 3: - return (reader.readBool(offset)) as P; - case 4: return (reader.readBoolOrNull(offset) ?? false) as P; + case 4: + return (reader.readBool(offset)) as P; case 5: return (reader.readBoolOrNull(offset) ?? false) as P; case 6: - return (reader.readBoolOrNull(offset) ?? true) as P; + return (reader.readBoolOrNull(offset) ?? false) as P; case 7: - return (reader.readString(offset)) as P; + return (reader.readBoolOrNull(offset) ?? true) as P; case 8: - return (reader.readStringOrNull(offset) ?? '') as P; + return (reader.readString(offset)) as P; case 9: + return (reader.readStringOrNull(offset) ?? '') as P; + case 10: return (reader.readDateTime(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); } } +const _UseravatarColorEnumValueMap = { + 'primary': 0, + 'pink': 1, + 'red': 2, + 'yellow': 3, + 'blue': 4, + 'green': 5, + 'purple': 6, + 'orange': 7, + 'gray': 8, + 'amber': 9, +}; +const _UseravatarColorValueEnumMap = { + 0: AvatarColorEnum.primary, + 1: AvatarColorEnum.pink, + 2: AvatarColorEnum.red, + 3: AvatarColorEnum.yellow, + 4: AvatarColorEnum.blue, + 5: AvatarColorEnum.green, + 6: AvatarColorEnum.purple, + 7: AvatarColorEnum.orange, + 8: AvatarColorEnum.gray, + 9: AvatarColorEnum.amber, +}; + Id _userGetId(User object) { return object.isarId; } @@ -382,6 +420,59 @@ extension UserQueryWhere on QueryBuilder { } extension UserQueryFilter on QueryBuilder { + QueryBuilder avatarColorEqualTo( + AvatarColorEnum value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'avatarColor', + value: value, + )); + }); + } + + QueryBuilder avatarColorGreaterThan( + AvatarColorEnum value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'avatarColor', + value: value, + )); + }); + } + + QueryBuilder avatarColorLessThan( + AvatarColorEnum value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'avatarColor', + value: value, + )); + }); + } + + QueryBuilder avatarColorBetween( + AvatarColorEnum lower, + AvatarColorEnum upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'avatarColor', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + QueryBuilder emailEqualTo( String value, { bool caseSensitive = true, @@ -1167,6 +1258,18 @@ extension UserQueryLinks on QueryBuilder { } extension UserQuerySortBy on QueryBuilder { + QueryBuilder sortByAvatarColor() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.asc); + }); + } + + QueryBuilder sortByAvatarColorDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.desc); + }); + } + QueryBuilder sortByEmail() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'email', Sort.asc); @@ -1289,6 +1392,18 @@ extension UserQuerySortBy on QueryBuilder { } extension UserQuerySortThenBy on QueryBuilder { + QueryBuilder thenByAvatarColor() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.asc); + }); + } + + QueryBuilder thenByAvatarColorDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.desc); + }); + } + QueryBuilder thenByEmail() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'email', Sort.asc); @@ -1423,6 +1538,12 @@ extension UserQuerySortThenBy on QueryBuilder { } extension UserQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByAvatarColor() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'avatarColor'); + }); + } + QueryBuilder distinctByEmail( {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -1496,6 +1617,12 @@ extension UserQueryProperty on QueryBuilder { }); } + QueryBuilder avatarColorProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'avatarColor'); + }); + } + QueryBuilder emailProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'email'); diff --git a/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart index c4951e339..abb81ca89 100644 --- a/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/shared/ui/app_bar_dialog/app_bar_profile_info.dart @@ -22,14 +22,12 @@ class AppBarProfileInfoBox extends HookConsumerWidget { final user = Store.tryGet(StoreKey.currentUser); buildUserProfileImage() { - const immichImage = CircleAvatar( - radius: 20, - backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), - backgroundColor: Colors.transparent, - ); - - if (authState.profileImagePath.isEmpty || user == null) { - return immichImage; + if (user == null) { + return const CircleAvatar( + radius: 20, + backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), + backgroundColor: Colors.transparent, + ); } final userImage = UserCircleAvatar( @@ -38,18 +36,6 @@ class AppBarProfileInfoBox extends HookConsumerWidget { user: user, ); - if (uploadProfileImageStatus == UploadProfileStatus.idle) { - return authState.profileImagePath.isNotEmpty ? userImage : immichImage; - } - - if (uploadProfileImageStatus == UploadProfileStatus.success) { - return userImage; - } - - if (uploadProfileImageStatus == UploadProfileStatus.failure) { - return immichImage; - } - if (uploadProfileImageStatus == UploadProfileStatus.loading) { return const SizedBox( height: 40, @@ -58,7 +44,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { ); } - return immichImage; + return userImage; } pickUserProfileImage() async { diff --git a/mobile/lib/shared/ui/immich_app_bar.dart b/mobile/lib/shared/ui/immich_app_bar.dart index bbf5a48a0..144adc787 100644 --- a/mobile/lib/shared/ui/immich_app_bar.dart +++ b/mobile/lib/shared/ui/immich_app_bar.dart @@ -4,8 +4,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_dialog.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; -import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; -import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; @@ -26,7 +24,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup; final ServerInfo serverInfoState = ref.watch(serverInfoProvider); - AuthenticationState authState = ref.watch(authenticationProvider); final user = Store.tryGet(StoreKey.currentUser); final isDarkTheme = context.isDarkTheme; const widgetSize = 30.0; @@ -55,7 +52,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { alignment: Alignment.bottomRight, isLabelVisible: serverInfoState.isVersionMismatch, offset: const Offset(2, 2), - child: authState.profileImagePath.isEmpty || user == null + child: user == null ? const Icon( Icons.face_outlined, size: widgetSize, diff --git a/mobile/lib/shared/ui/user_circle_avatar.dart b/mobile/lib/shared/ui/user_circle_avatar.dart index 5920758a7..1f6ef15f5 100644 --- a/mobile/lib/shared/ui/user_circle_avatar.dart +++ b/mobile/lib/shared/ui/user_circle_avatar.dart @@ -3,7 +3,6 @@ import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/ui/transparent_image.dart'; @@ -13,32 +12,17 @@ class UserCircleAvatar extends ConsumerWidget { final User user; double radius; double size; - bool useRandomBackgroundColor; UserCircleAvatar({ super.key, this.radius = 22, this.size = 44, - this.useRandomBackgroundColor = false, required this.user, }); @override Widget build(BuildContext context, WidgetRef ref) { - final randomColors = [ - Colors.red[200], - Colors.blue[200], - Colors.green[200], - Colors.yellow[200], - Colors.purple[200], - Colors.orange[200], - Colors.pink[200], - Colors.teal[200], - Colors.indigo[200], - Colors.cyan[200], - Colors.brown[200], - ]; - + bool isDarkTheme = Theme.of(context).brightness == Brightness.dark; final profileImageUrl = '${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}'; @@ -46,15 +30,16 @@ class UserCircleAvatar extends ConsumerWidget { user.name[0].toUpperCase(), style: TextStyle( fontWeight: FontWeight.bold, - color: context.isDarkTheme ? Colors.black : Colors.white, + fontSize: 12, + color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary + ? Colors.black + : Colors.white, ), ); return CircleAvatar( - backgroundColor: useRandomBackgroundColor - ? randomColors[Random().nextInt(randomColors.length)] - : context.primaryColor, + backgroundColor: user.avatarColor.toColor(), radius: radius, - child: user.profileImagePath == "" + child: user.profileImagePath.isEmpty ? textIcon : ClipRRect( borderRadius: BorderRadius.circular(50), diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index c4f967976..9ca833516 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -166,6 +166,7 @@ doc/UpdateTagDto.md doc/UpdateUserDto.md doc/UsageByUserDto.md doc/UserApi.md +doc/UserAvatarColor.md doc/UserDto.md doc/UserResponseDto.md doc/ValidateAccessTokenResponseDto.md @@ -343,6 +344,7 @@ lib/model/update_stack_parent_dto.dart lib/model/update_tag_dto.dart lib/model/update_user_dto.dart lib/model/usage_by_user_dto.dart +lib/model/user_avatar_color.dart lib/model/user_dto.dart lib/model/user_response_dto.dart lib/model/validate_access_token_response_dto.dart @@ -511,6 +513,7 @@ test/update_tag_dto_test.dart test/update_user_dto_test.dart test/usage_by_user_dto_test.dart test/user_api_test.dart +test/user_avatar_color_test.dart test/user_dto_test.dart test/user_response_dto_test.dart test/validate_access_token_response_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 551169a99..4d54d41c3 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -195,6 +195,7 @@ Class | Method | HTTP request | Description *TagApi* | [**updateTag**](doc//TagApi.md#updatetag) | **PATCH** /tag/{id} | *UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image | *UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user | +*UserApi* | [**deleteProfileImage**](doc//UserApi.md#deleteprofileimage) | **DELETE** /user/profile-image | *UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{id} | *UserApi* | [**getAllUsers**](doc//UserApi.md#getallusers) | **GET** /user | *UserApi* | [**getMyUserInfo**](doc//UserApi.md#getmyuserinfo) | **GET** /user/me | @@ -352,6 +353,7 @@ Class | Method | HTTP request | Description - [UpdateTagDto](doc//UpdateTagDto.md) - [UpdateUserDto](doc//UpdateUserDto.md) - [UsageByUserDto](doc//UsageByUserDto.md) + - [UserAvatarColor](doc//UserAvatarColor.md) - [UserDto](doc//UserDto.md) - [UserResponseDto](doc//UserResponseDto.md) - [ValidateAccessTokenResponseDto](doc//ValidateAccessTokenResponseDto.md) diff --git a/mobile/openapi/doc/PartnerResponseDto.md b/mobile/openapi/doc/PartnerResponseDto.md index f7133bbf7..574b96f8d 100644 --- a/mobile/openapi/doc/PartnerResponseDto.md +++ b/mobile/openapi/doc/PartnerResponseDto.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) | | **createdAt** | [**DateTime**](DateTime.md) | | **deletedAt** | [**DateTime**](DateTime.md) | | **email** | **String** | | diff --git a/mobile/openapi/doc/UpdateUserDto.md b/mobile/openapi/doc/UpdateUserDto.md index ffbe11253..567bc43eb 100644 --- a/mobile/openapi/doc/UpdateUserDto.md +++ b/mobile/openapi/doc/UpdateUserDto.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) | | [optional] **email** | **String** | | [optional] **externalPath** | **String** | | [optional] **id** | **String** | | diff --git a/mobile/openapi/doc/UserApi.md b/mobile/openapi/doc/UserApi.md index fb88d53bd..6d2a028da 100644 --- a/mobile/openapi/doc/UserApi.md +++ b/mobile/openapi/doc/UserApi.md @@ -11,6 +11,7 @@ Method | HTTP request | Description ------------- | ------------- | ------------- [**createProfileImage**](UserApi.md#createprofileimage) | **POST** /user/profile-image | [**createUser**](UserApi.md#createuser) | **POST** /user | +[**deleteProfileImage**](UserApi.md#deleteprofileimage) | **DELETE** /user/profile-image | [**deleteUser**](UserApi.md#deleteuser) | **DELETE** /user/{id} | [**getAllUsers**](UserApi.md#getallusers) | **GET** /user | [**getMyUserInfo**](UserApi.md#getmyuserinfo) | **GET** /user/me | @@ -130,6 +131,56 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **deleteProfileImage** +> deleteProfileImage() + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = UserApi(); + +try { + api_instance.deleteProfileImage(); +} catch (e) { + print('Exception when calling UserApi->deleteProfileImage: $e\n'); +} +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +void (empty response body) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **deleteUser** > UserResponseDto deleteUser(id) diff --git a/mobile/openapi/doc/UserAvatarColor.md b/mobile/openapi/doc/UserAvatarColor.md new file mode 100644 index 000000000..a07350de1 --- /dev/null +++ b/mobile/openapi/doc/UserAvatarColor.md @@ -0,0 +1,14 @@ +# openapi.model.UserAvatarColor + +## 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) + + diff --git a/mobile/openapi/doc/UserDto.md b/mobile/openapi/doc/UserDto.md index c8b750a1d..7e5770f84 100644 --- a/mobile/openapi/doc/UserDto.md +++ b/mobile/openapi/doc/UserDto.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) | | **email** | **String** | | **id** | **String** | | **name** | **String** | | diff --git a/mobile/openapi/doc/UserResponseDto.md b/mobile/openapi/doc/UserResponseDto.md index ddf7c574c..93f9aa62a 100644 --- a/mobile/openapi/doc/UserResponseDto.md +++ b/mobile/openapi/doc/UserResponseDto.md @@ -8,6 +8,7 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) | | **createdAt** | [**DateTime**](DateTime.md) | | **deletedAt** | [**DateTime**](DateTime.md) | | **email** | **String** | | diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index ed0b51f88..92bfa3f81 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -192,6 +192,7 @@ part 'model/update_stack_parent_dto.dart'; part 'model/update_tag_dto.dart'; part 'model/update_user_dto.dart'; part 'model/usage_by_user_dto.dart'; +part 'model/user_avatar_color.dart'; part 'model/user_dto.dart'; part 'model/user_response_dto.dart'; part 'model/validate_access_token_response_dto.dart'; diff --git a/mobile/openapi/lib/api/user_api.dart b/mobile/openapi/lib/api/user_api.dart index 23f25492c..26ab3dcd0 100644 --- a/mobile/openapi/lib/api/user_api.dart +++ b/mobile/openapi/lib/api/user_api.dart @@ -120,6 +120,39 @@ class UserApi { return null; } + /// Performs an HTTP 'DELETE /user/profile-image' operation and returns the [Response]. + Future deleteProfileImageWithHttpInfo() async { + // ignore: prefer_const_declarations + final path = r'/user/profile-image'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future deleteProfileImage() async { + final response = await deleteProfileImageWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + /// Performs an HTTP 'DELETE /user/{id}' operation and returns the [Response]. /// Parameters: /// diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index c03a469ae..4c24967ec 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -473,6 +473,8 @@ class ApiClient { return UpdateUserDto.fromJson(value); case 'UsageByUserDto': return UsageByUserDto.fromJson(value); + case 'UserAvatarColor': + return UserAvatarColorTypeTransformer().decode(value); case 'UserDto': return UserDto.fromJson(value); case 'UserResponseDto': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index f39131448..ddb16df0c 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -127,6 +127,9 @@ String parameterToString(dynamic value) { if (value is TranscodePolicy) { return TranscodePolicyTypeTransformer().encode(value).toString(); } + if (value is UserAvatarColor) { + return UserAvatarColorTypeTransformer().encode(value).toString(); + } if (value is VideoCodec) { return VideoCodecTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/partner_response_dto.dart b/mobile/openapi/lib/model/partner_response_dto.dart index 5ccef68d8..6e1776b26 100644 --- a/mobile/openapi/lib/model/partner_response_dto.dart +++ b/mobile/openapi/lib/model/partner_response_dto.dart @@ -13,6 +13,7 @@ part of openapi.api; class PartnerResponseDto { /// Returns a new [PartnerResponseDto] instance. PartnerResponseDto({ + required this.avatarColor, required this.createdAt, required this.deletedAt, required this.email, @@ -29,6 +30,8 @@ class PartnerResponseDto { required this.updatedAt, }); + UserAvatarColor avatarColor; + DateTime createdAt; DateTime? deletedAt; @@ -71,6 +74,7 @@ class PartnerResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is PartnerResponseDto && + other.avatarColor == avatarColor && other.createdAt == createdAt && other.deletedAt == deletedAt && other.email == email && @@ -89,6 +93,7 @@ class PartnerResponseDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (avatarColor.hashCode) + (createdAt.hashCode) + (deletedAt == null ? 0 : deletedAt!.hashCode) + (email.hashCode) + @@ -105,10 +110,11 @@ class PartnerResponseDto { (updatedAt.hashCode); @override - String toString() => 'PartnerResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; + String toString() => 'PartnerResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; Map toJson() { final json = {}; + json[r'avatarColor'] = this.avatarColor; json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); if (this.deletedAt != null) { json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); @@ -154,6 +160,7 @@ class PartnerResponseDto { final json = value.cast(); return PartnerResponseDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, createdAt: mapDateTime(json, r'createdAt', '')!, deletedAt: mapDateTime(json, r'deletedAt', ''), email: mapValueOfType(json, r'email')!, @@ -215,6 +222,7 @@ class PartnerResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'avatarColor', 'createdAt', 'deletedAt', 'email', diff --git a/mobile/openapi/lib/model/update_user_dto.dart b/mobile/openapi/lib/model/update_user_dto.dart index 9d381cd4b..d0e46e7f5 100644 --- a/mobile/openapi/lib/model/update_user_dto.dart +++ b/mobile/openapi/lib/model/update_user_dto.dart @@ -13,6 +13,7 @@ part of openapi.api; class UpdateUserDto { /// Returns a new [UpdateUserDto] instance. UpdateUserDto({ + this.avatarColor, this.email, this.externalPath, required this.id, @@ -24,6 +25,14 @@ class UpdateUserDto { this.storageLabel, }); + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + UserAvatarColor? avatarColor; + /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -92,6 +101,7 @@ class UpdateUserDto { @override bool operator ==(Object other) => identical(this, other) || other is UpdateUserDto && + other.avatarColor == avatarColor && other.email == email && other.externalPath == externalPath && other.id == id && @@ -105,6 +115,7 @@ class UpdateUserDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (avatarColor == null ? 0 : avatarColor!.hashCode) + (email == null ? 0 : email!.hashCode) + (externalPath == null ? 0 : externalPath!.hashCode) + (id.hashCode) + @@ -116,10 +127,15 @@ class UpdateUserDto { (storageLabel == null ? 0 : storageLabel!.hashCode); @override - String toString() => 'UpdateUserDto[email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; + String toString() => 'UpdateUserDto[avatarColor=$avatarColor, email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; Map toJson() { final json = {}; + if (this.avatarColor != null) { + json[r'avatarColor'] = this.avatarColor; + } else { + // json[r'avatarColor'] = null; + } if (this.email != null) { json[r'email'] = this.email; } else { @@ -172,6 +188,7 @@ class UpdateUserDto { final json = value.cast(); return UpdateUserDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), email: mapValueOfType(json, r'email'), externalPath: mapValueOfType(json, r'externalPath'), id: mapValueOfType(json, r'id')!, diff --git a/mobile/openapi/lib/model/user_avatar_color.dart b/mobile/openapi/lib/model/user_avatar_color.dart new file mode 100644 index 000000000..075f58d3a --- /dev/null +++ b/mobile/openapi/lib/model/user_avatar_color.dart @@ -0,0 +1,109 @@ +// +// 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 UserAvatarColor { + /// Instantiate a new enum with the provided [value]. + const UserAvatarColor._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const primary = UserAvatarColor._(r'primary'); + static const pink = UserAvatarColor._(r'pink'); + static const red = UserAvatarColor._(r'red'); + static const yellow = UserAvatarColor._(r'yellow'); + static const blue = UserAvatarColor._(r'blue'); + static const green = UserAvatarColor._(r'green'); + static const purple = UserAvatarColor._(r'purple'); + static const orange = UserAvatarColor._(r'orange'); + static const gray = UserAvatarColor._(r'gray'); + static const amber = UserAvatarColor._(r'amber'); + + /// List of all possible values in this [enum][UserAvatarColor]. + static const values = [ + primary, + pink, + red, + yellow, + blue, + green, + purple, + orange, + gray, + amber, + ]; + + static UserAvatarColor? fromJson(dynamic value) => UserAvatarColorTypeTransformer().decode(value); + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserAvatarColor.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [UserAvatarColor] to String, +/// and [decode] dynamic data back to [UserAvatarColor]. +class UserAvatarColorTypeTransformer { + factory UserAvatarColorTypeTransformer() => _instance ??= const UserAvatarColorTypeTransformer._(); + + const UserAvatarColorTypeTransformer._(); + + String encode(UserAvatarColor data) => data.value; + + /// Decodes a [dynamic value][data] to a UserAvatarColor. + /// + /// 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. + UserAvatarColor? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'primary': return UserAvatarColor.primary; + case r'pink': return UserAvatarColor.pink; + case r'red': return UserAvatarColor.red; + case r'yellow': return UserAvatarColor.yellow; + case r'blue': return UserAvatarColor.blue; + case r'green': return UserAvatarColor.green; + case r'purple': return UserAvatarColor.purple; + case r'orange': return UserAvatarColor.orange; + case r'gray': return UserAvatarColor.gray; + case r'amber': return UserAvatarColor.amber; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [UserAvatarColorTypeTransformer] instance. + static UserAvatarColorTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/user_dto.dart b/mobile/openapi/lib/model/user_dto.dart index de26cc8f2..ad39f84e0 100644 --- a/mobile/openapi/lib/model/user_dto.dart +++ b/mobile/openapi/lib/model/user_dto.dart @@ -13,12 +13,15 @@ part of openapi.api; class UserDto { /// Returns a new [UserDto] instance. UserDto({ + required this.avatarColor, required this.email, required this.id, required this.name, required this.profileImagePath, }); + UserAvatarColor avatarColor; + String email; String id; @@ -29,6 +32,7 @@ class UserDto { @override bool operator ==(Object other) => identical(this, other) || other is UserDto && + other.avatarColor == avatarColor && other.email == email && other.id == id && other.name == name && @@ -37,16 +41,18 @@ class UserDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (avatarColor.hashCode) + (email.hashCode) + (id.hashCode) + (name.hashCode) + (profileImagePath.hashCode); @override - String toString() => 'UserDto[email=$email, id=$id, name=$name, profileImagePath=$profileImagePath]'; + String toString() => 'UserDto[avatarColor=$avatarColor, email=$email, id=$id, name=$name, profileImagePath=$profileImagePath]'; Map toJson() { final json = {}; + json[r'avatarColor'] = this.avatarColor; json[r'email'] = this.email; json[r'id'] = this.id; json[r'name'] = this.name; @@ -62,6 +68,7 @@ class UserDto { final json = value.cast(); return UserDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, email: mapValueOfType(json, r'email')!, id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name')!, @@ -113,6 +120,7 @@ class UserDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'avatarColor', 'email', 'id', 'name', diff --git a/mobile/openapi/lib/model/user_response_dto.dart b/mobile/openapi/lib/model/user_response_dto.dart index 73b30881f..11a182b6b 100644 --- a/mobile/openapi/lib/model/user_response_dto.dart +++ b/mobile/openapi/lib/model/user_response_dto.dart @@ -13,6 +13,7 @@ part of openapi.api; class UserResponseDto { /// Returns a new [UserResponseDto] instance. UserResponseDto({ + required this.avatarColor, required this.createdAt, required this.deletedAt, required this.email, @@ -28,6 +29,8 @@ class UserResponseDto { required this.updatedAt, }); + UserAvatarColor avatarColor; + DateTime createdAt; DateTime? deletedAt; @@ -62,6 +65,7 @@ class UserResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is UserResponseDto && + other.avatarColor == avatarColor && other.createdAt == createdAt && other.deletedAt == deletedAt && other.email == email && @@ -79,6 +83,7 @@ class UserResponseDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (avatarColor.hashCode) + (createdAt.hashCode) + (deletedAt == null ? 0 : deletedAt!.hashCode) + (email.hashCode) + @@ -94,10 +99,11 @@ class UserResponseDto { (updatedAt.hashCode); @override - String toString() => 'UserResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; + String toString() => 'UserResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; Map toJson() { final json = {}; + json[r'avatarColor'] = this.avatarColor; json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); if (this.deletedAt != null) { json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); @@ -138,6 +144,7 @@ class UserResponseDto { final json = value.cast(); return UserResponseDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, createdAt: mapDateTime(json, r'createdAt', '')!, deletedAt: mapDateTime(json, r'deletedAt', ''), email: mapValueOfType(json, r'email')!, @@ -198,6 +205,7 @@ class UserResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'avatarColor', 'createdAt', 'deletedAt', 'email', diff --git a/mobile/openapi/test/partner_response_dto_test.dart b/mobile/openapi/test/partner_response_dto_test.dart index 495762d3b..50ac1d805 100644 --- a/mobile/openapi/test/partner_response_dto_test.dart +++ b/mobile/openapi/test/partner_response_dto_test.dart @@ -16,6 +16,11 @@ void main() { // final instance = PartnerResponseDto(); group('test PartnerResponseDto', () { + // UserAvatarColor avatarColor + test('to test the property `avatarColor`', () async { + // TODO + }); + // DateTime createdAt test('to test the property `createdAt`', () async { // TODO diff --git a/mobile/openapi/test/update_user_dto_test.dart b/mobile/openapi/test/update_user_dto_test.dart index 039a1a244..0b4cc0b65 100644 --- a/mobile/openapi/test/update_user_dto_test.dart +++ b/mobile/openapi/test/update_user_dto_test.dart @@ -16,6 +16,11 @@ void main() { // final instance = UpdateUserDto(); group('test UpdateUserDto', () { + // UserAvatarColor avatarColor + test('to test the property `avatarColor`', () async { + // TODO + }); + // String email test('to test the property `email`', () async { // TODO diff --git a/mobile/openapi/test/user_api_test.dart b/mobile/openapi/test/user_api_test.dart index 86c33c7e0..26ebf3d7e 100644 --- a/mobile/openapi/test/user_api_test.dart +++ b/mobile/openapi/test/user_api_test.dart @@ -27,6 +27,11 @@ void main() { // TODO }); + //Future deleteProfileImage() async + test('test deleteProfileImage', () async { + // TODO + }); + //Future deleteUser(String id) async test('test deleteUser', () async { // TODO diff --git a/mobile/openapi/test/user_avatar_color_test.dart b/mobile/openapi/test/user_avatar_color_test.dart new file mode 100644 index 000000000..83480b580 --- /dev/null +++ b/mobile/openapi/test/user_avatar_color_test.dart @@ -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 UserAvatarColor +void main() { + + group('test UserAvatarColor', () { + + }); + +} diff --git a/mobile/openapi/test/user_dto_test.dart b/mobile/openapi/test/user_dto_test.dart index e0866d12d..20229ff65 100644 --- a/mobile/openapi/test/user_dto_test.dart +++ b/mobile/openapi/test/user_dto_test.dart @@ -16,6 +16,11 @@ void main() { // final instance = UserDto(); group('test UserDto', () { + // UserAvatarColor avatarColor + test('to test the property `avatarColor`', () async { + // TODO + }); + // String email test('to test the property `email`', () async { // TODO diff --git a/mobile/openapi/test/user_response_dto_test.dart b/mobile/openapi/test/user_response_dto_test.dart index 28830a73f..aa0717e74 100644 --- a/mobile/openapi/test/user_response_dto_test.dart +++ b/mobile/openapi/test/user_response_dto_test.dart @@ -16,6 +16,11 @@ void main() { // final instance = UserResponseDto(); group('test UserResponseDto', () { + // UserAvatarColor avatarColor + test('to test the property `avatarColor`', () async { + // TODO + }); + // DateTime createdAt test('to test the property `createdAt`', () async { // TODO diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index b776e4b28..bcc301ee2 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -5578,6 +5578,29 @@ } }, "/user/profile-image": { + "delete": { + "operationId": "deleteProfileImage", + "parameters": [], + "responses": { + "204": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "User" + ] + }, "post": { "operationId": "createProfileImage", "parameters": [], @@ -7632,6 +7655,9 @@ }, "PartnerResponseDto": { "properties": { + "avatarColor": { + "$ref": "#/components/schemas/UserAvatarColor" + }, "createdAt": { "format": "date-time", "type": "string" @@ -7682,6 +7708,7 @@ } }, "required": [ + "avatarColor", "id", "name", "email", @@ -9140,6 +9167,9 @@ }, "UpdateUserDto": { "properties": { + "avatarColor": { + "$ref": "#/components/schemas/UserAvatarColor" + }, "email": { "type": "string" }, @@ -9202,8 +9232,26 @@ ], "type": "object" }, + "UserAvatarColor": { + "enum": [ + "primary", + "pink", + "red", + "yellow", + "blue", + "green", + "purple", + "orange", + "gray", + "amber" + ], + "type": "string" + }, "UserDto": { "properties": { + "avatarColor": { + "$ref": "#/components/schemas/UserAvatarColor" + }, "email": { "type": "string" }, @@ -9218,6 +9266,7 @@ } }, "required": [ + "avatarColor", "id", "name", "email", @@ -9227,6 +9276,9 @@ }, "UserResponseDto": { "properties": { + "avatarColor": { + "$ref": "#/components/schemas/UserAvatarColor" + }, "createdAt": { "format": "date-time", "type": "string" @@ -9274,6 +9326,7 @@ } }, "required": [ + "avatarColor", "id", "name", "email", diff --git a/server/src/domain/auth/auth.service.spec.ts b/server/src/domain/auth/auth.service.spec.ts index f915883a5..a815e22d1 100644 --- a/server/src/domain/auth/auth.service.spec.ts +++ b/server/src/domain/auth/auth.service.spec.ts @@ -248,6 +248,7 @@ describe('AuthService', () => { userMock.getAdmin.mockResolvedValue(null); userMock.create.mockResolvedValue({ ...dto, id: 'admin', createdAt: new Date('2021-01-01') } as UserEntity); await expect(sut.adminSignUp(dto)).resolves.toEqual({ + avatarColor: expect.any(String), id: 'admin', createdAt: new Date('2021-01-01'), email: 'test@immich.com', diff --git a/server/src/domain/partner/partner.service.spec.ts b/server/src/domain/partner/partner.service.spec.ts index 6eaa87ea0..c632cc8da 100644 --- a/server/src/domain/partner/partner.service.spec.ts +++ b/server/src/domain/partner/partner.service.spec.ts @@ -1,3 +1,4 @@ +import { UserAvatarColor } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; import { authStub, newPartnerRepositoryMock, partnerStub } from '@test'; import { IAccessRepository, IPartnerRepository, PartnerDirection } from '../repositories'; @@ -19,6 +20,7 @@ const responseDto = { updatedAt: new Date('2021-01-01'), externalPath: null, memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, inTimeline: true, }, user1: { @@ -35,6 +37,7 @@ const responseDto = { updatedAt: new Date('2021-01-01'), externalPath: null, memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, inTimeline: true, }, }; diff --git a/server/src/domain/user/dto/update-user.dto.ts b/server/src/domain/user/dto/update-user.dto.ts index 4f05498da..a71c0e21a 100644 --- a/server/src/domain/user/dto/update-user.dto.ts +++ b/server/src/domain/user/dto/update-user.dto.ts @@ -1,6 +1,7 @@ +import { UserAvatarColor } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsEmail, IsNotEmpty, IsString, IsUUID } from 'class-validator'; +import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsString, IsUUID } from 'class-validator'; import { Optional, toEmail, toSanitized } from '../../domain.util'; export class UpdateUserDto { @@ -44,4 +45,9 @@ export class UpdateUserDto { @Optional() @IsBoolean() memoriesEnabled?: boolean; + + @Optional() + @IsEnum(UserAvatarColor) + @ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor }) + avatarColor?: UserAvatarColor; } diff --git a/server/src/domain/user/response-dto/user-response.dto.ts b/server/src/domain/user/response-dto/user-response.dto.ts index fd9a12167..7b6aef191 100644 --- a/server/src/domain/user/response-dto/user-response.dto.ts +++ b/server/src/domain/user/response-dto/user-response.dto.ts @@ -1,10 +1,26 @@ -import { UserEntity } from '@app/infra/entities'; +import { UserAvatarColor, UserEntity } from '@app/infra/entities'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +export const getRandomAvatarColor = (user: UserEntity): UserAvatarColor => { + const values = Object.values(UserAvatarColor); + const randomIndex = Math.floor( + user.email + .split('') + .map((letter) => letter.charCodeAt(0)) + .reduce((a, b) => a + b, 0) % values.length, + ); + return values[randomIndex] as UserAvatarColor; +}; export class UserDto { id!: string; name!: string; email!: string; profileImagePath!: string; + @IsEnum(UserAvatarColor) + @ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor }) + avatarColor!: UserAvatarColor; } export class UserResponseDto extends UserDto { @@ -25,6 +41,7 @@ export const mapSimpleUser = (entity: UserEntity): UserDto => { email: entity.email, name: entity.name, profileImagePath: entity.profileImagePath, + avatarColor: entity.avatarColor ?? getRandomAvatarColor(entity), }; }; diff --git a/server/src/domain/user/user.core.ts b/server/src/domain/user/user.core.ts index 431caa8e1..c8e77a500 100644 --- a/server/src/domain/user/user.core.ts +++ b/server/src/domain/user/user.core.ts @@ -98,7 +98,6 @@ export class UserCore { if (payload.storageLabel) { payload.storageLabel = sanitize(payload.storageLabel); } - const userEntity = await this.userRepository.create(payload); await this.libraryRepository.create({ owner: { id: userEntity.id } as UserEntity, diff --git a/server/src/domain/user/user.service.spec.ts b/server/src/domain/user/user.service.spec.ts index 94f919178..04b4206ca 100644 --- a/server/src/domain/user/user.service.spec.ts +++ b/server/src/domain/user/user.service.spec.ts @@ -323,17 +323,52 @@ describe(UserService.name, () => { const file = { path: '/profile/path' } as Express.Multer.File; userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); - await sut.createProfileImage(userStub.admin, file); - - expect(userMock.update).toHaveBeenCalledWith(userStub.admin.id, { profileImagePath: file.path }); + await expect(sut.createProfileImage(userStub.admin, file)).rejects.toThrowError(BadRequestException); }); it('should throw an error if the user profile could not be updated with the new image', async () => { const file = { path: '/profile/path' } as Express.Multer.File; + userMock.get.mockResolvedValue(userStub.profilePath); userMock.update.mockRejectedValue(new InternalServerErrorException('mocked error')); await expect(sut.createProfileImage(userStub.admin, file)).rejects.toThrowError(InternalServerErrorException); }); + + it('should delete the previous profile image', async () => { + const file = { path: '/profile/path' } as Express.Multer.File; + userMock.get.mockResolvedValue(userStub.profilePath); + const files = [userStub.profilePath.profileImagePath]; + userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); + + await sut.createProfileImage(userStub.admin, file); + await expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]); + }); + + it('should not delete the profile image if it has not been set', async () => { + const file = { path: '/profile/path' } as Express.Multer.File; + userMock.get.mockResolvedValue(userStub.admin); + userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); + + await sut.createProfileImage(userStub.admin, file); + expect(jobMock.queue).not.toHaveBeenCalled(); + }); + }); + + describe('deleteProfileImage', () => { + it('should send an http error has no profile image', async () => { + userMock.get.mockResolvedValue(userStub.admin); + + await expect(sut.deleteProfileImage(userStub.admin)).rejects.toBeInstanceOf(BadRequestException); + expect(jobMock.queue).not.toHaveBeenCalled(); + }); + + it('should delete the profile image if user has one', async () => { + userMock.get.mockResolvedValue(userStub.profilePath); + const files = [userStub.profilePath.profileImagePath]; + + await sut.deleteProfileImage(userStub.admin); + await expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]); + }); }); describe('getUserProfileImage', () => { diff --git a/server/src/domain/user/user.service.ts b/server/src/domain/user/user.service.ts index a155d401d..3232a6f94 100644 --- a/server/src/domain/user/user.service.ts +++ b/server/src/domain/user/user.service.ts @@ -93,10 +93,23 @@ export class UserService { authUser: AuthUserDto, fileInfo: Express.Multer.File, ): Promise { + const { profileImagePath: oldpath } = await this.findOrFail(authUser.id, { withDeleted: false }); const updatedUser = await this.userRepository.update(authUser.id, { profileImagePath: fileInfo.path }); + if (oldpath !== '') { + await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [oldpath] } }); + } return mapCreateProfileImageResponse(updatedUser.id, updatedUser.profileImagePath); } + async deleteProfileImage(authUser: AuthUserDto): Promise { + const user = await this.findOrFail(authUser.id, { withDeleted: false }); + if (user.profileImagePath === '') { + throw new BadRequestException("Can't delete a missing profile Image"); + } + await this.userRepository.update(authUser.id, { profileImagePath: '' }); + await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [user.profileImagePath] } }); + } + async getProfileImage(id: string): Promise { const user = await this.findOrFail(id, {}); if (!user.profileImagePath) { @@ -111,7 +124,7 @@ export class UserService { throw new BadRequestException('Admin account does not exist'); } - const providedPassword = await ask(admin); + const providedPassword = await ask(mapUser(admin)); const password = providedPassword || randomBytes(24).toString('base64').replace(/\W/g, ''); await this.userCore.updateUser(admin, admin.id, { password }); diff --git a/server/src/immich/controllers/auth.controller.ts b/server/src/immich/controllers/auth.controller.ts index ae48a78eb..dda546cf0 100644 --- a/server/src/immich/controllers/auth.controller.ts +++ b/server/src/immich/controllers/auth.controller.ts @@ -12,6 +12,7 @@ import { SignUpDto, UserResponseDto, ValidateAccessTokenResponseDto, + mapUser, } from '@app/domain'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -71,7 +72,7 @@ export class AuthController { @Post('change-password') @HttpCode(HttpStatus.OK) changePassword(@AuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise { - return this.service.changePassword(authUser, dto); + return this.service.changePassword(authUser, dto).then(mapUser); } @Post('logout') diff --git a/server/src/immich/controllers/user.controller.ts b/server/src/immich/controllers/user.controller.ts index 92b3fdcc0..1772fb548 100644 --- a/server/src/immich/controllers/user.controller.ts +++ b/server/src/immich/controllers/user.controller.ts @@ -13,6 +13,8 @@ import { Delete, Get, Header, + HttpCode, + HttpStatus, Param, Post, Put, @@ -54,6 +56,12 @@ export class UserController { return this.service.create(createUserDto); } + @Delete('profile-image') + @HttpCode(HttpStatus.NO_CONTENT) + deleteProfileImage(@AuthUser() authUser: AuthUserDto): Promise { + return this.service.deleteProfileImage(authUser); + } + @AdminRoute() @Delete(':id') deleteUser(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { diff --git a/server/src/infra/entities/user.entity.ts b/server/src/infra/entities/user.entity.ts index 83f23bef6..5a0a6afd6 100644 --- a/server/src/infra/entities/user.entity.ts +++ b/server/src/infra/entities/user.entity.ts @@ -10,6 +10,19 @@ import { import { AssetEntity } from './asset.entity'; import { TagEntity } from './tag.entity'; +export enum UserAvatarColor { + PRIMARY = 'primary', + PINK = 'pink', + RED = 'red', + YELLOW = 'yellow', + BLUE = 'blue', + GREEN = 'green', + PURPLE = 'purple', + ORANGE = 'orange', + GRAY = 'gray', + AMBER = 'amber', +} + @Entity('users') export class UserEntity { @PrimaryGeneratedColumn('uuid') @@ -18,6 +31,9 @@ export class UserEntity { @Column({ default: '' }) name!: string; + @Column({ type: 'varchar', nullable: true }) + avatarColor!: UserAvatarColor | null; + @Column({ default: false }) isAdmin!: boolean; diff --git a/server/src/infra/migrations/1699889987493-AddAvatarColor.ts b/server/src/infra/migrations/1699889987493-AddAvatarColor.ts new file mode 100644 index 000000000..b075a5d2a --- /dev/null +++ b/server/src/infra/migrations/1699889987493-AddAvatarColor.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddAvatarColor1699889987493 implements MigrationInterface { + name = 'AddAvatarColor1699889987493' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" ADD "avatarColor" character varying`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "avatarColor"`); + } + +} diff --git a/server/test/e2e/auth.e2e-spec.ts b/server/test/e2e/auth.e2e-spec.ts index eb47a8725..97a8551ff 100644 --- a/server/test/e2e/auth.e2e-spec.ts +++ b/server/test/e2e/auth.e2e-spec.ts @@ -18,6 +18,7 @@ const password = 'Password123'; const email = 'admin@immich.app'; const adminSignupResponse = { + avatarColor: expect.any(String), id: expect.any(String), name: 'Immich Admin', email: 'admin@immich.app', diff --git a/server/test/fixtures/user.stub.ts b/server/test/fixtures/user.stub.ts index b528a107f..c070a6769 100644 --- a/server/test/fixtures/user.stub.ts +++ b/server/test/fixtures/user.stub.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra/entities'; +import { UserAvatarColor, UserEntity } from '@app/infra/entities'; import { authStub } from './auth.stub'; export const userStub = { @@ -17,6 +17,7 @@ export const userStub = { tags: [], assets: [], memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, }), user1: Object.freeze({ ...authStub.user1, @@ -33,6 +34,7 @@ export const userStub = { tags: [], assets: [], memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, }), user2: Object.freeze({ ...authStub.user2, @@ -49,6 +51,7 @@ export const userStub = { tags: [], assets: [], memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, }), storageLabel: Object.freeze({ ...authStub.user1, @@ -65,6 +68,7 @@ export const userStub = { tags: [], assets: [], memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, }), externalPath1: Object.freeze({ ...authStub.user1, @@ -81,6 +85,7 @@ export const userStub = { tags: [], assets: [], memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, }), externalPath2: Object.freeze({ ...authStub.user1, @@ -97,6 +102,7 @@ export const userStub = { tags: [], assets: [], memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, }), profilePath: Object.freeze({ ...authStub.user1, @@ -113,5 +119,6 @@ export const userStub = { tags: [], assets: [], memoriesEnabled: true, + avatarColor: UserAvatarColor.PRIMARY, }), }; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 8fb2c1b3d..4eb5e1228 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2355,6 +2355,12 @@ export interface OAuthConfigResponseDto { * @interface PartnerResponseDto */ export interface PartnerResponseDto { + /** + * + * @type {UserAvatarColor} + * @memberof PartnerResponseDto + */ + 'avatarColor': UserAvatarColor; /** * * @type {string} @@ -2440,6 +2446,8 @@ export interface PartnerResponseDto { */ 'updatedAt': string; } + + /** * * @export @@ -4344,6 +4352,12 @@ export interface UpdateTagDto { * @interface UpdateUserDto */ export interface UpdateUserDto { + /** + * + * @type {UserAvatarColor} + * @memberof UpdateUserDto + */ + 'avatarColor'?: UserAvatarColor; /** * * @type {string} @@ -4399,6 +4413,8 @@ export interface UpdateUserDto { */ 'storageLabel'?: string; } + + /** * * @export @@ -4436,12 +4452,40 @@ export interface UsageByUserDto { */ 'videos': number; } +/** + * + * @export + * @enum {string} + */ + +export const UserAvatarColor = { + Primary: 'primary', + Pink: 'pink', + Red: 'red', + Yellow: 'yellow', + Blue: 'blue', + Green: 'green', + Purple: 'purple', + Orange: 'orange', + Gray: 'gray', + Amber: 'amber' +} as const; + +export type UserAvatarColor = typeof UserAvatarColor[keyof typeof UserAvatarColor]; + + /** * * @export * @interface UserDto */ export interface UserDto { + /** + * + * @type {UserAvatarColor} + * @memberof UserDto + */ + 'avatarColor': UserAvatarColor; /** * * @type {string} @@ -4467,12 +4511,20 @@ export interface UserDto { */ 'profileImagePath': string; } + + /** * * @export * @interface UserResponseDto */ export interface UserResponseDto { + /** + * + * @type {UserAvatarColor} + * @memberof UserResponseDto + */ + 'avatarColor': UserAvatarColor; /** * * @type {string} @@ -4552,6 +4604,8 @@ export interface UserResponseDto { */ 'updatedAt': string; } + + /** * * @export @@ -16477,6 +16531,44 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteProfileImage: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/user/profile-image`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {string} id @@ -16802,6 +16894,15 @@ export const UserApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.createUser(createUserDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteProfileImage(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteProfileImage(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id @@ -16899,6 +17000,14 @@ export const UserApiFactory = function (configuration?: Configuration, basePath? createUser(requestParameters: UserApiCreateUserRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.createUser(requestParameters.createUserDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteProfileImage(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.deleteProfileImage(options).then((request) => request(axios, basePath)); + }, /** * * @param {UserApiDeleteUserRequest} requestParameters Request parameters. @@ -17105,6 +17214,16 @@ export class UserApi extends BaseAPI { return UserApiFp(this.configuration).createUser(requestParameters.createUserDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UserApi + */ + public deleteProfileImage(options?: AxiosRequestConfig) { + return UserApiFp(this.configuration).deleteProfileImage(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {UserApiDeleteUserRequest} requestParameters Request parameters. diff --git a/web/src/lib/components/album-page/share-info-modal.svelte b/web/src/lib/components/album-page/share-info-modal.svelte index 668946d83..b92f20028 100644 --- a/web/src/lib/components/album-page/share-info-modal.svelte +++ b/web/src/lib/components/album-page/share-info-modal.svelte @@ -77,7 +77,7 @@

    - +

    {album.owner.name}

    @@ -90,7 +90,7 @@ class="flex w-full place-items-center justify-between gap-4 p-5 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700" >
    - +

    {user.name}

    diff --git a/web/src/lib/components/album-page/user-selection-modal.svelte b/web/src/lib/components/album-page/user-selection-modal.svelte index 0b4818ad5..33b5494fc 100644 --- a/web/src/lib/components/album-page/user-selection-modal.svelte +++ b/web/src/lib/components/album-page/user-selection-modal.svelte @@ -71,7 +71,7 @@ on:click={() => handleUnselect(user)} class="flex place-items-center gap-1 rounded-full border border-gray-400 p-1 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" > - +

    {user.name}

    {/key} @@ -94,7 +94,7 @@ >✓ {:else} - + {/if}
    diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 70d76ca1a..8c36df2d8 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -333,7 +333,7 @@

    SHARED BY

    - +
    diff --git a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte index 6a135066f..27b7423bb 100644 --- a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte @@ -1,16 +1,48 @@
    - +
    + {#key user} + +
    + +
    + {/key} +

    {user.name} @@ -51,3 +97,10 @@ >

    +{#if isShowSelectAvatar} + (isShowSelectAvatar = false)} + on:choose={({ detail: color }) => handleSaveProfile(color)} + /> +{/if} diff --git a/web/src/lib/components/shared-components/navigation-bar/avatar-selector.svelte b/web/src/lib/components/shared-components/navigation-bar/avatar-selector.svelte new file mode 100644 index 000000000..6d7e39578 --- /dev/null +++ b/web/src/lib/components/shared-components/navigation-bar/avatar-selector.svelte @@ -0,0 +1,39 @@ + + + dispatch('close')} on:escape={() => dispatch('close')}> +
    +
    +
    +

    + SELECT AVATAR COLOR +

    +
    + dispatch('close')} /> +
    +
    +
    +
    + {#each colors as color} + + {/each} +
    +
    +
    +
    +
    diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte index c72803b54..2bb95495f 100644 --- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte @@ -124,7 +124,9 @@ on:mouseleave={() => (shouldShowAccountInfo = false)} on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)} > - + {#key user} + + {/key} {#if shouldShowAccountInfo && !shouldShowAccountInfoPanel} @@ -139,7 +141,7 @@ {/if} {#if shouldShowAccountInfoPanel} - + {/if}
    diff --git a/web/src/lib/components/shared-components/user-avatar.svelte b/web/src/lib/components/shared-components/user-avatar.svelte index 07e65d94a..a77a5f2bb 100644 --- a/web/src/lib/components/shared-components/user-avatar.svelte +++ b/web/src/lib/components/shared-components/user-avatar.svelte @@ -1,35 +1,40 @@ + diff --git a/web/src/hooks.client.ts b/web/src/hooks.client.ts new file mode 100644 index 000000000..1e29371fa --- /dev/null +++ b/web/src/hooks.client.ts @@ -0,0 +1,40 @@ +import type { HandleClientError } from '@sveltejs/kit'; +import type { AxiosError, AxiosResponse } from 'axios'; + +const LOG_PREFIX = '[hooks.client.ts]'; +const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?'; + +const parseError = (error: unknown) => { + const httpError = error as AxiosError; + const request = httpError?.request as Request & { path: string }; + const response = httpError?.response as AxiosResponse<{ + message: string; + statusCode: number; + error: string; + }>; + + let code = response?.data?.statusCode || response?.status || httpError.code || '500'; + if (response) { + code += ` - ${response.data?.error || response.statusText}`; + } + + if (request && response) { + console.log({ + status: response.status, + url: `${request.method} ${request.path}`, + response: response.data || 'No data', + }); + } + + return { + message: response?.data?.message || httpError?.message || DEFAULT_MESSAGE, + code, + stack: httpError?.stack, + }; +}; + +export const handleError: HandleClientError = ({ error }) => { + const result = parseError(error); + console.error(`${LOG_PREFIX}:handleError ${result.message}`); + return result; +}; diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts deleted file mode 100644 index 809e3b0c3..000000000 --- a/web/src/hooks.server.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { env } from '$env/dynamic/public'; -import type { Handle, HandleServerError } from '@sveltejs/kit'; -import type { AxiosError, AxiosResponse } from 'axios'; -import { ImmichApi } from './api/api'; - -const LOG_PREFIX = '[hooks.server.ts]'; - -export const handle = (async ({ event, resolve }) => { - const basePath = env.PUBLIC_IMMICH_SERVER_URL || 'http://immich-server:3001'; - const accessToken = event.cookies.get('immich_access_token'); - const api = new ImmichApi({ basePath, accessToken }); - - // API instance that should be used for all server-side requests. - event.locals.api = api; - - if (accessToken) { - try { - const { data: user } = await api.userApi.getMyUserInfo(); - event.locals.user = user; - } catch (err) { - console.log(`${LOG_PREFIX} Unable to get my user`, parseError(err)); - - const apiError = err as AxiosError; - // Ignore 401 unauthorized errors and log all others. - if (apiError.response?.status && apiError.response?.status !== 401) { - console.error(`${LOG_PREFIX}:handle`, err); - } else if (!apiError.response?.status) { - console.error(`${LOG_PREFIX}:handle`, apiError?.message); - } - } - } - - const res = await resolve(event); - - // The link header can grow quite big and has caused issues with our nginx - // proxy returning a 502 Bad Gateway error. Therefore the header gets deleted. - res.headers.delete('Link'); - - return res; -}) satisfies Handle; - -const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?'; - -const parseError = (error: unknown) => { - const httpError = error as AxiosError; - const request = httpError?.request as Request & { path: string }; - const response = httpError?.response as AxiosResponse<{ - message: string; - statusCode: number; - error: string; - }>; - - let code = response?.data?.statusCode || response?.status || httpError.code || '500'; - if (response) { - code += ` - ${response.data?.error || response.statusText}`; - } - - if (request && response) { - console.log({ - status: response.status, - url: `${request.method} ${request.path}`, - response: response.data || 'No data', - }); - } - - return { - message: response?.data?.message || httpError?.message || DEFAULT_MESSAGE, - code, - stack: httpError?.stack, - }; -}; - -export const handleError: HandleServerError = ({ error }) => { - const result = parseError(error); - console.error(`${LOG_PREFIX}:handleError ${result.message}`); - return result; -}; diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte index 2bb95495f..3fa607e41 100644 --- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte @@ -26,9 +26,6 @@ const logOut = async () => { const { data } = await api.authenticationApi.logout(); - - await fetch('/auth/logout', { method: 'POST' }); - goto(data.redirectUri || '/auth/login?autoLaunch=0'); }; diff --git a/web/src/lib/utils/auth.ts b/web/src/lib/utils/auth.ts new file mode 100644 index 000000000..c5cd99210 --- /dev/null +++ b/web/src/lib/utils/auth.ts @@ -0,0 +1,34 @@ +import { api } from '@api'; +import { redirect } from '@sveltejs/kit'; +import { AppRoute } from '../constants'; + +export interface AuthOptions { + admin?: true; +} + +export const getAuthUser = async () => { + try { + const { data: user } = await api.userApi.getMyUserInfo(); + return user; + } catch { + return null; + } +}; + +// TODO: re-use already loaded user (once) instead of fetching on each page navigation +export const authenticate = async (options?: AuthOptions) => { + options = options || {}; + + const user = await getAuthUser(); + if (!user) { + throw redirect(302, AppRoute.AUTH_LOGIN); + } + + if (options.admin && !user.isAdmin) { + throw redirect(302, AppRoute.PHOTOS); + } + + return user; +}; + +export const isLoggedIn = async () => getAuthUser().then((user) => !!user); diff --git a/web/src/routes/(user)/albums/+page.server.ts b/web/src/routes/(user)/albums/+page.server.ts deleted file mode 100644 index a9373d09c..000000000 --- a/web/src/routes/(user)/albums/+page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { api, user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - try { - const { data: albums } = await api.albumApi.getAllAlbums(); - - return { - user: user, - albums: albums, - meta: { - title: 'Albums', - }, - }; - } catch (e) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/albums/+page.ts b/web/src/routes/(user)/albums/+page.ts new file mode 100644 index 000000000..037fc1ef6 --- /dev/null +++ b/web/src/routes/(user)/albums/+page.ts @@ -0,0 +1,16 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + const { data: albums } = await api.albumApi.getAllAlbums(); + + return { + user, + albums, + meta: { + title: 'Albums', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/albums/[albumId]/+page.server.ts b/web/src/routes/(user)/albums/[albumId]/+page.server.ts deleted file mode 100644 index bd19e7cb1..000000000 --- a/web/src/routes/(user)/albums/[albumId]/+page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ params, locals: { api, user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - try { - const { data: album } = await api.albumApi.getAlbumInfo({ id: params.albumId, withoutAssets: true }); - - return { - album, - user, - meta: { - title: album.albumName, - }, - }; - } catch (e) { - throw redirect(302, AppRoute.ALBUMS); - } -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/albums/[albumId]/+page.ts b/web/src/routes/(user)/albums/[albumId]/+page.ts new file mode 100644 index 000000000..8a6e4913f --- /dev/null +++ b/web/src/routes/(user)/albums/[albumId]/+page.ts @@ -0,0 +1,16 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async ({ params }) => { + const user = await authenticate(); + const { data: album } = await api.albumApi.getAlbumInfo({ id: params.albumId, withoutAssets: true }); + + return { + album, + user, + meta: { + title: album.albumName, + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts index cb325776e..42fa435c4 100644 --- a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts @@ -1,15 +1,9 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const prerender = false; -export const load: PageLoad = async ({ params, parent }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const albumId = params['albumId']; +export const load: PageLoad = async ({ params }) => { + const albumId = params.albumId; if (albumId) { throw redirect(302, `${AppRoute.ALBUMS}/${albumId}`); diff --git a/web/src/routes/(user)/archive/+page.server.ts b/web/src/routes/(user)/archive/+page.server.ts deleted file mode 100644 index 43e7c8652..000000000 --- a/web/src/routes/(user)/archive/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - return { - user, - meta: { - title: 'Archive', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/archive/+page.ts b/web/src/routes/(user)/archive/+page.ts new file mode 100644 index 000000000..90c66d4d5 --- /dev/null +++ b/web/src/routes/(user)/archive/+page.ts @@ -0,0 +1,13 @@ +import { authenticate } from '$lib/utils/auth'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + + return { + user, + meta: { + title: 'Archive', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts b/web/src/routes/(user)/archive/photos/[assetId]/+page.ts index 88923a1af..c8ee61048 100644 --- a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/archive/photos/[assetId]/+page.ts @@ -1,13 +1,7 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const prerender = false; - -export const load: PageLoad = async ({ parent }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } +export const load: PageLoad = async () => { throw redirect(302, AppRoute.ARCHIVE); }; diff --git a/web/src/routes/(user)/explore/+page.server.ts b/web/src/routes/(user)/explore/+page.server.ts deleted file mode 100644 index 5d0491ddd..000000000 --- a/web/src/routes/(user)/explore/+page.server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals, parent }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const { data: items } = await locals.api.searchApi.getExploreData(); - const { data: response } = await locals.api.personApi.getAllPeople({ withHidden: false }); - return { - user, - items, - response, - meta: { - title: 'Explore', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/explore/+page.ts b/web/src/routes/(user)/explore/+page.ts new file mode 100644 index 000000000..a89ac8c83 --- /dev/null +++ b/web/src/routes/(user)/explore/+page.ts @@ -0,0 +1,17 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + const { data: items } = await api.searchApi.getExploreData(); + const { data: response } = await api.personApi.getAllPeople({ withHidden: false }); + return { + user, + items, + response, + meta: { + title: 'Explore', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/favorites/+page.server.ts b/web/src/routes/(user)/favorites/+page.server.ts deleted file mode 100644 index d65255b45..000000000 --- a/web/src/routes/(user)/favorites/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - return { - user, - meta: { - title: 'Favorites', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/favorites/+page.ts b/web/src/routes/(user)/favorites/+page.ts new file mode 100644 index 000000000..bc2d3d201 --- /dev/null +++ b/web/src/routes/(user)/favorites/+page.ts @@ -0,0 +1,12 @@ +import { authenticate } from '$lib/utils/auth'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + return { + user, + meta: { + title: 'Favorites', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/favorites/[assetId]/+page.server.ts b/web/src/routes/(user)/favorites/[assetId]/+page.server.ts deleted file mode 100644 index fd6aa42d4..000000000 --- a/web/src/routes/(user)/favorites/[assetId]/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { redirect } from '@sveltejs/kit'; -export const prerender = false; - -import { AppRoute } from '$lib/constants'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ parent }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else { - throw redirect(302, AppRoute.FAVORITES); - } -}; diff --git a/web/src/routes/(user)/favorites/[assetId]/+page.ts b/web/src/routes/(user)/favorites/[assetId]/+page.ts new file mode 100644 index 000000000..6a46acf9d --- /dev/null +++ b/web/src/routes/(user)/favorites/[assetId]/+page.ts @@ -0,0 +1,7 @@ +import { AppRoute } from '$lib/constants'; +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load: PageLoad = async () => { + throw redirect(302, AppRoute.FAVORITES); +}; diff --git a/web/src/routes/(user)/map/+page.server.ts b/web/src/routes/(user)/map/+page.server.ts deleted file mode 100644 index 50f450fa6..000000000 --- a/web/src/routes/(user)/map/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - return { - user, - meta: { - title: 'Map', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/map/+page.ts b/web/src/routes/(user)/map/+page.ts new file mode 100644 index 000000000..2ee84c876 --- /dev/null +++ b/web/src/routes/(user)/map/+page.ts @@ -0,0 +1,12 @@ +import { authenticate } from '$lib/utils/auth'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + return { + user, + meta: { + title: 'Map', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/memory/+page.server.ts b/web/src/routes/(user)/memory/+page.server.ts deleted file mode 100644 index 3973762d7..000000000 --- a/web/src/routes/(user)/memory/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - return { - user, - meta: { - title: 'Memory', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/memory/+page.ts b/web/src/routes/(user)/memory/+page.ts new file mode 100644 index 000000000..e56dd392d --- /dev/null +++ b/web/src/routes/(user)/memory/+page.ts @@ -0,0 +1,12 @@ +import { authenticate } from '$lib/utils/auth'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + return { + user, + meta: { + title: 'Memory', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/memory/photos/+page.server.ts b/web/src/routes/(user)/memory/photos/+page.server.ts deleted file mode 100644 index bd32438aa..000000000 --- a/web/src/routes/(user)/memory/photos/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { redirect } from '@sveltejs/kit'; -export const prerender = false; - -import { AppRoute } from '$lib/constants'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ parent }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else { - throw redirect(302, AppRoute.MEMORY); - } -}; diff --git a/web/src/routes/(user)/memory/photos/+page.ts b/web/src/routes/(user)/memory/photos/+page.ts new file mode 100644 index 000000000..07263cff5 --- /dev/null +++ b/web/src/routes/(user)/memory/photos/+page.ts @@ -0,0 +1,9 @@ +import { AppRoute } from '$lib/constants'; +import { authenticate } from '$lib/utils/auth'; +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + await authenticate(); + throw redirect(302, AppRoute.MEMORY); +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/memory/photos/[assetId]/+page.server.ts b/web/src/routes/(user)/memory/photos/[assetId]/+page.server.ts deleted file mode 100644 index bd32438aa..000000000 --- a/web/src/routes/(user)/memory/photos/[assetId]/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { redirect } from '@sveltejs/kit'; -export const prerender = false; - -import { AppRoute } from '$lib/constants'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ parent }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else { - throw redirect(302, AppRoute.MEMORY); - } -}; diff --git a/web/src/routes/(user)/memory/photos/[assetId]/+page.ts b/web/src/routes/(user)/memory/photos/[assetId]/+page.ts new file mode 100644 index 000000000..fb2fb42d1 --- /dev/null +++ b/web/src/routes/(user)/memory/photos/[assetId]/+page.ts @@ -0,0 +1,7 @@ +import { AppRoute } from '$lib/constants'; +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + throw redirect(302, AppRoute.PHOTOS); +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/partners/[userId]/+page.server.ts b/web/src/routes/(user)/partners/[userId]/+page.server.ts deleted file mode 100644 index 13fbb1c10..000000000 --- a/web/src/routes/(user)/partners/[userId]/+page.server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ params, parent, locals: { api } }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const { data: partner } = await api.userApi.getUserById({ id: params['userId'] }); - - return { - user, - partner, - meta: { - title: 'Partner', - }, - }; -}; diff --git a/web/src/routes/(user)/partners/[userId]/+page.ts b/web/src/routes/(user)/partners/[userId]/+page.ts new file mode 100644 index 000000000..6bbbdd42d --- /dev/null +++ b/web/src/routes/(user)/partners/[userId]/+page.ts @@ -0,0 +1,17 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async ({ params }) => { + const user = await authenticate(); + + const { data: partner } = await api.userApi.getUserById({ id: params.userId }); + + return { + user, + partner, + meta: { + title: 'Partner', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/people/+page.server.ts b/web/src/routes/(user)/people/+page.server.ts deleted file mode 100644 index de5354d06..000000000 --- a/web/src/routes/(user)/people/+page.server.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals, parent }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: true }); - return { - user, - people, - meta: { - title: 'People', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/people/+page.ts b/web/src/routes/(user)/people/+page.ts new file mode 100644 index 000000000..0d82a1fa4 --- /dev/null +++ b/web/src/routes/(user)/people/+page.ts @@ -0,0 +1,16 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + + const { data: people } = await api.personApi.getAllPeople({ withHidden: true }); + return { + user, + people, + meta: { + title: 'People', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/people/[personId]/+page.server.ts b/web/src/routes/(user)/people/[personId]/+page.server.ts deleted file mode 100644 index d81f893ab..000000000 --- a/web/src/routes/(user)/people/[personId]/+page.server.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals, parent, params }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const { data: person } = await locals.api.personApi.getPerson({ id: params.personId }); - const { data: statistics } = await locals.api.personApi.getPersonStatistics({ id: params.personId }); - - return { - user, - person, - statistics, - meta: { - title: person.name || 'Person', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/people/[personId]/+page.ts b/web/src/routes/(user)/people/[personId]/+page.ts new file mode 100644 index 000000000..71f6f46b3 --- /dev/null +++ b/web/src/routes/(user)/people/[personId]/+page.ts @@ -0,0 +1,19 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async ({ params }) => { + const user = await authenticate(); + + const { data: person } = await api.personApi.getPerson({ id: params.personId }); + const { data: statistics } = await api.personApi.getPersonStatistics({ id: params.personId }); + + return { + user, + person, + statistics, + meta: { + title: person.name || 'Person', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts index ff1ae1cf8..21ad37559 100644 --- a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts @@ -1,14 +1,7 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const prerender = false; -export const load: PageLoad = async ({ params, parent }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const personId = params['personId']; - throw redirect(302, `${AppRoute.PEOPLE}/${personId}`); -}; +export const load = (async ({ params }) => { + throw redirect(302, `${AppRoute.PEOPLE}/${params.personId}`); +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/photos/+page.server.ts b/web/src/routes/(user)/photos/+page.server.ts deleted file mode 100644 index e8965f02f..000000000 --- a/web/src/routes/(user)/photos/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - return { - user, - meta: { - title: 'Photos', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/photos/+page.ts b/web/src/routes/(user)/photos/+page.ts new file mode 100644 index 000000000..26e6ffb2d --- /dev/null +++ b/web/src/routes/(user)/photos/+page.ts @@ -0,0 +1,12 @@ +import { authenticate } from '$lib/utils/auth'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + return { + user, + meta: { + title: 'Photos', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/photos/[assetId]/+page.server.ts b/web/src/routes/(user)/photos/[assetId]/+page.server.ts deleted file mode 100644 index 1c8a2a51c..000000000 --- a/web/src/routes/(user)/photos/[assetId]/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { redirect } from '@sveltejs/kit'; -export const prerender = false; - -import { AppRoute } from '$lib/constants'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ parent }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else { - throw redirect(302, AppRoute.PHOTOS); - } -}; diff --git a/web/src/routes/(user)/photos/[assetId]/+page.ts b/web/src/routes/(user)/photos/[assetId]/+page.ts new file mode 100644 index 000000000..fb2fb42d1 --- /dev/null +++ b/web/src/routes/(user)/photos/[assetId]/+page.ts @@ -0,0 +1,7 @@ +import { AppRoute } from '$lib/constants'; +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + throw redirect(302, AppRoute.PHOTOS); +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/search/+page.server.ts b/web/src/routes/(user)/search/+page.server.ts deleted file mode 100644 index 17a09fefc..000000000 --- a/web/src/routes/(user)/search/+page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals, parent, url }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const term = url.searchParams.get('q') || url.searchParams.get('query') || undefined; - - const { data: results } = await locals.api.searchApi.search({}, { params: url.searchParams }); - - return { - user, - term, - results, - meta: { - title: 'Search', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/search/+page.ts b/web/src/routes/(user)/search/+page.ts new file mode 100644 index 000000000..ce150a37b --- /dev/null +++ b/web/src/routes/(user)/search/+page.ts @@ -0,0 +1,20 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + const url = new URL(location.href); + const term = url.searchParams.get('q') || url.searchParams.get('query') || undefined; + + const { data: results } = await api.searchApi.search({}, { params: url.searchParams }); + + return { + user, + term, + results, + meta: { + title: 'Search', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/search/photos/[assetId]/+page.ts b/web/src/routes/(user)/search/photos/[assetId]/+page.ts index 994670a69..512030675 100644 --- a/web/src/routes/(user)/search/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/search/photos/[assetId]/+page.ts @@ -1,13 +1,7 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const prerender = false; - -export const load: PageLoad = async ({ parent }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } +export const load = (async () => { throw redirect(302, AppRoute.SEARCH); -}; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/share/[key]/+page.server.ts b/web/src/routes/(user)/share/[key]/+page.ts similarity index 67% rename from web/src/routes/(user)/share/[key]/+page.server.ts rename to web/src/routes/(user)/share/[key]/+page.ts index 5ba044df9..21604ed68 100644 --- a/web/src/routes/(user)/share/[key]/+page.server.ts +++ b/web/src/routes/(user)/share/[key]/+page.ts @@ -1,31 +1,32 @@ import featurePanelUrl from '$lib/assets/feature-panel.png'; -import { api as clientApi, ThumbnailFormat } from '@api'; +import { getAuthUser } from '$lib/utils/auth'; +import { api, ThumbnailFormat } from '@api'; import { error } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; import type { AxiosError } from 'axios'; +import type { PageLoad } from './$types'; -export const load = (async ({ params, locals: { api }, cookies }) => { +export const load = (async ({ params }) => { const { key } = params; - const token = cookies.get('immich_shared_link_token'); + const user = await getAuthUser(); try { - const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key, token }); + const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key }); const assetCount = sharedLink.assets.length; const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id; return { + user, sharedLink, meta: { title: sharedLink.album ? sharedLink.album.albumName : 'Public Share', description: sharedLink.description || `${assetCount} shared photos & videos.`, - imageUrl: assetId - ? clientApi.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key) - : featurePanelUrl, + imageUrl: assetId ? api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key) : featurePanelUrl, }, }; } catch (e) { // handle unauthorized error + // TODO this doesn't allow for 404 shared links anymore if ((e as AxiosError).response?.status === 401) { return { passwordRequired: true, @@ -40,4 +41,4 @@ export const load = (async ({ params, locals: { api }, cookies }) => { message: 'Invalid shared link', }); } -}) satisfies PageServerLoad; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.server.ts b/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.server.ts deleted file mode 100644 index b0e26e842..000000000 --- a/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.server.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { error } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ params, locals: { api } }) => { - const { key, assetId } = params; - const { data: asset } = await api.assetApi.getAssetById({ id: assetId, key }); - - if (!asset) { - throw error(404, 'Asset not found'); - } - - return { - asset, - key, - meta: { - title: 'Public Share', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.ts b/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.ts new file mode 100644 index 000000000..b76a84be2 --- /dev/null +++ b/web/src/routes/(user)/share/[key]/photos/[assetId]/+page.ts @@ -0,0 +1,15 @@ +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async ({ params }) => { + const { key, assetId } = params; + const { data: asset } = await api.assetApi.getAssetById({ id: assetId, key }); + + return { + asset, + key, + meta: { + title: 'Public Share', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/sharing/+page.server.ts b/web/src/routes/(user)/sharing/+page.server.ts deleted file mode 100644 index 2b77b4677..000000000 --- a/web/src/routes/(user)/sharing/+page.server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { api, user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - try { - const { data: sharedAlbums } = await api.albumApi.getAllAlbums({ shared: true }); - const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-with' }); - - return { - user, - sharedAlbums, - partners, - meta: { - title: 'Sharing', - }, - }; - } catch (e) { - console.log(e); - throw redirect(302, AppRoute.AUTH_LOGIN); - } -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/sharing/+page.ts b/web/src/routes/(user)/sharing/+page.ts new file mode 100644 index 000000000..76932342c --- /dev/null +++ b/web/src/routes/(user)/sharing/+page.ts @@ -0,0 +1,18 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + const { data: sharedAlbums } = await api.albumApi.getAllAlbums({ shared: true }); + const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-with' }); + + return { + user, + sharedAlbums, + partners, + meta: { + title: 'Sharing', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.server.ts b/web/src/routes/(user)/sharing/sharedlinks/+page.server.ts deleted file mode 100644 index c849ff997..000000000 --- a/web/src/routes/(user)/sharing/sharedlinks/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - return { - user, - meta: { - title: 'Shared Links', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.ts b/web/src/routes/(user)/sharing/sharedlinks/+page.ts new file mode 100644 index 000000000..61865785c --- /dev/null +++ b/web/src/routes/(user)/sharing/sharedlinks/+page.ts @@ -0,0 +1,12 @@ +import { authenticate } from '$lib/utils/auth'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + return { + user, + meta: { + title: 'Shared Links', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/trash/+page.server.ts b/web/src/routes/(user)/trash/+page.server.ts deleted file mode 100644 index e9a726311..000000000 --- a/web/src/routes/(user)/trash/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - return { - user, - meta: { - title: 'Trash', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/trash/+page.ts b/web/src/routes/(user)/trash/+page.ts new file mode 100644 index 000000000..cc0e39f16 --- /dev/null +++ b/web/src/routes/(user)/trash/+page.ts @@ -0,0 +1,12 @@ +import { authenticate } from '$lib/utils/auth'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + return { + user, + meta: { + title: 'Trash', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/trash/photos/[assetId]/+page.ts b/web/src/routes/(user)/trash/photos/[assetId]/+page.ts index 6a840178c..051024e5e 100644 --- a/web/src/routes/(user)/trash/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/trash/photos/[assetId]/+page.ts @@ -1,13 +1,7 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const prerender = false; - -export const load: PageLoad = async ({ parent }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } +export const load = (async () => { throw redirect(302, AppRoute.TRASH); -}; +}) satisfies PageLoad; diff --git a/web/src/routes/(user)/user-settings/+page.server.ts b/web/src/routes/(user)/user-settings/+page.server.ts deleted file mode 100644 index 346582638..000000000 --- a/web/src/routes/(user)/user-settings/+page.server.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ parent, locals }) => { - const { user } = await parent(); - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } - - const { data: keys } = await locals.api.keyApi.getApiKeys(); - const { data: devices } = await locals.api.authenticationApi.getAuthDevices(); - - return { - user, - keys, - devices, - meta: { - title: 'Settings', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/(user)/user-settings/+page.ts b/web/src/routes/(user)/user-settings/+page.ts new file mode 100644 index 000000000..93aa5ddee --- /dev/null +++ b/web/src/routes/(user)/user-settings/+page.ts @@ -0,0 +1,19 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + + const { data: keys } = await api.keyApi.getApiKeys(); + const { data: devices } = await api.authenticationApi.getAuthDevices(); + + return { + user, + keys, + devices, + meta: { + title: 'Settings', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/+layout.server.ts b/web/src/routes/+layout.server.ts deleted file mode 100644 index 804071cd7..000000000 --- a/web/src/routes/+layout.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { LayoutServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - return { user }; -}) satisfies LayoutServerLoad; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 3c43b98ee..928164bb6 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -24,7 +24,10 @@ export let data: LayoutData; let albumId: string | undefined; - if ($page.route.id?.startsWith('/(user)/share/[key]')) { + const isSharedLinkRoute = (route: string | null) => route?.startsWith('/(user)/share/[key]'); + const isAuthRoute = (route?: string) => route?.startsWith('/auth'); + + if (isSharedLinkRoute($page.route?.id)) { api.setKey($page.params.key); } @@ -32,11 +35,11 @@ const fromRoute = from?.route?.id || ''; const toRoute = to?.route?.id || ''; - if (fromRoute.startsWith('/auth') && !toRoute.startsWith('/auth')) { + if (isAuthRoute(fromRoute) && !isAuthRoute(toRoute)) { openWebsocketConnection(); } - if (!fromRoute.startsWith('/auth') && toRoute.startsWith('/auth')) { + if (!isAuthRoute(fromRoute) && isAuthRoute(toRoute)) { closeWebsocketConnection(); } @@ -80,7 +83,6 @@ {$page.data.meta?.title || 'Web'} - Immich - diff --git a/web/src/routes/+layout.ts b/web/src/routes/+layout.ts new file mode 100644 index 000000000..d59082b9a --- /dev/null +++ b/web/src/routes/+layout.ts @@ -0,0 +1,25 @@ +import { api } from '../api'; +import type { LayoutLoad } from './$types'; + +const getUser = async () => { + try { + const { data: user } = await api.userApi.getMyUserInfo(); + return user; + } catch { + return null; + } +}; + +export const ssr = false; +export const csr = true; + +export const load = (async () => { + const user = await getUser(); + + return { + user, + meta: { + title: 'Immich', + }, + }; +}) satisfies LayoutLoad; diff --git a/web/src/routes/+page.server.ts b/web/src/routes/+page.ts similarity index 61% rename from web/src/routes/+page.server.ts rename to web/src/routes/+page.ts index b469170be..438b289ce 100644 --- a/web/src/routes/+page.server.ts +++ b/web/src/routes/+page.ts @@ -1,17 +1,19 @@ -export const prerender = false; - import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; +import { api } from '../api'; +import { isLoggedIn } from '../lib/utils/auth'; +import type { PageLoad } from './$types'; -export const load = (async ({ parent, locals: { api } }) => { - const { user } = await parent(); - if (user) { +export const ssr = false; +export const csr = true; + +export const load = (async () => { + const authenticated = await isLoggedIn(); + if (authenticated) { throw redirect(302, AppRoute.PHOTOS); } const { data } = await api.serverInfoApi.getServerConfig(); - if (data.isInitialized) { // Redirect to login page if there exists an admin account (i.e. server is initialized) throw redirect(302, AppRoute.AUTH_LOGIN); @@ -23,4 +25,4 @@ export const load = (async ({ parent, locals: { api } }) => { description: 'Immich Web Interface', }, }; -}) satisfies PageServerLoad; +}) satisfies PageLoad; diff --git a/web/src/routes/.well-known/immich/+server.ts b/web/src/routes/.well-known/immich/+server.ts deleted file mode 100644 index 37d778806..000000000 --- a/web/src/routes/.well-known/immich/+server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { json } from '@sveltejs/kit'; - -const endpoint = process.env.IMMICH_API_URL_EXTERNAL || '/api'; - -export const GET = async () => { - return json({ - api: { - endpoint, - }, - }); -}; diff --git a/web/src/routes/admin/+page.server.ts b/web/src/routes/admin/+page.server.ts deleted file mode 100644 index 777b940dc..000000000 --- a/web/src/routes/admin/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ parent }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else if (!user.isAdmin) { - throw redirect(302, AppRoute.PHOTOS); - } - - throw redirect(302, AppRoute.ADMIN_USER_MANAGEMENT); -}; diff --git a/web/src/routes/admin/+page.ts b/web/src/routes/admin/+page.ts new file mode 100644 index 000000000..3eca8c08d --- /dev/null +++ b/web/src/routes/admin/+page.ts @@ -0,0 +1,7 @@ +import { AppRoute } from '$lib/constants'; +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + throw redirect(302, AppRoute.ADMIN_USER_MANAGEMENT); +}) satisfies PageLoad; diff --git a/web/src/routes/admin/jobs-status/+page.server.ts b/web/src/routes/admin/jobs-status/+page.server.ts deleted file mode 100644 index 7e700ba33..000000000 --- a/web/src/routes/admin/jobs-status/+page.server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user, api } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else if (!user.isAdmin) { - throw redirect(302, AppRoute.PHOTOS); - } - - try { - const { data: jobs } = await api.jobApi.getAllJobsStatus(); - - return { - user, - jobs, - meta: { - title: 'Job Status', - }, - }; - } catch (err) { - console.error('[jobs] > getAllJobsStatus', err); - throw err; - } -}) satisfies PageServerLoad; diff --git a/web/src/routes/admin/jobs-status/+page.ts b/web/src/routes/admin/jobs-status/+page.ts new file mode 100644 index 000000000..b6face823 --- /dev/null +++ b/web/src/routes/admin/jobs-status/+page.ts @@ -0,0 +1,17 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate({ admin: true }); + + const { data: jobs } = await api.jobApi.getAllJobsStatus(); + + return { + user, + jobs, + meta: { + title: 'Job Status', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/admin/repair/+page.server.ts b/web/src/routes/admin/repair/+page.server.ts deleted file mode 100644 index 9f04e013c..000000000 --- a/web/src/routes/admin/repair/+page.server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ parent, locals: { api } }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else if (!user.isAdmin) { - throw redirect(302, AppRoute.PHOTOS); - } - - const { - data: { orphans, extras }, - } = await api.auditApi.getAuditFiles(); - - return { - user, - orphans, - extras, - meta: { - title: 'Repair', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/admin/repair/+page.ts b/web/src/routes/admin/repair/+page.ts new file mode 100644 index 000000000..9ad200116 --- /dev/null +++ b/web/src/routes/admin/repair/+page.ts @@ -0,0 +1,19 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate({ admin: true }); + const { + data: { orphans, extras }, + } = await api.auditApi.getAuditFiles(); + + return { + user, + orphans, + extras, + meta: { + title: 'Repair', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/admin/server-status/+page.server.ts b/web/src/routes/admin/server-status/+page.server.ts deleted file mode 100644 index a9b08e2a1..000000000 --- a/web/src/routes/admin/server-status/+page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ parent, locals: { api } }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else if (!user.isAdmin) { - throw redirect(302, AppRoute.PHOTOS); - } - - const { data: stats } = await api.serverInfoApi.getServerStatistics(); - - return { - user, - stats, - meta: { - title: 'Server Stats', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/admin/server-status/+page.ts b/web/src/routes/admin/server-status/+page.ts new file mode 100644 index 000000000..73d0ab229 --- /dev/null +++ b/web/src/routes/admin/server-status/+page.ts @@ -0,0 +1,16 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate({ admin: true }); + const { data: stats } = await api.serverInfoApi.getServerStatistics(); + + return { + user, + stats, + meta: { + title: 'Server Stats', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/admin/system-settings/+page.server.ts b/web/src/routes/admin/system-settings/+page.server.ts deleted file mode 100644 index d1b652518..000000000 --- a/web/src/routes/admin/system-settings/+page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ parent, locals: { api } }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else if (!user.isAdmin) { - throw redirect(302, AppRoute.PHOTOS); - } - - const { data: configs } = await api.systemConfigApi.getConfig(); - - return { - user, - configs, - meta: { - title: 'System Settings', - }, - }; -}; diff --git a/web/src/routes/admin/system-settings/+page.ts b/web/src/routes/admin/system-settings/+page.ts new file mode 100644 index 000000000..fa635ae82 --- /dev/null +++ b/web/src/routes/admin/system-settings/+page.ts @@ -0,0 +1,16 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate({ admin: true }); + const { data: configs } = await api.systemConfigApi.getConfig(); + + return { + user, + configs, + meta: { + title: 'System Settings', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/admin/user-management/+page.server.ts b/web/src/routes/admin/user-management/+page.server.ts deleted file mode 100644 index 54fdc4311..000000000 --- a/web/src/routes/admin/user-management/+page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ parent, locals: { api } }) => { - const { user } = await parent(); - - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else if (!user.isAdmin) { - throw redirect(302, AppRoute.PHOTOS); - } - - const { data: allUsers } = await api.userApi.getAllUsers({ isAll: false }); - - return { - user, - allUsers, - meta: { - title: 'User Management', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/admin/user-management/+page.ts b/web/src/routes/admin/user-management/+page.ts new file mode 100644 index 000000000..566a30753 --- /dev/null +++ b/web/src/routes/admin/user-management/+page.ts @@ -0,0 +1,16 @@ +import { authenticate } from '$lib/utils/auth'; +import { api } from '@api'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate({ admin: true }); + const { data: allUsers } = await api.userApi.getAllUsers({ isAll: false }); + + return { + user, + allUsers, + meta: { + title: 'User Management', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/auth/change-password/+page.server.ts b/web/src/routes/auth/change-password/+page.server.ts deleted file mode 100644 index 774f2f0b0..000000000 --- a/web/src/routes/auth/change-password/+page.server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ locals: { user } }) => { - if (!user) { - throw redirect(302, AppRoute.AUTH_LOGIN); - } else if (!user.shouldChangePassword) { - throw redirect(302, AppRoute.PHOTOS); - } - - return { - user, - meta: { - title: 'Change Password', - }, - }; -}) satisfies PageServerLoad; diff --git a/web/src/routes/auth/change-password/+page.ts b/web/src/routes/auth/change-password/+page.ts new file mode 100644 index 000000000..0f391a5ad --- /dev/null +++ b/web/src/routes/auth/change-password/+page.ts @@ -0,0 +1,18 @@ +import { AppRoute } from '$lib/constants'; +import { authenticate } from '$lib/utils/auth'; +import { redirect } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load = (async () => { + const user = await authenticate(); + if (!user.shouldChangePassword) { + throw redirect(302, AppRoute.PHOTOS); + } + + return { + user, + meta: { + title: 'Change Password', + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/auth/login/+page.server.ts b/web/src/routes/auth/login/+page.ts similarity index 70% rename from web/src/routes/auth/login/+page.server.ts rename to web/src/routes/auth/login/+page.ts index a294173b7..270fff2d8 100644 --- a/web/src/routes/auth/login/+page.server.ts +++ b/web/src/routes/auth/login/+page.ts @@ -1,8 +1,9 @@ import { AppRoute } from '$lib/constants'; +import { api } from '@api'; import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; +import type { PageLoad } from './$types'; -export const load = (async ({ locals: { api } }) => { +export const load = (async () => { const { data } = await api.serverInfoApi.getServerConfig(); if (!data.isInitialized) { // Admin not registered @@ -14,4 +15,4 @@ export const load = (async ({ locals: { api } }) => { title: 'Login', }, }; -}) satisfies PageServerLoad; +}) satisfies PageLoad; diff --git a/web/src/routes/auth/logout/+server.ts b/web/src/routes/auth/logout/+server.ts deleted file mode 100644 index dff2627b8..000000000 --- a/web/src/routes/auth/logout/+server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { api } from '@api'; -import type { RequestHandler } from '@sveltejs/kit'; -import { json } from '@sveltejs/kit'; - -export const POST = (async ({ cookies }) => { - api.removeAccessToken(); - - cookies.delete('immich_auth_type', { path: '/' }); - cookies.delete('immich_access_token', { path: '/' }); - - return json({ ok: true }); -}) satisfies RequestHandler; diff --git a/web/src/routes/auth/register/+page.server.ts b/web/src/routes/auth/register/+page.ts similarity index 72% rename from web/src/routes/auth/register/+page.server.ts rename to web/src/routes/auth/register/+page.ts index 186ab2e3d..d9709e3f0 100644 --- a/web/src/routes/auth/register/+page.server.ts +++ b/web/src/routes/auth/register/+page.ts @@ -1,8 +1,9 @@ import { AppRoute } from '$lib/constants'; +import { api } from '@api'; import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; +import type { PageLoad } from './$types'; -export const load = (async ({ locals: { api } }) => { +export const load = (async () => { const { data } = await api.serverInfoApi.getServerConfig(); if (data.isInitialized) { // Admin has been registered, redirect to login @@ -14,4 +15,4 @@ export const load = (async ({ locals: { api } }) => { title: 'Admin Registration', }, }; -}) satisfies PageServerLoad; +}) satisfies PageLoad; diff --git a/web/src/routes/custom.css/+server.ts b/web/src/routes/custom.css/+server.ts deleted file mode 100644 index e71728f2a..000000000 --- a/web/src/routes/custom.css/+server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RequestHandler, text } from '@sveltejs/kit'; -export const GET = (async ({ locals: { api } }) => { - const { - data: { customCss }, - } = await api.serverInfoApi.getTheme(); - return text(customCss, { - headers: { - 'Content-Type': 'text/css', - }, - }); -}) satisfies RequestHandler; diff --git a/web/svelte.config.js b/web/svelte.config.js index f9cef5166..ebbb2beed 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-node'; +import adapter from '@sveltejs/adapter-static'; import preprocess from 'svelte-preprocess'; /** @type {import('@sveltejs/kit').Config} */ @@ -11,7 +11,15 @@ const config = { handler(warning); }, kit: { - adapter: adapter({ out: 'build' }), + adapter: adapter({ + // default options are shown. On some platforms + // these options are set automatically — see below + pages: 'build', + assets: 'build', + fallback: 'index.html', + precompress: false, + strict: true, + }), }, }; diff --git a/web/vite.config.js b/web/vite.config.js index 4406c0ad0..6c550cd6f 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -1,6 +1,14 @@ import { sveltekit } from '@sveltejs/kit/vite'; import path from 'path'; +const upstream = { + target: process.env.IMMICH_SERVER_URL || 'http://immich-server:3001/', + secure: true, + changeOrigin: true, + logLevel: 'debug', + ws: true, +}; + /** @type {import('vite').UserConfig} */ const config = { resolve: { @@ -12,14 +20,9 @@ const config = { server: { // connect to a remote backend during web-only development proxy: { - '/api': { - target: process.env.PUBLIC_IMMICH_SERVER_URL, - secure: true, - changeOrigin: true, - logLevel: 'debug', - rewrite: (path) => path.replace(/^\/api/, ''), - ws: true, - }, + '/api': upstream, + '/.well-known/immich': upstream, + '/custom.css': upstream, }, }, plugins: [sveltekit()], From f2877c3a6e9aee6f3ba34eabd52a246a449e1e14 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Sat, 18 Nov 2023 18:43:10 +0100 Subject: [PATCH 81/88] chore: add warning to compose file and readme (#5123) * add warning to compose file * add readme --- docker/README.md | 5 +++++ docker/docker-compose.yml | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 docker/README.md diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..437aa274c --- /dev/null +++ b/docker/README.md @@ -0,0 +1,5 @@ +> [!CAUTION] +> Make sure to use the docker-compose.yml of the current release: +> https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml +> +> The compose file on main may not be compatible with the latest release. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d4399201e..7c5e3c6e3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,5 +1,13 @@ version: "3.8" +# +# WARNING: Make sure to use the docker-compose.yml of the current release: +# +# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml +# +# The the compose file on main may not be compatible with the latest release. +# + name: immich services: From 767fe87b2e2b01facf297a6162fadf37b883c42c Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Sat, 18 Nov 2023 18:49:30 +0100 Subject: [PATCH 82/88] fix typo (#5124) --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7c5e3c6e3..dc5269036 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -5,7 +5,7 @@ version: "3.8" # # https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml # -# The the compose file on main may not be compatible with the latest release. +# The compose file on main may not be compatible with the latest release. # name: immich From f5ce3deb3acbb9ac5b8768c01a417bccd2348b67 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:59:04 -0600 Subject: [PATCH 83/88] fix(deps): update server (#5057) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- server/Dockerfile | 2 +- server/package-lock.json | 363 ++++++++++++++++++++------------------- 2 files changed, 192 insertions(+), 173 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index 784da7fcc..c780a116e 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -13,7 +13,7 @@ RUN npm run build RUN npm prune --omit=dev --omit=optional # web build -FROM node:20.8-alpine3.18 as web +FROM node:20.9-alpine3.18 as web WORKDIR /usr/src/app COPY web/package.json web/package-lock.json ./ diff --git a/server/package-lock.json b/server/package-lock.json index c0e5e5bd3..3213b2aae 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -987,9 +987,9 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1828,9 +1828,9 @@ } }, "node_modules/@nestjs/common": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.8.tgz", - "integrity": "sha512-rmpwcdvq2IWMmsUVP8rsdKub6uDWk7dwCYo0aif50JTwcvcxzaP3iKVFKoSgvp0RKYu8h15+/AEOfaInmPpl0Q==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.9.tgz", + "integrity": "sha512-i7vb2zMLJUDIPqjfBhMkgIITK1AnKDkFYSsM+aaRHpNa9xv/CwsiQuINaXfzStMpnwjkq5FDE3aoF0wkTfD2cQ==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -1876,9 +1876,9 @@ } }, "node_modules/@nestjs/core": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.8.tgz", - "integrity": "sha512-9+MZ2s8ixfY9Bl/M9ofChiyYymcwdK9ZWNH4GDMF7Am7XRAQ1oqde6MYGG05rhQwiVXuTwaYLlXciJKfsrg5qg==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.9.tgz", + "integrity": "sha512-Hl6HC9hR7JD3YmzwcveBKeydaq9cguEsMdEghzLuVH3VEH0M+bTFHjCIKhsxMez4/O7/K6n3EhNx1Et4Z+BqWg==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", @@ -1937,9 +1937,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.8.tgz", - "integrity": "sha512-WoSSVtwIRc5AdGMHWVzWZK4JZLT0f4o2xW8P9gQvcX+omL8W1kXCfY8GQYXNBG84XmBNYH8r0FtC8oMe/lH5NQ==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.9.tgz", + "integrity": "sha512-r6BSMJmLLeNgyPZJ9F8wQWCXH6rrMHMd9QbCfvyUmETci5Ofy6atiYVVXl7Ms1rAi2EEnXpVCuoydHBBqSlTbg==", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -1962,9 +1962,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.8.tgz", - "integrity": "sha512-P/Olw9alAaKD7Q1vS/ol7K81x1l7Bmi+AXthBNUPGMmG/W8kxO1krerW4rEhtF3BKJ0qJIa5bhDlb80p4lZcNA==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.9.tgz", + "integrity": "sha512-xuXsUWUtgzdRnNBSWZADQv0sShBOsyHK7iEwwIpFMerqbthSMwjsyUv0s3hDoPEnS6Nf4MMf2KReD5JBAnMBFQ==", "dependencies": { "socket.io": "4.7.2", "tslib": "2.6.2" @@ -2027,9 +2027,9 @@ } }, "node_modules/@nestjs/swagger": { - "version": "7.1.15", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.15.tgz", - "integrity": "sha512-ZaAO90R9MQXk4iLQLijIH6jrsllkUSYoh0Su6DECGgu8Y4Q/9LfdESwsZ9nmzr/48aLOu+wrv+cdI5Wr6fLKJw==", + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.16.tgz", + "integrity": "sha512-f9KBk/BX9MUKPTj7tQNYJ124wV/jP5W2lwWHLGwe/4qQXixuDOo39zP55HIJ44LE7S04B7BOeUOo9GBJD/vRcw==", "dependencies": { "@nestjs/mapped-types": "2.0.3", "js-yaml": "4.1.0", @@ -2058,9 +2058,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz", - "integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.9.tgz", + "integrity": "sha512-E+66R27Op+WAQHHH6RnUsz7QpKApl4Bn42nheCAGvS/sxbaDJ8RKtm4stE4Iz2aioPCUvRi8j4z8Ze73k0CcGQ==", "dev": true, "dependencies": { "tslib": "2.6.2" @@ -2091,11 +2091,11 @@ "dev": true }, "node_modules/@nestjs/typeorm": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.0.tgz", - "integrity": "sha512-WQU4HCDTz4UavsFzvGUKDHqi0MO5K47yFoPXdmh+Z/hCNO7SHCMmV9jLiLukM8n5nKUqJ3jDqiljkWBcZPdCtA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.1.tgz", + "integrity": "sha512-YVFYL7D25VAVp5/G+KLXIgsRfYomA+VaFZBpm2rtwrrBOmkXNrxr7kuI2bBBO/Xy4kKBDe6wbvIVVFeEA7/ngA==", "dependencies": { - "uuid": "9.0.0" + "uuid": "9.0.1" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", @@ -2105,10 +2105,22 @@ "typeorm": "^0.3.0" } }, + "node_modules/@nestjs/typeorm/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nestjs/websockets": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.8.tgz", - "integrity": "sha512-oZN1VJFApN7d2eftr65a36QrV0IJNGba4znqyjFnyGvtDWTDcQwzDcnEfvJBTTYhOSBNS7KDfVhne0ythkl6tg==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.9.tgz", + "integrity": "sha512-Hp/ioNMcBtCzkubgcDHeA5KH4YLBL1jHfMEacLctKDa20Kn/LFXhJhXVWEr4jEzUKXbD+Q+GyYk1V/1rJi6eHA==", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -3043,9 +3055,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", "dependencies": { "undici-types": "~5.26.4" } @@ -3187,16 +3199,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz", - "integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz", + "integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/type-utils": "6.10.0", - "@typescript-eslint/utils": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", + "@typescript-eslint/scope-manager": "6.11.0", + "@typescript-eslint/type-utils": "6.11.0", + "@typescript-eslint/utils": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3222,15 +3234,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", - "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz", + "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/typescript-estree": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", + "@typescript-eslint/scope-manager": "6.11.0", + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/typescript-estree": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0", "debug": "^4.3.4" }, "engines": { @@ -3250,13 +3262,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", - "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz", + "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0" + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3267,13 +3279,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz", - "integrity": "sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz", + "integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.10.0", - "@typescript-eslint/utils": "6.10.0", + "@typescript-eslint/typescript-estree": "6.11.0", + "@typescript-eslint/utils": "6.11.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3294,9 +3306,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", - "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz", + "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3307,13 +3319,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", - "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz", + "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3334,17 +3346,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz", - "integrity": "sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz", + "integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/scope-manager": "6.11.0", + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/typescript-estree": "6.11.0", "semver": "^7.5.4" }, "engines": { @@ -3359,12 +3371,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", - "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz", + "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/types": "6.11.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3881,9 +3893,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -4298,9 +4310,9 @@ } }, "node_modules/bullmq": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.13.2.tgz", - "integrity": "sha512-JhGfRk2ddBlZMWhQeg7vgYjfKKVsAbbEs9SWu5EMMOHIPrlJ+ZEScLDVz0Yl/N+3VP9mumCZmN7zfDzctSvquw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.14.0.tgz", + "integrity": "sha512-2IjTzXfTkXQ+WNRy2/CVupnHJtqp6JpxacIvYbru2EvporUALnIcpiSpjJbk4V6kAbsYvrV2wRdUKllb+LfssQ==", "dependencies": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -5725,15 +5737,15 @@ } }, "node_modules/eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/js": "8.54.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -11894,9 +11906,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", - "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -13608,9 +13620,9 @@ } }, "@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true }, "@golevelup/nestjs-discovery": { @@ -14236,9 +14248,9 @@ } }, "@nestjs/common": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.8.tgz", - "integrity": "sha512-rmpwcdvq2IWMmsUVP8rsdKub6uDWk7dwCYo0aif50JTwcvcxzaP3iKVFKoSgvp0RKYu8h15+/AEOfaInmPpl0Q==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.9.tgz", + "integrity": "sha512-i7vb2zMLJUDIPqjfBhMkgIITK1AnKDkFYSsM+aaRHpNa9xv/CwsiQuINaXfzStMpnwjkq5FDE3aoF0wkTfD2cQ==", "requires": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -14264,9 +14276,9 @@ } }, "@nestjs/core": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.8.tgz", - "integrity": "sha512-9+MZ2s8ixfY9Bl/M9ofChiyYymcwdK9ZWNH4GDMF7Am7XRAQ1oqde6MYGG05rhQwiVXuTwaYLlXciJKfsrg5qg==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.2.9.tgz", + "integrity": "sha512-Hl6HC9hR7JD3YmzwcveBKeydaq9cguEsMdEghzLuVH3VEH0M+bTFHjCIKhsxMez4/O7/K6n3EhNx1Et4Z+BqWg==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -14290,9 +14302,9 @@ "requires": {} }, "@nestjs/platform-express": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.8.tgz", - "integrity": "sha512-WoSSVtwIRc5AdGMHWVzWZK4JZLT0f4o2xW8P9gQvcX+omL8W1kXCfY8GQYXNBG84XmBNYH8r0FtC8oMe/lH5NQ==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.9.tgz", + "integrity": "sha512-r6BSMJmLLeNgyPZJ9F8wQWCXH6rrMHMd9QbCfvyUmETci5Ofy6atiYVVXl7Ms1rAi2EEnXpVCuoydHBBqSlTbg==", "requires": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -14309,9 +14321,9 @@ } }, "@nestjs/platform-socket.io": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.8.tgz", - "integrity": "sha512-P/Olw9alAaKD7Q1vS/ol7K81x1l7Bmi+AXthBNUPGMmG/W8kxO1krerW4rEhtF3BKJ0qJIa5bhDlb80p4lZcNA==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.9.tgz", + "integrity": "sha512-xuXsUWUtgzdRnNBSWZADQv0sShBOsyHK7iEwwIpFMerqbthSMwjsyUv0s3hDoPEnS6Nf4MMf2KReD5JBAnMBFQ==", "requires": { "socket.io": "4.7.2", "tslib": "2.6.2" @@ -14354,9 +14366,9 @@ } }, "@nestjs/swagger": { - "version": "7.1.15", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.15.tgz", - "integrity": "sha512-ZaAO90R9MQXk4iLQLijIH6jrsllkUSYoh0Su6DECGgu8Y4Q/9LfdESwsZ9nmzr/48aLOu+wrv+cdI5Wr6fLKJw==", + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.16.tgz", + "integrity": "sha512-f9KBk/BX9MUKPTj7tQNYJ124wV/jP5W2lwWHLGwe/4qQXixuDOo39zP55HIJ44LE7S04B7BOeUOo9GBJD/vRcw==", "requires": { "@nestjs/mapped-types": "2.0.3", "js-yaml": "4.1.0", @@ -14366,9 +14378,9 @@ } }, "@nestjs/testing": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz", - "integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.9.tgz", + "integrity": "sha512-E+66R27Op+WAQHHH6RnUsz7QpKApl4Bn42nheCAGvS/sxbaDJ8RKtm4stE4Iz2aioPCUvRi8j4z8Ze73k0CcGQ==", "dev": true, "requires": { "tslib": "2.6.2" @@ -14383,17 +14395,24 @@ } }, "@nestjs/typeorm": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.0.tgz", - "integrity": "sha512-WQU4HCDTz4UavsFzvGUKDHqi0MO5K47yFoPXdmh+Z/hCNO7SHCMmV9jLiLukM8n5nKUqJ3jDqiljkWBcZPdCtA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.1.tgz", + "integrity": "sha512-YVFYL7D25VAVp5/G+KLXIgsRfYomA+VaFZBpm2rtwrrBOmkXNrxr7kuI2bBBO/Xy4kKBDe6wbvIVVFeEA7/ngA==", "requires": { - "uuid": "9.0.0" + "uuid": "9.0.1" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } } }, "@nestjs/websockets": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.8.tgz", - "integrity": "sha512-oZN1VJFApN7d2eftr65a36QrV0IJNGba4znqyjFnyGvtDWTDcQwzDcnEfvJBTTYhOSBNS7KDfVhne0ythkl6tg==", + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.9.tgz", + "integrity": "sha512-Hp/ioNMcBtCzkubgcDHeA5KH4YLBL1jHfMEacLctKDa20Kn/LFXhJhXVWEr4jEzUKXbD+Q+GyYk1V/1rJi6eHA==", "requires": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -15170,9 +15189,9 @@ "dev": true }, "@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", "requires": { "undici-types": "~5.26.4" } @@ -15314,16 +15333,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz", - "integrity": "sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz", + "integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/type-utils": "6.10.0", - "@typescript-eslint/utils": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", + "@typescript-eslint/scope-manager": "6.11.0", + "@typescript-eslint/type-utils": "6.11.0", + "@typescript-eslint/utils": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -15333,54 +15352,54 @@ } }, "@typescript-eslint/parser": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", - "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz", + "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/typescript-estree": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", + "@typescript-eslint/scope-manager": "6.11.0", + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/typescript-estree": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", - "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz", + "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==", "dev": true, "requires": { - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0" + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0" } }, "@typescript-eslint/type-utils": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz", - "integrity": "sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz", + "integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.10.0", - "@typescript-eslint/utils": "6.10.0", + "@typescript-eslint/typescript-estree": "6.11.0", + "@typescript-eslint/utils": "6.11.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", - "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz", + "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", - "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz", + "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==", "dev": true, "requires": { - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/visitor-keys": "6.10.0", + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/visitor-keys": "6.11.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -15389,27 +15408,27 @@ } }, "@typescript-eslint/utils": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz", - "integrity": "sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz", + "integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.10.0", - "@typescript-eslint/types": "6.10.0", - "@typescript-eslint/typescript-estree": "6.10.0", + "@typescript-eslint/scope-manager": "6.11.0", + "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/typescript-estree": "6.11.0", "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", - "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz", + "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==", "dev": true, "requires": { - "@typescript-eslint/types": "6.10.0", + "@typescript-eslint/types": "6.11.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -15841,9 +15860,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -16143,9 +16162,9 @@ "optional": true }, "bullmq": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.13.2.tgz", - "integrity": "sha512-JhGfRk2ddBlZMWhQeg7vgYjfKKVsAbbEs9SWu5EMMOHIPrlJ+ZEScLDVz0Yl/N+3VP9mumCZmN7zfDzctSvquw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.14.0.tgz", + "integrity": "sha512-2IjTzXfTkXQ+WNRy2/CVupnHJtqp6JpxacIvYbru2EvporUALnIcpiSpjJbk4V6kAbsYvrV2wRdUKllb+LfssQ==", "requires": { "cron-parser": "^4.6.0", "glob": "^8.0.3", @@ -17172,15 +17191,15 @@ "dev": true }, "eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/js": "8.54.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -21766,9 +21785,9 @@ } }, "ts-loader": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", - "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "requires": { "chalk": "^4.1.0", From 6d310d629700a6a87c5ed9fcc0928c0a8ff532e7 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Sat, 18 Nov 2023 21:32:28 -0500 Subject: [PATCH 84/88] fix(mobile): Mark more strings for translation (#5132) * fix(mobile): Mark more strings for translation Moving more strings to the `i18n` JSON file, and also including their es-US translations. * Add more translatable strings --- mobile/assets/i18n/en-US.json | 53 +++++++++++++++++- mobile/assets/i18n/es-US.json | 56 ++++++++++++++++++- .../album/views/album_options_part.dart | 12 ++-- mobile/lib/modules/login/ui/login_form.dart | 2 +- .../modules/map/ui/map_page_bottom_sheet.dart | 2 +- .../modules/map/ui/map_settings_dialog.dart | 21 ++++--- .../modules/search/ui/curated_places_row.dart | 11 ++-- .../search/ui/person_name_edit_form.dart | 17 +++--- .../search/views/person_result_page.dart | 13 +++-- .../lib/modules/search/views/search_page.dart | 2 +- .../advanced_settings/advanced_settings.dart | 9 ++- .../local_storage_settings.dart | 8 +-- .../views/shared_link_edit_page.dart | 35 ++++++------ .../providers/server_info.provider.dart | 12 ++-- 14 files changed, 181 insertions(+), 72 deletions(-) diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 286227979..8233fcd7f 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -1,6 +1,7 @@ { "add_to_album_bottom_sheet_added": "Added to {album}", "add_to_album_bottom_sheet_already_exists": "Already in {album}", + "advanced_settings_log_level_title": "Log level: {}", "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.", "advanced_settings_prefer_remote_title": "Prefer remote images", "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", @@ -106,6 +107,9 @@ "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)", "cache_settings_clear_cache_button": "Clear cache", "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", + "cache_settings_duplicated_assets_clear_button": "CLEAR", + "cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app", + "cache_settings_duplicated_assets_title": "Duplicated Assets ({})", "cache_settings_image_cache_size": "Image cache size ({} assets)", "cache_settings_statistics_album": "Library thumbnails", "cache_settings_statistics_assets": "{} assets ({})", @@ -195,6 +199,7 @@ "library_page_sort_title": "Album title", "login_disabled": "Login has been disabled", "login_form_api_exception": "API exception. Please check the server URL and try again.", + "login_form_back_button_text": "Back", "login_form_button_text": "Login", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port/api", @@ -217,6 +222,10 @@ "login_form_server_error": "Could not connect to server.", "login_password_changed_error": "There was an error updating your password", "login_password_changed_success": "Password updated successfully", + "map_assets_in_bounds": { + "one": "{} photo", + "many": "{} photos" + }, "map_cannot_get_user_location": "Cannot get user's location", "map_location_dialog_cancel": "Cancel", "map_location_dialog_yes": "Yes", @@ -226,6 +235,15 @@ "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", "map_no_location_permission_title": "Location Permission denied", "map_settings_dark_mode": "Dark mode", + "map_settings_date_range_option_all": "All", + "map_settings_date_range_option_days": { + "one": "Past 24 hours", + "other": "Past {} days" + }, + "map_settings_date_range_option_years": { + "one": "Past year", + "other": "Past {} years" + }, "map_settings_dialog_cancel": "Cancel", "map_settings_dialog_save": "Save", "map_settings_dialog_title": "Map Settings", @@ -261,9 +279,13 @@ "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "profile_drawer_app_logs": "Logs", + "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", + "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", "profile_drawer_documentation": "Documentation", "profile_drawer_github": "GitHub", + "profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.", + "profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.", "profile_drawer_settings": "Settings", "profile_drawer_sign_out": "Sign Out", "profile_drawer_trash": "Trash", @@ -275,6 +297,13 @@ "search_page_no_objects": "No Objects Info Available", "search_page_no_places": "No Places Info Available", "search_page_people": "People", + "search_page_person_add_name_dialog_cancel": "Cancel", + "search_page_person_add_name_dialog_save": "Save", + "search_page_person_add_name_dialog_hint": "Name", + "search_page_person_add_name_dialog_title": "Add a name", + "search_page_person_add_name_subtitle": "Find them fast by name with search", + "search_page_person_add_name_title": "Add a name", + "search_page_person_edit_name": "Edit name", "search_page_places": "Places", "search_page_recently_added": "Recently added", "search_page_screenshots": "Screenshots", @@ -283,6 +312,7 @@ "search_page_videos": "Videos", "search_page_view_all_button": "View all", "search_page_your_activity": "Your activity", + "search_page_your_map": "Your Map", "search_result_page_new_search_hint": "New Search", "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", "search_suggestion_list_smart_search_hint_2": "m:your-search-term", @@ -322,9 +352,17 @@ "shared_album_activity_remove_title": "Delete Activity", "shared_album_activity_setting_subtitle": "Let others respond", "shared_album_activity_setting_title": "Comments & likes", + "shared_album_section_people_action_error": "Error leaving/removing from album", + "shared_album_section_people_action_leave": "Remove user from album", + "shared_album_section_people_action_remove_user": "Remove user from album", + "shared_album_section_people_owner_label": "Owner", + "shared_album_section_people_title": "PEOPLE", "share_dialog_preparing": "Preparing...", "shared_link_app_bar_title": "Shared Links", + "shared_link_clipboard_copied_massage": "Copied to clipboard", + "shared_link_clipboard_text": "Link: {}\nPassword: {}", "shared_link_create_app_bar_title": "Create link to share", + "shared_link_create_error": "Error while creating shared link", "shared_link_create_info": "Let anyone with the link see the selected photo(s)", "shared_link_create_submit_button": "Create link", "shared_link_edit_allow_download": "Allow public user to download", @@ -334,6 +372,19 @@ "shared_link_edit_description": "Description", "shared_link_edit_description_hint": "Enter the share description", "shared_link_edit_expire_after": "Expire after", + "shared_link_edit_expire_after_option_days": { + "one": "{} day", + "other": "{} days" + }, + "shared_link_edit_expire_after_option_hours": { + "one": "{} hour", + "other": "{} hours" + }, + "shared_link_edit_expire_after_option_minutes": { + "one": "{} minute", + "other": "{} minutes" + }, + "shared_link_edit_expire_after_option_never": "Never", "shared_link_edit_password": "Password", "shared_link_edit_password_hint": "Enter the share password", "shared_link_edit_show_meta": "Show metadata", @@ -388,4 +439,4 @@ "viewer_remove_from_stack": "Remove from Stack", "viewer_stack_use_as_main_asset": "Use as Main Asset", "viewer_unstack": "Un-Stack" -} \ No newline at end of file +} diff --git a/mobile/assets/i18n/es-US.json b/mobile/assets/i18n/es-US.json index a932a6f88..24ea49a9d 100644 --- a/mobile/assets/i18n/es-US.json +++ b/mobile/assets/i18n/es-US.json @@ -1,6 +1,7 @@ { "add_to_album_bottom_sheet_added": "Agregado a {album}", "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", + "advanced_settings_log_level_title": "Nivel de registro: {}", "advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas de recursos encontrados en el dispositivo. Activa esta opción para cargar imágenes remotas en su lugar.", "advanced_settings_prefer_remote_title": "Preferir imágenes remotas", "advanced_settings_self_signed_ssl_subtitle": "Omite la verificación del certificado SSL para la URL del servidor. Requerido para certificados autofirmados.", @@ -106,6 +107,9 @@ "cache_settings_album_thumbnails": "Miniaturas de la página de la biblioteca ({} recursos)", "cache_settings_clear_cache_button": "Borrar caché", "cache_settings_clear_cache_button_title": "Borra la caché de la aplicación. Esto afectará significativamente el rendimiento de la aplicación hasta que se reconstruya la caché.", + "cache_settings_duplicated_assets_clear_button": "BORRAR", + "cache_settings_duplicated_assets_subtitle": "Fotos y videos que son ignorados por la aplicación", + "cache_settings_duplicated_assets_title": "Recursos duplicados ({})", "cache_settings_image_cache_size": "Tamaño de la caché de imágenes ({} recursos)", "cache_settings_statistics_album": "Miniaturas de la biblioteca", "cache_settings_statistics_assets": "{} recursos ({})", @@ -174,6 +178,7 @@ "home_page_building_timeline": "Construyendo la línea de tiempo", "home_page_favorite_err_local": "Aún no se pueden marcar recursos locales como favoritos, omitiendo", "home_page_first_time_notice": "Si ésta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.", + "home_page_share_err_local": "No se pueden compartir recursos locales a través de enlaces, omitiendo", "home_page_upload_err_limit": "Sólo se pueden subir un máximo de 30 recursos a la vez, omitiendo", "home_page_favorite_err_partner": "Aún no se pueden marcar recursos de compañeros como favoritos, omitiendo", "home_page_album_err_partner": "Aún no se pueden agregar recursos de compañeros a un álbum, omitiendo", @@ -194,6 +199,7 @@ "library_page_sort_title": "Título del álbum", "login_disabled": "El inicio de sesión ha sido deshabilitado", "login_form_api_exception": "Excepción de API. Por favor, verifica la URL del servidor e inténtalo de nuevo.", + "login_form_back_button_text": "Volver", "login_form_button_text": "Iniciar sesión", "login_form_email_hint": "tucorreo@correo.com", "login_form_endpoint_hint": "http://ip-de-tu-servidor:puerto/api", @@ -216,6 +222,10 @@ "login_form_server_error": "No se pudo conectar al servidor.", "login_password_changed_error": "Hubo un error al actualizar tu contraseña", "login_password_changed_success": "Contraseña actualizada exitosamente", + "map_assets_in_bounds": { + "one": "{} foto", + "other": "{} fotos" + }, "map_cannot_get_user_location": "No se puede obtener la ubicación del usuario", "map_location_dialog_cancel": "Cancelar", "map_location_dialog_yes": "Sí", @@ -225,6 +235,15 @@ "map_no_location_permission_content": "Se necesita permiso de ubicación para mostrar recursos desde tu ubicación actual. ¿Quieres permitirlo ahora?", "map_no_location_permission_title": "Permiso de ubicación denegado", "map_settings_dark_mode": "Modo oscuro", + "map_settings_date_range_option_all": "Todo", + "map_settings_date_range_option_days": { + "one": "Últimas 24 horas", + "other": "Últimos {} días" + }, + "map_settings_date_range_option_years": { + "one": "Último año", + "other": "Últimos {} años" + }, "map_settings_dialog_cancel": "Cancelar", "map_settings_dialog_save": "Guardar", "map_settings_dialog_title": "Configuración del mapa", @@ -249,6 +268,7 @@ "partner_page_stop_sharing_content": "{} ya no podrá acceder a tus fotos", "partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?", "partner_page_title": "Compañero", + "permission_onboarding_back": "Volver", "permission_onboarding_continue_anyway": "Continuar de todos modos", "permission_onboarding_get_started": "Empezar", "permission_onboarding_go_to_settings": "Ir a configuración", @@ -259,9 +279,13 @@ "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich haga copia de seguridad y gestione toda tu colección de galería, concede permisos de fotos y videos en Configuración.", "permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.", "profile_drawer_app_logs": "Registros", + "profile_drawer_client_out_of_date_major": "La aplicación móvil está desactualizada. Actualiza a la última versión mayor.", + "profile_drawer_client_out_of_date_minor": "La aplicación móvil está desactualizada. Actualiza a la última versión menor.", "profile_drawer_client_server_up_to_date": "El cliente y el servidor están actualizados", "profile_drawer_documentation": "Documentación", "profile_drawer_github": "GitHub", + "profile_drawer_server_out_of_date_major": "El servidor está desactualizado. Actualiza a la última versión mayor.", + "profile_drawer_server_out_of_date_minor": "El servidor está desactualizado. Actualiza a la última versión menor.", "profile_drawer_settings": "Configuración", "profile_drawer_sign_out": "Cerrar sesión", "profile_drawer_trash": "Papelera", @@ -269,10 +293,17 @@ "search_bar_hint": "Busca tus fotos", "search_page_categories": "Categorías", "search_page_favorites": "Favoritos", - "search_page_motion_photos": "Fotos en .ovimiento", + "search_page_motion_photos": "Fotos en movimiento", "search_page_no_objects": "No hay información de objetos disponible", "search_page_no_places": "No hay información de lugares disponible", "search_page_people": "Personas", + "search_page_person_add_name_dialog_cancel": "Cancelar", + "search_page_person_add_name_dialog_hint": "Nombre", + "search_page_person_add_name_dialog_save": "Guardar", + "search_page_person_add_name_dialog_title": "Agregar nombre", + "search_page_person_add_name_subtitle": "Encuéntralos rápidamente por nombre", + "search_page_person_add_name_title": "Agregar un nombre", + "search_page_person_edit_name": "Editar nombre", "search_page_places": "Lugares", "search_page_recently_added": "Recién agregados", "search_page_screenshots": "Capturas de pantalla", @@ -281,6 +312,7 @@ "search_page_videos": "Videos", "search_page_view_all_button": "Ver todo", "search_page_your_activity": "Tu actividad", + "search_page_your_map": "Tu mapa", "search_result_page_new_search_hint": "Nueva búsqueda", "search_suggestion_list_smart_search_hint_1": "La búsqueda inteligente está habilitada por defecto, para buscar metadatos utiliza la sintaxis ", "search_suggestion_list_smart_search_hint_2": "m:tu-término-de-búsqueda", @@ -290,6 +322,7 @@ "server_info_box_app_version": "Versión de la Aplicación", "server_info_box_server_url": "URL del Servidor", "server_info_box_server_version": "Versión del Servidor", + "server_info_box_latest_release": "Última versión", "setting_image_viewer_help": "El visor de detalles carga primero la miniatura pequeña, luego carga la vista previa de tamaño mediano (si está habilitada), finalmente carga la original (si está habilitada).", "setting_image_viewer_original_subtitle": "Activar para cargar la imagen en resolución original (¡muy grande!). Deshabilitar para reducir el consumo de datos (de red y caché).", "setting_image_viewer_original_title": "Cargar imagen original", @@ -319,9 +352,17 @@ "shared_album_activity_remove_title": "Eliminar actividad", "shared_album_activity_setting_subtitle": "Permitir que otros respondan", "shared_album_activity_setting_title": "Comentarios y me gusta", + "shared_album_section_people_action_error": "Error al dejar/remover del álbum", + "shared_album_section_people_action_leave": "Dejar álbum", + "shared_album_section_people_action_remove_user": "Remover usuario del álbum", + "shared_album_section_people_owner_label": "Dueño", + "shared_album_section_people_title": "PERSONAS", "share_dialog_preparing": "Preparando...", "shared_link_app_bar_title": "Enlaces compartidos", + "shared_link_clipboard_copied_massage": "Copiado al portapapeles", + "shared_link_clipboard_text": "Enlace: {}\nContraseña: {}", "shared_link_create_app_bar_title": "Crear enlace para compartir", + "shared_link_create_error": "Error al crear enlace compartido", "shared_link_create_info": "Permitir que cualquiera con el enlace vea la(s) foto(s) seleccionada(s)", "shared_link_create_submit_button": "Crear enlace", "shared_link_edit_allow_download": "Permitir que el usuario público pueda descargar", @@ -331,6 +372,19 @@ "shared_link_edit_description": "Descripción", "shared_link_edit_description_hint": "Introduce la descripción del enlace", "shared_link_edit_expire_after": "Expirar después de", + "shared_link_edit_expire_after_option_days": { + "one": "{} día", + "other": "{} días" + }, + "shared_link_edit_expire_after_option_hours": { + "one": "{} hora", + "other": "{} horas" + }, + "shared_link_edit_expire_after_option_minutes": { + "one": "{} minuto", + "other": "{} minutos" + }, + "shared_link_edit_expire_after_option_never": "Nunca", "shared_link_edit_password": "Contraseña", "shared_link_edit_password_hint": "Introduce la contraseña del enlace", "shared_link_edit_show_meta": "Mostrar metadatos", diff --git a/mobile/lib/modules/album/views/album_options_part.dart b/mobile/lib/modules/album/views/album_options_part.dart index 5e5a3a6b8..cac70df75 100644 --- a/mobile/lib/modules/album/views/album_options_part.dart +++ b/mobile/lib/modules/album/views/album_options_part.dart @@ -30,7 +30,7 @@ class AlbumOptionsPage extends HookConsumerWidget { Navigator.pop(context); ImmichToast.show( context: context, - msg: "Error leaving/removing from album", + msg: "shared_album_section_people_action_error".tr(), toastType: ToastType.error, gravity: ToastGravity.BOTTOM, ); @@ -81,7 +81,7 @@ class AlbumOptionsPage extends HookConsumerWidget { actions = [ ListTile( leading: const Icon(Icons.exit_to_app_rounded), - title: const Text("Leave album"), + title: const Text("shared_album_section_people_action_leave").tr(), onTap: leaveAlbum, ), ]; @@ -91,7 +91,7 @@ class AlbumOptionsPage extends HookConsumerWidget { actions = [ ListTile( leading: const Icon(Icons.person_remove_rounded), - title: const Text("Remove user from album"), + title: const Text("shared_album_section_people_remove_user").tr(), onTap: () => removeUserFromAlbum(user), ), ]; @@ -130,11 +130,11 @@ class AlbumOptionsPage extends HookConsumerWidget { style: TextStyle(color: Colors.grey[500]), ), trailing: const Text( - "Owner", + "shared_album_section_people_owner_label", style: TextStyle( fontWeight: FontWeight.bold, ), - ), + ).tr(), ); } @@ -215,7 +215,7 @@ class AlbumOptionsPage extends HookConsumerWidget { subtitle: const Text("shared_album_activity_setting_subtitle").tr(), ), - buildSectionTitle("PEOPLE"), + buildSectionTitle("shared_album_section_people_title".tr()), buildOwnerInfo(), buildSharedUsersList(), ], diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart index 5cdcbe034..835907d05 100644 --- a/mobile/lib/modules/login/ui/login_form.dart +++ b/mobile/lib/modules/login/ui/login_form.dart @@ -358,7 +358,7 @@ class LoginForm extends HookConsumerWidget { TextButton.icon( icon: const Icon(Icons.arrow_back), onPressed: () => serverEndpoint.value = null, - label: const Text('Back'), + label: const Text('login_form_back_button_text').tr(), ), ], ), diff --git a/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart b/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart index 0d0dfb412..20d4654b6 100644 --- a/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart +++ b/mobile/lib/modules/map/ui/map_page_bottom_sheet.dart @@ -176,7 +176,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState { Widget buildDragHandle(ScrollController scrollController) { final textToDisplay = assetsInBound.value.isNotEmpty - ? "${assetsInBound.value.length} photo${assetsInBound.value.length > 1 ? "s" : ""}" + ? "map_assets_in_bounds".plural(assetsInBound.value.length) : "map_no_assets_in_bounds".tr(); final dragHandle = Container( height: 60, diff --git a/mobile/lib/modules/map/ui/map_settings_dialog.dart b/mobile/lib/modules/map/ui/map_settings_dialog.dart index b7503cd52..1d1e8b4c0 100644 --- a/mobile/lib/modules/map/ui/map_settings_dialog.dart +++ b/mobile/lib/modules/map/ui/map_settings_dialog.dart @@ -76,18 +76,21 @@ class MapSettingsDialog extends HookConsumerWidget { showRelativeDate.value = value!; }, dropdownMenuEntries: [ - const DropdownMenuEntry(value: 0, label: "All"), - const DropdownMenuEntry( + DropdownMenuEntry( + value: 0, + label: "map_settings_date_range_option_all".tr(), + ), + DropdownMenuEntry( value: 1, - label: "Past 24 hours", + label: "map_settings_date_range_option_days".plural(1), ), - const DropdownMenuEntry( + DropdownMenuEntry( value: 7, - label: "Past 7 days", + label: "map_settings_date_range_option_days".plural(7), ), - const DropdownMenuEntry( + DropdownMenuEntry( value: 30, - label: "Past 30 days", + label: "map_settings_date_range_option_days".plural(30), ), DropdownMenuEntry( value: now @@ -102,7 +105,7 @@ class MapSettingsDialog extends HookConsumerWidget { ), ) .inDays, - label: "Past year", + label: "map_settings_date_range_option_years".plural(1), ), DropdownMenuEntry( value: now @@ -117,7 +120,7 @@ class MapSettingsDialog extends HookConsumerWidget { ), ) .inDays, - label: "Past 3 years", + label: "map_settings_date_range_option_years".plural(3), ), ], ); diff --git a/mobile/lib/modules/search/ui/curated_places_row.dart b/mobile/lib/modules/search/ui/curated_places_row.dart index 9c6324daa..b0343f5ed 100644 --- a/mobile/lib/modules/search/ui/curated_places_row.dart +++ b/mobile/lib/modules/search/ui/curated_places_row.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart'; @@ -64,18 +65,18 @@ class CuratedPlacesRow extends CuratedRow { ), ), ), - const Align( + Align( alignment: Alignment.bottomCenter, child: Padding( - padding: EdgeInsets.only(bottom: 10), - child: Text( - "Your Map", + padding: const EdgeInsets.only(bottom: 10), + child: const Text( + "search_page_your_map", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), - ), + ).tr(), ), ), ], diff --git a/mobile/lib/modules/search/ui/person_name_edit_form.dart b/mobile/lib/modules/search/ui/person_name_edit_form.dart index 0dbb03825..6e50131f9 100644 --- a/mobile/lib/modules/search/ui/person_name_edit_form.dart +++ b/mobile/lib/modules/search/ui/person_name_edit_form.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -27,15 +28,15 @@ class PersonNameEditForm extends HookConsumerWidget { return AlertDialog( title: const Text( - "Add a name", + "search_page_person_add_name_dialog_title", style: TextStyle(fontWeight: FontWeight.bold), - ), + ).tr(), content: SingleChildScrollView( child: TextFormField( controller: controller, autofocus: true, - decoration: const InputDecoration( - hintText: 'Name', + decoration: InputDecoration( + hintText: 'search_page_person_add_name_dialog_hint'.tr(), ), ), ), @@ -49,12 +50,12 @@ class PersonNameEditForm extends HookConsumerWidget { ); }, child: Text( - "Cancel", + "search_page_person_add_name_dialog_cancel", style: TextStyle( color: Colors.red[300], fontWeight: FontWeight.bold, ), - ), + ).tr(), ), TextButton( onPressed: () { @@ -70,12 +71,12 @@ class PersonNameEditForm extends HookConsumerWidget { ); }, child: Text( - "Save", + "search_page_person_add_name_dialog_save", style: TextStyle( color: context.primaryColor, fontWeight: FontWeight.bold, ), - ), + ).tr(), ), ], ); diff --git a/mobile/lib/modules/search/views/person_result_page.dart b/mobile/lib/modules/search/views/person_result_page.dart index bec5e4f1b..2e8637bc7 100644 --- a/mobile/lib/modules/search/views/person_result_page.dart +++ b/mobile/lib/modules/search/views/person_result_page.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -52,9 +53,9 @@ class PersonResultPage extends HookConsumerWidget { ListTile( leading: const Icon(Icons.edit_outlined), title: const Text( - 'Edit name', + 'search_page_person_edit_name', style: TextStyle(fontWeight: FontWeight.bold), - ), + ).tr(), onTap: showEditNameDialog, ), ], @@ -72,15 +73,15 @@ class PersonResultPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Add a name', + 'search_page_person_add_name_title', style: context.textTheme.titleSmall?.copyWith( color: context.themeData.colorScheme.secondary, ), - ), + ).tr(), Text( - 'Find them fast by name with search', + 'search_page_person_add_name_subtitle', style: context.textTheme.labelSmall, - ), + ).tr(), ], ), ); diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart index c06db53a3..562e42c32 100644 --- a/mobile/lib/modules/search/views/search_page.dart +++ b/mobile/lib/modules/search/views/search_page.dart @@ -198,7 +198,7 @@ class SearchPage extends HookConsumerWidget { ).tr(), ), ListTile( - title: Text('Screenshots', style: categoryTitleStyle).tr(), + title: Text('search_page_screenshots', style: categoryTitleStyle).tr(), leading: Icon( Icons.screenshot, color: categoryIconColor, diff --git a/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart b/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart index 83bf4bb94..e07c30dae 100644 --- a/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart +++ b/mobile/lib/modules/settings/ui/advanced_settings/advanced_settings.dart @@ -68,11 +68,10 @@ class AdvancedSettings extends HookConsumerWidget { ), ListTile( dense: true, - title: Text( - // Not translated because the levels are only English - "Log level: $logLevel", - style: const TextStyle(fontWeight: FontWeight.bold), - ), + title: const Text( + "advanced_settings_log_level_title", + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(args: [logLevel]), subtitle: Slider( value: levelId.value.toDouble(), onChanged: (double v) => levelId.value = v.toInt(), diff --git a/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart b/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart index cd753de31..df1bcbdf7 100644 --- a/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart +++ b/mobile/lib/modules/settings/ui/local_storage_settings/local_storage_settings.dart @@ -42,12 +42,12 @@ class LocalStorageSettings extends HookConsumerWidget { children: [ ListTile( title: Text( - "Duplicated Assets (${cacheItemCount.value})", + "cache_settings_duplicated_assets_title", style: context.textTheme.labelLarge ?.copyWith(fontWeight: FontWeight.bold), - ).tr(), + ).tr(args: ["${cacheItemCount.value}"]), subtitle: const Text( - "Photos and videos that are black listed by the app", + "cache_settings_duplicated_assets_subtitle", style: TextStyle( fontSize: 13, ), @@ -55,7 +55,7 @@ class LocalStorageSettings extends HookConsumerWidget { trailing: TextButton( onPressed: cacheItemCount.value > 0 ? clearCache : null, child: Text( - "CLEAR", + "cache_settings_duplicated_assets_clear_button", style: TextStyle( fontSize: 12, color: cacheItemCount.value > 0 ? Colors.red : Colors.grey, diff --git a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart index 71f2648c0..df14ba3d9 100644 --- a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart +++ b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart @@ -230,31 +230,34 @@ class SharedLinkEditPage extends HookConsumerWidget { borderSide: BorderSide(color: Colors.grey), ), ), - dropdownMenuEntries: const [ - DropdownMenuEntry(value: 0, label: "Never"), + dropdownMenuEntries: [ + DropdownMenuEntry( + value: 0, + label: "shared_link_edit_expire_after_option_never".tr(), + ), DropdownMenuEntry( value: 30, - label: '30 minutes', + label: "shared_link_edit_expire_after_option_minutes".plural(30), ), DropdownMenuEntry( value: 60, - label: '1 hour', + label: "shared_link_edit_expire_after_option_hours".plural(1), ), DropdownMenuEntry( value: 60 * 6, - label: '6 hours', + label: "shared_link_edit_expire_after_option_hours".plural(6), ), DropdownMenuEntry( value: 60 * 24, - label: '1 day', + label: "shared_link_edit_expire_after_option_days".plural(1), ), DropdownMenuEntry( value: 60 * 24 * 7, - label: '7 days', + label: "shared_link_edit_expire_after_option_days".plural(7), ), DropdownMenuEntry( value: 60 * 24 * 30, - label: '30 days', + label: "shared_link_edit_expire_after_option_days".plural(30), ), ], ); @@ -265,15 +268,15 @@ class SharedLinkEditPage extends HookConsumerWidget { ClipboardData( text: passwordController.text.isEmpty ? newShareLink.value - : "Link: ${newShareLink.value}\nPassword: ${passwordController.text}", + : "shared_link_clipboard_text".tr( + args: [newShareLink.value, passwordController.text], + ), ), ).then((_) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - "Copied to clipboard", - ), - duration: Duration(seconds: 2), + SnackBar( + content: const Text("shared_link_clipboard_copied_massage").tr(), + duration: const Duration(seconds: 2), ), ); }); @@ -310,7 +313,7 @@ class SharedLinkEditPage extends HookConsumerWidget { context.autoPop(); }, child: const Text( - "Done", + "share_done", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, @@ -353,7 +356,7 @@ class SharedLinkEditPage extends HookConsumerWidget { context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: 'Error while creating shared link', + msg: 'shared_link_create_error'.tr(), ); } } diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index a012379ef..b7389824b 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -74,8 +74,7 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["major"]! > serverVersion.major) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: - "Server is out of date. Please update to the latest major version.", + versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(), ); return; } @@ -83,8 +82,7 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["major"]! < serverVersion.major) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: - "Mobile App is out of date. Please update to the latest major version.", + versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(), ); return; } @@ -92,8 +90,7 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["minor"]! > serverVersion.minor) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: - "Server is out of date. Please update to the latest minor version.", + versionMismatchErrorMessage: "profile_drawer_server_out_of_date_minor".tr(), ); return; } @@ -101,8 +98,7 @@ class ServerInfoNotifier extends StateNotifier { if (appVersion["minor"]! < serverVersion.minor) { state = state.copyWith( isVersionMismatch: true, - versionMismatchErrorMessage: - "Mobile App is out of date. Please update to the latest minor version.", + versionMismatchErrorMessage: "profile_drawer_client_out_of_date_minor".tr(), ); return; } From fce8d48de6672f3fea571cfa78df06deb7b0e421 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Sun, 19 Nov 2023 05:13:38 +0000 Subject: [PATCH 85/88] fix(mobile): use proper context for popping out from share (#5138) * fix(mobile): use proper context for popping out from share * mobile: use proper context for popping --------- Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- mobile/lib/modules/album/ui/album_viewer_appbar.dart | 2 +- .../providers/image_viewer_page_state.provider.dart | 2 +- mobile/lib/utils/selection_handlers.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/lib/modules/album/ui/album_viewer_appbar.dart b/mobile/lib/modules/album/ui/album_viewer_appbar.dart index 1ac64e957..cd0bd2ba6 100644 --- a/mobile/lib/modules/album/ui/album_viewer_appbar.dart +++ b/mobile/lib/modules/album/ui/album_viewer_appbar.dart @@ -188,7 +188,7 @@ class AlbumViewerAppbar extends HookConsumerWidget gravity: ToastGravity.BOTTOM, ); } - context.pop(); + buildContext.pop(); }, ); return const ShareDialog(); diff --git a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart index b7f6ee095..06cc128f7 100644 --- a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart +++ b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart @@ -68,7 +68,7 @@ class ImageViewerStateNotifier extends StateNotifier { gravity: ToastGravity.BOTTOM, ); } - context.pop(); + buildContext.pop(); }, ); return const ShareDialog(); diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart index 5d13d20a2..d52f3ac1d 100644 --- a/mobile/lib/utils/selection_handlers.dart +++ b/mobile/lib/utils/selection_handlers.dart @@ -27,7 +27,7 @@ void handleShareAssets( gravity: ToastGravity.BOTTOM, ); } - context.pop(); + buildContext.pop(); }, ); return const ShareDialog(); From fa71641ea403cd175d6c02aa27ff65b51c7415d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 18 Nov 2023 23:16:52 -0600 Subject: [PATCH 86/88] chore(deps): update redis:6.2-alpine docker digest to 80cc851 (#5131) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.dev.yml | 2 +- docker/docker-compose.prod.yml | 2 +- docker/docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 646d6e9b6..f98861673 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -108,7 +108,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:3995fe6ea6a619313e31046bd3c8643f9e70f8f2b294ff82659d409b47d06abb + image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5 database: container_name: immich_postgres diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index ad764250c..eb1368add 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -65,7 +65,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:3995fe6ea6a619313e31046bd3c8643f9e70f8f2b294ff82659d409b47d06abb + image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5 restart: always database: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index dc5269036..16ca3a442 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -69,7 +69,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:3995fe6ea6a619313e31046bd3c8643f9e70f8f2b294ff82659d409b47d06abb + image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5 restart: always database: From 41d43acf5faaff9f740af8262263ef65bd558156 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Sun, 19 Nov 2023 05:17:08 +0000 Subject: [PATCH 87/88] mobile: add initial DCM analysis_options (#5136) Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- docs/docs/developer/setup.md | 8 +++++- mobile/analysis_options.yaml | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index 2dcd3038a..53e6cdd27 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -61,9 +61,15 @@ IMMICH_SERVER_URL=https://demo.immich.app/api npm run dev Setting these in the IDE give a better developer experience, auto-formatting code on save, and providing instant feedback on lint issues. +### Dart Code Metris + +The mobile app uses DCM (Dart Code Metrics) for linting and metrics calculation. Please refer to the [Getting Started](https://dcm.dev/docs/getting-started/#installation) page for more information on setting up DCM + +Note: Activating the license is not required. + ### VSCode -Install `Flutter`, `Prettier`, `ESLint` and `Svelte` extensions. +Install `Flutter`, `DCM`, `Prettier`, `ESLint` and `Svelte` extensions. in User `settings.json` (`cmd + shift + p` and search for `Open User Settings JSON`) add the following: diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index b570c2b2e..2d5071a4f 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -36,3 +36,55 @@ analyzer: - openapi/ - openapi/test/ - lib/generated_plugin_registrant.dart + +dart_code_metrics: + metrics: + cyclomatic-complexity: 20 + number-of-parameters: 4 + maximum-nesting-level: 5 + rules: + # Common + - avoid-accessing-collections-by-constant-index + - avoid-accessing-other-classes-private-members + - avoid-async-call-in-sync-function + - avoid-cascade-after-if-null + - avoid-collapsible-if + - avoid-collection-methods-with-unrelated-types + - avoid-declaring-call-method + - avoid-double-slash-imports + - avoid-duplicate-cascades + - avoid-duplicate-patterns + - avoid-generics-shadowing + - avoid-global-state + # Flutter + - add-copy-with: + file-name-pattern: '.model.dart' + - always-remove-listener + - avoid-border-all + - avoid-empty-setstate + - avoid-expanded-as-spacer + - avoid-incomplete-copy-with + - avoid-inherited-widget-in-initstate + - avoid-late-context + - avoid-recursive-widget-calls + - avoid-returning-widgets + - avoid-shrink-wrap-in-lists + - avoid-single-child-column-or-row + - avoid-state-constructors + - avoid-stateless-widget-initialized-fields + - avoid-unnecessary-overrides-in-state + - avoid-unnecessary-stateful-widgets + - avoid-wrapping-in-padding + - dispose-fields + - prefer-const-border-radius + - prefer-correct-edge-insets-constructor + - prefer-dedicated-media-query-methods + - prefer-define-hero-tag + - prefer-extracting-callbacks + - prefer-single-widget-per-file: + ignore-private-widgets: true + - prefer-sliver-prefix + - prefer-text-rich + - prefer-using-list-view + - proper-super-calls + - use-setstate-synchronously From 4ade8eae1700b312d3cff0bea1e240c715e625b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 19 Nov 2023 05:23:21 +0000 Subject: [PATCH 88/88] chore(deps): update dependency @types/node to v20.9.2 (#5139) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 1ea45ca41..a90a6d7ee 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1580,9 +1580,9 @@ } }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -7377,9 +7377,9 @@ } }, "@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", "dev": true, "requires": { "undici-types": "~5.26.4"