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

View File

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

View File

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

View File

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

View File

@ -99,4 +99,7 @@ export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto>
@ApiProperty({ type: JobStatusDto }) @ApiProperty({ type: JobStatusDto })
[QueueName.BACKUP_DATABASE]!: 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_CLEANUP = 'memory-cleanup',
MEMORY_CREATE = 'memory-create', MEMORY_CREATE = 'memory-create',
BACKUP_DATABASE = 'backup-database', BACKUP_DATABASE = 'backup-database',
INTEGRITY_DATABASE_CHECK = 'integrity-database-check',
} }
export enum AssetPathType { export enum AssetPathType {
@ -441,6 +442,7 @@ export enum QueueName {
LIBRARY = 'library', LIBRARY = 'library',
NOTIFICATION = 'notifications', NOTIFICATION = 'notifications',
BACKUP_DATABASE = 'backupDatabase', BACKUP_DATABASE = 'backupDatabase',
DATABASE_INTEGRITY_CHECK = 'integrityDatabaseCheck',
} }
export enum JobName { export enum JobName {
@ -532,6 +534,9 @@ export enum JobName {
// Version check // Version check
VERSION_CHECK = 'version-check', VERSION_CHECK = 'version-check',
// Integrity
DATABASE_INTEGRITY_CHECK = 'database-integrity-check',
} }
export enum JobCommand { export enum JobCommand {

View File

@ -463,3 +463,12 @@ where
and "libraryId" = $2::uuid and "libraryId" = $2::uuid
and "isExternal" = $3 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; 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 { DatabaseService } from 'src/services/database.service';
import { DownloadService } from 'src/services/download.service'; import { DownloadService } from 'src/services/download.service';
import { DuplicateService } from 'src/services/duplicate.service'; import { DuplicateService } from 'src/services/duplicate.service';
import { IntegrityService } from 'src/services/integrity.service';
import { JobService } from 'src/services/job.service'; import { JobService } from 'src/services/job.service';
import { LibraryService } from 'src/services/library.service'; import { LibraryService } from 'src/services/library.service';
import { MapService } from 'src/services/map.service'; import { MapService } from 'src/services/map.service';
@ -54,6 +55,7 @@ export const services = [
DatabaseService, DatabaseService,
DownloadService, DownloadService,
DuplicateService, DuplicateService,
IntegrityService,
JobService, JobService,
LibraryService, LibraryService,
MapService, 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 }; return { name: JobName.BACKUP_DATABASE };
} }
case ManualJobName.INTEGRITY_DATABASE_CHECK: {
return { name: JobName.DATABASE_INTEGRITY_CHECK };
}
default: { default: {
throw new BadRequestException('Invalid job name'); throw new BadRequestException('Invalid job name');
} }
@ -228,6 +232,7 @@ export class JobService extends BaseService {
QueueName.STORAGE_TEMPLATE_MIGRATION, QueueName.STORAGE_TEMPLATE_MIGRATION,
QueueName.DUPLICATE_DETECTION, QueueName.DUPLICATE_DETECTION,
QueueName.BACKUP_DATABASE, QueueName.BACKUP_DATABASE,
QueueName.DATABASE_INTEGRITY_CHECK,
].includes(name); ].includes(name);
} }

View File

@ -164,6 +164,7 @@ export type ConcurrentQueueName = Exclude<
| QueueName.FACIAL_RECOGNITION | QueueName.FACIAL_RECOGNITION
| QueueName.DUPLICATE_DETECTION | QueueName.DUPLICATE_DETECTION
| QueueName.BACKUP_DATABASE | QueueName.BACKUP_DATABASE
| QueueName.DATABASE_INTEGRITY_CHECK
>; >;
export type Jobs = { [K in JobItem['name']]: (JobItem & { name: K })['data'] }; export type Jobs = { [K in JobItem['name']]: (JobItem & { name: K })['data'] };
@ -363,9 +364,8 @@ export type JobItem =
// Version check // Version check
| { name: JobName.VERSION_CHECK; data: IBaseJob } | { name: JobName.VERSION_CHECK; data: IBaseJob }
// Memories // Integrity
| { name: JobName.MEMORIES_CLEANUP; data?: IBaseJob } | { name: JobName.DATABASE_INTEGRITY_CHECK; data?: IBaseJob };
| { name: JobName.MEMORIES_CREATE; data?: IBaseJob };
export type VectorExtension = DatabaseExtension.VECTOR | DatabaseExtension.VECTORS; 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_cleanup_job'), value: ManualJobName.MemoryCleanup },
{ title: $t('admin.memory_generate_job'), value: ManualJobName.MemoryCreate }, { title: $t('admin.memory_generate_job'), value: ManualJobName.MemoryCreate },
{ title: $t('admin.backup_database'), value: ManualJobName.BackupDatabase }, { title: $t('admin.backup_database'), value: ManualJobName.BackupDatabase },
{ title: 'integrity test', value: ManualJobName.IntegrityDatabaseCheck },
].map(({ value, title }) => ({ id: value, label: title, value })); ].map(({ value, title }) => ({ id: value, label: title, value }));
let selectedJob: ComboBoxOption | undefined = $state(undefined); let selectedJob: ComboBoxOption | undefined = $state(undefined);