import { BadRequestException, Body, Controller, Delete, Get, HttpCode, Param, ParseIntPipe, Post, Put, Request, Res, StreamableFile, } from '@nestjs/common'; import type { Response } from 'express'; import { createReadStream } from 'fs'; import { InboxService } from './inbox.service'; import { InboxPostprocessorService } from '../inbox-postprocessor/inbox-postprocessor.service'; import { BarcodeScannerService } from '../barcode/barcode-scanner.service'; import { UserSettingsService } from '../user-settings/user-settings.service'; import { RequirePermissions } from '../auth/permissions.decorator'; import { Permission } from '../auth/permissions.enum'; @Controller('api/inbox') @RequirePermissions(Permission.VIEW_SCANNER) export class InboxController { constructor( private readonly inboxService: InboxService, private readonly postprocessor: InboxPostprocessorService, private readonly barcodeScanner: BarcodeScannerService, private readonly userSettingsService: UserSettingsService, ) {} @Get() async list(@Request() req: any) { const preferredUsername: string | null = req.user?.preferredUsername ?? null; return this.inboxService.listFiles(preferredUsername); } @Post('rescan') async rescan() { return this.barcodeScanner.rescanAll(); } @Get(':id/preview') async preview( @Param('id') id: string, @Request() req: any, @Res({ passthrough: true }) res: Response, ): Promise { 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}"`, ); return new StreamableFile(createReadStream(pdfPath)); } @Get(':id/pages/:page/thumbnail') async pageThumbnail( @Param('id') id: string, @Param('page', ParseIntPipe) page: number, @Request() req: any, @Res({ passthrough: true }) res: Response, ): Promise { 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'); return new StreamableFile(createReadStream(filePath)); } @Delete(':id') @HttpCode(204) async remove(@Param('id') id: string, @Request() req: any): Promise { const preferredUsername: string | null = req.user?.preferredUsername ?? null; await this.inboxService.deleteDocument(id, preferredUsername); } @Delete(':id/pages/:page') @HttpCode(204) async removePage( @Param('id') id: string, @Param('page', ParseIntPipe) page: number, @Request() req: any, ): Promise { const preferredUsername: string | null = req.user?.preferredUsername ?? null; await this.inboxService.deletePage(id, page, preferredUsername); } @Post(':id/pages/:page/split') @HttpCode(204) async toggleManualSplit( @Param('id') id: string, @Param('page', ParseIntPipe) page: number, @Request() req: any, ): Promise { 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 { const preferredUsername: string | null = req.user?.preferredUsername ?? null; await this.inboxService.resetEdits(id, preferredUsername); } @Post(':id/postprocess') async postprocess( @Param('id') id: string, @Request() req: any, @Body() body: { sectionOffset?: number; processOnlyOne?: boolean; replaceDuplicate?: boolean; }, ) { const preferredUsername: string | null = req.user?.preferredUsername ?? null; const { results, totalSections } = await this.postprocessor.runForDocument( id, preferredUsername, body?.sectionOffset ?? 0, body?.processOnlyOne ?? false, body?.replaceDuplicate ?? false, ); return { results, totalSections }; } @Put(':id/pages/:page/rotation') @HttpCode(204) async setPageRotation( @Param('id') id: string, @Param('page', ParseIntPipe) page: number, @Body() body: { rotation?: number }, @Request() req: any, ): Promise { const rotation = Number(body?.rotation); 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, ); } @Get(':id/pages/:page/preview') async pagePreview( @Param('id') id: string, @Param('page', ParseIntPipe) page: number, @Request() req: any, @Res({ passthrough: true }) res: Response, ): Promise { 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'); return new StreamableFile(createReadStream(filePath)); } @Post(':id/pages/:page/scan-region') async scanRegion( @Param('id') id: string, @Param('page', ParseIntPipe) page: number, @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, ); } @Post(':id/source') @HttpCode(204) async updateSource( @Param('id') id: string, @Body() body: { source: any }, @Request() req: any, ): Promise { const preferredUsername: string | null = req.user?.preferredUsername ?? null; await this.inboxService.updateSource(id, body.source, preferredUsername); } @Post(':id/download-segment') async downloadSegment( @Param('id') id: string, @Body() body: { pages: number[] }, @Request() req: any, @Res({ passthrough: true }) res: Response, ): Promise { 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)}"`, ); return new StreamableFile(Readable.from(buffer)); } @Post(':id/send-email') @HttpCode(204) async sendEmail( @Param('id') id: string, @Body() body: { to: string; subject: string; body: string; html?: string; segments: { pages: number[]; filename: string }[]; sender?: string; }, @Request() req: any, ): Promise { 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, }); } }