feat(260929-if2): Erinnerungen anlegen und zur Faelligkeit benachrichtigen (Tracer)
- Reminder-Tabelle mit Zeilenschutz (Mandant+Benutzer, Systemlesen fuer den E-Mail-Planer), API reminders (Liste, Anlegen) - Kachel "Erinnerungen", globaler Melder im Portalrahmen (Browser und Desktop, je Faelligkeit einmal) - Desktop: Laufzeit-Berechtigung fuer Benachrichtigungen nur fuer die gespeicherte Server-Adresse - Zugriffsklassifikation nachgemessen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { BadRequestException, ConflictException, Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import type { CreateReminderDto } from './dto/reminder.dto';
|
||||
|
||||
/** Antwortfelder — genau diese, nichts anderes verlaesst den Dienst. */
|
||||
export const REMINDER_SELECT = {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
dueAt: true,
|
||||
emailEnabled: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
};
|
||||
|
||||
/** E-06: hoechstens so viele Erinnerungen je Benutzer. */
|
||||
export const MAX_REMINDERS_PER_USER = 100;
|
||||
/** E-06: die Faelligkeit darf hoechstens so weit in der Zukunft liegen. */
|
||||
const MAX_AHEAD_MS = 5 * 365 * 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Persoenliche Erinnerungen (quick-260929-if2). Eine Erinnerung gehoert genau
|
||||
* einem Benutzer (D-05): fuer fremde oder unbekannte Kennungen antwortet der
|
||||
* Dienst immer mit 404, nie mit 403 — kein Hinweis darauf, dass es sie gibt.
|
||||
*
|
||||
* `tenantId` und `userId` kommen ausschliesslich als Argumente (aus
|
||||
* `req.tenantId` und dem Anmelde-Token), nie aus dem DTO.
|
||||
*
|
||||
* RLS-BINDUNG (Muster DashboardImage, Migration 20260929140000): jede Methode
|
||||
* bindet mit Mandant UND Benutzer (`forTenant(prisma, tenantId, userId)`), die
|
||||
* Regel laesst dann nur eigene Zeilen zu. Zusaetzlich tragen alle `where`
|
||||
* `tenantId` und `userId` als Anwendungspruefung, solange der RLS-Schalter aus
|
||||
* ist.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RemindersService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/** Die eigenen Erinnerungen, die naechste Faelligkeit zuerst. */
|
||||
async list(tenantId: string, userId: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
return tenantPrisma.reminder.findMany({
|
||||
where: { tenantId, userId },
|
||||
orderBy: { dueAt: 'asc' },
|
||||
select: REMINDER_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
async create(tenantId: string, userId: string, dto: CreateReminderDto) {
|
||||
const dueAt = this.assertValidDueAt(dto.dueAt);
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const count = await tenantPrisma.reminder.count({ where: { tenantId, userId } });
|
||||
if (count >= MAX_REMINDERS_PER_USER) {
|
||||
throw new ConflictException(
|
||||
`Es sind höchstens ${MAX_REMINDERS_PER_USER} Erinnerungen möglich`,
|
||||
);
|
||||
}
|
||||
return tenantPrisma.reminder.create({
|
||||
data: {
|
||||
tenantId,
|
||||
userId,
|
||||
title: dto.title,
|
||||
description: dto.description ?? '',
|
||||
dueAt,
|
||||
},
|
||||
select: REMINDER_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
/** E-06: die Faelligkeit muss in der Zukunft und hoechstens 5 Jahre voraus liegen. */
|
||||
private assertValidDueAt(iso: string, now: number = Date.now()): Date {
|
||||
const dueAt = new Date(iso);
|
||||
if (Number.isNaN(dueAt.getTime())) {
|
||||
throw new BadRequestException('Ungültige Fälligkeit');
|
||||
}
|
||||
if (dueAt.getTime() <= now) {
|
||||
throw new BadRequestException('Die Fälligkeit muss in der Zukunft liegen');
|
||||
}
|
||||
if (dueAt.getTime() > now + MAX_AHEAD_MS) {
|
||||
throw new BadRequestException('Die Fälligkeit liegt zu weit in der Zukunft');
|
||||
}
|
||||
return dueAt;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user