Files
kooltab-backoffice/apps/api/src/planned-tabs/dto/import-planned-tabs.dto.ts
T
brammie15 8b1e38cb4d feat: reminders, Excel customer import and security hardening
Features
- Payment reminders via Resend (candidates, send, history, test email)
- Bulk import of the customer list from Excel/CSV in the dashboard
- Dashboard i18n (en/nl) with language selector

Security
- Enforce venue isolation on all /venues/:venueId routes
- Require admin for planned-tab changes and pairing codes
- Rate-limit login and device registration; opt-in TRUST_PROXY
- Require JWT_SECRET in production; stop leaking internal errors
- Escape customer names in reminder emails

Sync and commands
- Remove tabs the terminal no longer reports; validate nested sync payload
- Return all pending commands on every sync (fixes missed-command race)
- First command ack wins; atomic registration code claim
- Reject commands for revoked devices, stale versions or duplicates
- Remind only unpaid balances, with a 7-day cooldown

Dashboard
- Share a single token refresh across concurrent 401s (random logouts)
- Keep the session alive for the refresh token lifetime

Deployment
- Replace `db push --accept-data-loss` with migrations (baselines legacy DBs)
- Set NODE_ENV=production; pass Resend and TRUST_PROXY env in prod compose
2026-10-07 23:18:37 +02:00

34 lines
1.1 KiB
TypeScript

import { IsString, IsOptional, IsNotEmpty, IsEmail, MaxLength, IsArray, ArrayMaxSize, ValidateNested, IsIn } from 'class-validator';
import { Transform, Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class ImportPlannedTabEntryDto {
@ApiProperty({ example: 'Jan Peeters' })
@IsString()
@IsNotEmpty()
@MaxLength(100)
customerName: string;
@ApiPropertyOptional({ example: 'jan@example.com' })
@IsOptional()
@Transform(({ value }) => (typeof value === 'string' ? value.trim() || undefined : value))
@IsEmail()
email?: string;
}
export class ImportPlannedTabsDto {
@ApiProperty({ type: [ImportPlannedTabEntryDto] })
@IsArray()
@ArrayMaxSize(5000)
@ValidateNested({ each: true })
@Type(() => ImportPlannedTabEntryDto)
entries: ImportPlannedTabEntryDto[];
@ApiProperty({
enum: ['merge', 'replace'],
description: 'merge adds new names and updates emails; replace also removes names that are not in the import',
})
@IsIn(['merge', 'replace'])
mode: 'merge' | 'replace';
}