import { Body, Controller, Get, HttpCode, HttpStatus, MessageEvent, NotFoundException, Param, ParseIntPipe, Post, Query, Res, Sse, StreamableFile, } from '@nestjs/common'; import type { Response } from 'express'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { RequirePermissions } from '../auth/permissions.decorator'; import { Permission } from '../auth/permissions.enum'; import { LabelPrintAgentService } from './label-print-agent.service'; @Controller('api/label-print-agent') export class LabelPrintAgentController { constructor(private readonly service: LabelPrintAgentService) {} @Post('preview') @HttpCode(HttpStatus.OK) @RequirePermissions(Permission.VIEW_SCANNER) async preview( @Body() body: { templateId: number; fieldValues?: Record }, @Res({ passthrough: true }) res: Response, ): Promise { const buf = await this.service.renderPreview( body.templateId, body.fieldValues ?? {}, ); const { Readable } = await import('stream'); res.setHeader('Content-Type', 'image/png'); return new StreamableFile(Readable.from(buf)); } // Manuell einen Job anlegen (Frontend → Backend) @Post('jobs') @HttpCode(HttpStatus.CREATED) @RequirePermissions(Permission.VIEW_SCANNER) async createJob( @Body() body: { templateId: number; fieldValues?: Record }, ) { const job = await this.service.createJob( body.templateId, body.fieldValues ?? {}, ); return { jobId: String(job.Id) }; } // Agent: SSE-Stream für neue Druckaufträge @Sse('events') sseEvents( @Res({ passthrough: true }) res: Response, ): Observable { res.setHeader('X-Accel-Buffering', 'no'); return this.service.newJob$.pipe( map(() => ({ data: { type: 'label-job-available' } }) as MessageEvent), ); } // Agent: nächsten Job abholen (Polling) @Get('jobs/next') async getNextJob(@Query('agentId') agentId: string, @Res() res: Response) { const job = await this.service.claimNextJob(agentId ?? 'unknown'); if (!job) { res.status(HttpStatus.NO_CONTENT).send(); return; } res.status(HttpStatus.OK).json({ jobId: String(job.Id), labelImageBase64: job.LabelImageData ? job.LabelImageData.toString('base64') : null, labelImageContentType: 'image/png', labelWidthMm: job.LabelWidthMm, labelHeightMm: job.LabelHeightMm, }); } // Agent: Bild separat abrufen @Get('jobs/:id/image') async getImage( @Param('id', ParseIntPipe) id: number, @Res({ passthrough: true }) res: Response, ): Promise { const buf = await this.service.getJobImage(id); if (!buf) throw new NotFoundException('Bild nicht gefunden'); const { Readable } = await import('stream'); res.setHeader('Content-Type', 'image/png'); return new StreamableFile(Readable.from(buf)); } // Agent: Druck erfolgreich @Post('jobs/:id/printed') @HttpCode(HttpStatus.OK) async markPrinted( @Param('id', ParseIntPipe) id: number, @Body() body: { agentId?: string; printerName?: string }, ) { await this.service.markPrinted( id, body.agentId ?? 'unknown', body.printerName ?? '', ); return { ok: true }; } // Agent: Druckfehler @Post('jobs/:id/error') @HttpCode(HttpStatus.OK) async markError( @Param('id', ParseIntPipe) id: number, @Body() body: { agentId?: string; printerName?: string; errorMessage?: string }, ) { await this.service.markError( id, body.agentId ?? 'unknown', body.printerName ?? '', body.errorMessage ?? '', ); return { ok: true }; } }