07dfd7e840
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>
103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import sharp from 'sharp';
|
|
import jsQR from 'jsqr';
|
|
|
|
export interface QrCodeResult {
|
|
data: string;
|
|
location: {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
}
|
|
|
|
@Injectable()
|
|
export class QrCodeService {
|
|
private readonly logger = new Logger(QrCodeService.name);
|
|
|
|
/**
|
|
* Extrahiert ALLE QR-Codes aus einem Bild-Buffer (PNG/JPEG).
|
|
* jsQR findet nur einen Code pro Aufruf — daher iteratives Vorgehen:
|
|
* Code finden → Bereich weiß überdecken → erneut scannen, bis nichts mehr gefunden wird.
|
|
*/
|
|
async extractFromImage(imageBuffer: Buffer): Promise<QrCodeResult[]> {
|
|
const results: QrCodeResult[] = [];
|
|
const seen = new Set<string>();
|
|
let currentBuffer = imageBuffer;
|
|
const MAX_PASSES = 10;
|
|
|
|
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
const { data, info } = await sharp(currentBuffer)
|
|
.ensureAlpha()
|
|
.raw()
|
|
.toBuffer({ resolveWithObject: true });
|
|
|
|
const imageData = new Uint8ClampedArray(data.buffer);
|
|
const code = jsQR(imageData, info.width, info.height, {
|
|
inversionAttempts: 'attemptBoth',
|
|
});
|
|
if (!code) break;
|
|
|
|
const corners = [
|
|
code.location.topLeftCorner,
|
|
code.location.topRightCorner,
|
|
code.location.bottomLeftCorner,
|
|
code.location.bottomRightCorner,
|
|
];
|
|
const xs = corners.map((c) => c.x);
|
|
const ys = corners.map((c) => c.y);
|
|
const minX = Math.floor(Math.min(...xs));
|
|
const minY = Math.floor(Math.min(...ys));
|
|
const maxX = Math.ceil(Math.max(...xs));
|
|
const maxY = Math.ceil(Math.max(...ys));
|
|
const width = Math.max(1, maxX - minX);
|
|
const height = Math.max(1, maxY - minY);
|
|
|
|
if (!seen.has(code.data)) {
|
|
seen.add(code.data);
|
|
results.push({
|
|
data: code.data,
|
|
location: { x: minX, y: minY, width, height },
|
|
});
|
|
this.logger.debug(`QR-Code erkannt (Pass ${pass + 1}): ${code.data}`);
|
|
}
|
|
|
|
// Erkannten Bereich mit weißem Rechteck (inkl. Padding) überdecken,
|
|
// damit jsQR im nächsten Pass den nächsten QR findet.
|
|
const pad = 12;
|
|
const maskX = Math.max(0, minX - pad);
|
|
const maskY = Math.max(0, minY - pad);
|
|
const maskW = Math.min(info.width - maskX, width + 2 * pad);
|
|
const maskH = Math.min(info.height - maskY, height + 2 * pad);
|
|
const svg = `<svg width="${info.width}" height="${info.height}" xmlns="http://www.w3.org/2000/svg"><rect x="${maskX}" y="${maskY}" width="${maskW}" height="${maskH}" fill="white"/></svg>`;
|
|
currentBuffer = await sharp(currentBuffer)
|
|
.composite([{ input: Buffer.from(svg), top: 0, left: 0 }])
|
|
.png()
|
|
.toBuffer();
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Validiert ob der QR-Code-Inhalt dem erwarteten Schema entspricht.
|
|
* Schema: JSON mit X, Y, Jahr, Nummer, Eingangsdatum
|
|
*/
|
|
parseBarcode(qrData: string): Record<string, any> | null {
|
|
try {
|
|
const parsed = JSON.parse(qrData);
|
|
|
|
if (parsed.Jahr !== undefined && parsed.Nummer !== undefined) {
|
|
return parsed;
|
|
}
|
|
|
|
this.logger.warn(`QR-Code-Daten passen nicht zum Schema: ${qrData}`);
|
|
return null;
|
|
} catch {
|
|
this.logger.debug(`QR-Code ist kein JSON: ${qrData}`);
|
|
return null;
|
|
}
|
|
}
|
|
}
|