This commit is contained in:
Zack Pollard 2025-05-19 18:16:23 +01:00
parent 14970c5539
commit 5b0ea3397f
14 changed files with 81 additions and 8 deletions

View File

@ -18,6 +18,7 @@ class AllJobStatusResponseDto {
required this.duplicateDetection,
required this.faceDetection,
required this.facialRecognition,
required this.integrityDatabaseCheck,
required this.library_,
required this.metadataExtraction,
required this.migration,
@ -40,6 +41,8 @@ class AllJobStatusResponseDto {
JobStatusDto facialRecognition;
JobStatusDto integrityDatabaseCheck;
JobStatusDto library_;
JobStatusDto metadataExtraction;
@ -67,6 +70,7 @@ class AllJobStatusResponseDto {
other.duplicateDetection == duplicateDetection &&
other.faceDetection == faceDetection &&
other.facialRecognition == facialRecognition &&
other.integrityDatabaseCheck == integrityDatabaseCheck &&
other.library_ == library_ &&
other.metadataExtraction == metadataExtraction &&
other.migration == migration &&
@ -86,6 +90,7 @@ class AllJobStatusResponseDto {
(duplicateDetection.hashCode) +
(faceDetection.hashCode) +
(facialRecognition.hashCode) +
(integrityDatabaseCheck.hashCode) +
(library_.hashCode) +
(metadataExtraction.hashCode) +
(migration.hashCode) +
@ -98,7 +103,7 @@ class AllJobStatusResponseDto {
(videoConversion.hashCode);
@override
String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, integrityDatabaseCheck=$integrityDatabaseCheck, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -107,6 +112,7 @@ class AllJobStatusResponseDto {
json[r'duplicateDetection'] = this.duplicateDetection;
json[r'faceDetection'] = this.faceDetection;
json[r'facialRecognition'] = this.facialRecognition;
json[r'integrityDatabaseCheck'] = this.integrityDatabaseCheck;
json[r'library'] = this.library_;
json[r'metadataExtraction'] = this.metadataExtraction;
json[r'migration'] = this.migration;
@ -134,6 +140,7 @@ class AllJobStatusResponseDto {
duplicateDetection: JobStatusDto.fromJson(json[r'duplicateDetection'])!,
faceDetection: JobStatusDto.fromJson(json[r'faceDetection'])!,
facialRecognition: JobStatusDto.fromJson(json[r'facialRecognition'])!,
integrityDatabaseCheck: JobStatusDto.fromJson(json[r'integrityDatabaseCheck'])!,
library_: JobStatusDto.fromJson(json[r'library'])!,
metadataExtraction: JobStatusDto.fromJson(json[r'metadataExtraction'])!,
migration: JobStatusDto.fromJson(json[r'migration'])!,
@ -196,6 +203,7 @@ class AllJobStatusResponseDto {
'duplicateDetection',
'faceDetection',
'facialRecognition',
'integrityDatabaseCheck',
'library',
'metadataExtraction',
'migration',

View File

@ -38,6 +38,7 @@ class JobName {
static const library_ = JobName._(r'library');
static const notifications = JobName._(r'notifications');
static const backupDatabase = JobName._(r'backupDatabase');
static const integrityDatabaseCheck = JobName._(r'integrityDatabaseCheck');
/// List of all possible values in this [enum][JobName].
static const values = <JobName>[
@ -56,6 +57,7 @@ class JobName {
library_,
notifications,
backupDatabase,
integrityDatabaseCheck,
];
static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value);
@ -109,6 +111,7 @@ class JobNameTypeTransformer {
case r'library': return JobName.library_;
case r'notifications': return JobName.notifications;
case r'backupDatabase': return JobName.backupDatabase;
case r'integrityDatabaseCheck': return JobName.integrityDatabaseCheck;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@ -29,6 +29,7 @@ class ManualJobName {
static const memoryCleanup = ManualJobName._(r'memory-cleanup');
static const memoryCreate = ManualJobName._(r'memory-create');
static const backupDatabase = ManualJobName._(r'backup-database');
static const integrityDatabaseCheck = ManualJobName._(r'integrity-database-check');
/// List of all possible values in this [enum][ManualJobName].
static const values = <ManualJobName>[
@ -38,6 +39,7 @@ class ManualJobName {
memoryCleanup,
memoryCreate,
backupDatabase,
integrityDatabaseCheck,
];
static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value);
@ -82,6 +84,7 @@ class ManualJobNameTypeTransformer {
case r'memory-cleanup': return ManualJobName.memoryCleanup;
case r'memory-create': return ManualJobName.memoryCreate;
case r'backup-database': return ManualJobName.backupDatabase;
case r'integrity-database-check': return ManualJobName.integrityDatabaseCheck;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');

View File

@ -8532,6 +8532,9 @@
"facialRecognition": {
"$ref": "#/components/schemas/JobStatusDto"
},
"integrityDatabaseCheck": {
"$ref": "#/components/schemas/JobStatusDto"
},
"library": {
"$ref": "#/components/schemas/JobStatusDto"
},
@ -8569,6 +8572,7 @@
"duplicateDetection",
"faceDetection",
"facialRecognition",
"integrityDatabaseCheck",
"library",
"metadataExtraction",
"migration",
@ -10101,7 +10105,8 @@
"sidecar",
"library",
"notifications",
"backupDatabase"
"backupDatabase",
"integrityDatabaseCheck"
],
"type": "string"
},
@ -10336,7 +10341,8 @@
"user-cleanup",
"memory-cleanup",
"memory-create",
"backup-database"
"backup-database",
"integrity-database-check"
],
"type": "string"
},

View File

@ -622,6 +622,7 @@ export type AllJobStatusResponseDto = {
duplicateDetection: JobStatusDto;
faceDetection: JobStatusDto;
facialRecognition: JobStatusDto;
integrityDatabaseCheck: JobStatusDto;
library: JobStatusDto;
metadataExtraction: JobStatusDto;
migration: JobStatusDto;
@ -3789,7 +3790,8 @@ export enum ManualJobName {
UserCleanup = "user-cleanup",
MemoryCleanup = "memory-cleanup",
MemoryCreate = "memory-create",
BackupDatabase = "backup-database"
BackupDatabase = "backup-database",
IntegrityDatabaseCheck = "integrity-database-check"
}
export enum JobName {
ThumbnailGeneration = "thumbnailGeneration",
@ -3806,7 +3808,8 @@ export enum JobName {
Sidecar = "sidecar",
Library = "library",
Notifications = "notifications",
BackupDatabase = "backupDatabase"
BackupDatabase = "backupDatabase",
IntegrityDatabaseCheck = "integrityDatabaseCheck"
}
export enum JobCommand {
Start = "start",

View File

@ -99,4 +99,7 @@ export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto>
@ApiProperty({ type: JobStatusDto })
[QueueName.BACKUP_DATABASE]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.DATABASE_INTEGRITY_CHECK]!: JobStatusDto;
}

View File

@ -251,6 +251,7 @@ export enum ManualJobName {
MEMORY_CLEANUP = 'memory-cleanup',
MEMORY_CREATE = 'memory-create',
BACKUP_DATABASE = 'backup-database',
INTEGRITY_DATABASE_CHECK = 'integrity-database-check',
}
export enum AssetPathType {
@ -441,6 +442,7 @@ export enum QueueName {
LIBRARY = 'library',
NOTIFICATION = 'notifications',
BACKUP_DATABASE = 'backupDatabase',
DATABASE_INTEGRITY_CHECK = 'integrityDatabaseCheck',
}
export enum JobName {
@ -532,6 +534,9 @@ export enum JobName {
// Version check
VERSION_CHECK = 'version-check',
// Integrity
DATABASE_INTEGRITY_CHECK = 'database-integrity-check',
}
export enum JobCommand {

View File

@ -463,3 +463,12 @@ where
and "libraryId" = $2::uuid
and "isExternal" = $3
)
-- AssetRepository.integrityCheckExif
select
"id"
from
"assets"
left join "exif" on "assets"."id" = "exif"."assetId"
where
"exif"."assetId" is null

View File

@ -875,4 +875,16 @@ export class AssetRepository {
return count;
}
@GenerateSql()
async integrityCheckExif(): Promise<string[]> {
const result = await this.db
.selectFrom('assets')
.select('id')
.leftJoin('exif', 'assets.id', 'exif.assetId')
.where('exif.assetId', 'is', null)
.execute();
return result.map((row) => row.id);
}
}

View File

@ -11,6 +11,7 @@ import { CliService } from 'src/services/cli.service';
import { DatabaseService } from 'src/services/database.service';
import { DownloadService } from 'src/services/download.service';
import { DuplicateService } from 'src/services/duplicate.service';
import { IntegrityService } from 'src/services/integrity.service';
import { JobService } from 'src/services/job.service';
import { LibraryService } from 'src/services/library.service';
import { MapService } from 'src/services/map.service';
@ -54,6 +55,7 @@ export const services = [
DatabaseService,
DownloadService,
DuplicateService,
IntegrityService,
JobService,
LibraryService,
MapService,

View File

@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';
import { OnJob } from 'src/decorators';
import { JobName, JobStatus, QueueName } from 'src/enum';
import { BaseService } from 'src/services/base.service';
@Injectable()
export class IntegrityService extends BaseService {
@OnJob({ name: JobName.DATABASE_INTEGRITY_CHECK, queue: QueueName.DATABASE_INTEGRITY_CHECK })
async handleDatabaseIntegrityCheck(): Promise<JobStatus> {
console.log(JSON.stringify(await this.assetRepository.integrityCheckExif()));
return JobStatus.SUCCESS;
}
}

View File

@ -46,6 +46,10 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
return { name: JobName.BACKUP_DATABASE };
}
case ManualJobName.INTEGRITY_DATABASE_CHECK: {
return { name: JobName.DATABASE_INTEGRITY_CHECK };
}
default: {
throw new BadRequestException('Invalid job name');
}
@ -228,6 +232,7 @@ export class JobService extends BaseService {
QueueName.STORAGE_TEMPLATE_MIGRATION,
QueueName.DUPLICATE_DETECTION,
QueueName.BACKUP_DATABASE,
QueueName.DATABASE_INTEGRITY_CHECK,
].includes(name);
}

View File

@ -164,6 +164,7 @@ export type ConcurrentQueueName = Exclude<
| QueueName.FACIAL_RECOGNITION
| QueueName.DUPLICATE_DETECTION
| QueueName.BACKUP_DATABASE
| QueueName.DATABASE_INTEGRITY_CHECK
>;
export type Jobs = { [K in JobItem['name']]: (JobItem & { name: K })['data'] };
@ -363,9 +364,8 @@ export type JobItem =
// Version check
| { name: JobName.VERSION_CHECK; data: IBaseJob }
// Memories
| { name: JobName.MEMORIES_CLEANUP; data?: IBaseJob }
| { name: JobName.MEMORIES_CREATE; data?: IBaseJob };
// Integrity
| { name: JobName.DATABASE_INTEGRITY_CHECK; data?: IBaseJob };
export type VectorExtension = DatabaseExtension.VECTOR | DatabaseExtension.VECTORS;

View File

@ -20,6 +20,7 @@
{ title: $t('admin.memory_cleanup_job'), value: ManualJobName.MemoryCleanup },
{ title: $t('admin.memory_generate_job'), value: ManualJobName.MemoryCreate },
{ title: $t('admin.backup_database'), value: ManualJobName.BackupDatabase },
{ title: 'integrity test', value: ManualJobName.IntegrityDatabaseCheck },
].map(({ value, title }) => ({ id: value, label: title, value }));
let selectedJob: ComboBoxOption | undefined = $state(undefined);