Files
paperlessmanager/paperless-backend/src/barcode/barcode-templates.controller.ts
T

136 lines
4.3 KiB
TypeScript

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<BarcodeTemplate>,
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 };
}
}