chore: apply ESLint auto-fix across entire backend
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s
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:
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user