refactor: enum casing (#19946)

This commit is contained in:
Jason Rasmussen
2025-07-15 14:50:13 -04:00
committed by GitHub
parent 920d7de349
commit e73abe0762
174 changed files with 2675 additions and 2459 deletions

View File

@@ -78,7 +78,7 @@ export class PersonService extends BaseService {
}
async reassignFaces(auth: AuthDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [personId] });
await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [personId] });
const person = await this.findOrFail(personId);
const result: PersonResponseDto[] = [];
const changeFeaturePhoto: string[] = [];
@@ -86,7 +86,7 @@ export class PersonService extends BaseService {
const faces = await this.personRepository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]);
for (const face of faces) {
await this.requireAccess({ auth, permission: Permission.PERSON_CREATE, ids: [face.id] });
await this.requireAccess({ auth, permission: Permission.PersonCreate, ids: [face.id] });
if (person.faceAssetId === null) {
changeFeaturePhoto.push(person.id);
}
@@ -107,8 +107,8 @@ export class PersonService extends BaseService {
}
async reassignFacesById(auth: AuthDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [personId] });
await this.requireAccess({ auth, permission: Permission.PERSON_CREATE, ids: [dto.id] });
await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [personId] });
await this.requireAccess({ auth, permission: Permission.PersonCreate, ids: [dto.id] });
const face = await this.personRepository.getFaceById(dto.id);
const person = await this.findOrFail(personId);
@@ -124,7 +124,7 @@ export class PersonService extends BaseService {
}
async getFacesById(auth: AuthDto, dto: FaceDto): Promise<AssetFaceResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.ASSET_READ, ids: [dto.id] });
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.id] });
const faces = await this.personRepository.getFaces(dto.id);
return faces.map((asset) => mapFaces(asset, auth));
}
@@ -140,7 +140,7 @@ export class PersonService extends BaseService {
if (assetFace) {
await this.personRepository.update({ id: personId, faceAssetId: assetFace.id });
jobs.push({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: personId } });
jobs.push({ name: JobName.GeneratePersonThumbnail, data: { id: personId } });
}
}
@@ -148,17 +148,17 @@ export class PersonService extends BaseService {
}
async getById(auth: AuthDto, id: string): Promise<PersonResponseDto> {
await this.requireAccess({ auth, permission: Permission.PERSON_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] });
return this.findOrFail(id).then(mapPerson);
}
async getStatistics(auth: AuthDto, id: string): Promise<PersonStatisticsResponseDto> {
await this.requireAccess({ auth, permission: Permission.PERSON_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] });
return this.personRepository.getStatistics(id);
}
async getThumbnail(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
await this.requireAccess({ auth, permission: Permission.PERSON_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] });
const person = await this.personRepository.getById(id);
if (!person || !person.thumbnailPath) {
throw new NotFoundException();
@@ -167,7 +167,7 @@ export class PersonService extends BaseService {
return new ImmichFileResponse({
path: person.thumbnailPath,
contentType: mimeTypes.lookup(person.thumbnailPath),
cacheControl: CacheControl.PRIVATE_WITHOUT_CACHE,
cacheControl: CacheControl.PrivateWithoutCache,
});
}
@@ -185,13 +185,13 @@ export class PersonService extends BaseService {
}
async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {
await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [id] });
const { name, birthDate, isHidden, featureFaceAssetId: assetId, isFavorite, color } = dto;
// TODO: set by faceId directly
let faceId: string | undefined = undefined;
if (assetId) {
await this.requireAccess({ auth, permission: Permission.ASSET_READ, ids: [assetId] });
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [assetId] });
const [face] = await this.personRepository.getFacesByIds([{ personId: id, assetId }]);
if (!face) {
throw new BadRequestException('Invalid assetId for feature face');
@@ -211,7 +211,7 @@ export class PersonService extends BaseService {
});
if (assetId) {
await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id } });
await this.jobRepository.queue({ name: JobName.GeneratePersonThumbnail, data: { id } });
}
return mapPerson(person);
@@ -242,7 +242,7 @@ export class PersonService extends BaseService {
}
async deleteAll(auth: AuthDto, { ids }: BulkIdsDto): Promise<void> {
await this.requireAccess({ auth, permission: Permission.PERSON_DELETE, ids });
await this.requireAccess({ auth, permission: Permission.PersonDelete, ids });
const people = await this.personRepository.getForPeopleDelete(ids);
await this.removeAllPeople(people);
}
@@ -254,22 +254,22 @@ export class PersonService extends BaseService {
this.logger.debug(`Deleted ${people.length} people`);
}
@OnJob({ name: JobName.PERSON_CLEANUP, queue: QueueName.BACKGROUND_TASK })
@OnJob({ name: JobName.PersonCleanup, queue: QueueName.BackgroundTask })
async handlePersonCleanup(): Promise<JobStatus> {
const people = await this.personRepository.getAllWithoutFaces();
await this.removeAllPeople(people);
return JobStatus.SUCCESS;
return JobStatus.Success;
}
@OnJob({ name: JobName.QUEUE_FACE_DETECTION, queue: QueueName.FACE_DETECTION })
async handleQueueDetectFaces({ force }: JobOf<JobName.QUEUE_FACE_DETECTION>): Promise<JobStatus> {
@OnJob({ name: JobName.QueueFaceDetection, queue: QueueName.FaceDetection })
async handleQueueDetectFaces({ force }: JobOf<JobName.QueueFaceDetection>): Promise<JobStatus> {
const { machineLearning } = await this.getConfig({ withCache: false });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
if (force) {
await this.personRepository.deleteFaces({ sourceType: SourceType.MACHINE_LEARNING });
await this.personRepository.deleteFaces({ sourceType: SourceType.MachineLearning });
await this.handlePersonCleanup();
await this.personRepository.vacuum({ reindexVectors: true });
}
@@ -277,7 +277,7 @@ export class PersonService extends BaseService {
let jobs: JobItem[] = [];
const assets = this.assetJobRepository.streamForDetectFacesJob(force);
for await (const asset of assets) {
jobs.push({ name: JobName.FACE_DETECTION, data: { id: asset.id } });
jobs.push({ name: JobName.FaceDetection, data: { id: asset.id } });
if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) {
await this.jobRepository.queueAll(jobs);
@@ -288,27 +288,27 @@ export class PersonService extends BaseService {
await this.jobRepository.queueAll(jobs);
if (force === undefined) {
await this.jobRepository.queue({ name: JobName.PERSON_CLEANUP });
await this.jobRepository.queue({ name: JobName.PersonCleanup });
}
return JobStatus.SUCCESS;
return JobStatus.Success;
}
@OnJob({ name: JobName.FACE_DETECTION, queue: QueueName.FACE_DETECTION })
async handleDetectFaces({ id }: JobOf<JobName.FACE_DETECTION>): Promise<JobStatus> {
@OnJob({ name: JobName.FaceDetection, queue: QueueName.FaceDetection })
async handleDetectFaces({ id }: JobOf<JobName.FaceDetection>): Promise<JobStatus> {
const { machineLearning } = await this.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
const asset = await this.assetJobRepository.getForDetectFacesJob(id);
const previewFile = asset?.files[0];
if (!asset || asset.files.length !== 1 || !previewFile) {
return JobStatus.FAILED;
return JobStatus.Failed;
}
if (asset.visibility === AssetVisibility.HIDDEN) {
return JobStatus.SKIPPED;
if (asset.visibility === AssetVisibility.Hidden) {
return JobStatus.Skipped;
}
const { imageHeight, imageWidth, faces } = await this.machineLearningRepository.detectFaces(
@@ -323,7 +323,7 @@ export class PersonService extends BaseService {
const mlFaceIds = new Set<string>();
for (const face of asset.faces) {
if (face.sourceType === SourceType.MACHINE_LEARNING) {
if (face.sourceType === SourceType.MachineLearning) {
mlFaceIds.add(face.id);
}
}
@@ -368,15 +368,15 @@ export class PersonService extends BaseService {
if (facesToAdd.length > 0) {
this.logger.log(`Detected ${facesToAdd.length} new faces in asset ${id}`);
const jobs = facesToAdd.map((face) => ({ name: JobName.FACIAL_RECOGNITION, data: { id: face.id } }) as const);
await this.jobRepository.queueAll([{ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } }, ...jobs]);
const jobs = facesToAdd.map((face) => ({ name: JobName.FacialRecognition, data: { id: face.id } }) as const);
await this.jobRepository.queueAll([{ name: JobName.QueueFacialRecognition, data: { force: false } }, ...jobs]);
} else if (embeddings.length > 0) {
this.logger.log(`Added ${embeddings.length} face embeddings for asset ${id}`);
}
await this.assetRepository.upsertJobStatus({ assetId: asset.id, facesRecognizedAt: new Date() });
return JobStatus.SUCCESS;
return JobStatus.Success;
}
private iou(
@@ -396,50 +396,50 @@ export class PersonService extends BaseService {
return intersection / union;
}
@OnJob({ name: JobName.QUEUE_FACIAL_RECOGNITION, queue: QueueName.FACIAL_RECOGNITION })
async handleQueueRecognizeFaces({ force, nightly }: JobOf<JobName.QUEUE_FACIAL_RECOGNITION>): Promise<JobStatus> {
@OnJob({ name: JobName.QueueFacialRecognition, queue: QueueName.FacialRecognition })
async handleQueueRecognizeFaces({ force, nightly }: JobOf<JobName.QueueFacialRecognition>): Promise<JobStatus> {
const { machineLearning } = await this.getConfig({ withCache: false });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
await this.jobRepository.waitForQueueCompletion(QueueName.THUMBNAIL_GENERATION, QueueName.FACE_DETECTION);
await this.jobRepository.waitForQueueCompletion(QueueName.ThumbnailGeneration, QueueName.FaceDetection);
if (nightly) {
const [state, latestFaceDate] = await Promise.all([
this.systemMetadataRepository.get(SystemMetadataKey.FACIAL_RECOGNITION_STATE),
this.systemMetadataRepository.get(SystemMetadataKey.FacialRecognitionState),
this.personRepository.getLatestFaceDate(),
]);
if (state?.lastRun && latestFaceDate && state.lastRun > latestFaceDate) {
this.logger.debug('Skipping facial recognition nightly since no face has been added since the last run');
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
}
const { waiting } = await this.jobRepository.getJobCounts(QueueName.FACIAL_RECOGNITION);
const { waiting } = await this.jobRepository.getJobCounts(QueueName.FacialRecognition);
if (force) {
await this.personRepository.unassignFaces({ sourceType: SourceType.MACHINE_LEARNING });
await this.personRepository.unassignFaces({ sourceType: SourceType.MachineLearning });
await this.handlePersonCleanup();
await this.personRepository.vacuum({ reindexVectors: false });
} else if (waiting) {
this.logger.debug(
`Skipping facial recognition queueing because ${waiting} job${waiting > 1 ? 's are' : ' is'} already queued`,
);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
await this.databaseRepository.prewarm(VectorIndex.FACE);
await this.databaseRepository.prewarm(VectorIndex.Face);
const lastRun = new Date().toISOString();
const facePagination = this.personRepository.getAllFaces(
force ? undefined : { personId: null, sourceType: SourceType.MACHINE_LEARNING },
force ? undefined : { personId: null, sourceType: SourceType.MachineLearning },
);
let jobs: { name: JobName.FACIAL_RECOGNITION; data: { id: string; deferred: false } }[] = [];
let jobs: { name: JobName.FacialRecognition; data: { id: string; deferred: false } }[] = [];
for await (const face of facePagination) {
jobs.push({ name: JobName.FACIAL_RECOGNITION, data: { id: face.id, deferred: false } });
jobs.push({ name: JobName.FacialRecognition, data: { id: face.id, deferred: false } });
if (jobs.length === JOBS_ASSET_PAGINATION_SIZE) {
await this.jobRepository.queueAll(jobs);
@@ -449,37 +449,37 @@ export class PersonService extends BaseService {
await this.jobRepository.queueAll(jobs);
await this.systemMetadataRepository.set(SystemMetadataKey.FACIAL_RECOGNITION_STATE, { lastRun });
await this.systemMetadataRepository.set(SystemMetadataKey.FacialRecognitionState, { lastRun });
return JobStatus.SUCCESS;
return JobStatus.Success;
}
@OnJob({ name: JobName.FACIAL_RECOGNITION, queue: QueueName.FACIAL_RECOGNITION })
async handleRecognizeFaces({ id, deferred }: JobOf<JobName.FACIAL_RECOGNITION>): Promise<JobStatus> {
@OnJob({ name: JobName.FacialRecognition, queue: QueueName.FacialRecognition })
async handleRecognizeFaces({ id, deferred }: JobOf<JobName.FacialRecognition>): Promise<JobStatus> {
const { machineLearning } = await this.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
const face = await this.personRepository.getFaceForFacialRecognitionJob(id);
if (!face || !face.asset) {
this.logger.warn(`Face ${id} not found`);
return JobStatus.FAILED;
return JobStatus.Failed;
}
if (face.sourceType !== SourceType.MACHINE_LEARNING) {
if (face.sourceType !== SourceType.MachineLearning) {
this.logger.warn(`Skipping face ${id} due to source ${face.sourceType}`);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
if (!face.faceSearch?.embedding) {
this.logger.warn(`Face ${id} does not have an embedding`);
return JobStatus.FAILED;
return JobStatus.Failed;
}
if (face.personId) {
this.logger.debug(`Face ${id} already has a person assigned`);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
const matches = await this.searchRepository.searchFaces({
@@ -493,18 +493,18 @@ export class PersonService extends BaseService {
// `matches` also includes the face itself
if (machineLearning.facialRecognition.minFaces > 1 && matches.length <= 1) {
this.logger.debug(`Face ${id} only matched the face itself, skipping`);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
this.logger.debug(`Face ${id} has ${matches.length} matches`);
const isCore =
matches.length >= machineLearning.facialRecognition.minFaces &&
face.asset.visibility === AssetVisibility.TIMELINE;
face.asset.visibility === AssetVisibility.Timeline;
if (!isCore && !deferred) {
this.logger.debug(`Deferring non-core face ${id} for later processing`);
await this.jobRepository.queue({ name: JobName.FACIAL_RECOGNITION, data: { id, deferred: true } });
return JobStatus.SKIPPED;
await this.jobRepository.queue({ name: JobName.FacialRecognition, data: { id, deferred: true } });
return JobStatus.Skipped;
}
let personId = matches.find((match) => match.personId)?.personId;
@@ -526,7 +526,7 @@ export class PersonService extends BaseService {
if (isCore && !personId) {
this.logger.log(`Creating new person for face ${id}`);
const newPerson = await this.personRepository.create({ ownerId: face.asset.ownerId, faceAssetId: face.id });
await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: newPerson.id } });
await this.jobRepository.queue({ name: JobName.GeneratePersonThumbnail, data: { id: newPerson.id } });
personId = newPerson.id;
}
@@ -535,19 +535,19 @@ export class PersonService extends BaseService {
await this.personRepository.reassignFaces({ faceIds: [id], newPersonId: personId });
}
return JobStatus.SUCCESS;
return JobStatus.Success;
}
@OnJob({ name: JobName.MIGRATE_PERSON, queue: QueueName.MIGRATION })
async handlePersonMigration({ id }: JobOf<JobName.MIGRATE_PERSON>): Promise<JobStatus> {
@OnJob({ name: JobName.MigratePerson, queue: QueueName.Migration })
async handlePersonMigration({ id }: JobOf<JobName.MigratePerson>): Promise<JobStatus> {
const person = await this.personRepository.getById(id);
if (!person) {
return JobStatus.FAILED;
return JobStatus.Failed;
}
await this.storageCore.movePersonFile(person, PersonPathType.FACE);
await this.storageCore.movePersonFile(person, PersonPathType.Face);
return JobStatus.SUCCESS;
return JobStatus.Success;
}
async mergePerson(auth: AuthDto, id: string, dto: MergePersonDto): Promise<BulkIdResponseDto[]> {
@@ -556,7 +556,7 @@ export class PersonService extends BaseService {
throw new BadRequestException('Cannot merge a person into themselves');
}
await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [id] });
let primaryPerson = await this.findOrFail(id);
const primaryName = primaryPerson.name || primaryPerson.id;
@@ -564,7 +564,7 @@ export class PersonService extends BaseService {
const allowedIds = await this.checkAccess({
auth,
permission: Permission.PERSON_MERGE,
permission: Permission.PersonMerge,
ids: mergeIds,
});
@@ -623,8 +623,8 @@ export class PersonService extends BaseService {
// TODO return a asset face response
async createFace(auth: AuthDto, dto: AssetFaceCreateDto): Promise<void> {
await Promise.all([
this.requireAccess({ auth, permission: Permission.ASSET_READ, ids: [dto.assetId] }),
this.requireAccess({ auth, permission: Permission.PERSON_READ, ids: [dto.personId] }),
this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.assetId] }),
this.requireAccess({ auth, permission: Permission.PersonRead, ids: [dto.personId] }),
]);
await this.personRepository.createAssetFace({
@@ -636,12 +636,12 @@ export class PersonService extends BaseService {
boundingBoxX2: dto.x + dto.width,
boundingBoxY1: dto.y,
boundingBoxY2: dto.y + dto.height,
sourceType: SourceType.MANUAL,
sourceType: SourceType.Manual,
});
}
async deleteFace(auth: AuthDto, id: string, dto: AssetFaceDeleteDto): Promise<void> {
await this.requireAccess({ auth, permission: Permission.FACE_DELETE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.FaceDelete, ids: [id] });
return dto.force ? this.personRepository.deleteAssetFace(id) : this.personRepository.softDeleteAssetFaces(id);
}