feat(quick-260914-m97): Fehlermeldungen per E-Mail — Empfaenger in SmtpConfig (Migration), MailService-Anhaenge, Modul bug-reports mit Drossel, PNG-Pruefung und Mandant aus der Sitzung
- SmtpConfig.bugReportRecipient (nullable, additive Migration 20260914170000), DTO @IsOptional @IsEmail, SAFE_SELECT, getBugReportRecipient gebunden - MailService: Versandkern deliver (wirft, Anhaenge), sendViaTenantTransport bleibt verschluckender Mantel (T-02-12), sendBugReport laesst Fehler durch - POST /bug-reports: Multipart 4 MiB je Route, alle angemeldeten Rollen, Drossel 5/10 min -> 429, PNG-Signatur -> 400, kein Empfaenger -> 409, Versandfehler -> 502, eine Protokollzeile - Falsifizierungen (a)-(d) als Specs; @Expose() im DTO, damit errors auch bei fehlendem Feld zu [] wird - Doku-Zeile fuer rls-access-inventory, TESSERA_BUGREPORT_TO in docker-compose.prod.yml Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { Expose, Transform } from 'class-transformer';
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
IsArray,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
|
||||
/**
|
||||
* Rumpf von `POST /bug-reports` (quick-260914-m97, Fehler-melden-Knopf).
|
||||
*
|
||||
* Die Felder kommen als `multipart/form-data` (das Bild liegt als Datei
|
||||
* `screenshot` daneben, siehe Controller) — multer liefert deshalb alle
|
||||
* Textfelder als Strings. Ein EINZELNES wiederholtes Feld `errors` kommt
|
||||
* als String, mehrere als Array, keines als undefined (append-field,
|
||||
* gemessen zur Planungszeit); ohne die Normalisierung unten wuerde
|
||||
* `@IsArray()` bei genau einer Fehlermeldung scheitern.
|
||||
*
|
||||
* Mandant und Benutzer stehen BEWUSST NICHT in diesem DTO (T-M97-06): der
|
||||
* Dienst nimmt beides ausschliesslich aus dem Sitzungsnachweis
|
||||
* (`@CurrentUser()`), und `whitelist: true` der globalen ValidationPipe
|
||||
* entfernt jedes Fremdfeld, das ein Client hier trotzdem mitschickt.
|
||||
*/
|
||||
export class BugReportDto {
|
||||
/** Freitext „Was ist passiert?“ — optional, hoechstens 4000 Zeichen. */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(4000)
|
||||
description?: string;
|
||||
|
||||
/** Pfad plus Suchteil der Seite, ohne Host. */
|
||||
@IsString()
|
||||
@MaxLength(2000)
|
||||
page!: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
webVersion!: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
webChannel!: string;
|
||||
|
||||
/** Kurzer Commit-Hash; Leerstring ist erlaubt (lokaler Bau ohne Stempel). */
|
||||
@IsString()
|
||||
@MaxLength(64)
|
||||
webCommit!: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(1000)
|
||||
userAgent!: string;
|
||||
|
||||
/** `<Breite>x<Hoehe>` des Browserfensters. */
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
viewport!: string;
|
||||
|
||||
/** ISO-Zeitstempel des Browsers zum Sendezeitpunkt. */
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
clientTime!: string;
|
||||
|
||||
/**
|
||||
* Die letzten Fehlermeldungen aus dem Browser-Ringpuffer, je
|
||||
* `[<ISO>] <Art>: <Meldung>`. `@Expose()` sorgt dafuer, dass die
|
||||
* Normalisierung auch laeuft, wenn das Feld im Rumpf GANZ fehlt
|
||||
* (class-transformer ruft `@Transform` sonst nur fuer vorhandene
|
||||
* Schluessel auf — gemessen: ohne `@Expose()` scheitert `@IsArray()`).
|
||||
*/
|
||||
@Expose()
|
||||
@Transform(({ value }) =>
|
||||
value === undefined || value === null ? [] : Array.isArray(value) ? value : [value],
|
||||
)
|
||||
@IsArray()
|
||||
@ArrayMaxSize(30)
|
||||
@IsString({ each: true })
|
||||
@MaxLength(1000, { each: true })
|
||||
errors!: string[];
|
||||
}
|
||||
Reference in New Issue
Block a user