mirror of
https://github.com/immich-app/immich.git
synced 2025-12-21 01:11:16 +03:00
feat: write integrity report to database
This commit is contained in:
@@ -482,6 +482,12 @@ export enum CacheControl {
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export enum IntegrityReportType {
|
||||
OrphanFile = 'orphan_file',
|
||||
MissingFile = 'missing_file',
|
||||
ChecksumFail = 'checksum_fail',
|
||||
}
|
||||
|
||||
export enum ImmichEnvironment {
|
||||
Development = 'development',
|
||||
Testing = 'testing',
|
||||
|
||||
@@ -15,6 +15,7 @@ import { DownloadRepository } from 'src/repositories/download.repository';
|
||||
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
|
||||
import { EmailRepository } from 'src/repositories/email.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { IntegrityReportRepository } from 'src/repositories/integrity-report.repository';
|
||||
import { JobRepository } from 'src/repositories/job.repository';
|
||||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
@@ -68,6 +69,7 @@ export const repositories = [
|
||||
DuplicateRepository,
|
||||
EmailRepository,
|
||||
EventRepository,
|
||||
IntegrityReportRepository,
|
||||
JobRepository,
|
||||
LibraryRepository,
|
||||
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 { FaceSearchTable } from 'src/schema/tables/face-search.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 { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
||||
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
||||
@@ -98,6 +99,7 @@ export class ImmichDatabase {
|
||||
AssetExifTable,
|
||||
FaceSearchTable,
|
||||
GeodataPlacesTable,
|
||||
IntegrityReportTable,
|
||||
LibraryTable,
|
||||
MemoryTable,
|
||||
MemoryAuditTable,
|
||||
@@ -195,6 +197,8 @@ export interface DB {
|
||||
|
||||
geodata_places: GeodataPlacesTable;
|
||||
|
||||
integrity_report: IntegrityReportTable;
|
||||
|
||||
library: LibraryTable;
|
||||
|
||||
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 { EmailRepository } from 'src/repositories/email.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { IntegrityReportRepository } from 'src/repositories/integrity-report.repository';
|
||||
import { JobRepository } from 'src/repositories/job.repository';
|
||||
import { LibraryRepository } from 'src/repositories/library.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
@@ -137,6 +138,7 @@ export class BaseService {
|
||||
protected duplicateRepository: DuplicateRepository,
|
||||
protected emailRepository: EmailRepository,
|
||||
protected eventRepository: EventRepository,
|
||||
protected integrityReportRepository: IntegrityReportRepository,
|
||||
protected jobRepository: JobRepository,
|
||||
protected libraryRepository: LibraryRepository,
|
||||
protected machineLearningRepository: MachineLearningRepository,
|
||||
|
||||
@@ -7,7 +7,15 @@ import { pipeline } from 'node:stream/promises';
|
||||
import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
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 { BaseService } from 'src/services/base.service';
|
||||
import { IIntegrityMissingFilesJob, IIntegrityOrphanedFilesJob } from 'src/types';
|
||||
@@ -31,16 +39,17 @@ export class IntegrityService extends BaseService {
|
||||
// start: database.enabled,
|
||||
// });
|
||||
// }
|
||||
setTimeout(() => {
|
||||
// this.jobRepository.queue({
|
||||
// name: JobName.IntegrityOrphanedFilesQueueAll,
|
||||
// data: {},
|
||||
// });
|
||||
|
||||
// this.jobRepository.queue({
|
||||
// name: JobName.IntegrityMissingFilesQueueAll,
|
||||
// data: {},
|
||||
// });
|
||||
setTimeout(() => {
|
||||
this.jobRepository.queue({
|
||||
name: JobName.IntegrityOrphanedFilesQueueAll,
|
||||
data: {},
|
||||
});
|
||||
|
||||
this.jobRepository.queue({
|
||||
name: JobName.IntegrityMissingFilesQueueAll,
|
||||
data: {},
|
||||
});
|
||||
|
||||
this.jobRepository.queue({
|
||||
name: JobName.IntegrityChecksumFiles,
|
||||
@@ -129,8 +138,12 @@ export class IntegrityService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
// todo: do something with orphanedFiles
|
||||
console.info(orphanedFiles);
|
||||
await this.integrityReportRepository.create(
|
||||
[...orphanedFiles].map((path) => ({
|
||||
type: IntegrityReportType.OrphanFile,
|
||||
path,
|
||||
})),
|
||||
);
|
||||
|
||||
this.logger.log(`Processed ${paths.length} and found ${orphanedFiles.size} orphaned file(s).`);
|
||||
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
|
||||
console.info(missingFiles);
|
||||
await this.integrityReportRepository.create(
|
||||
missingFiles.map((path) => ({
|
||||
type: IntegrityReportType.MissingFile,
|
||||
path,
|
||||
})),
|
||||
);
|
||||
|
||||
this.logger.log(`Processed ${paths.length} and found ${missingFiles.length} missing file(s).`);
|
||||
return JobStatus.Success;
|
||||
@@ -266,7 +283,10 @@ export class IntegrityService extends BaseService {
|
||||
}
|
||||
} catch (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++;
|
||||
|
||||
Reference in New Issue
Block a user