From 94b9013da552b2326c8b084542eb2f1cf19c4114 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey Date: Tue, 25 Jun 2024 14:45:43 +0200 Subject: [PATCH] fix(server): keep full datetime precision from database --- server/src/dtos/activity.dto.ts | 3 ++- server/src/dtos/album.dto.ts | 6 ++++-- server/src/dtos/api-key.dto.ts | 7 +++++-- server/src/dtos/asset-response.dto.ts | 3 ++- server/src/dtos/audit.dto.ts | 6 +++--- server/src/dtos/library.dto.ts | 6 ++++-- server/src/dtos/memory.dto.ts | 9 ++++++--- server/src/dtos/session.dto.ts | 4 ++-- server/src/dtos/shared-link.dto.ts | 3 ++- server/src/dtos/sync.dto.ts | 10 +++++----- server/src/dtos/user.dto.ts | 9 ++++++--- server/src/entities/activity.entity.ts | 4 ++-- server/src/entities/album.entity.ts | 6 +++--- server/src/entities/api-key.entity.ts | 4 ++-- server/src/entities/asset.entity.ts | 6 +++--- server/src/entities/audit.entity.ts | 2 +- server/src/entities/library.entity.ts | 6 +++--- server/src/entities/memory.entity.ts | 6 +++--- server/src/entities/partner.entity.ts | 4 ++-- server/src/entities/person.entity.ts | 4 ++-- server/src/entities/session.entity.ts | 4 ++-- server/src/entities/shared-link.entity.ts | 2 +- server/src/entities/user.entity.ts | 6 +++--- server/src/interfaces/asset.interface.ts | 4 ++-- server/src/interfaces/audit.interface.ts | 2 +- server/src/repositories/audit.repository.ts | 4 ++-- server/src/repositories/session.repository.ts | 2 +- server/src/services/album.service.spec.ts | 14 +++++++------- server/src/services/album.service.ts | 4 ++-- server/src/services/asset.service.spec.ts | 10 +++++----- server/src/services/asset.service.ts | 8 +++++--- server/src/services/audit.service.spec.ts | 2 +- server/src/services/audit.service.ts | 2 +- server/src/services/auth.service.spec.ts | 2 +- server/src/services/auth.service.ts | 4 ++-- server/src/services/memory.service.ts | 4 ++-- server/src/services/session.service.spec.ts | 4 ++-- server/src/services/sync.service.spec.ts | 9 ++++++--- server/src/services/sync.service.ts | 2 +- server/src/services/user-admin.service.spec.ts | 6 +++--- server/src/services/user-admin.service.ts | 4 ++-- server/src/services/user.service.spec.ts | 2 +- server/src/services/user.service.ts | 2 +- server/test/fixtures/session.stub.ts | 8 ++++---- 44 files changed, 120 insertions(+), 99 deletions(-) diff --git a/server/src/dtos/activity.dto.ts b/server/src/dtos/activity.dto.ts index 4a3de208ff..66e9c69e9f 100644 --- a/server/src/dtos/activity.dto.ts +++ b/server/src/dtos/activity.dto.ts @@ -18,7 +18,8 @@ export type MaybeDuplicate = { duplicate: boolean; value: T }; export class ActivityResponseDto { id!: string; - createdAt!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + createdAt!: string; type!: ReactionType; user!: UserResponseDto; assetId!: string | null; diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 21eb649e11..6592ebe644 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -123,8 +123,10 @@ export class AlbumResponseDto { ownerId!: string; albumName!: string; description!: string; - createdAt!: Date; - updatedAt!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + createdAt!: string; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedAt!: string; albumThumbnailAssetId!: string | null; shared!: boolean; albumUsers!: AlbumUserResponseDto[]; diff --git a/server/src/dtos/api-key.dto.ts b/server/src/dtos/api-key.dto.ts index 1f4f855216..6a705de417 100644 --- a/server/src/dtos/api-key.dto.ts +++ b/server/src/dtos/api-key.dto.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; import { Optional } from 'src/validation'; export class APIKeyCreateDto { @@ -21,6 +22,8 @@ export class APIKeyCreateResponseDto { export class APIKeyResponseDto { id!: string; name!: string; - createdAt!: Date; - updatedAt!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + createdAt!: string; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedAt!: string; } diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 03fa2f8b3d..ac568030ec 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -39,7 +39,8 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { originalFileName!: string; fileCreatedAt!: Date; fileModifiedAt!: Date; - updatedAt!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedAt!: string; isFavorite!: boolean; isArchived!: boolean; isTrashed!: boolean; diff --git a/server/src/dtos/audit.dto.ts b/server/src/dtos/audit.dto.ts index e83efca768..71902994fc 100644 --- a/server/src/dtos/audit.dto.ts +++ b/server/src/dtos/audit.dto.ts @@ -3,13 +3,13 @@ import { Type } from 'class-transformer'; import { IsArray, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator'; import { EntityType } from 'src/entities/audit.entity'; import { AssetPathType, PathType, PersonPathType, UserPathType } from 'src/entities/move.entity'; -import { Optional, ValidateDate, ValidateUUID } from 'src/validation'; +import { Optional, ValidateUUID } from 'src/validation'; const PathEnum = Object.values({ ...AssetPathType, ...PersonPathType, ...UserPathType }); export class AuditDeletesDto { - @ValidateDate() - after!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + after!: string; @ApiProperty({ enum: EntityType, enumName: 'EntityType' }) @IsEnum(EntityType) diff --git a/server/src/dtos/library.dto.ts b/server/src/dtos/library.dto.ts index b9578a2c37..f7f81fc41f 100644 --- a/server/src/dtos/library.dto.ts +++ b/server/src/dtos/library.dto.ts @@ -105,8 +105,10 @@ export class LibraryResponseDto { exclusionPatterns!: string[]; - createdAt!: Date; - updatedAt!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + createdAt!: string; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedAt!: string; refreshedAt!: Date | null; } diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts index ecd62785f8..42ee779989 100644 --- a/server/src/dtos/memory.dto.ts +++ b/server/src/dtos/memory.dto.ts @@ -55,9 +55,12 @@ export class MemoryCreateDto extends MemoryBaseDto { export class MemoryResponseDto { id!: string; - createdAt!: Date; - updatedAt!: Date; - deletedAt?: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + createdAt!: string; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedAt!: string; + @ApiProperty({ type: 'string', format: 'date-time' }) + deletedAt?: string; memoryAt!: Date; seenAt?: Date; ownerId!: string; diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts index d96d7819ad..ceaa290a29 100644 --- a/server/src/dtos/session.dto.ts +++ b/server/src/dtos/session.dto.ts @@ -11,8 +11,8 @@ export class SessionResponseDto { export const mapSession = (entity: SessionEntity, currentId?: string): SessionResponseDto => ({ id: entity.id, - createdAt: entity.createdAt.toISOString(), - updatedAt: entity.updatedAt.toISOString(), + createdAt: entity.createdAt, + updatedAt: entity.updatedAt, current: currentId === entity.id, deviceOS: entity.deviceOS, deviceType: entity.deviceType, diff --git a/server/src/dtos/shared-link.dto.ts b/server/src/dtos/shared-link.dto.ts index 9a90901d27..9e9f01f304 100644 --- a/server/src/dtos/shared-link.dto.ts +++ b/server/src/dtos/shared-link.dto.ts @@ -86,7 +86,8 @@ export class SharedLinkResponseDto { @ApiProperty({ enumName: 'SharedLinkType', enum: SharedLinkType }) type!: SharedLinkType; - createdAt!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + createdAt!: string; expiresAt!: Date | null; assets!: AssetResponseDto[]; album?: AlbumResponseDto; diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index 820de8d6c3..91dc0b828a 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -1,14 +1,14 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsInt, IsPositive } from 'class-validator'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { ValidateDate, ValidateUUID } from 'src/validation'; +import { ValidateUUID } from 'src/validation'; export class AssetFullSyncDto { @ValidateUUID({ optional: true }) lastId?: string; - @ValidateDate() - updatedUntil!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedUntil!: string; @IsInt() @IsPositive() @@ -20,8 +20,8 @@ export class AssetFullSyncDto { } export class AssetDeltaSyncDto { - @ValidateDate() - updatedAfter!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedAfter!: string; @ValidateUUID({ each: true }) userIds!: string[]; diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 63bac60d06..c4ae5729bf 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -120,9 +120,12 @@ export class UserAdminResponseDto extends UserResponseDto { storageLabel!: string | null; shouldChangePassword!: boolean; isAdmin!: boolean; - createdAt!: Date; - deletedAt!: Date | null; - updatedAt!: Date; + @ApiProperty({ type: 'string', format: 'date-time' }) + createdAt!: string; + @ApiProperty({ type: 'string', format: 'date-time' }) + deletedAt!: string | null; + @ApiProperty({ type: 'string', format: 'date-time' }) + updatedAt!: string; oauthId!: string; @ApiProperty({ type: 'integer', format: 'int64' }) quotaSizeInBytes!: number | null; diff --git a/server/src/entities/activity.entity.ts b/server/src/entities/activity.entity.ts index 8de76ac894..4a65434e0a 100644 --- a/server/src/entities/activity.entity.ts +++ b/server/src/entities/activity.entity.ts @@ -20,10 +20,10 @@ export class ActivityEntity { id!: string; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @Column() albumId!: string; diff --git a/server/src/entities/album.entity.ts b/server/src/entities/album.entity.ts index 39d5b72bf2..c40b1c83ff 100644 --- a/server/src/entities/album.entity.ts +++ b/server/src/entities/album.entity.ts @@ -39,13 +39,13 @@ export class AlbumEntity { description!: string; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @DeleteDateColumn({ type: 'timestamptz' }) - deletedAt!: Date | null; + deletedAt!: string | null; @ManyToOne(() => AssetEntity, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' }) albumThumbnailAsset!: AssetEntity | null; diff --git a/server/src/entities/api-key.entity.ts b/server/src/entities/api-key.entity.ts index 18aaa83041..347a73fd07 100644 --- a/server/src/entities/api-key.entity.ts +++ b/server/src/entities/api-key.entity.ts @@ -19,8 +19,8 @@ export class APIKeyEntity { userId!: string; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; } diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 2189d5389a..83400ceb4e 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -84,13 +84,13 @@ export class AssetEntity { encodedVideoPath!: string | null; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @DeleteDateColumn({ type: 'timestamptz', nullable: true }) - deletedAt!: Date | null; + deletedAt!: string | null; @Index('idx_asset_file_created_at') @Column({ type: 'timestamptz' }) diff --git a/server/src/entities/audit.entity.ts b/server/src/entities/audit.entity.ts index be5e14891c..4f959bc97e 100644 --- a/server/src/entities/audit.entity.ts +++ b/server/src/entities/audit.entity.ts @@ -30,5 +30,5 @@ export class AuditEntity { ownerId!: string; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; } diff --git a/server/src/entities/library.entity.ts b/server/src/entities/library.entity.ts index a6053e4213..ab8a79d4d4 100644 --- a/server/src/entities/library.entity.ts +++ b/server/src/entities/library.entity.ts @@ -37,13 +37,13 @@ export class LibraryEntity { exclusionPatterns!: string[]; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @DeleteDateColumn({ type: 'timestamptz' }) - deletedAt?: Date; + deletedAt?: string; @Column({ type: 'timestamptz', nullable: true }) refreshedAt!: Date | null; diff --git a/server/src/entities/memory.entity.ts b/server/src/entities/memory.entity.ts index d7dcff4b80..7551c1e0f3 100644 --- a/server/src/entities/memory.entity.ts +++ b/server/src/entities/memory.entity.ts @@ -29,13 +29,13 @@ export class MemoryEntity { id!: string; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @DeleteDateColumn({ type: 'timestamptz' }) - deletedAt?: Date; + deletedAt?: string; @ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) owner!: UserEntity; diff --git a/server/src/entities/partner.entity.ts b/server/src/entities/partner.entity.ts index 189f6f51a7..5946e7ddbb 100644 --- a/server/src/entities/partner.entity.ts +++ b/server/src/entities/partner.entity.ts @@ -18,10 +18,10 @@ export class PartnerEntity { sharedWith!: UserEntity; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @Column({ type: 'boolean', default: false }) inTimeline!: boolean; diff --git a/server/src/entities/person.entity.ts b/server/src/entities/person.entity.ts index bc60efcd6e..fd338c2ec7 100644 --- a/server/src/entities/person.entity.ts +++ b/server/src/entities/person.entity.ts @@ -18,10 +18,10 @@ export class PersonEntity { id!: string; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @Column() ownerId!: string; diff --git a/server/src/entities/session.entity.ts b/server/src/entities/session.entity.ts index 1cc9ad9857..669315ea37 100644 --- a/server/src/entities/session.entity.ts +++ b/server/src/entities/session.entity.ts @@ -16,10 +16,10 @@ export class SessionEntity { user!: UserEntity; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @Column({ default: '' }) deviceType!: string; diff --git a/server/src/entities/shared-link.entity.ts b/server/src/entities/shared-link.entity.ts index f328192f7f..e7cfd07e24 100644 --- a/server/src/entities/shared-link.entity.ts +++ b/server/src/entities/shared-link.entity.ts @@ -38,7 +38,7 @@ export class SharedLinkEntity { type!: SharedLinkType; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @Column({ type: 'timestamptz', nullable: true }) expiresAt!: Date | null; diff --git a/server/src/entities/user.entity.ts b/server/src/entities/user.entity.ts index 6878292ab0..bfc0509315 100644 --- a/server/src/entities/user.entity.ts +++ b/server/src/entities/user.entity.ts @@ -47,16 +47,16 @@ export class UserEntity { shouldChangePassword!: boolean; @CreateDateColumn({ type: 'timestamptz' }) - createdAt!: Date; + createdAt!: string; @DeleteDateColumn({ type: 'timestamptz' }) - deletedAt!: Date | null; + deletedAt!: string | null; @Column({ type: 'varchar', default: UserStatus.ACTIVE }) status!: UserStatus; @UpdateDateColumn({ type: 'timestamptz' }) - updatedAt!: Date; + updatedAt!: string; @OneToMany(() => TagEntity, (tag) => tag.user) tags!: TagEntity[]; diff --git a/server/src/interfaces/asset.interface.ts b/server/src/interfaces/asset.interface.ts index a9a74f711b..9cf2d58d62 100644 --- a/server/src/interfaces/asset.interface.ts +++ b/server/src/interfaces/asset.interface.ts @@ -123,13 +123,13 @@ export interface AssetExploreOptions extends AssetExploreFieldOptions { export interface AssetFullSyncOptions { ownerId: string; lastId?: string; - updatedUntil: Date; + updatedUntil: string; limit: number; } export interface AssetDeltaSyncOptions { userIds: string[]; - updatedAfter: Date; + updatedAfter: string; limit: number; } diff --git a/server/src/interfaces/audit.interface.ts b/server/src/interfaces/audit.interface.ts index b023d00d56..f92e37cb22 100644 --- a/server/src/interfaces/audit.interface.ts +++ b/server/src/interfaces/audit.interface.ts @@ -9,6 +9,6 @@ export interface AuditSearch { } export interface IAuditRepository { - getAfter(since: Date, options: AuditSearch): Promise; + getAfter(since: string, options: AuditSearch): Promise; removeBefore(before: Date): Promise; } diff --git a/server/src/repositories/audit.repository.ts b/server/src/repositories/audit.repository.ts index deb0d0f6f1..1e6253a91f 100644 --- a/server/src/repositories/audit.repository.ts +++ b/server/src/repositories/audit.repository.ts @@ -10,7 +10,7 @@ import { In, LessThan, MoreThan, Repository } from 'typeorm'; export class AuditRepository implements IAuditRepository { constructor(@InjectRepository(AuditEntity) private repository: Repository) {} - async getAfter(since: Date, options: AuditSearch): Promise { + async getAfter(since: string, options: AuditSearch): Promise { const records = await this.repository .createQueryBuilder('audit') .where({ @@ -28,6 +28,6 @@ export class AuditRepository implements IAuditRepository { } async removeBefore(before: Date): Promise { - await this.repository.delete({ createdAt: LessThan(before) }); + await this.repository.delete({ createdAt: LessThan(before.toISOString()) }); } } diff --git a/server/src/repositories/session.repository.ts b/server/src/repositories/session.repository.ts index a4b55a19d7..b6b1b68c63 100644 --- a/server/src/repositories/session.repository.ts +++ b/server/src/repositories/session.repository.ts @@ -13,7 +13,7 @@ export class SessionRepository implements ISessionRepository { @GenerateSql({ params: [DummyValue.DATE] }) search(options: SessionSearchOptions): Promise { - return this.repository.find({ where: { updatedAt: LessThanOrEqual(options.updatedBefore) } }); + return this.repository.find({ where: { updatedAt: LessThanOrEqual(options.updatedBefore.toISOString()) } }); } @GenerateSql({ params: [DummyValue.STRING] }) diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 7a2df77710..03a74fec3a 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -569,7 +569,7 @@ describe(AlbumService.name, () => { expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', - updatedAt: expect.any(Date), + updatedAt: expect.any(String), albumThumbnailAssetId: 'asset-1', }); expect(albumMock.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']); @@ -595,7 +595,7 @@ describe(AlbumService.name, () => { expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', - updatedAt: expect.any(Date), + updatedAt: expect.any(String), albumThumbnailAssetId: 'asset-id', }); expect(albumMock.addAssetIds).toHaveBeenCalled(); @@ -617,7 +617,7 @@ describe(AlbumService.name, () => { expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', - updatedAt: expect.any(Date), + updatedAt: expect.any(String), albumThumbnailAssetId: 'asset-1', }); expect(albumMock.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']); @@ -658,7 +658,7 @@ describe(AlbumService.name, () => { expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', - updatedAt: expect.any(Date), + updatedAt: expect.any(String), albumThumbnailAssetId: 'asset-1', }); expect(albumMock.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']); @@ -681,7 +681,7 @@ describe(AlbumService.name, () => { expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', - updatedAt: expect.any(Date), + updatedAt: expect.any(String), albumThumbnailAssetId: 'asset-1', }); expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1'])); @@ -746,7 +746,7 @@ describe(AlbumService.name, () => { { success: true, id: 'asset-id' }, ]); - expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', updatedAt: expect.any(Date) }); + expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', updatedAt: expect.any(String) }); expect(albumMock.removeAssetIds).toHaveBeenCalledWith('album-123', ['asset-id']); }); @@ -790,7 +790,7 @@ describe(AlbumService.name, () => { expect(albumMock.update).toHaveBeenCalledWith({ id: 'album-123', - updatedAt: expect.any(Date), + updatedAt: expect.any(String), }); expect(albumMock.updateThumbnails).toHaveBeenCalled(); }); diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index cf179bf289..ad84bbd9ee 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -185,7 +185,7 @@ export class AlbumService { if (firstNewAssetId) { await this.albumRepository.update({ id, - updatedAt: new Date(), + updatedAt: new Date().toISOString(), albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId, }); } @@ -211,7 +211,7 @@ export class AlbumService { const removedIds = results.filter(({ success }) => success).map(({ id }) => id); if (removedIds.length > 0) { - await this.albumRepository.update({ id, updatedAt: new Date() }); + await this.albumRepository.update({ id, updatedAt: new Date().toISOString() }); if (album.albumThumbnailAssetId && removedIds.includes(album.albumThumbnailAssetId)) { await this.albumRepository.updateThumbnails(); } diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 73dbfb6393..6085a7a276 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -265,7 +265,7 @@ describe(AssetService.name, () => { ids: [], stackParentId: 'parent', }), - expect(assetMock.updateAll).toHaveBeenCalledWith(['parent'], { updatedAt: expect.any(Date) }); + expect(assetMock.updateAll).toHaveBeenCalledWith(['parent'], { updatedAt: expect.any(String) }); }); it('should update parent asset when children are removed', async () => { @@ -285,7 +285,7 @@ describe(AssetService.name, () => { }); expect(assetMock.updateAll).toHaveBeenCalledWith(expect.arrayContaining(['child-1']), { stack: null }); expect(assetMock.updateAll).toHaveBeenCalledWith(expect.arrayContaining(['parent']), { - updatedAt: expect.any(Date), + updatedAt: expect.any(String), }); expect(assetStackMock.delete).toHaveBeenCalledWith('stack-1'); }); @@ -316,7 +316,7 @@ describe(AssetService.name, () => { ]), primaryAsset: undefined, }); - expect(assetMock.updateAll).toBeCalledWith(['child-1', 'child-2', 'parent'], { updatedAt: expect.any(Date) }); + expect(assetMock.updateAll).toBeCalledWith(['child-1', 'child-2', 'parent'], { updatedAt: expect.any(String) }); }); it('remove stack for removed children', async () => { @@ -353,7 +353,7 @@ describe(AssetService.name, () => { primaryAssetId: 'parent', }); expect(assetMock.updateAll).toBeCalledWith(['child-1', 'parent', 'child-1', 'child-2'], { - updatedAt: expect.any(Date), + updatedAt: expect.any(String), }); }); @@ -532,7 +532,7 @@ describe(AssetService.name, () => { expect(assetStackMock.update).toBeCalledWith({ id: 'stack-1', primaryAssetId: 'new' }); expect(assetMock.updateAll).toBeCalledWith([assetStub.image.id, 'new', assetStub.image.id], { - updatedAt: expect.any(Date), + updatedAt: expect.any(String), }); }); }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 1c3e81be17..afd77c64a4 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -191,7 +191,7 @@ export class AssetService { // All the unique parent's -> parent is set to null await this.assetRepository.updateAll( assets.filter((a) => !!a.stack?.primaryAssetId).map((a) => a.stack!.primaryAssetId!), - { updatedAt: new Date() }, + { updatedAt: new Date().toISOString() }, ); } else if (options.stackParentId) { //Creating new stack if parent doesn't have one already. If it does, then we add to the existing stack @@ -225,7 +225,7 @@ export class AssetService { // Merge stacks options.stackParentId = undefined; - (options as Partial).updatedAt = new Date(); + (options as Partial).updatedAt = new Date().toISOString(); } for (const id of ids) { @@ -372,7 +372,9 @@ export class AssetService { newParentId, oldParentId, ]); - await this.assetRepository.updateAll([oldParentId, newParentId, ...childIds], { updatedAt: new Date() }); + await this.assetRepository.updateAll([oldParentId, newParentId, ...childIds], { + updatedAt: new Date().toISOString(), + }); } async run(auth: AuthDto, dto: AssetJobsDto) { diff --git a/server/src/services/audit.service.spec.ts b/server/src/services/audit.service.spec.ts index 8557677f92..d438541597 100644 --- a/server/src/services/audit.service.spec.ts +++ b/server/src/services/audit.service.spec.ts @@ -58,7 +58,7 @@ describe(AuditService.name, () => { it('should require full sync if the request is older than 100 days', async () => { auditMock.getAfter.mockResolvedValue([]); - const date = new Date(2022, 0, 1); + const date = new Date(2022, 0, 1).toISOString(); await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({ needsFullSync: true, ids: [], diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index bfff09c0bc..83616b3fc9 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -59,7 +59,7 @@ export class AuditService { action: DatabaseAction.DELETE, }); - const duration = DateTime.now().diff(DateTime.fromJSDate(dto.after)); + const duration = DateTime.now().diff(DateTime.fromISO(dto.after)); return { needsFullSync: duration > AUDIT_LOG_MAX_DURATION, diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index 7aa03e6bdd..7826bb11de 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -331,7 +331,7 @@ describe('AuthService', () => { sessionMock.update.mockResolvedValue(sessionStub.valid); const headers: IncomingHttpHeaders = { cookie: 'immich_access_token=auth_token' }; await expect(sut.validate(headers, {})).resolves.toBeDefined(); - expect(sessionMock.update.mock.calls[0][0]).toMatchObject({ id: 'not_active', updatedAt: expect.any(Date) }); + expect(sessionMock.update.mock.calls[0][0]).toMatchObject({ id: 'not_active', updatedAt: expect.any(String) }); }); }); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 182933de1a..96f11d8ec1 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -382,10 +382,10 @@ export class AuthService { if (session?.user) { const now = DateTime.now(); - const updatedAt = DateTime.fromJSDate(session.updatedAt); + const updatedAt = DateTime.fromISO(session.updatedAt); const diff = now.diff(updatedAt, ['hours']); if (diff.hours > 1) { - await this.sessionRepository.update({ id: session.id, updatedAt: new Date() }); + await this.sessionRepository.update({ id: session.id, updatedAt: new Date().toISOString() }); } return { user: session.user, session: session }; diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index a73eb3ec04..1c015b3465 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -74,7 +74,7 @@ export class MemoryService { const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.repository.update({ id, updatedAt: new Date() }); + await this.repository.update({ id, updatedAt: new Date().toISOString() }); } return results; @@ -89,7 +89,7 @@ export class MemoryService { const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.repository.update({ id, updatedAt: new Date() }); + await this.repository.update({ id, updatedAt: new Date().toISOString() }); } return results; diff --git a/server/src/services/session.service.spec.ts b/server/src/services/session.service.spec.ts index ca3d2fd858..bc4f8284d7 100644 --- a/server/src/services/session.service.spec.ts +++ b/server/src/services/session.service.spec.ts @@ -38,8 +38,8 @@ describe('SessionService', () => { it('should delete sessions', async () => { sessionMock.search.mockResolvedValue([ { - createdAt: new Date('1970-01-01T00:00:00.00Z'), - updatedAt: new Date('1970-01-02T00:00:00.00Z'), + createdAt: new Date('1970-01-01T00:00:00.00Z').toISOString(), + updatedAt: new Date('1970-01-02T00:00:00.00Z').toISOString(), deviceOS: '', deviceType: '', id: '123', diff --git a/server/src/services/sync.service.spec.ts b/server/src/services/sync.service.spec.ts index a0ded6dba3..12e16fb44d 100644 --- a/server/src/services/sync.service.spec.ts +++ b/server/src/services/sync.service.spec.ts @@ -14,7 +14,7 @@ import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock' import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; import { Mocked } from 'vitest'; -const untilDate = new Date(2024); +const untilDate = new Date(2024).toISOString(); const mapAssetOpts = { auth: authStub.user1, stripMetadata: false, withStack: true }; describe(SyncService.name, () => { @@ -55,7 +55,7 @@ describe(SyncService.name, () => { it('should return a response requiring a full sync when partners are out of sync', async () => { partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1]); await expect( - sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }), + sut.getDeltaSync(authStub.user1, { updatedAfter: new Date().toISOString(), userIds: [authStub.user1.user.id] }), ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); expect(assetMock.getChangedDeltaSync).toHaveBeenCalledTimes(0); expect(auditMock.getAfter).toHaveBeenCalledTimes(0); @@ -64,7 +64,10 @@ describe(SyncService.name, () => { it('should return a response requiring a full sync when last sync was too long ago', async () => { partnerMock.getAll.mockResolvedValue([]); await expect( - sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(2000), userIds: [authStub.user1.user.id] }), + sut.getDeltaSync(authStub.user1, { + updatedAfter: new Date(2000).toISOString(), + userIds: [authStub.user1.user.id], + }), ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); expect(assetMock.getChangedDeltaSync).toHaveBeenCalledTimes(0); expect(auditMock.getAfter).toHaveBeenCalledTimes(0); diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts index 1a7a74d699..4b7334b4e6 100644 --- a/server/src/services/sync.service.ts +++ b/server/src/services/sync.service.ts @@ -42,7 +42,7 @@ export class SyncService { async getDeltaSync(auth: AuthDto, dto: AssetDeltaSyncDto): Promise { // app has not synced in the last 100 days - const duration = DateTime.now().diff(DateTime.fromJSDate(dto.updatedAfter)); + const duration = DateTime.now().diff(DateTime.fromISO(dto.updatedAfter)); if (duration > AUDIT_LOG_MAX_DURATION) { return FULL_SYNC; } diff --git a/server/src/services/user-admin.service.spec.ts b/server/src/services/user-admin.service.spec.ts index b7060b1786..bc616de6ab 100644 --- a/server/src/services/user-admin.service.spec.ts +++ b/server/src/services/user-admin.service.spec.ts @@ -97,7 +97,7 @@ describe(UserAdminService.name, () => { await sut.update(authStub.admin, userStub.user1.id, { storageLabel: '' }); expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { storageLabel: null, - updatedAt: expect.any(Date), + updatedAt: expect.any(String), }); }); @@ -157,7 +157,7 @@ describe(UserAdminService.name, () => { await expect(sut.delete(authStub.admin, userStub.user1.id, {})).resolves.toEqual(mapUserAdmin(userStub.user1)); expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { status: UserStatus.DELETED, - deletedAt: expect.any(Date), + deletedAt: expect.any(String), }); }); @@ -171,7 +171,7 @@ describe(UserAdminService.name, () => { expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { status: UserStatus.REMOVING, - deletedAt: expect.any(Date), + deletedAt: expect.any(String), }); expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.USER_DELETION, diff --git a/server/src/services/user-admin.service.ts b/server/src/services/user-admin.service.ts index 72330ac9b7..93d6678679 100644 --- a/server/src/services/user-admin.service.ts +++ b/server/src/services/user-admin.service.ts @@ -85,7 +85,7 @@ export class UserAdminService { dto.storageLabel = null; } - const updatedUser = await this.userRepository.update(id, { ...dto, updatedAt: new Date() }); + const updatedUser = await this.userRepository.update(id, { ...dto, updatedAt: new Date().toISOString() }); return mapUserAdmin(updatedUser); } @@ -100,7 +100,7 @@ export class UserAdminService { await this.albumRepository.softDeleteAll(id); const status = force ? UserStatus.REMOVING : UserStatus.DELETED; - const user = await this.userRepository.update(id, { status, deletedAt: new Date() }); + const user = await this.userRepository.update(id, { status, deletedAt: new Date().toISOString() }); if (force) { await this.jobRepository.queue({ name: JobName.USER_DELETION, data: { id: user.id, force } }); diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts index bc4a1e2874..c3393d5052 100644 --- a/server/src/services/user.service.spec.ts +++ b/server/src/services/user.service.spec.ts @@ -24,7 +24,7 @@ import { Mocked } from 'vitest'; const makeDeletedAt = (daysAgo: number) => { const deletedAt = new Date(); deletedAt.setDate(deletedAt.getDate() - daysAgo); - return deletedAt; + return deletedAt.toISOString(); }; describe(UserService.name, () => { diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index 4ba19a97f8..7f249934ed 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -179,7 +179,7 @@ export class UserService { return false; } - return DateTime.now().minus({ days: deleteDelay }) > DateTime.fromJSDate(user.deletedAt); + return DateTime.now().minus({ days: deleteDelay }) > DateTime.fromISO(user.deletedAt); } private async findOrFail(id: string, options: UserFindOptions) { diff --git a/server/test/fixtures/session.stub.ts b/server/test/fixtures/session.stub.ts index cdf499c8d1..0d03908adc 100644 --- a/server/test/fixtures/session.stub.ts +++ b/server/test/fixtures/session.stub.ts @@ -7,8 +7,8 @@ export const sessionStub = { token: 'auth_token', userId: userStub.user1.id, user: userStub.user1, - createdAt: new Date('2021-01-01'), - updatedAt: new Date(), + createdAt: new Date('2021-01-01').toISOString(), + updatedAt: new Date().toISOString(), deviceType: '', deviceOS: '', }), @@ -17,8 +17,8 @@ export const sessionStub = { token: 'auth_token', userId: userStub.user1.id, user: userStub.user1, - createdAt: new Date('2021-01-01'), - updatedAt: new Date('2021-01-01'), + createdAt: new Date('2021-01-01').toISOString(), + updatedAt: new Date('2021-01-01').toISOString(), deviceType: 'Mobile', deviceOS: 'Android', }),