From fbb0bc6e3968b39875997d9eedc16a0077e4bbe0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:17:25 +0000 Subject: [PATCH 01/66] chore(deps): update ghcr.io/jdx/mise docker tag to v2026.6.10 (#29153) --- server/Dockerfile | 2 +- server/Dockerfile.dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index 058938a42a..1df476dccd 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -56,7 +56,7 @@ FROM builder AS plugins ARG TARGETPLATFORM -COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise +COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise WORKDIR /app COPY ./mise.toml ./mise.toml diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index 83fdc3eb02..b54e10831c 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -2,7 +2,7 @@ FROM ghcr.io/immich-app/base-server-dev:202606161235@sha256:9f88b07acc8b7bf37a1dd3d5a19193f664443eaaab4e08e9f9341414c5e4b23f AS dev -COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise +COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise RUN echo "devdir=/buildcache/node-gyp" >> /usr/local/etc/npmrc && \ echo "store-dir=/buildcache/pnpm-store" >> /usr/local/etc/npmrc && \ From 430a2bbfd3cc2100b5aeb0ce8fd86f89214f516a Mon Sep 17 00:00:00 2001 From: Mees Frensel <33722705+meesfrensel@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:04:41 +0200 Subject: [PATCH 02/66] chore(server): add switch case exhaustiveness lint (#29029) --- server/eslint.config.mjs | 1 + server/src/commands/schema-check.ts | 6 +++++- server/src/cores/storage.core.ts | 9 ++++++++- .../maintenance/maintenance-worker.service.ts | 9 ++++++--- server/src/repositories/config.repository.ts | 18 ++++++++++-------- server/src/repositories/media.repository.ts | 4 +++- server/src/services/database-backup.service.ts | 3 +++ server/src/services/job.service.ts | 2 ++ server/src/services/library.service.ts | 3 +++ server/src/services/metadata.service.ts | 4 +++- 10 files changed, 44 insertions(+), 15 deletions(-) diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs index bdbac34da6..c86bc3d72f 100644 --- a/server/eslint.config.mjs +++ b/server/eslint.config.mjs @@ -51,6 +51,7 @@ export default typescriptEslint.config([ 'unicorn/no-array-sort': 'off', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/switch-exhaustiveness-check': ['error', { considerDefaultExhaustiveForUnions: true }], 'require-await': 'off', '@typescript-eslint/require-await': 'error', curly: 2, diff --git a/server/src/commands/schema-check.ts b/server/src/commands/schema-check.ts index e0ccae8469..fc38254dde 100644 --- a/server/src/commands/schema-check.ts +++ b/server/src/commands/schema-check.ts @@ -28,9 +28,13 @@ export class SchemaCheck extends CommandRunner { } case 'missing': { - console.log(` - ${migration.name} exists, but has not been applied to the database`); + console.log(` - ${migration.name} exists on disk, but has not been applied to the database`); break; } + + case 'applied': { + break; // happy path, do nothing + } } } } diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index 8802145020..6c7d6f0e3e 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -9,6 +9,7 @@ import { PersonPathType, RawExtractedFormat, StorageFolder, + UserPathType, } from 'src/enum'; import { AssetRepository } from 'src/repositories/asset.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; @@ -327,13 +328,19 @@ export class StorageCore { case AssetFileType.EncodedVideo: case AssetFileType.Thumbnail: case AssetFileType.Preview: - case AssetFileType.Sidecar: { + case AssetFileType.Sidecar: + case AssetPathType.EncodedVideo: { return this.assetRepository.upsertFile({ assetId: id, type: pathType as AssetFileType, path: newPath }); } case PersonPathType.Face: { return this.personRepository.update({ id, thumbnailPath: newPath }); } + + case UserPathType.Profile: { + this.logger.warn('Unexpected path type:', pathType); + return; + } } } diff --git a/server/src/maintenance/maintenance-worker.service.ts b/server/src/maintenance/maintenance-worker.service.ts index 61958a0d8a..f2c9086e87 100644 --- a/server/src/maintenance/maintenance-worker.service.ts +++ b/server/src/maintenance/maintenance-worker.service.ts @@ -281,17 +281,20 @@ export class MaintenanceWorkerService { async runAction(action: SetMaintenanceModeDto) { switch (action.action) { - case MaintenanceAction.Start: { + case MaintenanceAction.Start: + case MaintenanceAction.SelectDatabaseRestore: { return; } case MaintenanceAction.End: { return this.endMaintenance(); } - case MaintenanceAction.SelectDatabaseRestore: { - return; + case MaintenanceAction.RestoreDatabase: { + return this.runRestoreDatabase(action); } } + } + async runRestoreDatabase(action: SetMaintenanceModeDto) { const lock = await this.databaseRepository.tryLock(DatabaseLock.MaintenanceOperation); if (!lock) { return; diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index f88deca09d..909d6d62dd 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -243,14 +243,16 @@ const getEnv = (): EnvData => { }; let vectorExtension: VectorExtension | undefined; - switch (dto.DB_VECTOR_EXTENSION) { - case 'pgvector': { - vectorExtension = DatabaseExtension.Vector; - break; - } - case 'vectorchord': { - vectorExtension = DatabaseExtension.VectorChord; - break; + if (dto.DB_VECTOR_EXTENSION) { + switch (dto.DB_VECTOR_EXTENSION) { + case 'pgvector': { + vectorExtension = DatabaseExtension.Vector; + break; + } + case 'vectorchord': { + vectorExtension = DatabaseExtension.VectorChord; + break; + } } } diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index c2ec95636a..5f8449e692 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -478,8 +478,10 @@ export class MediaRepository { case 'av1': { return this.parseEnum(Av1Profile, profile); } + default: { + return null; + } } - return null; } private compareStreams(a: FfprobeStream, b: FfprobeStream): number { diff --git a/server/src/services/database-backup.service.ts b/server/src/services/database-backup.service.ts index 6afc46c7da..55d69d37b7 100644 --- a/server/src/services/database-backup.service.ts +++ b/server/src/services/database-backup.service.ts @@ -163,6 +163,9 @@ export class DatabaseBackupService { ); switch (bin) { + case 'pg_dump': { + break; + } case 'pg_dumpall': { args.push('--database'); break; diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 00105103e8..6d4f04df37 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -257,6 +257,8 @@ export class JobService extends BaseService { } break; } + + // no default } } } diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 085650be6f..7e84a8ec52 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -498,6 +498,9 @@ export class LibraryService extends BaseService { const stat = stats[i]; const action = this.checkExistingAsset(asset, stat); switch (action) { + case AssetSyncResult.DO_NOTHING: { + break; + } case AssetSyncResult.OFFLINE: { if (asset.status === AssetStatus.Trashed) { trashedAssetIdsToOffline.push(asset.id); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index dfa254b78a..a4639db262 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -1138,7 +1138,9 @@ export class MetadataService extends BaseService { case 3: { return ExifOrientation.Rotate90CW; } + default: { + return null; + } } - return null; } } From 3f2e51c5d4f489313193a095981bbe18606fb185 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 17 Jun 2026 14:50:17 +0200 Subject: [PATCH 03/66] refactor(server): use uuids in schemas (#29140) --- open-api/immich-openapi-specs.json | 182 ++++++++++++++++++++ server/src/dtos/album.dto.ts | 6 +- server/src/dtos/api-key.dto.ts | 2 +- server/src/dtos/asset-ids.response.dto.ts | 4 +- server/src/dtos/asset-media-response.dto.ts | 4 +- server/src/dtos/asset-response.dto.ts | 10 +- server/src/dtos/asset.dto.ts | 2 +- server/src/dtos/auth.dto.ts | 2 +- server/src/dtos/download.dto.ts | 2 +- server/src/dtos/duplicate.dto.ts | 2 +- server/src/dtos/integrity.dto.ts | 2 +- server/src/dtos/library.dto.ts | 4 +- server/src/dtos/map.dto.ts | 2 +- server/src/dtos/memory.dto.ts | 4 +- server/src/dtos/notification.dto.ts | 2 +- server/src/dtos/person.dto.ts | 4 +- server/src/dtos/plugin.dto.ts | 2 +- server/src/dtos/server.dto.ts | 4 +- server/src/dtos/session.dto.ts | 2 +- server/src/dtos/shared-link.dto.ts | 4 +- server/src/dtos/stack.dto.ts | 4 +- server/src/dtos/sync.dto.ts | 100 +++++------ server/src/dtos/tag.dto.ts | 2 +- server/src/dtos/user-profile.dto.ts | 2 +- server/src/dtos/workflow.dto.ts | 2 +- 25 files changed, 269 insertions(+), 87 deletions(-) diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 481f94b3dd..199dca8d76 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -16508,7 +16508,9 @@ }, "albumThumbnailAssetId": { "description": "Thumbnail asset ID", + "format": "uuid", "nullable": true, + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "albumUsers": { @@ -16551,6 +16553,8 @@ }, "id": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isActivityEnabled": { @@ -16793,6 +16797,8 @@ }, "id": { "description": "API key ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { @@ -17001,6 +17007,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isTrashed": { @@ -17379,6 +17387,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "error": { @@ -17494,6 +17504,8 @@ "properties": { "id": { "description": "Asset media ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "status": { @@ -17562,6 +17574,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -17809,7 +17823,9 @@ }, "duplicateId": { "description": "Duplicate group ID", + "format": "uuid", "nullable": true, + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "duration": { @@ -17845,6 +17861,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isArchived": { @@ -17923,6 +17941,8 @@ }, "ownerId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "people": { @@ -18020,10 +18040,14 @@ }, "id": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "primaryAssetId": { "description": "Primary asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -18159,6 +18183,8 @@ }, "id": { "description": "ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "success": { @@ -18337,6 +18363,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -18441,6 +18469,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -18600,6 +18630,8 @@ "assetIds": { "description": "Asset IDs in this archive", "items": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "type": "array" @@ -18785,6 +18817,8 @@ }, "duplicateId": { "description": "Duplicate group ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "suggestedKeepAssetIds": { @@ -19101,6 +19135,8 @@ "properties": { "id": { "description": "Integrity report item id", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "path": { @@ -19275,6 +19311,8 @@ }, "id": { "description": "Library ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "importPaths": { @@ -19290,6 +19328,8 @@ }, "ownerId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "refreshedAt": { @@ -19444,6 +19484,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -19634,6 +19676,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "lat": { @@ -19834,6 +19878,8 @@ }, "id": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isSaved": { @@ -19849,6 +19895,8 @@ }, "ownerId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "seenAt": { @@ -20310,6 +20358,8 @@ }, "id": { "description": "Notification ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "level": { @@ -20754,6 +20804,8 @@ }, "id": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isFavorite": { @@ -20989,6 +21041,8 @@ }, "id": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isFavorite": { @@ -21244,6 +21298,8 @@ }, "id": { "description": "Plugin ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "methods": { @@ -22639,6 +22695,8 @@ }, "id": { "description": "Version history entry ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "version": { @@ -22743,6 +22801,8 @@ }, "id": { "description": "Session ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isPendingSyncReset": { @@ -22800,6 +22860,8 @@ }, "id": { "description": "Session ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isPendingSyncReset": { @@ -23022,6 +23084,8 @@ }, "id": { "description": "Shared link ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -23047,6 +23111,8 @@ }, "userId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23388,10 +23454,14 @@ }, "id": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "primaryAssetId": { "description": "Primary asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23665,6 +23735,8 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23677,10 +23749,14 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23694,10 +23770,14 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23711,10 +23791,14 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23728,6 +23812,8 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "role": { @@ -23735,6 +23821,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23760,6 +23848,8 @@ }, "id": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isActivityEnabled": { @@ -23775,6 +23865,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "thumbnailAssetId": { @@ -23818,6 +23910,8 @@ }, "id": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isActivityEnabled": { @@ -23860,6 +23954,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23872,6 +23968,8 @@ "properties": { "editId": { "description": "Edit ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23887,10 +23985,14 @@ }, "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "id": { "description": "Edit ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "parameters": { @@ -23918,6 +24020,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "city": { @@ -24095,6 +24199,8 @@ "properties": { "assetFaceId": { "description": "Asset face ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24107,6 +24213,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "boundingBoxX1": { @@ -24135,6 +24243,8 @@ }, "id": { "description": "Asset face ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "imageHeight": { @@ -24177,6 +24287,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "boundingBoxX1": { @@ -24213,6 +24325,8 @@ }, "id": { "description": "Asset face ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "imageHeight": { @@ -24261,6 +24375,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -24278,6 +24394,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -24326,6 +24444,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "boxScore": { @@ -24335,6 +24455,8 @@ }, "id": { "description": "OCR entry ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isVisible": { @@ -24461,6 +24583,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isEdited": { @@ -24495,6 +24619,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "stackId": { @@ -24599,6 +24725,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isEdited": { @@ -24633,6 +24761,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "stackId": { @@ -24711,6 +24841,8 @@ }, "id": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isAdmin": { @@ -24845,10 +24977,14 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "memoryId": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24862,10 +24998,14 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "memoryId": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24879,6 +25019,8 @@ "properties": { "memoryId": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24919,6 +25061,8 @@ }, "id": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isSaved": { @@ -24934,6 +25078,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "seenAt": { @@ -24983,10 +25129,14 @@ "properties": { "sharedById": { "description": "Shared by ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "sharedWithId": { "description": "Shared with ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25004,10 +25154,14 @@ }, "sharedById": { "description": "Shared by ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "sharedWithId": { "description": "Shared with ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25022,6 +25176,8 @@ "properties": { "personId": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25059,6 +25215,8 @@ }, "id": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isFavorite": { @@ -25075,6 +25233,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "updatedAt": { @@ -25140,6 +25300,8 @@ "properties": { "stackId": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25159,14 +25321,20 @@ }, "id": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "primaryAssetId": { "description": "Primary asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "updatedAt": { @@ -25209,6 +25377,8 @@ "properties": { "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25224,6 +25394,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25240,6 +25412,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "value": { @@ -25283,6 +25457,8 @@ }, "id": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { @@ -26466,6 +26642,8 @@ }, "id": { "description": "Tag ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { @@ -26989,6 +27167,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "userName": { @@ -27631,6 +27811,8 @@ }, "id": { "description": "Workflow ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index ee6f2c07eb..a66f912972 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -100,21 +100,21 @@ const AlbumUserResponseSchema = z const ContributorCountResponseSchema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), assetCount: z.int().min(0).describe('Number of assets contributed'), }) .meta({ id: 'ContributorCountResponseDto' }); export const AlbumResponseSchema = z .object({ - id: z.string().describe('Album ID'), + id: z.uuidv4().describe('Album ID'), albumName: z.string().describe('Album name'), description: z.string().describe('Album description'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. createdAt: z.string().meta({ format: 'date-time' }).describe('Creation date'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. updatedAt: z.string().meta({ format: 'date-time' }).describe('Last update date'), - albumThumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'), + albumThumbnailAssetId: z.uuidv4().nullable().describe('Thumbnail asset ID'), shared: z.boolean().describe('Is shared album'), albumUsers: z .array(AlbumUserResponseSchema) diff --git a/server/src/dtos/api-key.dto.ts b/server/src/dtos/api-key.dto.ts index 176812cc4f..470808e430 100644 --- a/server/src/dtos/api-key.dto.ts +++ b/server/src/dtos/api-key.dto.ts @@ -21,7 +21,7 @@ const ApiKeyUpdateSchema = z const ApiKeyResponseSchema = z .object({ - id: z.string().describe('API key ID'), + id: z.uuidv4().describe('API key ID'), name: z.string().describe('API key name'), createdAt: isoDatetimeToDate.describe('Creation date'), updatedAt: isoDatetimeToDate.describe('Last update date'), diff --git a/server/src/dtos/asset-ids.response.dto.ts b/server/src/dtos/asset-ids.response.dto.ts index 346829e644..e86eee0973 100644 --- a/server/src/dtos/asset-ids.response.dto.ts +++ b/server/src/dtos/asset-ids.response.dto.ts @@ -16,7 +16,7 @@ const AssetIdErrorReasonSchema = z /** @deprecated Use `BulkIdResponseDto` instead */ const AssetIdsResponseSchema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), success: z.boolean().describe('Whether operation succeeded'), error: AssetIdErrorReasonSchema.optional(), }) @@ -43,7 +43,7 @@ export const BulkIdsSchema = z const BulkIdResponseSchema = z .object({ - id: z.string().describe('ID'), + id: z.uuidv4().describe('ID'), success: z.boolean().describe('Whether operation succeeded'), error: BulkIdErrorReasonSchema.optional(), errorMessage: z.string().optional(), diff --git a/server/src/dtos/asset-media-response.dto.ts b/server/src/dtos/asset-media-response.dto.ts index 0f27fc5974..b0c50794b9 100644 --- a/server/src/dtos/asset-media-response.dto.ts +++ b/server/src/dtos/asset-media-response.dto.ts @@ -11,7 +11,7 @@ const AssetMediaStatusSchema = z.enum(AssetMediaStatus).describe('Upload status' const AssetMediaResponseSchema = z .object({ status: AssetMediaStatusSchema, - id: z.string().describe('Asset media ID'), + id: z.uuidv4().describe('Asset media ID'), }) .meta({ id: 'AssetMediaResponseDto' }); @@ -34,7 +34,7 @@ const AssetRejectReasonSchema = z const AssetBulkUploadCheckResultSchema = z .object({ - id: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Asset ID'), action: AssetUploadActionSchema, reason: AssetRejectReasonSchema.optional(), assetId: z.string().optional().describe('Existing asset ID if duplicate'), diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index b5b0b04a2d..ba64286647 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -24,7 +24,7 @@ import z from 'zod'; const SanitizedAssetResponseSchema = z .object({ - id: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Asset ID'), type: AssetTypeSchema, thumbhash: z .string() @@ -52,8 +52,8 @@ export class SanitizedAssetResponseDto extends createZodDto(SanitizedAssetRespon const AssetStackResponseSchema = z .object({ - id: z.string().describe('Stack ID'), - primaryAssetId: z.string().describe('Primary asset ID'), + id: z.uuidv4().describe('Stack ID'), + primaryAssetId: z.uuidv4().describe('Primary asset ID'), assetCount: z.int().min(0).describe('Number of assets in stack'), }) .meta({ id: 'AssetStackResponseDto' }); @@ -65,7 +65,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend( .string() .meta({ format: 'date-time' }) .describe('The UTC timestamp when the asset was originally uploaded to Immich.'), - ownerId: z.string().describe('Owner user ID'), + ownerId: z.uuidv4().describe('Owner user ID'), owner: UserResponseSchema.optional(), libraryId: z .uuidv4() @@ -103,7 +103,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend( people: z.array(PersonResponseSchema).optional(), checksum: z.string().describe('Base64 encoded SHA1 hash'), stack: AssetStackResponseSchema.nullish(), - duplicateId: z.string().nullish().describe('Duplicate group ID'), + duplicateId: z.uuidv4().nullish().describe('Duplicate group ID'), resized: z .boolean() .optional() diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 3dddcfaa3e..26c983a7de 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -148,7 +148,7 @@ const AssetMetadataResponseSchema = z .meta({ id: 'AssetMetadataResponseDto' }); const AssetMetadataBulkResponseSchema = AssetMetadataResponseSchema.extend({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), }).meta({ id: 'AssetMetadataBulkResponseDto' }); const AssetCopySchema = z diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts index 1f75401e33..40b4e25b6d 100644 --- a/server/src/dtos/auth.dto.ts +++ b/server/src/dtos/auth.dto.ts @@ -29,7 +29,7 @@ const LoginCredentialSchema = z const LoginResponseSchema = z .object({ accessToken: z.string().describe('Access token'), - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), userEmail: toEmail.describe('User email'), name: z.string().describe('User name'), profileImagePath: z.string().describe('Profile image path'), diff --git a/server/src/dtos/download.dto.ts b/server/src/dtos/download.dto.ts index b44a6a7afc..c37fa5c113 100644 --- a/server/src/dtos/download.dto.ts +++ b/server/src/dtos/download.dto.ts @@ -14,7 +14,7 @@ const DownloadInfoSchema = z const DownloadArchiveInfoSchema = z .object({ size: z.int().describe('Archive size in bytes'), - assetIds: z.array(z.string()).describe('Asset IDs in this archive'), + assetIds: z.array(z.uuidv4()).describe('Asset IDs in this archive'), }) .meta({ id: 'DownloadArchiveInfo' }); diff --git a/server/src/dtos/duplicate.dto.ts b/server/src/dtos/duplicate.dto.ts index 55427e36aa..66d14e605b 100644 --- a/server/src/dtos/duplicate.dto.ts +++ b/server/src/dtos/duplicate.dto.ts @@ -4,7 +4,7 @@ import z from 'zod'; const DuplicateResponseSchema = z .object({ - duplicateId: z.string().describe('Duplicate group ID'), + duplicateId: z.uuidv4().describe('Duplicate group ID'), assets: z.array(AssetResponseSchema).describe('Duplicate assets'), suggestedKeepAssetIds: z.array(z.uuidv4()).describe('Suggested asset IDs to keep based on file size and EXIF data'), }) diff --git a/server/src/dtos/integrity.dto.ts b/server/src/dtos/integrity.dto.ts index b4faf6005e..3b89b117ea 100644 --- a/server/src/dtos/integrity.dto.ts +++ b/server/src/dtos/integrity.dto.ts @@ -27,7 +27,7 @@ const IntegrityDeleteReportSchema = z.object({ type: IntegrityReport }).meta({ i export class IntegrityDeleteReportDto extends createZodDto(IntegrityDeleteReportSchema) {} const IntegrityReportResponseItemSchema = z.object({ - id: z.string().describe('Integrity report item id'), + id: z.uuidv4().describe('Integrity report item id'), type: IntegrityReportSchema, path: z.string().describe('Integrity report item path'), }); diff --git a/server/src/dtos/library.dto.ts b/server/src/dtos/library.dto.ts index aafdd9f793..0819be278e 100644 --- a/server/src/dtos/library.dto.ts +++ b/server/src/dtos/library.dto.ts @@ -62,8 +62,8 @@ const ValidateLibraryResponseSchema = z const LibraryResponseSchema = z .object({ - id: z.string().describe('Library ID'), - ownerId: z.string().describe('Owner user ID'), + id: z.uuidv4().describe('Library ID'), + ownerId: z.uuidv4().describe('Owner user ID'), name: z.string().describe('Library name'), assetCount: z.int().describe('Number of assets'), importPaths: z.array(z.string()).describe('Import paths'), diff --git a/server/src/dtos/map.dto.ts b/server/src/dtos/map.dto.ts index 6a4776d49d..f442850936 100644 --- a/server/src/dtos/map.dto.ts +++ b/server/src/dtos/map.dto.ts @@ -30,7 +30,7 @@ const MapMarkerSchema = z const MapMarkerResponseSchema = z .object({ - id: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Asset ID'), lat: z.number().meta({ format: 'double' }).describe('Latitude'), lon: z.number().meta({ format: 'double' }).describe('Longitude'), city: z.string().nullable().describe('City name'), diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts index ce2e9fda6c..324474a1e1 100644 --- a/server/src/dtos/memory.dto.ts +++ b/server/src/dtos/memory.dto.ts @@ -59,7 +59,7 @@ const MemoryStatisticsResponseSchema = z const MemoryResponseSchema = z .object({ - id: z.string().describe('Memory ID'), + id: z.uuidv4().describe('Memory ID'), createdAt: isoDatetimeToDate.describe('Creation date'), updatedAt: isoDatetimeToDate.describe('Last update date'), deletedAt: isoDatetimeToDate.optional().describe('Deletion date'), @@ -67,7 +67,7 @@ const MemoryResponseSchema = z seenAt: isoDatetimeToDate.optional().describe('Date when memory was seen'), showAt: isoDatetimeToDate.optional().describe('Date when memory should be shown'), hideAt: isoDatetimeToDate.optional().describe('Date when memory should be hidden'), - ownerId: z.string().describe('Owner user ID'), + ownerId: z.uuidv4().describe('Owner user ID'), type: MemoryTypeSchema, data: OnThisDaySchema, isSaved: z.boolean().describe('Is memory saved'), diff --git a/server/src/dtos/notification.dto.ts b/server/src/dtos/notification.dto.ts index f474cfc0a1..04f937fc00 100644 --- a/server/src/dtos/notification.dto.ts +++ b/server/src/dtos/notification.dto.ts @@ -24,7 +24,7 @@ const TemplateSchema = z const NotificationSchema = z .object({ - id: z.string().describe('Notification ID'), + id: z.uuidv4().describe('Notification ID'), createdAt: isoDatetimeToDate.describe('Creation date'), level: NotificationLevelSchema, type: NotificationTypeSchema, diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index 38e856b8c1..fa56ded176 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -33,7 +33,7 @@ const PersonUpdateSchema = PersonCreateSchema.extend({ }).meta({ id: 'PersonUpdateDto' }); const PeopleUpdateItemSchema = PersonUpdateSchema.extend({ - id: z.string().describe('Person ID'), + id: z.uuidv4().describe('Person ID'), }).meta({ id: 'PeopleUpdateItem' }); const PeopleUpdateSchema = z @@ -60,7 +60,7 @@ const PersonSearchSchema = z export const PersonResponseSchema = z .object({ - id: z.string().describe('Person ID'), + id: z.uuidv4().describe('Person ID'), name: z.string().describe('Person name'), // TODO: use `isoDateToDate` when using `ZodSerializerDto` on the controllers. birthDate: z.string().meta({ format: 'date' }).describe('Person date of birth').nullable(), diff --git a/server/src/dtos/plugin.dto.ts b/server/src/dtos/plugin.dto.ts index 8ee695d61c..777380a107 100644 --- a/server/src/dtos/plugin.dto.ts +++ b/server/src/dtos/plugin.dto.ts @@ -32,7 +32,7 @@ const PluginMethodResponseSchema = z const PluginResponseSchema = z .object({ - id: z.string().describe('Plugin ID'), + id: z.uuidv4().describe('Plugin ID'), name: z.string().describe('Plugin name'), title: z.string().describe('Plugin title'), description: z.string().describe('Plugin description'), diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index 03d45fab1c..0215558aab 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -73,7 +73,7 @@ const ServerVersionResponseSchema = z const ServerVersionHistoryResponseSchema = z .object({ - id: z.string().describe('Version history entry ID'), + id: z.uuidv4().describe('Version history entry ID'), createdAt: isoDatetimeToDate.describe('When this version was first seen'), version: z.string().describe('Version string'), }) @@ -81,7 +81,7 @@ const ServerVersionHistoryResponseSchema = z const UsageByUserSchema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), userName: z.string().describe('User name'), photos: z.int().describe('Number of photos'), videos: z.int().describe('Number of videos'), diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts index 424d104053..c81dac6d79 100644 --- a/server/src/dtos/session.dto.ts +++ b/server/src/dtos/session.dto.ts @@ -18,7 +18,7 @@ const SessionUpdateSchema = z const SessionResponseSchema = z .object({ - id: z.string().describe('Session ID'), + id: z.uuidv4().describe('Session ID'), createdAt: z.string().describe('Creation date'), updatedAt: z.string().describe('Last update date'), expiresAt: z.string().optional().describe('Expiration date'), diff --git a/server/src/dtos/shared-link.dto.ts b/server/src/dtos/shared-link.dto.ts index aebe6fcab1..6763f6ab74 100644 --- a/server/src/dtos/shared-link.dto.ts +++ b/server/src/dtos/shared-link.dto.ts @@ -53,10 +53,10 @@ const SharedLinkLoginSchema = z const SharedLinkResponseSchema = z .object({ - id: z.string().describe('Shared link ID'), + id: z.uuidv4().describe('Shared link ID'), description: z.string().nullable().describe('Link description'), password: z.string().nullable().describe('Has password'), - userId: z.string().describe('Owner user ID'), + userId: z.uuidv4().describe('Owner user ID'), key: z.string().describe('Encryption key (base64url)'), type: SharedLinkTypeSchema, createdAt: isoDatetimeToDate.describe('Creation date'), diff --git a/server/src/dtos/stack.dto.ts b/server/src/dtos/stack.dto.ts index 48354cec6b..1956974ba1 100644 --- a/server/src/dtos/stack.dto.ts +++ b/server/src/dtos/stack.dto.ts @@ -24,8 +24,8 @@ const StackUpdateSchema = z const StackResponseSchema = z .object({ - id: z.string().describe('Stack ID'), - primaryAssetId: z.string().describe('Primary asset ID'), + id: z.uuidv4().describe('Stack ID'), + primaryAssetId: z.uuidv4().describe('Primary asset ID'), assets: z.array(AssetResponseSchema), }) .describe('Stack response') diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index 531ac6277c..1d5fe64aa1 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -19,7 +19,7 @@ import z from 'zod'; const SyncUserV1Schema = z .object({ - id: z.string().describe('User ID'), + id: z.uuidv4().describe('User ID'), name: z.string().describe('User name'), email: z.string().describe('User email'), avatarColor: UserAvatarColorSchema.nullish(), @@ -40,27 +40,27 @@ const SyncAuthUserV1Schema = SyncUserV1Schema.merge( }), ).meta({ id: 'SyncAuthUserV1' }); -const SyncUserDeleteV1Schema = z.object({ userId: z.string().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' }); +const SyncUserDeleteV1Schema = z.object({ userId: z.uuidv4().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' }); const SyncPartnerV1Schema = z .object({ - sharedById: z.string().describe('Shared by ID'), - sharedWithId: z.string().describe('Shared with ID'), + sharedById: z.uuidv4().describe('Shared by ID'), + sharedWithId: z.uuidv4().describe('Shared with ID'), inTimeline: z.boolean().describe('In timeline'), }) .meta({ id: 'SyncPartnerV1' }); const SyncPartnerDeleteV1Schema = z .object({ - sharedById: z.string().describe('Shared by ID'), - sharedWithId: z.string().describe('Shared with ID'), + sharedById: z.uuidv4().describe('Shared by ID'), + sharedWithId: z.uuidv4().describe('Shared with ID'), }) .meta({ id: 'SyncPartnerDeleteV1' }); const SyncAssetV1Schema = z .object({ - id: z.string().describe('Asset ID'), - ownerId: z.string().describe('Owner ID'), + id: z.uuidv4().describe('Asset ID'), + ownerId: z.uuidv4().describe('Owner ID'), originalFileName: z.string().describe('Original file name'), thumbhash: z.string().nullable().describe('Thumbhash'), checksum: z.string().describe('Checksum'), @@ -84,8 +84,8 @@ const SyncAssetV1Schema = z const SyncAssetV2Schema = z .object({ - id: z.string().describe('Asset ID'), - ownerId: z.string().describe('Owner ID'), + id: z.uuidv4().describe('Asset ID'), + ownerId: z.uuidv4().describe('Owner ID'), originalFileName: z.string().describe('Original file name'), thumbhash: z.string().nullable().describe('Thumbhash'), checksum: z.string().describe('Checksum'), @@ -123,12 +123,12 @@ export class SyncAssetV1 extends createZodDto(SyncAssetV1Schema) {} export class SyncAssetV2 extends createZodDto(SyncAssetV2Schema) {} const SyncAssetDeleteV1Schema = z - .object({ assetId: z.string().describe('Asset ID') }) + .object({ assetId: z.uuidv4().describe('Asset ID') }) .meta({ id: 'SyncAssetDeleteV1' }); const SyncAssetExifV1Schema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), description: z.string().nullable().describe('Description'), exifImageWidth: z.int().nullable().describe('Exif image width'), exifImageHeight: z.int().nullable().describe('Exif image height'), @@ -158,7 +158,7 @@ const SyncAssetExifV1Schema = z const SyncAssetMetadataV1Schema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), key: z.string().describe('Key'), value: z.record(z.string(), z.unknown()).describe('Value'), }) @@ -166,15 +166,15 @@ const SyncAssetMetadataV1Schema = z const SyncAssetMetadataDeleteV1Schema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), key: z.string().describe('Key'), }) .meta({ id: 'SyncAssetMetadataDeleteV1' }); const SyncAssetEditV1Schema = z .object({ - id: z.string().describe('Edit ID'), - assetId: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Edit ID'), + assetId: z.uuidv4().describe('Asset ID'), action: AssetEditActionSchema, parameters: z.record(z.string(), z.unknown()).describe('Edit parameters'), sequence: z.int().describe('Edit sequence'), @@ -182,7 +182,7 @@ const SyncAssetEditV1Schema = z .meta({ id: 'SyncAssetEditV1' }); const SyncAssetEditDeleteV1Schema = z - .object({ editId: z.string().describe('Edit ID') }) + .object({ editId: z.uuidv4().describe('Edit ID') }) .meta({ id: 'SyncAssetEditDeleteV1' }); @ExtraModel() @@ -199,28 +199,28 @@ export class SyncAssetEditV1 extends createZodDto(SyncAssetEditV1Schema) {} class SyncAssetEditDeleteV1 extends createZodDto(SyncAssetEditDeleteV1Schema) {} const SyncAlbumDeleteV1Schema = z - .object({ albumId: z.string().describe('Album ID') }) + .object({ albumId: z.uuidv4().describe('Album ID') }) .meta({ id: 'SyncAlbumDeleteV1' }); const SyncAlbumUserDeleteV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - userId: z.string().describe('User ID'), + albumId: z.uuidv4().describe('Album ID'), + userId: z.uuidv4().describe('User ID'), }) .meta({ id: 'SyncAlbumUserDeleteV1' }); const SyncAlbumUserV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - userId: z.string().describe('User ID'), + albumId: z.uuidv4().describe('Album ID'), + userId: z.uuidv4().describe('User ID'), role: AlbumUserRoleSchema, }) .meta({ id: 'SyncAlbumUserV1' }); const SyncAlbumV1Schema = z .object({ - id: z.string().describe('Album ID'), - ownerId: z.string().describe('Owner ID'), + id: z.uuidv4().describe('Album ID'), + ownerId: z.uuidv4().describe('Owner ID'), name: z.string().describe('Album name'), description: z.string().describe('Album description'), createdAt: isoDatetimeToDate.describe('Created at'), @@ -233,7 +233,7 @@ const SyncAlbumV1Schema = z const SyncAlbumV2Schema = z .object({ - id: z.string().describe('Album ID'), + id: z.uuidv4().describe('Album ID'), name: z.string().describe('Album name'), description: z.string().describe('Album description'), createdAt: isoDatetimeToDate.describe('Created at'), @@ -246,15 +246,15 @@ const SyncAlbumV2Schema = z const SyncAlbumToAssetV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - assetId: z.string().describe('Asset ID'), + albumId: z.uuidv4().describe('Album ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncAlbumToAssetV1' }); const SyncAlbumToAssetDeleteV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - assetId: z.string().describe('Asset ID'), + albumId: z.uuidv4().describe('Album ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncAlbumToAssetDeleteV1' }); @@ -284,11 +284,11 @@ export function syncAlbumV2ToV1( const SyncMemoryV1Schema = z .object({ - id: z.string().describe('Memory ID'), + id: z.uuidv4().describe('Memory ID'), createdAt: isoDatetimeToDate.describe('Created at'), updatedAt: isoDatetimeToDate.describe('Updated at'), deletedAt: isoDatetimeToDate.nullable().describe('Deleted at'), - ownerId: z.string().describe('Owner ID'), + ownerId: z.uuidv4().describe('Owner ID'), type: MemoryTypeSchema, data: z.record(z.string(), z.unknown()).describe('Data'), isSaved: z.boolean().describe('Is saved'), @@ -300,43 +300,43 @@ const SyncMemoryV1Schema = z .meta({ id: 'SyncMemoryV1' }); const SyncMemoryDeleteV1Schema = z - .object({ memoryId: z.string().describe('Memory ID') }) + .object({ memoryId: z.uuidv4().describe('Memory ID') }) .meta({ id: 'SyncMemoryDeleteV1' }); const SyncMemoryAssetV1Schema = z .object({ - memoryId: z.string().describe('Memory ID'), - assetId: z.string().describe('Asset ID'), + memoryId: z.uuidv4().describe('Memory ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncMemoryAssetV1' }); const SyncMemoryAssetDeleteV1Schema = z .object({ - memoryId: z.string().describe('Memory ID'), - assetId: z.string().describe('Asset ID'), + memoryId: z.uuidv4().describe('Memory ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncMemoryAssetDeleteV1' }); const SyncStackV1Schema = z .object({ - id: z.string().describe('Stack ID'), + id: z.uuidv4().describe('Stack ID'), createdAt: isoDatetimeToDate.describe('Created at'), updatedAt: isoDatetimeToDate.describe('Updated at'), - primaryAssetId: z.string().describe('Primary asset ID'), - ownerId: z.string().describe('Owner ID'), + primaryAssetId: z.uuidv4().describe('Primary asset ID'), + ownerId: z.uuidv4().describe('Owner ID'), }) .meta({ id: 'SyncStackV1' }); const SyncStackDeleteV1Schema = z - .object({ stackId: z.string().describe('Stack ID') }) + .object({ stackId: z.uuidv4().describe('Stack ID') }) .meta({ id: 'SyncStackDeleteV1' }); const SyncPersonV1Schema = z .object({ - id: z.string().describe('Person ID'), + id: z.uuidv4().describe('Person ID'), createdAt: isoDatetimeToDate.describe('Created at'), updatedAt: isoDatetimeToDate.describe('Updated at'), - ownerId: z.string().describe('Owner ID'), + ownerId: z.uuidv4().describe('Owner ID'), name: z.string().describe('Person name'), birthDate: isoDatetimeToDate.nullable().describe('Birth date'), isHidden: z.boolean().describe('Is hidden'), @@ -347,13 +347,13 @@ const SyncPersonV1Schema = z .meta({ id: 'SyncPersonV1' }); const SyncPersonDeleteV1Schema = z - .object({ personId: z.string().describe('Person ID') }) + .object({ personId: z.uuidv4().describe('Person ID') }) .meta({ id: 'SyncPersonDeleteV1' }); const SyncAssetFaceV1Schema = z .object({ - id: z.string().describe('Asset face ID'), - assetId: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Asset face ID'), + assetId: z.uuidv4().describe('Asset ID'), personId: z.string().nullable().describe('Person ID'), imageWidth: z.int().describe('Image width'), imageHeight: z.int().describe('Image height'), @@ -371,12 +371,12 @@ const SyncAssetFaceV2Schema = SyncAssetFaceV1Schema.extend({ }).meta({ id: 'SyncAssetFaceV2' }); const SyncAssetFaceDeleteV1Schema = z - .object({ assetFaceId: z.string().describe('Asset face ID') }) + .object({ assetFaceId: z.uuidv4().describe('Asset face ID') }) .meta({ id: 'SyncAssetFaceDeleteV1' }); const SyncUserMetadataV1Schema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), key: UserMetadataKeySchema, value: z.record(z.string(), z.unknown()).describe('User metadata value'), }) @@ -384,7 +384,7 @@ const SyncUserMetadataV1Schema = z const SyncUserMetadataDeleteV1Schema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), key: UserMetadataKeySchema, }) .meta({ id: 'SyncUserMetadataDeleteV1' }); @@ -404,8 +404,8 @@ class SyncMemoryAssetDeleteV1 extends createZodDto(SyncMemoryAssetDeleteV1Schema const SyncAssetOcrV1Schema = z .object({ - id: z.string().describe('OCR entry ID'), - assetId: z.string().describe('Asset ID'), + id: z.uuidv4().describe('OCR entry ID'), + assetId: z.uuidv4().describe('Asset ID'), x1: z.number().meta({ format: 'double' }).describe('Top-left X coordinate (normalized 0–1)'), y1: z.number().meta({ format: 'double' }).describe('Top-left Y coordinate (normalized 0–1)'), diff --git a/server/src/dtos/tag.dto.ts b/server/src/dtos/tag.dto.ts index dd679c97cb..cfbae551a8 100644 --- a/server/src/dtos/tag.dto.ts +++ b/server/src/dtos/tag.dto.ts @@ -40,7 +40,7 @@ const TagBulkAssetsResponseSchema = z export const TagResponseSchema = z .object({ - id: z.string().describe('Tag ID'), + id: z.uuidv4().describe('Tag ID'), parentId: z.string().optional().describe('Parent tag ID'), name: z.string().describe('Tag name'), value: z.string().describe('Tag value (full path)'), diff --git a/server/src/dtos/user-profile.dto.ts b/server/src/dtos/user-profile.dto.ts index c3c91d3d95..e4ab29b6d5 100644 --- a/server/src/dtos/user-profile.dto.ts +++ b/server/src/dtos/user-profile.dto.ts @@ -11,7 +11,7 @@ export class CreateProfileImageDto { const CreateProfileImageResponseSchema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), profileChangedAt: isoDatetimeToDate.describe('Profile image change date'), profileImagePath: z.string().describe('Profile image file path'), }) diff --git a/server/src/dtos/workflow.dto.ts b/server/src/dtos/workflow.dto.ts index 1a2c2ac9f9..b4269ea0c9 100644 --- a/server/src/dtos/workflow.dto.ts +++ b/server/src/dtos/workflow.dto.ts @@ -58,7 +58,7 @@ const WorkflowUpdateSchema = z const WorkflowResponseSchema = z .object({ - id: z.string().describe('Workflow ID'), + id: z.uuidv4().describe('Workflow ID'), trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), name: z.string().nullable().describe('Workflow name'), description: z.string().nullable().describe('Workflow description'), From f9db76433e82e8822a3e01c1aced61a2e7672432 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:54:19 -0400 Subject: [PATCH 04/66] chore(deps): update github-actions to v1.313.0 (#29154) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-mobile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 224f673ff9..ea40ab6e1c 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -237,7 +237,7 @@ jobs: run: flutter build ios --config-only --no-codesign - name: Setup Ruby - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 + uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0 with: ruby-version: '3.3' bundler-cache: true From a364b56b1c296a1ef747441f5533373b3aabb294 Mon Sep 17 00:00:00 2001 From: Jeevan Mohan Pawar <35501212+jeevan6996@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:54:20 +0100 Subject: [PATCH 05/66] fix(server): skip existing users when sharing albums (#28884) Co-authored-by: Daniel Dietzler --- e2e/src/specs/server/api/album.e2e-spec.ts | 8 ++-- server/src/services/album.service.spec.ts | 45 +++++++++++++++++++--- server/src/services/album.service.ts | 2 +- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/e2e/src/specs/server/api/album.e2e-spec.ts b/e2e/src/specs/server/api/album.e2e-spec.ts index 1d3be1248c..0aa7b7ec03 100644 --- a/e2e/src/specs/server/api/album.e2e-spec.ts +++ b/e2e/src/specs/server/api/album.e2e-spec.ts @@ -730,8 +730,8 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`) .send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('User already added')); + expect(status).toBe(200); + expect(body.albumUsers.length).toEqual(1); }); it('should not be able to add existing user to shared album', async () => { @@ -745,8 +745,8 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`) .send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('User already added')); + expect(status).toBe(200); + expect(body.albumUsers.length).toEqual(2); }); }); diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 374cd58a0e..f1d125993c 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -472,17 +472,19 @@ describe(AlbumService.name, () => { expect(mocks.album.update).not.toHaveBeenCalled(); }); - it('should throw an error if the userId is already added', async () => { + it('should skip if the userId is already added', async () => { const userId = newUuid(); const album = AlbumFactory.from().albumUser({ userId }).build(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); - await expect( - sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] }), - ).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] })).resolves.toEqual( + expect.objectContaining({ id: album.id }), + ); expect(mocks.album.update).not.toHaveBeenCalled(); expect(mocks.user.get).not.toHaveBeenCalled(); + expect(mocks.albumUser.create).not.toHaveBeenCalled(); + expect(mocks.event.emit).not.toHaveBeenCalled(); }); it('should throw an error if the userId does not exist', async () => { @@ -498,7 +500,7 @@ describe(AlbumService.name, () => { expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {}); }); - it('should throw an error if the userId is the ownerId', async () => { + it('should skip if the userId is the ownerId', async () => { const album = AlbumFactory.create(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); @@ -507,9 +509,11 @@ describe(AlbumService.name, () => { sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId: owner.id }], }), - ).rejects.toBeInstanceOf(BadRequestException); + ).resolves.toEqual(expect.objectContaining({ id: album.id })); expect(mocks.album.update).not.toHaveBeenCalled(); expect(mocks.user.get).not.toHaveBeenCalled(); + expect(mocks.albumUser.create).not.toHaveBeenCalled(); + expect(mocks.event.emit).not.toHaveBeenCalled(); }); it('should add valid shared users', async () => { @@ -534,6 +538,35 @@ describe(AlbumService.name, () => { senderName: owner.name, }); }); + + it('should add new users when already-added users are included', async () => { + const existingUserId = newUuid(); + const album = AlbumFactory.from().albumUser({ userId: existingUserId }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + const user = UserFactory.create(); + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); + mocks.album.getById.mockResolvedValue(getForAlbum(album)); + mocks.user.get.mockResolvedValue(user); + mocks.albumUser.create.mockResolvedValue(AlbumUserFactory.from().album(album).user(user).build()); + + await sut.addUsers(AuthFactory.create(owner), album.id, { + albumUsers: [{ userId: existingUserId }, { userId: user.id }], + }); + + expect(mocks.user.get).toHaveBeenCalledTimes(1); + expect(mocks.user.get).toHaveBeenCalledWith(user.id, {}); + expect(mocks.albumUser.create).toHaveBeenCalledTimes(1); + expect(mocks.albumUser.create).toHaveBeenCalledWith({ + userId: user.id, + albumId: album.id, + }); + expect(mocks.event.emit).toHaveBeenCalledTimes(1); + expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { + id: album.id, + userId: user.id, + senderName: owner.name, + }); + }); }); describe('removeUser', () => { diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index cc0b0d899c..5f4bc56d98 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -290,7 +290,7 @@ export class AlbumService extends BaseService { const exists = album.albumUsers.find(({ user: { id } }) => id === userId); if (exists) { - throw new BadRequestException('User already added'); + continue; } const user = await this.userRepository.get(userId, {}); From 3be803d0c0d5c8105697951ce045cc4e76bbe889 Mon Sep 17 00:00:00 2001 From: Tom Vincent Date: Wed, 17 Jun 2026 15:19:04 +0100 Subject: [PATCH 06/66] docs(mobile-app): add Play App Signing certificate hash (#29168) --- docs/docs/features/mobile-app.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx index 59a4844c46..1b7c7e21d5 100644 --- a/docs/docs/features/mobile-app.mdx +++ b/docs/docs/features/mobile-app.mdx @@ -13,7 +13,9 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; :::info Android verification Below are the SHA-256 fingerprints for the certificates signing the android applications. -- Playstore / Github releases: +- Google Play releases: + `5A:22:C1:83:47:54:05:F5:49:C4:EB:9F:B2:6C:2E:93:A3:EF:9C:57:66:15:0A:7A:F3:8C:8D:3F:E5:15:CC:D6` +- GitHub releases: `86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20` - F-Droid releases: `FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF` From 327521fa279704b14e11044c2d214fed5d571aa3 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 17 Jun 2026 07:22:45 -0700 Subject: [PATCH 07/66] docs(mobile): point users towards shared setup docs (#29078) --- mobile/README.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/mobile/README.md b/mobile/README.md index 1f0860ced6..4115891e99 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -4,20 +4,7 @@ The Immich mobile app is a Flutter-based solution leveraging the Isar Database f ## Setup -1. [Install mise](https://mise.jdx.dev/installing-mise.html). -2. Change to the immich directory and trust the mise config with `mise trust`. -3. Install tools with mise: `mise install`. -4. Run `flutter pub get` to install the dependencies. -5. Run `make translation` to generate the translation file. -6. Run `flutter run` to start the app. - -## Translation - -To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run - -```bash -make translation -``` +See [setup](https://docs.immich.app/developer/setup) for how to set up the mobile build environment. ## Static Analysis From 14f6f2c04f97143834e48b8b72cee3b942eddf20 Mon Sep 17 00:00:00 2001 From: Mees Frensel <33722705+meesfrensel@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:31:49 +0200 Subject: [PATCH 08/66] refactor(web): simplify places page controls and use ui's `Select` (#29102) --- web/src/lib/utils/places-utils.ts | 32 ---------- .../(user)/places/PlacesControls.svelte | 62 ++++--------------- .../routes/(user)/places/PlacesList.svelte | 9 ++- 3 files changed, 17 insertions(+), 86 deletions(-) diff --git a/web/src/lib/utils/places-utils.ts b/web/src/lib/utils/places-utils.ts index b100d004d0..08ba2588a8 100644 --- a/web/src/lib/utils/places-utils.ts +++ b/web/src/lib/utils/places-utils.ts @@ -13,38 +13,6 @@ export interface PlacesGroup { places: AssetResponseDto[]; } -export interface PlacesGroupOptionMetadata { - id: PlacesGroupBy; - isDisabled: () => boolean; -} - -export const groupOptionsMetadata: PlacesGroupOptionMetadata[] = [ - { - id: PlacesGroupBy.None, - isDisabled: () => false, - }, - { - id: PlacesGroupBy.Country, - isDisabled: () => false, - }, -]; - -export const findGroupOptionMetadata = (groupBy: string) => { - // Default is no grouping - const defaultGroupOption = groupOptionsMetadata[0]; - return groupOptionsMetadata.find(({ id }) => groupBy === id) ?? defaultGroupOption; -}; - -export const getSelectedPlacesGroupOption = (settings: PlacesViewSettings) => { - const defaultGroupOption = PlacesGroupBy.None; - const albumGroupOption = settings.groupBy ?? defaultGroupOption; - - if (findGroupOptionMetadata(albumGroupOption).isDisabled()) { - return defaultGroupOption; - } - return albumGroupOption; -}; - /** * ---------------------------- * Places Groups Collapse/Expand diff --git a/web/src/routes/(user)/places/PlacesControls.svelte b/web/src/routes/(user)/places/PlacesControls.svelte index ae1308fdd3..f3fa38cb28 100644 --- a/web/src/routes/(user)/places/PlacesControls.svelte +++ b/web/src/routes/(user)/places/PlacesControls.svelte @@ -1,24 +1,11 @@ - - - ({ - title: placesGroupByNames[id], - icon: groupIcon, - disabled: isDisabled(), - })} -/> +
+