From 9870ad9687ef6279909b8350f5bc38844a22a1a7 Mon Sep 17 00:00:00 2001 From: David Bourgault Date: Sun, 9 Mar 2025 22:32:05 -0400 Subject: [PATCH] fix(server): adjust type of person.birthDate (#16628) The API currently does not respect the documentation when returning a person's birthDate. The doc/swagger says it will be of "YYYY-MM-DD" format but the string is a full ISO8601-with-tz string. This causes issue #16216 because the tag is strict about supported value formats. I believe this was introduced by #15242 which switched some queries from TypeORM to Kysely for the person repository. TypeORM does not parse date, but our Kysely configuration does (explicitely). This commits updates the types to represent both possibilities and ensure the API always returns the correct format. --- e2e/src/api/specs/person.e2e-spec.ts | 4 ++-- server/src/dtos/person.dto.ts | 5 +++-- server/src/entities/person.entity.ts | 2 +- server/src/services/person.service.spec.ts | 4 ++-- server/src/utils/date.ts | 3 +++ 5 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 server/src/utils/date.ts diff --git a/e2e/src/api/specs/person.e2e-spec.ts b/e2e/src/api/specs/person.e2e-spec.ts index 1a589da1f7..6e7eba74ba 100644 --- a/e2e/src/api/specs/person.e2e-spec.ts +++ b/e2e/src/api/specs/person.e2e-spec.ts @@ -201,7 +201,7 @@ describe('/people', () => { expect(body).toMatchObject({ id: expect.any(String), name: 'New Person', - birthDate: '1990-01-01T00:00:00.000Z', + birthDate: '1990-01-01', }); }); @@ -262,7 +262,7 @@ describe('/people', () => { .set('Authorization', `Bearer ${admin.accessToken}`) .send({ birthDate: '1990-01-01' }); expect(status).toBe(200); - expect(body).toMatchObject({ birthDate: '1990-01-01T00:00:00.000Z' }); + expect(body).toMatchObject({ birthDate: '1990-01-01' }); }); it('should clear a date of birth', async () => { diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index 0778c35b8f..49f3416b9a 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -7,6 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SourceType } from 'src/enum'; +import { asDateString } from 'src/utils/date'; import { IsDateStringFormat, MaxDateString, @@ -32,7 +33,7 @@ export class PersonCreateDto { @MaxDateString(() => DateTime.now(), { message: 'Birth date cannot be in the future' }) @IsDateStringFormat('yyyy-MM-dd') @Optional({ nullable: true }) - birthDate?: string | null; + birthDate?: Date | null; /** * Person visibility @@ -222,7 +223,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto { return { id: person.id, name: person.name, - birthDate: person.birthDate, + birthDate: asDateString(person.birthDate), thumbnailPath: person.thumbnailPath, isHidden: person.isHidden, isFavorite: person.isFavorite, diff --git a/server/src/entities/person.entity.ts b/server/src/entities/person.entity.ts index 5ca74c12d2..5efa602cc8 100644 --- a/server/src/entities/person.entity.ts +++ b/server/src/entities/person.entity.ts @@ -38,7 +38,7 @@ export class PersonEntity { name!: string; @Column({ type: 'date', nullable: true }) - birthDate!: string | null; + birthDate!: Date | string | null; @Column({ default: '' }) thumbnailPath!: string; diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index ba327ddae9..8a8cb254fa 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -222,7 +222,7 @@ describe(PersonService.name, () => { mocks.person.update.mockResolvedValue(personStub.withBirthDate); mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.update(authStub.admin, 'person-1', { birthDate: '1976-06-30' })).resolves.toEqual({ + await expect(sut.update(authStub.admin, 'person-1', { birthDate: new Date('1976-06-30') })).resolves.toEqual({ id: 'person-1', name: 'Person 1', birthDate: '1976-06-30', @@ -231,7 +231,7 @@ describe(PersonService.name, () => { isFavorite: false, updatedAt: expect.any(Date), }); - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: '1976-06-30' }); + expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') }); expect(mocks.job.queue).not.toHaveBeenCalled(); expect(mocks.job.queueAll).not.toHaveBeenCalled(); expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); diff --git a/server/src/utils/date.ts b/server/src/utils/date.ts new file mode 100644 index 0000000000..67ce549050 --- /dev/null +++ b/server/src/utils/date.ts @@ -0,0 +1,3 @@ +export const asDateString = (x: Date | string | null): string | null => { + return x instanceof Date ? x.toISOString().split('T')[0] : x; +};