feat(07-02): InboxProvider interface + config/vehicle/history DTOs
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 40s
Tessera CI/CD / Tests (push) Waiting to run

- inbox-provider.interface.ts: InboxProvider contract with fetchPdfAttachments + testConnection; re-exports InboxConfig/InboxEmail/InboxAttachment with export type (isolatedModules)
- dkv-config.dto.ts: DkvConfigDto with @IsEmail() senderFilter/exportRecipient, @Min(5) pollIntervalMin, @IsIn() protocol/encryption (T-07-04)
- dkv-vehicle.dto.ts: CreateVehicleDto (all required @IsNotEmpty) + UpdateVehicleDto (all optional)
- dkv-history.dto.ts: DkvHistoryQueryDto with @IsInt @Min(1) page/limit pagination (T-07-06)
- Fix: export type re-exports required for isolatedModules TypeScript setting
This commit is contained in:
2026-06-27 00:00:14 +02:00
parent 6bf032ef94
commit 86ec8966d0
4 changed files with 248 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
import {
IsBoolean,
IsEmail,
IsIn,
IsInt,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';
/**
* DTO for creating or updating DKV module configuration.
*
* Security:
* - T-07-04: senderFilter validated as email address (injection mitigation)
* - T-07-04: port constrained to 1–65535 (invalid port rejection)
* - T-07-04: protocol and encryption constrained with @IsIn (enum injection mitigation)
* - T-07-05: pollIntervalMin minimum 5 (DoS mitigation — no sub-5-minute polling)
*/
export class DkvConfigDto {
/**
* Inbox protocol — 'imap' for IMAP, 'exchange' for Exchange (EWS).
*/
@IsIn(['imap', 'exchange'])
protocol!: string;
/**
* Mail server hostname or IP address (IMAP: IMAP server, Exchange: EWS endpoint host).
*/
@IsOptional()
@IsString()
host?: string;
/**
* TCP port. Standard values: 993 (IMAP SSL/TLS), 143 (IMAP STARTTLS), 443 (EWS).
*/
@IsOptional()
@IsInt()
@Min(1)
@Max(65535)
port?: number;
/**
* TLS mode: 'none' = plain TCP, 'starttls' = STARTTLS upgrade, 'ssl-tls' = implicit TLS.
*/
@IsIn(['none', 'starttls', 'ssl-tls'])
encryption!: string;
/**
* IMAP folder to monitor (e.g. "INBOX", "DKV"). Exchange always uses Inbox.
*/
@IsOptional()
@IsString()
folder?: string;
/**
* Sender email address to filter by. Only emails from this sender are processed.
* Validated as email address to prevent header injection (T-07-04 / Research V5).
*/
@IsOptional()
@IsEmail()
senderFilter?: string;
/**
* Email address to which the generated Excel export is sent.
* Validated as email address (T-07-04).
*/
@IsOptional()
@IsEmail()
exportRecipient?: string;
/**
* Polling interval in minutes. Minimum 5 minutes to prevent DoS via excessive polling
* (T-07-05, UI-SPEC minimum = 5).
*/
@IsOptional()
@IsInt()
@Min(5)
pollIntervalMin?: number;
/**
* Whether the automatic polling cron job is active.
*/
@IsOptional()
@IsBoolean()
isActive?: boolean;
/**
* Format string for the Fahrzeug column in the Excel export.
* Placeholders: {Marke}, {Modell}, {Kennzeichen}, {Fahrer}.
* Default: "{Marke}/{Modell}/{Kennzeichen}".
*/
@IsOptional()
@IsString()
vehicleFormatString?: string;
/**
* Inbox username (stored encrypted; cleared after save).
*/
@IsOptional()
@IsString()
username?: string;
/**
* Inbox password (stored encrypted; cleared after save).
* T-07-03: never returned to the frontend in responses.
*/
@IsOptional()
@IsString()
password?: string;
}
+30
View File
@@ -0,0 +1,30 @@
import { IsInt, IsOptional, Min } from 'class-validator';
/**
* Query DTO for paginating the DKV invoice processing history.
*
* Security:
* - T-07-06: Pagination limits prevent unbounded history accumulation DoS
* (Research Security Domain: "Excessive history accumulation" pattern)
*
* Used for: GET /dkv/history?page=1&limit=20
*/
export class DkvHistoryQueryDto {
/**
* Page number (1-based). Defaults to 1 when omitted.
* T-07-06: bounded integer prevents negative-page or non-integer injection.
*/
@IsOptional()
@IsInt()
@Min(1)
page?: number;
/**
* Number of records per page. Defaults to 20 when omitted.
* T-07-06: bounded integer mitigates oversized result-set DoS.
*/
@IsOptional()
@IsInt()
@Min(1)
limit?: number;
}
+74
View File
@@ -0,0 +1,74 @@
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
/**
* DTO for creating a vehicle master entry.
*
* All four fields represent the vehicle identity and driver mapping.
* Used for: POST /dkv/vehicles
*/
export class CreateVehicleDto {
/**
* License plate (Kennzeichen), e.g. "GP-JL 728E".
* Unique per tenant — used to map DKV invoice vehicle blocks to master data.
*/
@IsString()
@IsNotEmpty()
kennzeichen!: string;
/**
* Vehicle make (Marke), e.g. "Mercedes".
*/
@IsString()
@IsNotEmpty()
marke!: string;
/**
* Vehicle model (Modell), e.g. "GLC 300 de 4MATIC".
*/
@IsString()
@IsNotEmpty()
modell!: string;
/**
* Driver name in "Vorname Nachname" format, e.g. "Max Mustermann".
* Mapped to the Fahrer column in the Excel export (D-13).
*/
@IsString()
@IsNotEmpty()
fahrer!: string;
}
/**
* DTO for updating a vehicle master entry (all fields optional).
*
* Used for: PATCH /dkv/vehicles/:id
*/
export class UpdateVehicleDto {
/**
* New license plate. Updating changes the lookup key — use with care.
*/
@IsOptional()
@IsString()
kennzeichen?: string;
/**
* New vehicle make.
*/
@IsOptional()
@IsString()
marke?: string;
/**
* New vehicle model.
*/
@IsOptional()
@IsString()
modell?: string;
/**
* New driver name.
*/
@IsOptional()
@IsString()
fahrer?: string;
}
@@ -0,0 +1,32 @@
import { InboxAttachment, InboxConfig, InboxEmail } from '../dkv.types';
// Re-export types for downstream consumers that import from this module
export type { InboxAttachment, InboxConfig, InboxEmail };
/**
* Abstract inbox provider contract — implemented by ImapProvider and
* ExchangeInboxProvider. Consumers (DkvService, DkvSchedulerService) depend
* only on this interface, not on the concrete implementations.
*
* Security:
* - T-07-03: Implementations MUST NOT log credential values
* - InboxConfig carries decrypted credentials — never persist, never serialize
*/
export interface InboxProvider {
/**
* Connects to the configured inbox, searches for emails from the configured
* sender filter, downloads PDF attachments, and returns all matching emails.
*
* @param config Decrypted inbox connection parameters
* @returns Array of emails with their PDF attachments as Buffers
*/
fetchPdfAttachments(config: InboxConfig): Promise<InboxEmail[]>;
/**
* Tests whether the inbox connection can be established.
*
* @param config Decrypted inbox connection parameters
* @returns true if connection succeeded, false on any auth/network error
*/
testConnection(config: InboxConfig): Promise<boolean>;
}