Files
paperlessmanager/paperless-backend/src/label-print-agent/label-print-agent.controller.ts
T
bjoernpoettker dad0136365
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s
chore: apply ESLint auto-fix across entire backend
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>
2026-06-08 09:02:02 +02:00

133 lines
3.7 KiB
TypeScript

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<string, string> },
@Res({ passthrough: true }) res: Response,
): Promise<StreamableFile> {
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<string, string> },
) {
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<MessageEvent> {
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<StreamableFile> {
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 };
}
}