From e213a741943d352017f3ac53484295947da914fe Mon Sep 17 00:00:00 2001
From: Jonathan Jogenfors <jonathan@jogenfors.se>
Date: Mon, 27 Jan 2025 23:53:15 +0100
Subject: [PATCH] wip

---
 e2e/src/api/specs/library.e2e-spec.ts         |  1 +
 server/src/dtos/asset-response.dto.ts         |  8 ++++++
 server/src/entities/asset.entity.ts           |  5 +++-
 server/src/interfaces/search.interface.ts     |  3 ++
 server/src/queries/access.repository.sql      | 12 ++++++++
 server/src/queries/activity.repository.sql    |  6 ++++
 server/src/queries/album.repository.sql       |  6 ++++
 server/src/queries/asset.repository.sql       |  8 ++++--
 server/src/queries/memory.repository.sql      |  6 ++++
 server/src/queries/person.repository.sql      |  6 ++++
 server/src/queries/search.repository.sql      | 24 ++++++++++++++++
 server/src/queries/shared.link.repository.sql |  9 ++++++
 server/src/queries/stack.repository.sql       |  9 ++++++
 server/src/repositories/access.repository.ts  | 28 ++++++++++++++++---
 .../src/repositories/activity.repository.ts   | 12 +++++++-
 server/src/repositories/album.repository.ts   | 13 ++++++++-
 server/src/repositories/asset.repository.ts   |  8 ++++--
 server/src/repositories/memory.repository.ts  |  5 +++-
 server/src/repositories/person.repository.ts  | 11 +++++++-
 server/src/repositories/search.repository.ts  | 12 ++++++++
 .../repositories/shared-link.repository.ts    |  9 ++++++
 server/src/repositories/stack.repository.ts   |  8 +++++-
 .../src/services/asset-media.service.spec.ts  | 10 +++++++
 server/src/utils/date-time.ts                 |  4 +--
 24 files changed, 207 insertions(+), 16 deletions(-)

diff --git a/e2e/src/api/specs/library.e2e-spec.ts b/e2e/src/api/specs/library.e2e-spec.ts
index e2e69529fbb9d0..21fb945d1a9a0e 100644
--- a/e2e/src/api/specs/library.e2e-spec.ts
+++ b/e2e/src/api/specs/library.e2e-spec.ts
@@ -298,6 +298,7 @@ describe('/libraries', () => {
       expect(status).toBe(204);
 
       await utils.waitForQueueFinish(admin.accessToken, 'library');
+      await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
 
       const { assets } = await utils.searchAssets(admin.accessToken, {
         originalPath: `${testAssetDirInternal}/temp/directoryA/assetA.png`,
diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts
index 0658567912a35a..5ac10c57a30b47 100644
--- a/server/src/dtos/asset-response.dto.ts
+++ b/server/src/dtos/asset-response.dto.ts
@@ -113,6 +113,14 @@ const hexOrBufferToBase64 = (encoded: string | Buffer) => {
 export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto {
   const { stripMetadata = false, withStack = false } = options;
 
+  if (!entity.localDateTime) {
+    throw new Error('Asset localDateTime is missing');
+  } else if (!entity.fileCreatedAt) {
+    throw new Error('Asset fileCreatedAt is missing');
+  } else if (!entity.fileModifiedAt) {
+    throw new Error('Asset fileModifiedAt is missing');
+  }
+
   if (stripMetadata) {
     const sanitizedAssetResponse: SanitizedAssetResponseDto = {
       id: entity.id,
diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts
index 420fedfc53e6f8..4ed74acec7d4ec 100644
--- a/server/src/entities/asset.entity.ts
+++ b/server/src/entities/asset.entity.ts
@@ -399,5 +399,8 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
     )
     .$if(!!options.withExif, withExifInner)
     .$if(!!(options.withFaces || options.withPeople || options.personIds), (qb) => qb.select(withFacesAndPeople))
-    .$if(!options.withDeleted, (qb) => qb.where('assets.deletedAt', 'is', null));
+    .$if(!options.withDeleted, (qb) => qb.where('assets.deletedAt', 'is', null))
+    .$if(!options.withNullFileModifiedAt, (qb) => qb.where('assets.fileModifiedAt', 'is not', null))
+    .$if(!options.withNullFileCreatedAt, (qb) => qb.where('assets.fileCreatedAt', 'is not', null))
+    .$if(!options.withNullLocalDateTime, (qb) => qb.where('assets.localDateTime', 'is not', null));
 }
diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts
index bb76ff7b1fd044..d6a109e89a7cb3 100644
--- a/server/src/interfaces/search.interface.ts
+++ b/server/src/interfaces/search.interface.ts
@@ -63,6 +63,9 @@ export interface SearchStatusOptions {
   status?: AssetStatus;
   withArchived?: boolean;
   withDeleted?: boolean;
+  withNullLocalDateTime?: boolean;
+  withNullFileCreatedAt?: boolean;
+  withNullFileModifiedAt?: boolean;
 }
 
 export interface SearchOneToOneRelationOptions {
diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql
index dd58aebcb2034c..3fdf92561aedbd 100644
--- a/server/src/queries/access.repository.sql
+++ b/server/src/queries/access.repository.sql
@@ -79,6 +79,9 @@ from
   inner join "albums_assets_assets" as "albumAssets" on "albums"."id" = "albumAssets"."albumsId"
   inner join "assets" on "assets"."id" = "albumAssets"."assetsId"
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
   left join "albums_shared_users_users" as "albumUsers" on "albumUsers"."albumsId" = "albums"."id"
   left join "users" on "users"."id" = "albumUsers"."usersId"
   and "users"."deletedAt" is null
@@ -108,6 +111,9 @@ from
   and "sharedBy"."deletedAt" is null
   inner join "assets" on "assets"."ownerId" = "sharedBy"."id"
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
 where
   "partner"."sharedWithId" = $1
   and "assets"."isArchived" = $2
@@ -126,6 +132,9 @@ from
   left join "shared_link__asset" on "shared_link__asset"."sharedLinksId" = "shared_links"."id"
   left join "assets" on "assets"."id" = "shared_link__asset"."assetsId"
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
   left join "albums_assets_assets" on "albums_assets_assets"."albumsId" = "albums"."id"
   left join "assets" as "albumAssets" on "albumAssets"."id" = "albums_assets_assets"."assetsId"
   and "albumAssets"."deletedAt" is null
@@ -173,6 +182,9 @@ from
   "asset_faces"
   left join "assets" on "assets"."id" = "asset_faces"."assetId"
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
 where
   "asset_faces"."id" in ($1)
   and "assets"."ownerId" = $2
diff --git a/server/src/queries/activity.repository.sql b/server/src/queries/activity.repository.sql
index 8e9bb11f2500cd..782d4fb160a30e 100644
--- a/server/src/queries/activity.repository.sql
+++ b/server/src/queries/activity.repository.sql
@@ -25,6 +25,9 @@ from
   "activity"
   left join "assets" on "assets"."id" = "activity"."assetId"
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
 where
   "activity"."albumId" = $1
 order by
@@ -43,3 +46,6 @@ where
   and "activity"."albumId" = $2
   and "activity"."isLiked" = $3
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql
index 08ea078f730cdf..525a634d6281bc 100644
--- a/server/src/queries/album.repository.sql
+++ b/server/src/queries/album.repository.sql
@@ -98,6 +98,9 @@ select
         where
           "albums_assets_assets"."albumsId" = "albums"."id"
           and "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
         order by
           "assets"."fileCreatedAt" desc
       ) as "asset"
@@ -212,6 +215,9 @@ from
 where
   "albums"."id" in ($1)
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
 group by
   "albums"."id"
 
diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql
index 9bbed8dbe1b485..3431ce7540b67c 100644
--- a/server/src/queries/asset.repository.sql
+++ b/server/src/queries/asset.repository.sql
@@ -37,8 +37,6 @@ with
           and (assets."localDateTime" at time zone 'UTC')::date = today.date
           and "assets"."ownerId" = any ($3::uuid[])
           and "assets"."isVisible" = $4
-          and "assets"."fileCreatedAt" is not null
-          and "assets"."fileModifiedAt" is not null
           and "assets"."isArchived" = $5
           and exists (
             select
@@ -49,6 +47,9 @@ with
               and "asset_files"."type" = $6
           )
           and "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
         limit
           $7
       ) as "a" on true
@@ -266,6 +267,7 @@ with
       and "assets"."isVisible" = $2
       and "assets"."fileCreatedAt" is not null
       and "assets"."fileModifiedAt" is not null
+      and "assets"."localDateTime" is not null
   )
 select
   "timeBucket",
@@ -308,6 +310,7 @@ where
   and "assets"."isVisible" = $2
   and "assets"."fileCreatedAt" is not null
   and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
   and date_trunc($3, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $4
 order by
   "assets"."localDateTime" desc
@@ -336,6 +339,7 @@ with
       and "assets"."isVisible" = $2
       and "assets"."fileCreatedAt" is not null
       and "assets"."fileModifiedAt" is not null
+      and "assets"."localDateTime" is not null
     group by
       "assets"."duplicateId"
   ),
diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql
index 3144f314dde7b8..98b4b206dfe329 100644
--- a/server/src/queries/memory.repository.sql
+++ b/server/src/queries/memory.repository.sql
@@ -26,6 +26,9 @@ select
         where
           "memories_assets_assets"."memoriesId" = "memories"."id"
           and "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
       ) as agg
   ) as "assets"
 from
@@ -56,6 +59,9 @@ select
         where
           "memories_assets_assets"."memoriesId" = "memories"."id"
           and "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
       ) as agg
   ) as "assets"
 from
diff --git a/server/src/queries/person.repository.sql b/server/src/queries/person.repository.sql
index 2c06d7c3f2c270..f89fe7b342e55a 100644
--- a/server/src/queries/person.repository.sql
+++ b/server/src/queries/person.repository.sql
@@ -169,6 +169,9 @@ from
   and "asset_faces"."personId" = $1
   and "assets"."isArchived" = $2
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
   and "assets"."livePhotoVideoId" is null
 
 -- PersonRepository.getNumberOfPeople
@@ -183,6 +186,9 @@ from
   inner join "asset_faces" on "asset_faces"."personId" = "person"."id"
   inner join "assets" on "assets"."id" = "asset_faces"."assetId"
   and "assets"."deletedAt" is null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."localDateTime" is not null
   and "assets"."isArchived" = $2
 where
   "person"."ownerId" = $3
diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql
index 72e8a6941d3629..490568847f0aee 100644
--- a/server/src/queries/search.repository.sql
+++ b/server/src/queries/search.repository.sql
@@ -13,6 +13,9 @@ where
   and "assets"."isFavorite" = $4
   and "assets"."isArchived" = $5
   and "assets"."deletedAt" is null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."localDateTime" is not null
 order by
   "assets"."fileCreatedAt" desc
 limit
@@ -34,6 +37,9 @@ offset
     and "assets"."isFavorite" = $4
     and "assets"."isArchived" = $5
     and "assets"."deletedAt" is null
+    and "assets"."fileModifiedAt" is not null
+    and "assets"."fileCreatedAt" is not null
+    and "assets"."localDateTime" is not null
     and "assets"."id" < $6
   order by
     random()
@@ -54,6 +60,9 @@ union all
     and "assets"."isFavorite" = $11
     and "assets"."isArchived" = $12
     and "assets"."deletedAt" is null
+    and "assets"."fileModifiedAt" is not null
+    and "assets"."fileCreatedAt" is not null
+    and "assets"."localDateTime" is not null
     and "assets"."id" > $13
   order by
     random()
@@ -77,6 +86,9 @@ where
   and "assets"."isFavorite" = $4
   and "assets"."isArchived" = $5
   and "assets"."deletedAt" is null
+  and "assets"."fileModifiedAt" is not null
+  and "assets"."fileCreatedAt" is not null
+  and "assets"."localDateTime" is not null
 order by
   smart_search.embedding <=> $6
 limit
@@ -98,6 +110,9 @@ with
       "assets"."ownerId" = any ($2::uuid[])
       and "assets"."deletedAt" is null
       and "assets"."isVisible" = $3
+      and "assets"."fileCreatedAt" is not null
+      and "assets"."fileModifiedAt" is not null
+      and "assets"."localDateTime" is not null
       and "assets"."type" = $4
       and "assets"."id" != $5::uuid
     order by
@@ -126,6 +141,9 @@ with
     where
       "assets"."ownerId" = any ($2::uuid[])
       and "assets"."deletedAt" is null
+      and "assets"."fileCreatedAt" is not null
+      and "assets"."fileModifiedAt" is not null
+      and "assets"."localDateTime" is not null
     order by
       face_search.embedding <=> $3
     limit
@@ -178,6 +196,9 @@ with recursive
         and "assets"."isArchived" = $3
         and "assets"."type" = $4
         and "assets"."deletedAt" is null
+        and "assets"."fileCreatedAt" is not null
+        and "assets"."fileModifiedAt" is not null
+        and "assets"."localDateTime" is not null
       order by
         "city"
       limit
@@ -203,6 +224,9 @@ with recursive
             and "assets"."isArchived" = $8
             and "assets"."type" = $9
             and "assets"."deletedAt" is null
+            and "assets"."fileCreatedAt" is not null
+            and "assets"."fileModifiedAt" is not null
+            and "assets"."localDateTime" is not null
             and "exif"."city" > "cte"."city"
           order by
             "city"
diff --git a/server/src/queries/shared.link.repository.sql b/server/src/queries/shared.link.repository.sql
index 1861ed86e472ce..98b0334ea3e16e 100644
--- a/server/src/queries/shared.link.repository.sql
+++ b/server/src/queries/shared.link.repository.sql
@@ -31,6 +31,9 @@ from
     where
       "shared_links"."id" = "shared_link__asset"."sharedLinksId"
       and "assets"."deletedAt" is null
+      and "assets"."fileCreatedAt" is not null
+      and "assets"."fileModifiedAt" is not null
+      and "assets"."localDateTime" is not null
     order by
       "assets"."fileCreatedAt" asc
   ) as "a" on true
@@ -65,6 +68,9 @@ from
         where
           "albums_assets_assets"."assetsId" = "assets"."id"
           and "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
         order by
           "assets"."fileCreatedAt" asc
       ) as "assets" on true
@@ -112,6 +118,9 @@ from
     where
       "assets"."id" = "shared_link__asset"."assetsId"
       and "assets"."deletedAt" is null
+      and "assets"."fileCreatedAt" is not null
+      and "assets"."fileModifiedAt" is not null
+      and "assets"."localDateTime" is not null
   ) as "assets" on true
   left join lateral (
     select
diff --git a/server/src/queries/stack.repository.sql b/server/src/queries/stack.repository.sql
index 0fd1b233be4131..8a95109f4d0767 100644
--- a/server/src/queries/stack.repository.sql
+++ b/server/src/queries/stack.repository.sql
@@ -23,6 +23,9 @@ select
           ) as "exifInfo" on true
         where
           "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
           and "assets"."stackId" = "asset_stack"."id"
       ) as agg
   ) as "assets"
@@ -68,6 +71,9 @@ select
           ) as "exifInfo" on true
         where
           "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
           and "assets"."stackId" = "asset_stack"."id"
       ) as agg
   ) as "assets"
@@ -113,6 +119,9 @@ select
           ) as "exifInfo" on true
         where
           "assets"."deletedAt" is null
+          and "assets"."fileCreatedAt" is not null
+          and "assets"."fileModifiedAt" is not null
+          and "assets"."localDateTime" is not null
           and "assets"."stackId" = "asset_stack"."id"
       ) as agg
   ) as "assets"
diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts
index 9fa8b6243c8928..ebee1194f16c3a 100644
--- a/server/src/repositories/access.repository.ts
+++ b/server/src/repositories/access.repository.ts
@@ -138,7 +138,12 @@ class AssetAccess {
       .selectFrom('albums')
       .innerJoin('albums_assets_assets as albumAssets', 'albums.id', 'albumAssets.albumsId')
       .innerJoin('assets', (join) =>
-        join.onRef('assets.id', '=', 'albumAssets.assetsId').on('assets.deletedAt', 'is', null),
+        join
+          .onRef('assets.id', '=', 'albumAssets.assetsId')
+          .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null),
       )
       .leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id')
       .leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null))
@@ -194,7 +199,12 @@ class AssetAccess {
         join.onRef('sharedBy.id', '=', 'partner.sharedById').on('sharedBy.deletedAt', 'is', null),
       )
       .innerJoin('assets', (join) =>
-        join.onRef('assets.ownerId', '=', 'sharedBy.id').on('assets.deletedAt', 'is', null),
+        join
+          .onRef('assets.ownerId', '=', 'sharedBy.id')
+          .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null),
       )
       .select('assets.id')
       .where('partner.sharedWithId', '=', userId)
@@ -218,7 +228,12 @@ class AssetAccess {
       )
       .leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id')
       .leftJoin('assets', (join) =>
-        join.onRef('assets.id', '=', 'shared_link__asset.assetsId').on('assets.deletedAt', 'is', null),
+        join
+          .onRef('assets.id', '=', 'shared_link__asset.assetsId')
+          .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null),
       )
       .leftJoin('albums_assets_assets', 'albums_assets_assets.albumsId', 'albums.id')
       .leftJoin('assets as albumAssets', (join) =>
@@ -369,7 +384,12 @@ class PersonAccess {
       .selectFrom('asset_faces')
       .select('asset_faces.id')
       .leftJoin('assets', (join) =>
-        join.onRef('assets.id', '=', 'asset_faces.assetId').on('assets.deletedAt', 'is', null),
+        join
+          .onRef('assets.id', '=', 'asset_faces.assetId')
+          .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null),
       )
       .where('asset_faces.id', 'in', [...assetFaceIds])
       .where('assets.ownerId', '=', userId)
diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts
index 99d3192341d864..dd4fd41bc07ec8 100644
--- a/server/src/repositories/activity.repository.ts
+++ b/server/src/repositories/activity.repository.ts
@@ -36,7 +36,14 @@ export class ActivityRepository {
       .selectFrom('activity')
       .selectAll('activity')
       .select(withUser)
-      .leftJoin('assets', (join) => join.onRef('assets.id', '=', 'activity.assetId').on('assets.deletedAt', 'is', null))
+      .leftJoin('assets', (join) =>
+        join
+          .onRef('assets.id', '=', 'activity.assetId')
+          .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null),
+      )
       .$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!))
       .$if(assetId === null, (qb) => qb.where('assetId', 'is', null))
       .$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!))
@@ -65,6 +72,9 @@ export class ActivityRepository {
       .where('activity.albumId', '=', albumId)
       .where('activity.isLiked', '=', false)
       .where('assets.deletedAt', 'is', null)
+      .where('assets.fileCreatedAt', 'is not', null)
+      .where('assets.fileModifiedAt', 'is not', null)
+      .where('assets.localDateTime', 'is not', null)
       .executeTakeFirstOrThrow();
 
     return count as number;
diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts
index 6c81395a583001..29902f258c97bb 100644
--- a/server/src/repositories/album.repository.ts
+++ b/server/src/repositories/album.repository.ts
@@ -63,6 +63,9 @@ const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
         .innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
         .whereRef('albums_assets_assets.albumsId', '=', 'albums.id')
         .where('assets.deletedAt', 'is', null)
+        .where('assets.fileCreatedAt', 'is not', null)
+        .where('assets.fileModifiedAt', 'is not', null)
+        .where('assets.localDateTime', 'is not', null)
         .orderBy('assets.fileCreatedAt', 'desc')
         .as('asset'),
     )
@@ -132,6 +135,9 @@ export class AlbumRepository implements IAlbumRepository {
       .select((eb) => sql<number>`${eb.fn.count('assets.id')}::int`.as('assetCount'))
       .where('albums.id', 'in', ids)
       .where('assets.deletedAt', 'is', null)
+      .where('assets.fileCreatedAt', 'is not', null)
+      .where('assets.fileModifiedAt', 'is not', null)
+      .where('assets.localDateTime', 'is not', null)
       .groupBy('albums.id')
       .execute();
   }
@@ -371,7 +377,12 @@ export class AlbumRepository implements IAlbumRepository {
     return eb
       .selectFrom('albums_assets_assets as album_assets')
       .innerJoin('assets', (join) =>
-        join.onRef('album_assets.assetsId', '=', 'assets.id').on('assets.deletedAt', 'is', null),
+        join
+          .onRef('album_assets.assetsId', '=', 'assets.id')
+          .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null),
       )
       .whereRef('album_assets.albumsId', '=', 'albums.id');
   }
diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts
index 914f3929ffed15..f1eac47d02ed3b 100644
--- a/server/src/repositories/asset.repository.ts
+++ b/server/src/repositories/asset.repository.ts
@@ -112,8 +112,6 @@ export class AssetRepository implements IAssetRepository {
                 .where(sql`(assets."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
                 .where('assets.ownerId', '=', anyUuid(ownerIds))
                 .where('assets.isVisible', '=', true)
-                .where('assets.fileCreatedAt', 'is not', null)
-                .where('assets.fileModifiedAt', 'is not', null)
                 .where('assets.isArchived', '=', false)
                 .where((eb) =>
                   eb.exists((qb) =>
@@ -124,6 +122,9 @@ export class AssetRepository implements IAssetRepository {
                   ),
                 )
                 .where('assets.deletedAt', 'is', null)
+                .where('assets.fileCreatedAt', 'is not', null)
+                .where('assets.fileModifiedAt', 'is not', null)
+                .where('assets.localDateTime', 'is not', null)
                 .limit(20)
                 .as('a'),
             (join) => join.onTrue(),
@@ -608,6 +609,7 @@ export class AssetRepository implements IAssetRepository {
             .where('assets.isVisible', '=', true)
             .where('assets.fileCreatedAt', 'is not', null)
             .where('assets.fileModifiedAt', 'is not', null)
+            .where('assets.localDateTime', 'is not', null)
             .$if(!!options.albumId, (qb) =>
               qb
                 .innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId')
@@ -690,6 +692,7 @@ export class AssetRepository implements IAssetRepository {
       .where('assets.isVisible', '=', true)
       .where('assets.fileCreatedAt', 'is not', null)
       .where('assets.fileModifiedAt', 'is not', null)
+      .where('assets.localDateTime', 'is not', null)
       .where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, ''))
       .orderBy('assets.localDateTime', options.order ?? 'desc')
       .execute() as any as Promise<AssetEntity[]>;
@@ -720,6 +723,7 @@ export class AssetRepository implements IAssetRepository {
             .where('assets.isVisible', '=', true)
             .where('assets.fileCreatedAt', 'is not', null)
             .where('assets.fileModifiedAt', 'is not', null)
+            .where('assets.localDateTime', 'is not', null)
             .groupBy('assets.duplicateId'),
         )
         .with('unique', (qb) =>
diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts
index 042738fe4c0f6f..ba8b4eca68a9dd 100644
--- a/server/src/repositories/memory.repository.ts
+++ b/server/src/repositories/memory.repository.ts
@@ -105,7 +105,10 @@ export class MemoryRepository implements IBulkAsset {
             .selectAll('assets')
             .innerJoin('memories_assets_assets', 'assets.id', 'memories_assets_assets.assetsId')
             .whereRef('memories_assets_assets.memoriesId', '=', 'memories.id')
-            .where('assets.deletedAt', 'is', null),
+            .where('assets.deletedAt', 'is', null)
+            .where('assets.fileCreatedAt', 'is not', null)
+            .where('assets.fileModifiedAt', 'is not', null)
+            .where('assets.localDateTime', 'is not', null),
         ).as('assets'),
       )
       .where('id', '=', id)
diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts
index 7c2512aa26f155..fe240bca8b6779 100644
--- a/server/src/repositories/person.repository.ts
+++ b/server/src/repositories/person.repository.ts
@@ -128,7 +128,10 @@ export class PersonRepository implements IPersonRepository {
         join
           .onRef('asset_faces.assetId', '=', 'assets.id')
           .on('assets.isArchived', '=', false)
-          .on('assets.deletedAt', 'is', null),
+          .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null),
       )
       .where('person.ownerId', '=', userId)
       .orderBy('person.isHidden', 'asc')
@@ -284,6 +287,9 @@ export class PersonRepository implements IPersonRepository {
           .on('asset_faces.personId', '=', personId)
           .on('assets.isArchived', '=', false)
           .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null)
           .on('assets.livePhotoVideoId', 'is', null),
       )
       .select((eb) => eb.fn.count(eb.fn('distinct', ['assets.id'])).as('count'))
@@ -304,6 +310,9 @@ export class PersonRepository implements IPersonRepository {
         join
           .onRef('assets.id', '=', 'asset_faces.assetId')
           .on('assets.deletedAt', 'is', null)
+          .on('assets.fileCreatedAt', 'is not', null)
+          .on('assets.fileModifiedAt', 'is not', null)
+          .on('assets.localDateTime', 'is not', null)
           .on('assets.isArchived', '=', false),
       )
       .select((eb) => eb.fn.count(eb.fn('distinct', ['person.id'])).as('total'))
diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts
index 76b6653e3d73e8..613ab37629ae9f 100644
--- a/server/src/repositories/search.repository.ts
+++ b/server/src/repositories/search.repository.ts
@@ -139,6 +139,9 @@ export class SearchRepository implements ISearchRepository {
           .where('assets.ownerId', '=', anyUuid(userIds))
           .where('assets.deletedAt', 'is', null)
           .where('assets.isVisible', '=', true)
+          .where('assets.fileCreatedAt', 'is not', null)
+          .where('assets.fileModifiedAt', 'is not', null)
+          .where('assets.localDateTime', 'is not', null)
           .where('assets.type', '=', type)
           .where('assets.id', '!=', asUuid(assetId))
           .orderBy(sql`smart_search.embedding <=> ${embedding}`)
@@ -178,6 +181,9 @@ export class SearchRepository implements ISearchRepository {
           .innerJoin('face_search', 'face_search.faceId', 'asset_faces.id')
           .where('assets.ownerId', '=', anyUuid(userIds))
           .where('assets.deletedAt', 'is', null)
+          .where('assets.fileCreatedAt', 'is not', null)
+          .where('assets.fileModifiedAt', 'is not', null)
+          .where('assets.localDateTime', 'is not', null)
           .$if(!!hasPerson, (qb) => qb.where('asset_faces.personId', 'is not', null))
           .orderBy(sql`face_search.embedding <=> ${embedding}`)
           .limit(numResults),
@@ -228,6 +234,9 @@ export class SearchRepository implements ISearchRepository {
           .where('assets.isArchived', '=', false)
           .where('assets.type', '=', 'IMAGE')
           .where('assets.deletedAt', 'is', null)
+          .where('assets.fileCreatedAt', 'is not', null)
+          .where('assets.fileModifiedAt', 'is not', null)
+          .where('assets.localDateTime', 'is not', null)
           .orderBy('city')
           .limit(1);
 
@@ -245,6 +254,9 @@ export class SearchRepository implements ISearchRepository {
                 .where('assets.isArchived', '=', false)
                 .where('assets.type', '=', 'IMAGE')
                 .where('assets.deletedAt', 'is', null)
+                .where('assets.fileCreatedAt', 'is not', null)
+                .where('assets.fileModifiedAt', 'is not', null)
+                .where('assets.localDateTime', 'is not', null)
                 .whereRef('exif.city', '>', 'cte.city')
                 .orderBy('city')
                 .limit(1)
diff --git a/server/src/repositories/shared-link.repository.ts b/server/src/repositories/shared-link.repository.ts
index 6473100387e8c3..cb860b33ce64f9 100644
--- a/server/src/repositories/shared-link.repository.ts
+++ b/server/src/repositories/shared-link.repository.ts
@@ -25,6 +25,9 @@ export class SharedLinkRepository implements ISharedLinkRepository {
             .whereRef('shared_links.id', '=', 'shared_link__asset.sharedLinksId')
             .innerJoin('assets', 'assets.id', 'shared_link__asset.assetsId')
             .where('assets.deletedAt', 'is', null)
+            .where('assets.fileCreatedAt', 'is not', null)
+            .where('assets.fileModifiedAt', 'is not', null)
+            .where('assets.localDateTime', 'is not', null)
             .selectAll('assets')
             .innerJoinLateral(
               (eb) => eb.selectFrom('exif').selectAll('exif').whereRef('exif.assetId', '=', 'assets.id').as('exifInfo'),
@@ -50,6 +53,9 @@ export class SharedLinkRepository implements ISharedLinkRepository {
                   .selectAll('assets')
                   .whereRef('albums_assets_assets.assetsId', '=', 'assets.id')
                   .where('assets.deletedAt', 'is', null)
+                  .where('assets.fileCreatedAt', 'is not', null)
+                  .where('assets.fileModifiedAt', 'is not', null)
+                  .where('assets.localDateTime', 'is not', null)
                   .innerJoinLateral(
                     (eb) =>
                       eb
@@ -105,6 +111,9 @@ export class SharedLinkRepository implements ISharedLinkRepository {
             .selectFrom('assets')
             .whereRef('assets.id', '=', 'shared_link__asset.assetsId')
             .where('assets.deletedAt', 'is', null)
+            .where('assets.fileCreatedAt', 'is not', null)
+            .where('assets.fileModifiedAt', 'is not', null)
+            .where('assets.localDateTime', 'is not', null)
             .selectAll('assets')
             .as('assets'),
         (join) => join.onTrue(),
diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts
index 018d7e77a459af..02bf5b9a66775c 100644
--- a/server/src/repositories/stack.repository.ts
+++ b/server/src/repositories/stack.repository.ts
@@ -30,6 +30,9 @@ const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false)
       )
       .select((eb) => eb.fn.toJson('exifInfo').as('exifInfo'))
       .where('assets.deletedAt', 'is', null)
+      .where('assets.fileCreatedAt', 'is not', null)
+      .where('assets.fileModifiedAt', 'is not', null)
+      .where('assets.localDateTime', 'is not', null)
       .whereRef('assets.stackId', '=', 'asset_stack.id'),
   ).as('assets');
 };
@@ -62,7 +65,10 @@ export class StackRepository implements IStackRepository {
               .selectFrom('assets')
               .select('assets.id')
               .whereRef('assets.stackId', '=', 'asset_stack.id')
-              .where('assets.deletedAt', 'is', null),
+              .where('assets.deletedAt', 'is', null)
+              .where('assets.fileCreatedAt', 'is not', null)
+              .where('assets.fileModifiedAt', 'is not', null)
+              .where('assets.localDateTime', 'is not', null),
           ).as('assets'),
         )
         .execute();
diff --git a/server/src/services/asset-media.service.spec.ts b/server/src/services/asset-media.service.spec.ts
index 9dcfa3cbd9ce51..d9c649b9225fa3 100644
--- a/server/src/services/asset-media.service.spec.ts
+++ b/server/src/services/asset-media.service.spec.ts
@@ -541,6 +541,11 @@ describe(AssetMediaService.name, () => {
 
     it('should throw an error if the requested preview file does not exist', async () => {
       accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
+
+      if (!assetStub.image.fileCreatedAt) {
+        throw new Error('fileCreatedAt is missing');
+      }
+
       assetMock.getById.mockResolvedValue({
         ...assetStub.image,
         files: [
@@ -561,6 +566,11 @@ describe(AssetMediaService.name, () => {
 
     it('should fall back to preview if the requested thumbnail file does not exist', async () => {
       accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
+
+      if (!assetStub.image.fileCreatedAt) {
+        throw new Error('fileCreatedAt is missing');
+      }
+
       assetMock.getById.mockResolvedValue({
         ...assetStub.image,
         files: [
diff --git a/server/src/utils/date-time.ts b/server/src/utils/date-time.ts
index e1578cbb19ad2b..bbeef465e4b40a 100644
--- a/server/src/utils/date-time.ts
+++ b/server/src/utils/date-time.ts
@@ -1,5 +1,5 @@
 import { AssetEntity } from 'src/entities/asset.entity';
 
-export const getAssetDateTime = (asset: AssetEntity | undefined) => {
-  return asset?.exifInfo?.dateTimeOriginal || asset?.fileCreatedAt;
+export const getAssetDateTime = (asset: AssetEntity | undefined): Date | undefined => {
+  return (asset?.exifInfo?.dateTimeOriginal || asset?.fileCreatedAt) ?? undefined;
 };