1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 06:31:58 +00:00

fix(server): keep full datetime precision from database

This commit is contained in:
Fynn Petersen-Frey 2024-06-25 14:45:43 +02:00
parent d7a33c8ec2
commit 94b9013da5
44 changed files with 120 additions and 99 deletions

View file

@ -18,7 +18,8 @@ export type MaybeDuplicate<T> = { 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;

View file

@ -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[];

View file

@ -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;
}

View file

@ -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;

View file

@ -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)

View file

@ -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;
}

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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[];

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -19,8 +19,8 @@ export class APIKeyEntity {
userId!: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt!: Date;
createdAt!: string;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: Date;
updatedAt!: string;
}

View file

@ -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' })

View file

@ -30,5 +30,5 @@ export class AuditEntity {
ownerId!: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt!: Date;
createdAt!: string;
}

View file

@ -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;

View file

@ -29,13 +29,13 @@ export class MemoryEntity<T extends MemoryType = MemoryType> {
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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -38,7 +38,7 @@ export class SharedLinkEntity {
type!: SharedLinkType;
@CreateDateColumn({ type: 'timestamptz' })
createdAt!: Date;
createdAt!: string;
@Column({ type: 'timestamptz', nullable: true })
expiresAt!: Date | null;

View file

@ -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[];

View file

@ -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;
}

View file

@ -9,6 +9,6 @@ export interface AuditSearch {
}
export interface IAuditRepository {
getAfter(since: Date, options: AuditSearch): Promise<string[]>;
getAfter(since: string, options: AuditSearch): Promise<string[]>;
removeBefore(before: Date): Promise<void>;
}

View file

@ -10,7 +10,7 @@ import { In, LessThan, MoreThan, Repository } from 'typeorm';
export class AuditRepository implements IAuditRepository {
constructor(@InjectRepository(AuditEntity) private repository: Repository<AuditEntity>) {}
async getAfter(since: Date, options: AuditSearch): Promise<string[]> {
async getAfter(since: string, options: AuditSearch): Promise<string[]> {
const records = await this.repository
.createQueryBuilder('audit')
.where({
@ -28,6 +28,6 @@ export class AuditRepository implements IAuditRepository {
}
async removeBefore(before: Date): Promise<void> {
await this.repository.delete({ createdAt: LessThan(before) });
await this.repository.delete({ createdAt: LessThan(before.toISOString()) });
}
}

View file

@ -13,7 +13,7 @@ export class SessionRepository implements ISessionRepository {
@GenerateSql({ params: [DummyValue.DATE] })
search(options: SessionSearchOptions): Promise<SessionEntity[]> {
return this.repository.find({ where: { updatedAt: LessThanOrEqual(options.updatedBefore) } });
return this.repository.find({ where: { updatedAt: LessThanOrEqual(options.updatedBefore.toISOString()) } });
}
@GenerateSql({ params: [DummyValue.STRING] })

View file

@ -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();
});

View file

@ -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();
}

View file

@ -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),
});
});
});

View file

@ -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<AssetEntity>).updatedAt = new Date();
(options as Partial<AssetEntity>).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) {

View file

@ -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: [],

View file

@ -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,

View file

@ -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) });
});
});

View file

@ -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 };

View file

@ -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;

View file

@ -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',

View file

@ -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);

View file

@ -42,7 +42,7 @@ export class SyncService {
async getDeltaSync(auth: AuthDto, dto: AssetDeltaSyncDto): Promise<AssetDeltaSyncResponseDto> {
// 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;
}

View file

@ -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,

View file

@ -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 } });

View file

@ -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, () => {

View file

@ -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) {

View file

@ -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',
}),