Files
Immich/server/src/controllers/person.controller.ts
T
ceea058662 fix: person management ids validation and permissions (#31902)
fix: person management ids validation and permissions (#31898)

(cherry picked from commit 4408f49d15d5b38eb2fbfa40b68cf697a0e16ca8)

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-09-29 16:22:56 +00:00

262 lines
8.6 KiB
TypeScript

import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Next,
Param,
Patch,
Post,
Put,
Query,
Res,
} from '@nestjs/common';
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
import type { NextFunction, Response } from 'express';
import type { AuthDto } from 'src/dtos/auth.dto.js';
import { Endpoint, HistoryBuilder } from 'src/decorators.js';
import { BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto.js';
import {
AssetFaceUpdateDto,
MergePersonDto,
PeopleDeleteDto,
PeopleResponseDto,
PeopleUpdateDto,
PersonCreateDto,
PersonDeleteDto,
PersonResponseDto,
PersonSearchDto,
PersonStatisticsResponseDto,
PersonUpdateDto,
PersonUsersCreateDto,
PersonUsersDeleteDto,
PersonUsersResponseDto,
PersonUsersSearchDto,
} from 'src/dtos/person.dto.js';
import { ApiTag, Permission } from 'src/enum.js';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard.js';
import { LoggingRepository } from 'src/repositories/logging.repository.js';
import { PersonService } from 'src/services/person.service.js';
import { sendFile } from 'src/utils/file.js';
import { UUIDParamDto } from 'src/validation.js';
@ApiTags(ApiTag.People)
@Controller('people')
export class PersonController {
constructor(
private service: PersonService,
private logger: LoggingRepository,
) {
this.logger.setContext(PersonController.name);
}
@Get()
@Authenticated({ permission: Permission.PersonRead })
@Endpoint({
summary: 'Get all people',
description: 'Retrieve a list of all people.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
getAllPeople(@Auth() auth: AuthDto, @Query() options: PersonSearchDto): Promise<PeopleResponseDto> {
return this.service.getAll(auth, options);
}
@Post()
@Authenticated({ permission: Permission.PersonCreate })
@Endpoint({
summary: 'Create a person',
description: 'Create a new person that can have multiple faces assigned to them.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
return this.service.create(auth, dto);
}
@Put()
@Authenticated({ permission: Permission.PersonUpdate })
@Endpoint({
summary: 'Update people',
description: 'Bulk update multiple people at once.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
return this.service.updateAll(auth, dto);
}
@Delete()
@Authenticated({ permission: Permission.PersonDelete })
@HttpCode(HttpStatus.NO_CONTENT)
@Endpoint({
summary: 'Delete people',
description: 'Bulk delete a list of people at once.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
deletePeople(@Auth() auth: AuthDto, @Body() dto: PeopleDeleteDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Get(':id')
@Authenticated({ permission: Permission.PersonRead })
@Endpoint({
summary: 'Get a person',
description: 'Retrieve a person by id.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
return this.service.getById(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.PersonUpdate })
@Endpoint({
summary: 'Update person',
description: 'Update an individual person.',
history: new HistoryBuilder()
.added('v1')
.beta('v1')
.stable('v2')
.deprecated('v3', { replacementId: 'updatePerson' }),
})
updatePerson(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: PersonUpdateDto,
): Promise<PersonResponseDto> {
return this.service.update(auth, id, dto);
}
@Patch(':id')
@ApiExcludeEndpoint()
@Authenticated({ permission: Permission.PersonUpdate })
updatePersonV3(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: PersonUpdateDto,
): Promise<PersonResponseDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
@Authenticated({ permission: Permission.PersonDelete })
@HttpCode(HttpStatus.NO_CONTENT)
@Endpoint({
summary: 'Delete person',
description: 'Delete an individual person.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
deletePerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: PersonDeleteDto): Promise<void> {
return this.service.delete(auth, id, dto);
}
@Get(':id/statistics')
@Authenticated({ permission: Permission.PersonStatistics })
@Endpoint({
summary: 'Get person statistics',
description: 'Retrieve statistics about a specific person.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
return this.service.getStatistics(auth, id);
}
@Get(':id/thumbnail')
@FileResponse()
@Authenticated({ permission: Permission.PersonRead })
@Endpoint({
summary: 'Get person thumbnail',
description: 'Retrieve the thumbnail file for a person.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
async getPersonThumbnail(
@Res() res: Response,
@Next() next: NextFunction,
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
) {
await sendFile(res, next, () => this.service.getThumbnail(auth, id), this.logger);
}
@Put(':id/reassign')
@Authenticated({ permission: Permission.PersonReassign })
@Endpoint({
summary: 'Reassign faces',
description: 'Bulk reassign a list of faces to a different person.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
reassignFaces(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: AssetFaceUpdateDto,
): Promise<PersonResponseDto[]> {
return this.service.reassignFaces(auth, id, dto);
}
@Post('merge')
@Authenticated({ permission: Permission.PersonMerge })
@HttpCode(HttpStatus.OK)
@Endpoint({
summary: 'Merge people',
description:
'Merge an ordered list of people together into a single person. The final name and birth date are always the first defined value, following the order. Also automatically merges people for other users in the cluster group, skipping people that would result in overriding a previously set name or birth date.',
history: new HistoryBuilder().added('v3.2.1').stable('v3.2.1'),
})
mergePeople(@Auth() auth: AuthDto, @Body() dto: MergePersonDto): Promise<BulkIdResponseDto[]> {
return this.service.mergePeople(auth, dto);
}
@Post(':id/merge')
@Authenticated({ permission: Permission.PersonMerge })
@HttpCode(HttpStatus.OK)
@Endpoint({
summary: 'Merge people',
description: 'Merge a list of people into the person specified in the path parameter.',
history: new HistoryBuilder()
.added('v1')
.beta('v1')
.stable('v2')
.deprecated('v3.2.1', { replacementId: 'mergePeople' }),
})
mergePersonLegacy(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: MergePersonDto,
): Promise<BulkIdResponseDto[]> {
return this.service.mergePeople(auth, { ids: [id, ...dto.ids] });
}
@Get('users')
@Authenticated({ permission: Permission.PersonRead })
@Endpoint({
summary: 'Get people access',
description: 'Retrieve a list of users and the people to which they have been given access',
history: new HistoryBuilder().added('v3.3').stable('v3.3'),
})
getUsersForPeople(@Auth() auth: AuthDto, @Query() dto: PersonUsersSearchDto): Promise<PersonUsersResponseDto> {
return this.service.getUsersForPeople(auth, dto);
}
@Put('users')
@Authenticated({ permission: Permission.PersonUpdate })
@Endpoint({
summary: 'Give users access to people',
description: 'Give users access to people',
history: new HistoryBuilder().added('v3.3').stable('v3.3'),
})
addUsersToPeople(@Auth() auth: AuthDto, @Body() dto: PersonUsersCreateDto): Promise<void> {
return this.service.addUsersToPeople(auth, dto);
}
@Delete('users')
@Authenticated({ permission: Permission.PersonUpdate })
@Endpoint({
summary: 'Remove users from people',
description: 'Remove user access to people',
history: new HistoryBuilder().added('v3.3').stable('v3.3'),
})
removeUsersFromPeople(@Auth() auth: AuthDto, @Body() dto: PersonUsersDeleteDto): Promise<void> {
return this.service.removeUsersFromPeople(auth, dto);
}
}