Files
paperlessmanager/paperless-backend/src/inbox/inbox-migration.service.ts
T
bjoernpoettker 07dfd7e840 fix: resolve all ESLint errors in backend and frontend
Backend 958→0 errors, frontend 98→0 errors. Builds and tsc clean.

Echte Fixes:
- Auth: AuthenticatedUser/AuthenticatedRequest, JwtStrategy + alle 5
  Controller von `@Request() req: any` auf typisierten Request umgestellt
- Error-Handling: neuer getErrorMessage/Stack/Code/getResponseData-Helper;
  alle 50 `catch (err: any)`-Blöcke auf `unknown` + Helper umgestellt
- 24 echte Bugs: require-await, require-imports→ES-Imports, useless-escape,
  misused-promises, tote Imports/Vars, leere catch-Blöcke kommentiert
- document-pipeline: OCR-Ergebnis wird nicht gespeichert (als TODO markiert)

Pragmatisch auf warn herabgestuft (untypisierte Paperless-NGX-API):
no-unsafe-*, restrict-template-expressions, no-base-to-string,
no-explicit-any (FE), react-refresh/only-export-components

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 21:33:37 +02:00

158 lines
4.4 KiB
TypeScript

import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { randomUUID } from 'crypto';
import * as path from 'path';
import * as fs from 'fs/promises';
import {
InboxDocument,
type InboxSource,
type StoredQrCode,
} from '../database/entities/inbox-document.entity';
import { getErrorMessage, getErrorCode } from '../common/error.util';
import { PageCacheService } from '../barcode/page-cache.service';
interface LegacyScanRow {
QrCodes: string | StoredQrCode[];
}
@Injectable()
export class InboxMigrationService implements OnApplicationBootstrap {
private readonly logger = new Logger(InboxMigrationService.name);
private readonly legacyRoot: string;
constructor(
private readonly configService: ConfigService,
private readonly pageCache: PageCacheService,
private readonly dataSource: DataSource,
@InjectRepository(InboxDocument)
private readonly documentRepo: Repository<InboxDocument>,
) {
this.legacyRoot = this.configService.get<string>(
'SCANS_DATA_DIR',
'/mnt/data/scans',
);
}
async onApplicationBootstrap(): Promise<void> {
let subdirs: string[];
try {
const entries = await fs.readdir(this.legacyRoot, {
withFileTypes: true,
});
subdirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
} catch (err: unknown) {
if (getErrorCode(err) !== 'ENOENT') {
this.logger.warn(
`Migration: ${this.legacyRoot} nicht lesbar: ${getErrorMessage(err)}`,
);
}
return;
}
let migrated = 0;
for (const subdir of subdirs) {
const dir = path.join(this.legacyRoot, subdir);
let files: string[];
try {
files = await fs.readdir(dir);
} catch (err: unknown) {
this.logger.warn(
`Migration: ${dir} nicht lesbar: ${getErrorMessage(err)}`,
);
continue;
}
for (const name of files) {
if (path.extname(name).toLowerCase() !== '.pdf') continue;
const src = path.join(dir, name);
try {
await this.migrateFile(src, subdir, name);
migrated += 1;
} catch (err: unknown) {
this.logger.error(
`Migration fehlgeschlagen (${src}): ${getErrorMessage(err)}`,
);
}
}
await fs.rmdir(dir).catch(() => undefined);
}
if (migrated > 0) {
this.logger.log(`Migration: ${migrated} Datei(en) übernommen`);
} else {
this.logger.log('Migration: keine Altdaten gefunden');
}
}
private async migrateFile(
src: string,
subdir: string,
name: string,
): Promise<void> {
const id = randomUUID();
const source: InboxSource = subdir === 'all' ? 'all' : 'user';
const owner = source === 'all' ? null : subdir;
const targetDir = this.pageCache.documentDir(id);
const targetPdf = this.pageCache.documentPdfPath(id);
await fs.mkdir(targetDir, { recursive: true });
await this.move(src, targetPdf);
const qrCodes = await this.loadLegacyQrCodes(src);
const doc = this.documentRepo.create({
Id: id,
OriginalName: name,
Source: source,
OwnerUsername: owner,
PageCount: 0,
QrCodes: qrCodes,
});
await this.documentRepo.save(doc);
}
private async move(src: string, dest: string): Promise<void> {
try {
await fs.rename(src, dest);
return;
} catch (err: unknown) {
if (getErrorCode(err) !== 'EXDEV') throw err;
}
await fs.copyFile(src, dest);
try {
await fs.unlink(src);
} catch (err) {
await fs.unlink(dest).catch(() => undefined);
throw err;
}
}
private async loadLegacyQrCodes(
oldFilePath: string,
): Promise<StoredQrCode[]> {
try {
const rows = await this.dataSource.query<LegacyScanRow[]>(
'SELECT QrCodes FROM barcode_scans WHERE FilePath = ? LIMIT 1',
[oldFilePath],
);
if (!rows || rows.length === 0) return [];
const raw = rows[0].QrCodes;
if (typeof raw === 'string') {
try {
return JSON.parse(raw) as StoredQrCode[];
} catch {
return [];
}
}
return Array.isArray(raw) ? raw : [];
} catch {
// Tabelle fehlt oder anderes DB-Problem: Backfill scannt später.
return [];
}
}
}