import { Body, Controller, Delete, Get, Logger, Param, ParseIntPipe, Post, Put, BadRequestException, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { BarcodeTemplate, type BarcodeActionType } from '../database/entities/barcode-template.entity'; import { RequirePermissions } from '../auth/permissions.decorator'; import { Permission } from '../auth/permissions.enum'; import { BarcodeScannerService } from './barcode-scanner.service'; const VALID_ACTIONS: BarcodeActionType[] = ['SEND_TO_PAPERLESS', 'SEND_BY_EMAIL']; interface UpsertDto { Name?: string; Regex?: string; SplitBefore?: boolean; DateinameTemplate?: string | null; Actions?: BarcodeActionType[]; } function validate(dto: UpsertDto, partial = false): void { if (!partial || dto.Name !== undefined) { if (typeof dto.Name !== 'string' || !dto.Name.trim()) { throw new BadRequestException('Name ist erforderlich'); } } if (!partial || dto.Regex !== undefined) { if (typeof dto.Regex !== 'string' || !dto.Regex.trim()) { throw new BadRequestException('Regex ist erforderlich'); } try { new RegExp(dto.Regex); } catch { throw new BadRequestException('Regex ist ungültig'); } } if (dto.SplitBefore !== undefined && typeof dto.SplitBefore !== 'boolean') { throw new BadRequestException('SplitBefore muss ein Boolean sein'); } if (dto.DateinameTemplate !== undefined && dto.DateinameTemplate !== null && typeof dto.DateinameTemplate !== 'string') { throw new BadRequestException('DateinameTemplate muss ein String sein'); } if (!partial || dto.Actions !== undefined) { if (!Array.isArray(dto.Actions)) { throw new BadRequestException('Actions muss eine Liste sein'); } for (const a of dto.Actions) { if (!VALID_ACTIONS.includes(a)) { throw new BadRequestException(`Unbekannte Aktion: ${a}`); } } } } @Controller('api/barcode-templates') @RequirePermissions(Permission.MANAGE_SETTINGS) export class BarcodeTemplatesController { private readonly logger = new Logger(BarcodeTemplatesController.name); constructor( @InjectRepository(BarcodeTemplate) private readonly repo: Repository, private readonly scanner: BarcodeScannerService, ) {} private triggerRescan(): void { this.scanner.rescanAll().catch((err) => { this.logger.error(`Rescan nach Template-Änderung fehlgeschlagen: ${err.message}`); }); } @Get() async list() { return this.repo.find({ order: { Id: 'ASC' } }); } @Post() async create(@Body() dto: UpsertDto) { validate(dto); const entity = this.repo.create({ Name: dto.Name!.trim(), Regex: dto.Regex!, SplitBefore: dto.SplitBefore ?? false, DateinameTemplate: dto.DateinameTemplate ?? null, Actions: dto.Actions!, }); const saved = await this.repo.save(entity); this.scanner.invalidateTemplates(); this.triggerRescan(); return saved; } @Put(':id') async update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpsertDto) { validate(dto, true); const existing = await this.repo.findOneBy({ Id: id }); if (!existing) throw new NotFoundException('Vorlage nicht gefunden'); const regexChanged = dto.Regex !== undefined && dto.Regex !== existing.Regex; if (dto.Name !== undefined) existing.Name = dto.Name.trim(); if (dto.Regex !== undefined) existing.Regex = dto.Regex; if (dto.SplitBefore !== undefined) existing.SplitBefore = dto.SplitBefore; if (dto.DateinameTemplate !== undefined) existing.DateinameTemplate = dto.DateinameTemplate ?? null; if (dto.Actions !== undefined) existing.Actions = dto.Actions; const saved = await this.repo.save(existing); this.scanner.invalidateTemplates(); if (regexChanged) { // Nur bei Regex-Änderungen rescannen — Name/Aktionen ändern nichts am Match-Set. this.triggerRescan(); } return saved; } @Delete(':id') async remove(@Param('id', ParseIntPipe) id: number) { const res = await this.repo.delete(id); if (!res.affected) throw new NotFoundException('Vorlage nicht gefunden'); this.scanner.invalidateTemplates(); this.triggerRescan(); return { ok: true }; } }