import { PartialType } from '@nestjs/mapped-types'; import { MODULE_CATEGORIES } from '@tessera/shared'; import { Transform } from 'class-transformer'; import { IsIn, IsNotEmpty, IsString, MaxLength, Validate, ValidatorConstraint, type ValidatorConstraintInterface, } from 'class-validator'; /** * Adresse eines eigenen Moduls (T-9WC-03, T-9WC-06): gueltig nur, wenn der * URL-Parser sie annimmt, das Schema `https:` ist, ein Rechnername da ist und * weder Benutzername noch Kennwort in der Adresse stehen — sonst saehe jeder * Benutzer die Zugangsdaten. `javascript:`, `data:`, `http:` und `ftp:` fallen * damit heraus. */ @ValidatorConstraint({ name: 'nurHttpsOhneZugangsdaten', async: false }) class NurHttpsOhneZugangsdatenConstraint implements ValidatorConstraintInterface { validate(value: unknown): boolean { if (typeof value !== 'string') return false; let parsed: URL; try { parsed = new URL(value); } catch { return false; } return ( parsed.protocol === 'https:' && parsed.hostname !== '' && parsed.username === '' && parsed.password === '' ); } defaultMessage(): string { return 'Nur https-Adressen ohne Zugangsdaten sind erlaubt.'; } } const trimString = ({ value }: { value: unknown }) => typeof value === 'string' ? value.trim() : value; /** DTO fuer das Anlegen eines eigenen Moduls. */ export class CreateCustomModuleDto { @Transform(trimString) @IsString() @IsNotEmpty() @MaxLength(100) name!: string; @Transform(trimString) @IsString() @MaxLength(2048) @Validate(NurHttpsOhneZugangsdatenConstraint) url!: string; @IsIn([...MODULE_CATEGORIES]) category!: (typeof MODULE_CATEGORIES)[number]; } /** Teil-Update: jedes gesetzte Feld wird genauso geprueft wie beim Anlegen. */ export class UpdateCustomModuleDto extends PartialType(CreateCustomModuleDto) {}