From f5f4059fa59ac33cde5d56116b2ea481abca23f2 Mon Sep 17 00:00:00 2001 From: Jonathan Jogenfors Date: Sun, 26 Jan 2025 00:26:07 +0100 Subject: [PATCH] wip --- .../openapi/lib/model/asset_response_dto.dart | 36 ++++++++++++------- open-api/immich-openapi-specs.json | 3 ++ open-api/typescript-sdk/src/fetch-client.ts | 6 ++-- server/src/db.d.ts | 32 ++++++++++++++--- server/src/dtos/album.dto.ts | 4 +-- server/src/dtos/asset-response.dto.ts | 6 ++-- server/src/queries/access.repository.sql | 4 +-- server/src/queries/asset.repository.sql | 14 ++++---- server/src/queries/search.repository.sql | 34 +++++++++--------- .../queries/system.metadata.repository.sql | 3 +- server/test/medium/metadata.service.spec.ts | 12 +++++++ 11 files changed, 103 insertions(+), 51 deletions(-) diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 5f01f84419e4e0..5d4142b3bf00a1 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -64,9 +64,9 @@ class AssetResponseDto { /// ExifResponseDto? exifInfo; - DateTime fileCreatedAt; + DateTime? fileCreatedAt; - DateTime fileModifiedAt; + DateTime? fileModifiedAt; bool hasMetadata; @@ -85,7 +85,7 @@ class AssetResponseDto { String? livePhotoVideoId; - DateTime localDateTime; + DateTime? localDateTime; String originalFileName; @@ -174,8 +174,8 @@ class AssetResponseDto { (duplicateId == null ? 0 : duplicateId!.hashCode) + (duration.hashCode) + (exifInfo == null ? 0 : exifInfo!.hashCode) + - (fileCreatedAt.hashCode) + - (fileModifiedAt.hashCode) + + (fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) + + (fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) + (hasMetadata.hashCode) + (id.hashCode) + (isArchived.hashCode) + @@ -184,7 +184,7 @@ class AssetResponseDto { (isTrashed.hashCode) + (libraryId == null ? 0 : libraryId!.hashCode) + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + - (localDateTime.hashCode) + + (localDateTime == null ? 0 : localDateTime!.hashCode) + (originalFileName.hashCode) + (originalMimeType == null ? 0 : originalMimeType!.hashCode) + (originalPath.hashCode) + @@ -218,8 +218,16 @@ class AssetResponseDto { } else { // json[r'exifInfo'] = null; } - json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String(); - json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String(); + if (this.fileCreatedAt != null) { + json[r'fileCreatedAt'] = this.fileCreatedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileCreatedAt'] = null; + } + if (this.fileModifiedAt != null) { + json[r'fileModifiedAt'] = this.fileModifiedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileModifiedAt'] = null; + } json[r'hasMetadata'] = this.hasMetadata; json[r'id'] = this.id; json[r'isArchived'] = this.isArchived; @@ -236,7 +244,11 @@ class AssetResponseDto { } else { // json[r'livePhotoVideoId'] = null; } - json[r'localDateTime'] = this.localDateTime.toUtc().toIso8601String(); + if (this.localDateTime != null) { + json[r'localDateTime'] = this.localDateTime!.toUtc().toIso8601String(); + } else { + // json[r'localDateTime'] = null; + } json[r'originalFileName'] = this.originalFileName; if (this.originalMimeType != null) { json[r'originalMimeType'] = this.originalMimeType; @@ -288,8 +300,8 @@ class AssetResponseDto { duplicateId: mapValueOfType(json, r'duplicateId'), duration: mapValueOfType(json, r'duration')!, exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']), - fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!, - fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!, + fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r''), + fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''), hasMetadata: mapValueOfType(json, r'hasMetadata')!, id: mapValueOfType(json, r'id')!, isArchived: mapValueOfType(json, r'isArchived')!, @@ -298,7 +310,7 @@ class AssetResponseDto { isTrashed: mapValueOfType(json, r'isTrashed')!, libraryId: mapValueOfType(json, r'libraryId'), livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), - localDateTime: mapDateTime(json, r'localDateTime', r'')!, + localDateTime: mapDateTime(json, r'localDateTime', r''), originalFileName: mapValueOfType(json, r'originalFileName')!, originalMimeType: mapValueOfType(json, r'originalMimeType'), originalPath: mapValueOfType(json, r'originalPath')!, diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 5aac20c1c04e64..7fbce8ab017fda 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -8450,10 +8450,12 @@ }, "fileCreatedAt": { "format": "date-time", + "nullable": true, "type": "string" }, "fileModifiedAt": { "format": "date-time", + "nullable": true, "type": "string" }, "hasMetadata": { @@ -8486,6 +8488,7 @@ }, "localDateTime": { "format": "date-time", + "nullable": true, "type": "string" }, "originalFileName": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index ce521bf8472af7..0fde2448502b37 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -243,8 +243,8 @@ export type AssetResponseDto = { duplicateId?: string | null; duration: string; exifInfo?: ExifResponseDto; - fileCreatedAt: string; - fileModifiedAt: string; + fileCreatedAt: string | null; + fileModifiedAt: string | null; hasMetadata: boolean; id: string; isArchived: boolean; @@ -254,7 +254,7 @@ export type AssetResponseDto = { /** This property was deprecated in v1.106.0 */ libraryId?: string | null; livePhotoVideoId?: string | null; - localDateTime: string; + localDateTime: string | null; originalFileName: string; originalMimeType?: string; originalPath: string; diff --git a/server/src/db.d.ts b/server/src/db.d.ts index 1f87f346c71b1e..63bcc9cbf4e5ee 100644 --- a/server/src/db.d.ts +++ b/server/src/db.d.ts @@ -126,8 +126,8 @@ export interface Assets { duplicateId: string | null; duration: string | null; encodedVideoPath: Generated; - fileCreatedAt: Timestamp; - fileModifiedAt: Timestamp; + fileCreatedAt: Timestamp | null; + fileModifiedAt: Timestamp | null; id: Generated; isArchived: Generated; isExternal: Generated; @@ -136,7 +136,7 @@ export interface Assets { isVisible: Generated; libraryId: string | null; livePhotoVideoId: string | null; - localDateTime: Timestamp; + localDateTime: Timestamp | null; originalFileName: string; originalPath: string; ownerId: string; @@ -214,6 +214,20 @@ export interface GeodataPlaces { name: string; } +export interface GeodataPlacesTmp { + admin1Code: string | null; + admin1Name: string | null; + admin2Code: string | null; + admin2Name: string | null; + alternateNames: string | null; + countryCode: string; + id: number; + latitude: number; + longitude: number; + modificationDate: Timestamp; + name: string; +} + export interface Libraries { createdAt: Generated; deletedAt: Timestamp | null; @@ -262,7 +276,15 @@ export interface NaturalearthCountries { admin: string; admin_a3: string; coordinates: string; - id: number; + id: Generated; + type: string; +} + +export interface NaturalearthCountriesTmp { + admin: string; + admin_a3: string; + coordinates: string; + id: Generated; type: string; } @@ -418,12 +440,14 @@ export interface DB { exif: Exif; face_search: FaceSearch; geodata_places: GeodataPlaces; + geodata_places_tmp: GeodataPlacesTmp; libraries: Libraries; memories: Memories; memories_assets_assets: MemoriesAssetsAssets; migrations: Migrations; move_history: MoveHistory; naturalearth_countries: NaturalearthCountries; + naturalearth_countries_tmp: NaturalearthCountriesTmp; partners: Partners; person: Person; sessions: Sessions; diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 2f99b958c41783..f43ab9886b65dd 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -165,8 +165,8 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt const hasSharedLink = entity.sharedLinks?.length > 0; const hasSharedUser = sharedUsers.length > 0; - let startDate = getAssetDateTime(assets.at(0)); - let endDate = getAssetDateTime(assets.at(-1)); + let startDate = getAssetDateTime(assets.at(0)) ?? undefined; + let endDate = getAssetDateTime(assets.at(-1)) ?? undefined; // Swap dates if start date is greater than end date. if (startDate && endDate && startDate > endDate) { [startDate, endDate] = [endDate, startDate]; diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 0658567912a35a..6f0e557cc236b0 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -21,7 +21,7 @@ export class SanitizedAssetResponseDto { type!: AssetType; thumbhash!: string | null; originalMimeType?: string; - localDateTime!: Date; + localDateTime!: Date | null; duration!: string; livePhotoVideoId?: string | null; hasMetadata!: boolean; @@ -36,8 +36,8 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { libraryId?: string | null; originalPath!: string; originalFileName!: string; - fileCreatedAt!: Date; - fileModifiedAt!: Date; + fileCreatedAt!: Date | null; + fileModifiedAt!: Date | null; updatedAt!: Date; isFavorite!: boolean; isArchived!: boolean; diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql index dd58aebcb2034c..db69ac670e0244 100644 --- a/server/src/queries/access.repository.sql +++ b/server/src/queries/access.repository.sql @@ -83,7 +83,7 @@ from left join "users" on "users"."id" = "albumUsers"."usersId" and "users"."deletedAt" is null where - array["assets"."id", "assets"."livePhotoVideoId"] && array[$1]::uuid[] + array["assets"."id", "assets"."livePhotoVideoId"] && array[$1]::uuid [] and ( "albums"."ownerId" = $2 or "users"."id" = $3 @@ -136,7 +136,7 @@ where "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId" - ] && array[$2]::uuid[] + ] && array[$2]::uuid [] -- AccessRepository.authDevice.checkOwnerAccess select diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index 948f7dd1143fa2..89670748a4b909 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -35,7 +35,7 @@ with where "asset_job_status"."previewAt" is not null and (assets."localDateTime" at time zone 'UTC')::date = today.date - and "assets"."ownerId" = any ($3::uuid[]) + and "assets"."ownerId" = any ($3::uuid []) and "assets"."isVisible" = $4 and "assets"."isArchived" = $5 and exists ( @@ -72,7 +72,7 @@ select from "assets" where - "assets"."id" = any ($1::uuid[]) + "assets"."id" = any ($1::uuid []) -- AssetRepository.getByIdsWithAllRelations select @@ -130,7 +130,7 @@ from "asset_stack"."id" ) as "stacked_assets" on "asset_stack"."id" is not null where - "assets"."id" = any ($2::uuid[]) + "assets"."id" = any ($2::uuid []) -- AssetRepository.deleteAll delete from "assets" @@ -182,7 +182,7 @@ update "assets" set "deviceId" = $1 where - "id" = any ($2::uuid[]) + "id" = any ($2::uuid []) -- AssetRepository.updateDuplicates update "assets" @@ -190,8 +190,8 @@ set "duplicateId" = $1 where ( - "duplicateId" = any ($2::uuid[]) - or "id" = any ($3::uuid[]) + "duplicateId" = any ($2::uuid []) + or "id" = any ($3::uuid []) ) -- AssetRepository.getByChecksum @@ -438,7 +438,7 @@ from "asset_stack"."id" ) as "stacked_assets" on "asset_stack"."id" is not null where - "assets"."ownerId" = any ($1::uuid[]) + "assets"."ownerId" = any ($1::uuid []) and "isVisible" = $2 and "updatedAt" > $3 limit diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql index 2d5da4d3811126..c714eec720782a 100644 --- a/server/src/queries/search.repository.sql +++ b/server/src/queries/search.repository.sql @@ -9,7 +9,7 @@ from where "assets"."fileCreatedAt" >= $1 and "exif"."lensModel" = $2 - and "assets"."ownerId" = any ($3::uuid[]) + and "assets"."ownerId" = any ($3::uuid []) and "assets"."isFavorite" = $4 and "assets"."isArchived" = $5 and "assets"."deletedAt" is null @@ -30,7 +30,7 @@ offset where "assets"."fileCreatedAt" >= $1 and "exif"."lensModel" = $2 - and "assets"."ownerId" = any ($3::uuid[]) + and "assets"."ownerId" = any ($3::uuid []) and "assets"."isFavorite" = $4 and "assets"."isArchived" = $5 and "assets"."deletedAt" is null @@ -50,7 +50,7 @@ union all where "assets"."fileCreatedAt" >= $8 and "exif"."lensModel" = $9 - and "assets"."ownerId" = any ($10::uuid[]) + and "assets"."ownerId" = any ($10::uuid []) and "assets"."isFavorite" = $11 and "assets"."isArchived" = $12 and "assets"."deletedAt" is null @@ -73,12 +73,12 @@ from where "assets"."fileCreatedAt" >= $1 and "exif"."lensModel" = $2 - and "assets"."ownerId" = any ($3::uuid[]) + and "assets"."ownerId" = any ($3::uuid []) and "assets"."isFavorite" = $4 and "assets"."isArchived" = $5 and "assets"."deletedAt" is null order by - smart_search.embedding <=> $6 + smart_search.embedding <= > $6 limit $7 offset @@ -90,18 +90,18 @@ with select "assets"."id" as "assetId", "assets"."duplicateId", - smart_search.embedding <=> $1 as "distance" + smart_search.embedding <= > $1 as "distance" from "assets" inner join "smart_search" on "assets"."id" = "smart_search"."assetId" where - "assets"."ownerId" = any ($2::uuid[]) + "assets"."ownerId" = any ($2::uuid []) and "assets"."deletedAt" is null and "assets"."isVisible" = $3 and "assets"."type" = $4 and "assets"."id" != $5::uuid order by - smart_search.embedding <=> $6 + smart_search.embedding <= > $6 limit $7 ) @@ -118,16 +118,16 @@ with select "asset_faces"."id", "asset_faces"."personId", - face_search.embedding <=> $1 as "distance" + face_search.embedding <= > $1 as "distance" from "asset_faces" inner join "assets" on "assets"."id" = "asset_faces"."assetId" inner join "face_search" on "face_search"."faceId" = "asset_faces"."id" where - "assets"."ownerId" = any ($2::uuid[]) + "assets"."ownerId" = any ($2::uuid []) and "assets"."deletedAt" is null order by - face_search.embedding <=> $3 + face_search.embedding <= > $3 limit $4 ) @@ -173,7 +173,7 @@ with recursive "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "assets"."ownerId" = any ($1::uuid[]) + "assets"."ownerId" = any ($1::uuid []) and "assets"."isVisible" = $2 and "assets"."isArchived" = $3 and "assets"."type" = $4 @@ -198,7 +198,7 @@ with recursive "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "assets"."ownerId" = any ($6::uuid[]) + "assets"."ownerId" = any ($6::uuid []) and "assets"."isVisible" = $7 and "assets"."isArchived" = $8 and "assets"."type" = $9 @@ -228,7 +228,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid[]) + "ownerId" = any ($1::uuid []) and "isVisible" = $2 and "deletedAt" is null and "state" is not null @@ -240,7 +240,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid[]) + "ownerId" = any ($1::uuid []) and "isVisible" = $2 and "deletedAt" is null and "city" is not null @@ -252,7 +252,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid[]) + "ownerId" = any ($1::uuid []) and "isVisible" = $2 and "deletedAt" is null and "make" is not null @@ -264,7 +264,7 @@ from "exif" inner join "assets" on "assets"."id" = "exif"."assetId" where - "ownerId" = any ($1::uuid[]) + "ownerId" = any ($1::uuid []) and "isVisible" = $2 and "deletedAt" is null and "model" is not null diff --git a/server/src/queries/system.metadata.repository.sql b/server/src/queries/system.metadata.repository.sql index c4fd7b96f86dd6..996ac148030921 100644 --- a/server/src/queries/system.metadata.repository.sql +++ b/server/src/queries/system.metadata.repository.sql @@ -13,7 +13,8 @@ insert into "system_metadata" ("key", "value") values ($1, $2) -on conflict ("key") do update +on conflict ("key") do +update set "value" = $3 diff --git a/server/test/medium/metadata.service.spec.ts b/server/test/medium/metadata.service.spec.ts index 17505840180c97..00a2ef5c34406c 100644 --- a/server/test/medium/metadata.service.spec.ts +++ b/server/test/medium/metadata.service.spec.ts @@ -29,6 +29,8 @@ type TimeZoneTest = { description: string; serverTimeZone?: string; exifData: Record; + fileCreatedAt: string; + fileModifiedAt: string; expected: { localDateTime: string; dateTimeOriginal: string; @@ -58,6 +60,8 @@ describe(MetadataService.name, () => { const timeZoneTests: TimeZoneTest[] = [ { description: 'should handle no time zone information', + fileCreatedAt: '2022-01-01T00:00:00.000Z', + fileModifiedAt: '2022-01-01T00:00:00.000Z', exifData: { DateTimeOriginal: '2022:01:01 00:00:00', }, @@ -69,6 +73,8 @@ describe(MetadataService.name, () => { }, { description: 'should handle no time zone information and server behind UTC', + fileCreatedAt: '2022-01-01T00:00:00.000Z', + fileModifiedAt: '2022-01-01T00:00:00.000Z', serverTimeZone: 'America/Los_Angeles', exifData: { DateTimeOriginal: '2022:01:01 00:00:00', @@ -81,6 +87,8 @@ describe(MetadataService.name, () => { }, { description: 'should handle no time zone information and server ahead of UTC', + fileCreatedAt: '2022-01-01T00:00:00.000Z', + fileModifiedAt: '2022-01-01T00:00:00.000Z', serverTimeZone: 'Europe/Brussels', exifData: { DateTimeOriginal: '2022:01:01 00:00:00', @@ -93,6 +101,8 @@ describe(MetadataService.name, () => { }, { description: 'should handle no time zone information and server ahead of UTC in the summer', + fileCreatedAt: '2022-01-01T00:00:00.000Z', + fileModifiedAt: '2022-01-01T00:00:00.000Z', serverTimeZone: 'Europe/Brussels', exifData: { DateTimeOriginal: '2022:06:01 00:00:00', @@ -105,6 +115,8 @@ describe(MetadataService.name, () => { }, { description: 'should handle a +13:00 time zone', + fileCreatedAt: '2022-01-01T00:00:00.000Z', + fileModifiedAt: '2022-01-01T00:00:00.000Z', exifData: { DateTimeOriginal: '2022:01:01 00:00:00+13:00', },