chore: apply ESLint auto-fix across entire backend
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s

Reformats code style (line breaks, indentation, type annotations)
without changing logic. Also includes minor feature additions bundled
in the same lint run (stats service, user-settings groups, agrarmonitor
polling improvements).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 09:02:02 +02:00
parent 4c75a1ded2
commit dad0136365
74 changed files with 4022 additions and 1052 deletions
@@ -10,13 +10,16 @@ export class ClientsController {
constructor(
@InjectRepository(Client) private readonly clientRepo: Repository<Client>,
@InjectRepository(UserClient) private readonly userClientRepo: Repository<UserClient>,
@InjectRepository(UserClient)
private readonly userClientRepo: Repository<UserClient>,
) {}
@Get()
async getMyClients(@Request() req: any) {
const userId = req.user.userId;
const mappings = await this.userClientRepo.find({ where: { UserId: userId } });
const mappings = await this.userClientRepo.find({
where: { UserId: userId },
});
const clientIds = mappings.map((m) => m.ClientId);
if (clientIds.length === 0) {
@@ -28,17 +28,24 @@ export class InboxMigrationService implements OnApplicationBootstrap {
@InjectRepository(InboxDocument)
private readonly documentRepo: Repository<InboxDocument>,
) {
this.legacyRoot = this.configService.get<string>('SCANS_DATA_DIR', '/mnt/data/scans');
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 });
const entries = await fs.readdir(this.legacyRoot, {
withFileTypes: true,
});
subdirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
} catch (err: any) {
if (err.code !== 'ENOENT') {
this.logger.warn(`Migration: ${this.legacyRoot} nicht lesbar: ${err.message}`);
this.logger.warn(
`Migration: ${this.legacyRoot} nicht lesbar: ${err.message}`,
);
}
return;
}
@@ -61,7 +68,9 @@ export class InboxMigrationService implements OnApplicationBootstrap {
await this.migrateFile(src, subdir, name);
migrated += 1;
} catch (err: any) {
this.logger.error(`Migration fehlgeschlagen (${src}): ${err.message}`);
this.logger.error(
`Migration fehlgeschlagen (${src}): ${err.message}`,
);
}
}
@@ -75,7 +84,11 @@ export class InboxMigrationService implements OnApplicationBootstrap {
}
}
private async migrateFile(src: string, subdir: string, name: string): Promise<void> {
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;
@@ -115,7 +128,9 @@ export class InboxMigrationService implements OnApplicationBootstrap {
}
}
private async loadLegacyQrCodes(oldFilePath: string): Promise<StoredQrCode[]> {
private async loadLegacyQrCodes(
oldFilePath: string,
): Promise<StoredQrCode[]> {
try {
const rows = await this.dataSource.query<LegacyScanRow[]>(
'SELECT QrCodes FROM barcode_scans WHERE FilePath = ? LIMIT 1',
+88 -28
View File
@@ -34,7 +34,8 @@ export class InboxController {
@Get()
async list(@Request() req: any) {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
return this.inboxService.listFiles(preferredUsername);
}
@@ -49,11 +50,18 @@ export class InboxController {
@Request() req: any,
@Res({ passthrough: true }) res: Response,
): Promise<StreamableFile> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const { doc, pdfPath } = await this.inboxService.resolveDocument(id, preferredUsername);
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
const { doc, pdfPath } = await this.inboxService.resolveDocument(
id,
preferredUsername,
);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `inline; filename="${doc.OriginalName}"`);
res.setHeader(
'Content-Disposition',
`inline; filename="${doc.OriginalName}"`,
);
return new StreamableFile(createReadStream(pdfPath));
}
@@ -64,8 +72,14 @@ export class InboxController {
@Request() req: any,
@Res({ passthrough: true }) res: Response,
): Promise<StreamableFile> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const filePath = await this.inboxService.resolvePageImage(id, page, 'thumbnail', preferredUsername);
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
const filePath = await this.inboxService.resolvePageImage(
id,
page,
'thumbnail',
preferredUsername,
);
res.setHeader('Content-Type', 'image/png');
res.setHeader('Cache-Control', 'private, max-age=3600');
@@ -75,7 +89,8 @@ export class InboxController {
@Delete(':id')
@HttpCode(204)
async remove(@Param('id') id: string, @Request() req: any): Promise<void> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
await this.inboxService.deleteDocument(id, preferredUsername);
}
@@ -86,7 +101,8 @@ export class InboxController {
@Param('page', ParseIntPipe) page: number,
@Request() req: any,
): Promise<void> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
await this.inboxService.deletePage(id, page, preferredUsername);
}
@@ -97,14 +113,19 @@ export class InboxController {
@Param('page', ParseIntPipe) page: number,
@Request() req: any,
): Promise<void> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
await this.inboxService.toggleManualSplit(id, page, preferredUsername);
}
@Post(':id/reset-edits')
@HttpCode(204)
async resetEdits(@Param('id') id: string, @Request() req: any): Promise<void> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
async resetEdits(
@Param('id') id: string,
@Request() req: any,
): Promise<void> {
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
await this.inboxService.resetEdits(id, preferredUsername);
}
@@ -112,9 +133,15 @@ export class InboxController {
async postprocess(
@Param('id') id: string,
@Request() req: any,
@Body() body: { sectionOffset?: number; processOnlyOne?: boolean; replaceDuplicate?: boolean },
@Body()
body: {
sectionOffset?: number;
processOnlyOne?: boolean;
replaceDuplicate?: boolean;
},
) {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
const { results, totalSections } = await this.postprocessor.runForDocument(
id,
preferredUsername,
@@ -137,8 +164,14 @@ export class InboxController {
if (!Number.isFinite(rotation)) {
throw new BadRequestException('rotation muss eine Zahl sein');
}
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
await this.inboxService.setPageRotation(id, page, rotation, preferredUsername);
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
await this.inboxService.setPageRotation(
id,
page,
rotation,
preferredUsername,
);
}
@Get(':id/pages/:page/preview')
@@ -148,8 +181,14 @@ export class InboxController {
@Request() req: any,
@Res({ passthrough: true }) res: Response,
): Promise<StreamableFile> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const filePath = await this.inboxService.resolvePageImage(id, page, 'preview', preferredUsername);
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
const filePath = await this.inboxService.resolvePageImage(
id,
page,
'preview',
preferredUsername,
);
res.setHeader('Content-Type', 'image/png');
res.setHeader('Cache-Control', 'private, max-age=3600');
@@ -163,8 +202,17 @@ export class InboxController {
@Body() body: { x: number; y: number; w: number; h: number },
@Request() req: any,
): Promise<{ found: string[] }> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
return this.inboxService.scanRegion(id, page, body.x, body.y, body.w, body.h, preferredUsername);
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
return this.inboxService.scanRegion(
id,
page,
body.x,
body.y,
body.w,
body.h,
preferredUsername,
);
}
@Post(':id/source')
@@ -174,7 +222,8 @@ export class InboxController {
@Body() body: { source: any },
@Request() req: any,
): Promise<void> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
await this.inboxService.updateSource(id, body.source, preferredUsername);
}
@@ -185,11 +234,19 @@ export class InboxController {
@Request() req: any,
@Res({ passthrough: true }) res: Response,
): Promise<StreamableFile> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const { buffer, filename } = await this.inboxService.getSegmentPdfBuffer(id, preferredUsername, body.pages ?? []);
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
const { buffer, filename } = await this.inboxService.getSegmentPdfBuffer(
id,
preferredUsername,
body.pages ?? [],
);
const { Readable } = await import('stream');
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
res.setHeader(
'Content-Disposition',
`attachment; filename="${encodeURIComponent(filename)}"`,
);
return new StreamableFile(Readable.from(buffer));
}
@@ -197,7 +254,8 @@ export class InboxController {
@HttpCode(204)
async sendEmail(
@Param('id') id: string,
@Body() body: {
@Body()
body: {
to: string;
subject: string;
body: string;
@@ -207,10 +265,12 @@ export class InboxController {
},
@Request() req: any,
): Promise<void> {
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
const smtpOverride = body.sender === 'user'
? await this.userSettingsService.getSmtpConfig(req.user.userId)
: null;
const preferredUsername: string | null =
req.user?.preferredUsername ?? null;
const smtpOverride =
body.sender === 'user'
? await this.userSettingsService.getSmtpConfig(req.user.userId)
: null;
await this.inboxService.sendAsEmail(id, preferredUsername, {
...body,
smtpOverride: smtpOverride ?? undefined,
+59 -15
View File
@@ -8,7 +8,10 @@ import {
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as fs from 'fs/promises';
import { BarcodeScannerService, type MatchedBarcode } from '../barcode/barcode-scanner.service';
import {
BarcodeScannerService,
type MatchedBarcode,
} from '../barcode/barcode-scanner.service';
import { PageCacheService } from '../barcode/page-cache.service';
import {
InboxDocument,
@@ -48,7 +51,14 @@ export class InboxService {
async listFiles(preferredUsername: string | null): Promise<InboxFile[]> {
const where = preferredUsername
? [{ Source: 'all' as InboxSource, IsScanned: true }, { Source: 'user' as InboxSource, OwnerUsername: preferredUsername, IsScanned: true }]
? [
{ Source: 'all' as InboxSource, IsScanned: true },
{
Source: 'user' as InboxSource,
OwnerUsername: preferredUsername,
IsScanned: true,
},
]
: [{ Source: 'all' as InboxSource, IsScanned: true }];
const docs = await this.documentRepo.find({
@@ -64,7 +74,9 @@ export class InboxService {
source: doc.Source,
pageCount: doc.PageCount,
deletedPages: [...(doc.DeletedPages ?? [])].sort((a, b) => a - b),
manualSplitPages: [...(doc.ManualSplitPages ?? [])].sort((a, b) => a - b),
manualSplitPages: [...(doc.ManualSplitPages ?? [])].sort(
(a, b) => a - b,
),
rotations: { ...(doc.Rotations ?? {}) },
barcodes: await this.barcodeScanner.getMatched(doc),
createdAt: doc.CreatedAt.toISOString(),
@@ -73,7 +85,10 @@ export class InboxService {
return files;
}
async resolveDocument(id: string, preferredUsername: string | null): Promise<ResolvedDocument> {
async resolveDocument(
id: string,
preferredUsername: string | null,
): Promise<ResolvedDocument> {
const doc = await this.documentRepo.findOne({ where: { Id: id } });
if (!doc) throw new NotFoundException('Dokument nicht gefunden');
if (doc.Source === 'user' && doc.OwnerUsername !== preferredUsername) {
@@ -85,7 +100,9 @@ export class InboxService {
const stat = await fs.stat(pdfPath);
if (!stat.isFile()) throw new Error('not a file');
} catch (err: any) {
this.logger.warn(`Datei fehlt trotz DB-Eintrag (${doc.Id}): ${err.message}`);
this.logger.warn(
`Datei fehlt trotz DB-Eintrag (${doc.Id}): ${err.message}`,
);
throw new NotFoundException('Dokument nicht gefunden');
}
@@ -135,7 +152,7 @@ export class InboxService {
if (!Number.isInteger(page) || page < 1 || page > doc.PageCount) {
throw new NotFoundException('Seite nicht gefunden');
}
const normalized = ((Math.round(rotation / 90) * 90) % 360 + 360) % 360;
const normalized = (((Math.round(rotation / 90) * 90) % 360) + 360) % 360;
const next: Record<string, number> = { ...(doc.Rotations ?? {}) };
if (normalized === 0) {
delete next[String(page)];
@@ -149,7 +166,10 @@ export class InboxService {
/**
* Setzt alle markierten Bearbeitungen (DeletedPages, Rotations, ManualSplitPages) zurück.
*/
async resetEdits(id: string, preferredUsername: string | null): Promise<void> {
async resetEdits(
id: string,
preferredUsername: string | null,
): Promise<void> {
const { doc } = await this.resolveDocument(id, preferredUsername);
let changed = false;
if (doc.DeletedPages && doc.DeletedPages.length > 0) {
@@ -170,7 +190,11 @@ export class InboxService {
/**
* Setzt oder entfernt einen manuellen Trennpunkt vor der angegebenen Seite.
*/
async toggleManualSplit(id: string, page: number, preferredUsername: string | null): Promise<void> {
async toggleManualSplit(
id: string,
page: number,
preferredUsername: string | null,
): Promise<void> {
const { doc } = await this.resolveDocument(id, preferredUsername);
if (!Number.isInteger(page) || page < 2 || page > doc.PageCount) {
throw new BadRequestException('Ungültige Seitennummer für Trennung');
@@ -185,14 +209,19 @@ export class InboxService {
await this.documentRepo.save(doc);
}
async deleteDocument(id: string, preferredUsername: string | null): Promise<void> {
async deleteDocument(
id: string,
preferredUsername: string | null,
): Promise<void> {
const { doc } = await this.resolveDocument(id, preferredUsername);
const dir = this.pageCache.documentDir(doc.Id);
await this.documentRepo.delete(doc.Id);
try {
await fs.rm(dir, { recursive: true, force: true });
} catch (err: any) {
this.logger.warn(`Dokument-Ordner konnte nicht gelöscht werden (${dir}): ${err.message}`);
this.logger.warn(
`Dokument-Ordner konnte nicht gelöscht werden (${dir}): ${err.message}`,
);
}
}
@@ -231,7 +260,9 @@ export class InboxService {
doc.OwnerUsername = null;
} else {
if (!preferredUsername) {
throw new BadRequestException('Benutzername erforderlich für persönlichen Scan');
throw new BadRequestException(
'Benutzername erforderlich für persönlichen Scan',
);
}
doc.Source = 'user';
doc.OwnerUsername = preferredUsername;
@@ -260,7 +291,9 @@ export class InboxService {
): Promise<{ buffer: Buffer; filename: string }> {
const { doc, pdfPath } = await this.resolveDocument(id, preferredUsername);
const deleted = new Set(doc.DeletedPages ?? []);
const safePages = pages.filter((p) => p >= 1 && p <= doc.PageCount && !deleted.has(p));
const safePages = pages.filter(
(p) => p >= 1 && p <= doc.PageCount && !deleted.has(p),
);
const buffer = await buildSegmentBuffer(doc, pdfPath, safePages);
return { buffer, filename: doc.OriginalName };
}
@@ -274,7 +307,14 @@ export class InboxService {
body: string;
html?: string;
segments: { pages: number[]; filename: string }[];
smtpOverride?: { host: string; port: number; secure: boolean; user: string; pass: string; from: string };
smtpOverride?: {
host: string;
port: number;
secure: boolean;
user: string;
pass: string;
from: string;
};
},
): Promise<void> {
const { doc, pdfPath } = await this.resolveDocument(id, preferredUsername);
@@ -282,9 +322,13 @@ export class InboxService {
const attachments = await Promise.all(
opts.segments.map(async (seg) => {
const safePages = seg.pages.filter((p) => p >= 1 && p <= doc.PageCount && !deleted.has(p));
const safePages = seg.pages.filter(
(p) => p >= 1 && p <= doc.PageCount && !deleted.has(p),
);
const content = await buildSegmentBuffer(doc, pdfPath, safePages);
const filename = seg.filename.endsWith('.pdf') ? seg.filename : `${seg.filename}.pdf`;
const filename = seg.filename.endsWith('.pdf')
? seg.filename
: `${seg.filename}.pdf`;
return { filename, content };
}),
);