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 <input> 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.
This commit is contained in:
David Bourgault 2025-03-09 22:32:05 -04:00 committed by GitHub
parent 097749d872
commit 9870ad9687
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 11 additions and 7 deletions

View File

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

View File

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

View File

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

View File

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

3
server/src/utils/date.ts Normal file
View File

@ -0,0 +1,3 @@
export const asDateString = (x: Date | string | null): string | null => {
return x instanceof Date ? x.toISOString().split('T')[0] : x;
};