From 5fb8d651ecbac621ae1216f29cbc1e1465d46222 Mon Sep 17 00:00:00 2001 From: Wingy Date: Mon, 25 Aug 2025 11:27:21 -0400 Subject: [PATCH] feat: expose createdAt in getAssetInfo (#21184) * Expose createdAt in getAssetInfo * Add missing createdAt fields --- mobile/openapi/lib/model/asset_response_dto.dart | 11 ++++++++++- open-api/immich-openapi-specs.json | 7 +++++++ open-api/typescript-sdk/src/fetch-client.ts | 2 ++ server/src/dtos/asset-response.dto.ts | 8 ++++++++ server/test/fixtures/shared-link.stub.ts | 1 + web/src/test-data/factories/asset-factory.ts | 1 + 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index e2f60937f8..dc957b3bfc 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -14,6 +14,7 @@ class AssetResponseDto { /// Returns a new [AssetResponseDto] instance. AssetResponseDto({ required this.checksum, + required this.createdAt, required this.deviceAssetId, required this.deviceId, this.duplicateId, @@ -49,6 +50,9 @@ class AssetResponseDto { /// base64 encoded sha1 hash String checksum; + /// The UTC timestamp when the asset was originally uploaded to Immich. + DateTime createdAt; + String deviceAssetId; String deviceId; @@ -142,6 +146,7 @@ class AssetResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && other.checksum == checksum && + other.createdAt == createdAt && other.deviceAssetId == deviceAssetId && other.deviceId == deviceId && other.duplicateId == duplicateId && @@ -177,6 +182,7 @@ class AssetResponseDto { int get hashCode => // ignore: unnecessary_parenthesis (checksum.hashCode) + + (createdAt.hashCode) + (deviceAssetId.hashCode) + (deviceId.hashCode) + (duplicateId == null ? 0 : duplicateId!.hashCode) + @@ -209,11 +215,12 @@ class AssetResponseDto { (visibility.hashCode); @override - String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt, visibility=$visibility]'; + String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt, visibility=$visibility]'; Map toJson() { final json = {}; json[r'checksum'] = this.checksum; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); json[r'deviceAssetId'] = this.deviceAssetId; json[r'deviceId'] = this.deviceId; if (this.duplicateId != null) { @@ -293,6 +300,7 @@ class AssetResponseDto { return AssetResponseDto( checksum: mapValueOfType(json, r'checksum')!, + createdAt: mapDateTime(json, r'createdAt', r'')!, deviceAssetId: mapValueOfType(json, r'deviceAssetId')!, deviceId: mapValueOfType(json, r'deviceId')!, duplicateId: mapValueOfType(json, r'duplicateId'), @@ -371,6 +379,7 @@ class AssetResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { 'checksum', + 'createdAt', 'deviceAssetId', 'deviceId', 'duration', diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index d9d10e0d66..eb9b6ac5a9 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -10720,6 +10720,12 @@ "description": "base64 encoded sha1 hash", "type": "string" }, + "createdAt": { + "description": "The UTC timestamp when the asset was originally uploaded to Immich.", + "example": "2024-01-15T20:30:00.000Z", + "format": "date-time", + "type": "string" + }, "deviceAssetId": { "type": "string" }, @@ -10855,6 +10861,7 @@ }, "required": [ "checksum", + "createdAt", "deviceAssetId", "deviceId", "duration", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 8d83f9d119..08fa714823 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -317,6 +317,8 @@ export type TagResponseDto = { export type AssetResponseDto = { /** base64 encoded sha1 hash */ checksum: string; + /** The UTC timestamp when the asset was originally uploaded to Immich. */ + createdAt: string; deviceAssetId: string; deviceId: string; duplicateId?: string | null; diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 98ed8669f0..f60f2a8824 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -37,6 +37,13 @@ export class SanitizedAssetResponseDto { } export class AssetResponseDto extends SanitizedAssetResponseDto { + @ApiProperty({ + type: 'string', + format: 'date-time', + description: 'The UTC timestamp when the asset was originally uploaded to Immich.', + example: '2024-01-15T20:30:00.000Z', + }) + createdAt!: Date; deviceAssetId!: string; deviceId!: string; ownerId!: string; @@ -190,6 +197,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset return { id: entity.id, + createdAt: entity.createdAt, deviceAssetId: entity.deviceAssetId, ownerId: entity.ownerId, owner: entity.owner ? mapUser(entity.owner) : undefined, diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index 1cd36f1f23..19a62ad193 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -46,6 +46,7 @@ const assetInfo: ExifResponseDto = { const assetResponse: AssetResponseDto = { id: 'id_1', + createdAt: today, deviceAssetId: 'device_asset_id_1', ownerId: 'user_id_1', deviceId: 'device_id_1', diff --git a/web/src/test-data/factories/asset-factory.ts b/web/src/test-data/factories/asset-factory.ts index c2f03f9c6a..8d328ddcc7 100644 --- a/web/src/test-data/factories/asset-factory.ts +++ b/web/src/test-data/factories/asset-factory.ts @@ -6,6 +6,7 @@ import { Sync } from 'factory.ts'; export const assetFactory = Sync.makeFactory({ id: Sync.each(() => faker.string.uuid()), + createdAt: Sync.each(() => faker.date.past().toISOString()), deviceAssetId: Sync.each(() => faker.string.uuid()), ownerId: Sync.each(() => faker.string.uuid()), deviceId: '',