mirror of
https://github.com/immich-app/immich.git
synced 2025-12-21 09:15:44 +03:00
feat: write integrity report to database
This commit is contained in:
@@ -482,6 +482,12 @@ export enum CacheControl {
|
|||||||
None = 'none',
|
None = 'none',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum IntegrityReportType {
|
||||||
|
OrphanFile = 'orphan_file',
|
||||||
|
MissingFile = 'missing_file',
|
||||||
|
ChecksumFail = 'checksum_fail',
|
||||||
|
}
|
||||||
|
|
||||||
export enum ImmichEnvironment {
|
export enum ImmichEnvironment {
|
||||||
Development = 'development',
|
Development = 'development',
|
||||||
Testing = 'testing',
|
Testing = 'testing',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { DownloadRepository } from 'src/repositories/download.repository';
|
|||||||
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
|
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
|
||||||
import { EmailRepository } from 'src/repositories/email.repository';
|
import { EmailRepository } from 'src/repositories/email.repository';
|
||||||
import { EventRepository } from 'src/repositories/event.repository';
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
|
import { IntegrityReportRepository } from 'src/repositories/integrity-report.repository';
|
||||||
import { JobRepository } from 'src/repositories/job.repository';
|
import { JobRepository } from 'src/repositories/job.repository';
|
||||||
import { LibraryRepository } from 'src/repositories/library.repository';
|
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
@@ -68,6 +69,7 @@ export const repositories = [
|
|||||||
DuplicateRepository,
|
DuplicateRepository,
|
||||||
EmailRepository,
|
EmailRepository,
|
||||||
EventRepository,
|
EventRepository,
|
||||||
|
IntegrityReportRepository,
|
||||||
JobRepository,
|
JobRepository,
|
||||||
LibraryRepository,
|
LibraryRepository,
|
||||||
LoggingRepository,
|
LoggingRepository,
|
||||||
|
|||||||
19
server/src/repositories/integrity-report.repository.ts
Normal file
19
server/src/repositories/integrity-report.repository.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Insertable, Kysely } from 'kysely';
|
||||||
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
|
import { DB } from 'src/schema';
|
||||||
|
import { IntegrityReportTable } from 'src/schema/tables/integrity-report.table';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class IntegrityReportRepository {
|
||||||
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
|
create(dto: Insertable<IntegrityReportTable> | Insertable<IntegrityReportTable>[]) {
|
||||||
|
return this.db
|
||||||
|
.insertInto('integrity_report')
|
||||||
|
.values(dto)
|
||||||
|
.onConflict((oc) => oc.doNothing())
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ import { AssetTable } from 'src/schema/tables/asset.table';
|
|||||||
import { AuditTable } from 'src/schema/tables/audit.table';
|
import { AuditTable } from 'src/schema/tables/audit.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||||
|
import { IntegrityReportTable } from 'src/schema/tables/integrity-report.table';
|
||||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||||
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
||||||
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
||||||
@@ -98,6 +99,7 @@ export class ImmichDatabase {
|
|||||||
AssetExifTable,
|
AssetExifTable,
|
||||||
FaceSearchTable,
|
FaceSearchTable,
|
||||||
GeodataPlacesTable,
|
GeodataPlacesTable,
|
||||||
|
IntegrityReportTable,
|
||||||
LibraryTable,
|
LibraryTable,
|
||||||
MemoryTable,
|
MemoryTable,
|
||||||
MemoryAuditTable,
|
MemoryAuditTable,
|
||||||
@@ -195,6 +197,8 @@ export interface DB {
|
|||||||
|
|
||||||
geodata_places: GeodataPlacesTable;
|
geodata_places: GeodataPlacesTable;
|
||||||
|
|
||||||
|
integrity_report: IntegrityReportTable;
|
||||||
|
|
||||||
library: LibraryTable;
|
library: LibraryTable;
|
||||||
|
|
||||||
memory: MemoryTable;
|
memory: MemoryTable;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`CREATE TABLE "integrity_report" (
|
||||||
|
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||||
|
"type" character varying NOT NULL,
|
||||||
|
"path" character varying NOT NULL,
|
||||||
|
CONSTRAINT "integrity_report_type_path_uq" UNIQUE ("type", "path"),
|
||||||
|
CONSTRAINT "integrity_report_pkey" PRIMARY KEY ("id")
|
||||||
|
);`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`DROP TABLE "integrity_report";`.execute(db);
|
||||||
|
}
|
||||||
21
server/src/schema/tables/integrity-report.table.ts
Normal file
21
server/src/schema/tables/integrity-report.table.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { IntegrityReportType } from 'src/enum';
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Generated,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Table,
|
||||||
|
Unique
|
||||||
|
} from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table('integrity_report')
|
||||||
|
@Unique({ columns: ['type', 'path'] })
|
||||||
|
export class IntegrityReportTable {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id!: Generated<string>;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
type!: IntegrityReportType;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
path!: string;
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import { DownloadRepository } from 'src/repositories/download.repository';
|
|||||||
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
|
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
|
||||||
import { EmailRepository } from 'src/repositories/email.repository';
|
import { EmailRepository } from 'src/repositories/email.repository';
|
||||||
import { EventRepository } from 'src/repositories/event.repository';
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
|
import { IntegrityReportRepository } from 'src/repositories/integrity-report.repository';
|
||||||
import { JobRepository } from 'src/repositories/job.repository';
|
import { JobRepository } from 'src/repositories/job.repository';
|
||||||
import { LibraryRepository } from 'src/repositories/library.repository';
|
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
@@ -137,6 +138,7 @@ export class BaseService {
|
|||||||
protected duplicateRepository: DuplicateRepository,
|
protected duplicateRepository: DuplicateRepository,
|
||||||
protected emailRepository: EmailRepository,
|
protected emailRepository: EmailRepository,
|
||||||
protected eventRepository: EventRepository,
|
protected eventRepository: EventRepository,
|
||||||
|
protected integrityReportRepository: IntegrityReportRepository,
|
||||||
protected jobRepository: JobRepository,
|
protected jobRepository: JobRepository,
|
||||||
protected libraryRepository: LibraryRepository,
|
protected libraryRepository: LibraryRepository,
|
||||||
protected machineLearningRepository: MachineLearningRepository,
|
protected machineLearningRepository: MachineLearningRepository,
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ import { pipeline } from 'node:stream/promises';
|
|||||||
import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants';
|
import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { OnEvent, OnJob } from 'src/decorators';
|
import { OnEvent, OnJob } from 'src/decorators';
|
||||||
import { ImmichWorker, JobName, JobStatus, QueueName, StorageFolder, SystemMetadataKey } from 'src/enum';
|
import {
|
||||||
|
ImmichWorker,
|
||||||
|
IntegrityReportType,
|
||||||
|
JobName,
|
||||||
|
JobStatus,
|
||||||
|
QueueName,
|
||||||
|
StorageFolder,
|
||||||
|
SystemMetadataKey,
|
||||||
|
} from 'src/enum';
|
||||||
import { ArgOf } from 'src/repositories/event.repository';
|
import { ArgOf } from 'src/repositories/event.repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { IIntegrityMissingFilesJob, IIntegrityOrphanedFilesJob } from 'src/types';
|
import { IIntegrityMissingFilesJob, IIntegrityOrphanedFilesJob } from 'src/types';
|
||||||
@@ -31,16 +39,17 @@ export class IntegrityService extends BaseService {
|
|||||||
// start: database.enabled,
|
// start: database.enabled,
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
setTimeout(() => {
|
|
||||||
// this.jobRepository.queue({
|
|
||||||
// name: JobName.IntegrityOrphanedFilesQueueAll,
|
|
||||||
// data: {},
|
|
||||||
// });
|
|
||||||
|
|
||||||
// this.jobRepository.queue({
|
setTimeout(() => {
|
||||||
// name: JobName.IntegrityMissingFilesQueueAll,
|
this.jobRepository.queue({
|
||||||
// data: {},
|
name: JobName.IntegrityOrphanedFilesQueueAll,
|
||||||
// });
|
data: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jobRepository.queue({
|
||||||
|
name: JobName.IntegrityMissingFilesQueueAll,
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
|
||||||
this.jobRepository.queue({
|
this.jobRepository.queue({
|
||||||
name: JobName.IntegrityChecksumFiles,
|
name: JobName.IntegrityChecksumFiles,
|
||||||
@@ -129,8 +138,12 @@ export class IntegrityService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: do something with orphanedFiles
|
await this.integrityReportRepository.create(
|
||||||
console.info(orphanedFiles);
|
[...orphanedFiles].map((path) => ({
|
||||||
|
type: IntegrityReportType.OrphanFile,
|
||||||
|
path,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.log(`Processed ${paths.length} and found ${orphanedFiles.size} orphaned file(s).`);
|
this.logger.log(`Processed ${paths.length} and found ${orphanedFiles.size} orphaned file(s).`);
|
||||||
return JobStatus.Success;
|
return JobStatus.Success;
|
||||||
@@ -201,10 +214,14 @@ export class IntegrityService extends BaseService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const missingFiles = result.filter((path) => path);
|
const missingFiles = result.filter((path) => path) as string[];
|
||||||
|
|
||||||
// todo: do something with missingFiles
|
await this.integrityReportRepository.create(
|
||||||
console.info(missingFiles);
|
missingFiles.map((path) => ({
|
||||||
|
type: IntegrityReportType.MissingFile,
|
||||||
|
path,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.log(`Processed ${paths.length} and found ${missingFiles.length} missing file(s).`);
|
this.logger.log(`Processed ${paths.length} and found ${missingFiles.length} missing file(s).`);
|
||||||
return JobStatus.Success;
|
return JobStatus.Success;
|
||||||
@@ -266,7 +283,10 @@ export class IntegrityService extends BaseService {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn('Failed to process a file: ' + error);
|
this.logger.warn('Failed to process a file: ' + error);
|
||||||
// todo: do something with originalPath
|
await this.integrityReportRepository.create({
|
||||||
|
path: originalPath,
|
||||||
|
type: IntegrityReportType.ChecksumFail,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
processed++;
|
processed++;
|
||||||
|
|||||||
Reference in New Issue
Block a user