feat(260929-if2): faellige Erinnerungen erledigen, spaeter erinnern, bearbeiten und loeschen
- API: Aendern (409 wenn faellig), Spaeter erinnern (409 wenn nicht faellig, setzt E-Mail-Spur zurueck), Loeschen; fremde Kennungen 404 - Kachel: faellige Zeilen hervorgehoben mit Erledigt und drei Spaeter-Optionen, kuenftige mit Bearbeiten und Loeschen - Zeit-Hilfen (morgen zur gleichen Uhrzeit), Zugriffsklassifikation nachgemessen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { BadRequestException, ConflictException, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import type { CreateReminderDto } from './dto/reminder.dto';
|
||||
import type { CreateReminderDto, SnoozeReminderDto, UpdateReminderDto } from './dto/reminder.dto';
|
||||
|
||||
/** Antwortfelder — genau diese, nichts anderes verlaesst den Dienst. */
|
||||
export const REMINDER_SELECT = {
|
||||
@@ -68,6 +73,76 @@ export class RemindersService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bearbeiten einer noch NICHT faelligen Erinnerung. Eine faellige ist tabu
|
||||
* (409): dafuer gibt es „Erledigt“ und „Spaeter erinnern“ (D-03).
|
||||
*/
|
||||
async update(tenantId: string, userId: string, id: string, dto: UpdateReminderDto) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const row = await this.loadOwn(tenantPrisma, tenantId, userId, id);
|
||||
if (row.dueAt.getTime() <= Date.now()) {
|
||||
throw new ConflictException('Die Erinnerung ist bereits fällig');
|
||||
}
|
||||
const data: { title?: string; description?: string; dueAt?: Date } = {};
|
||||
if (dto.title !== undefined) data.title = dto.title;
|
||||
if (dto.description !== undefined) data.description = dto.description;
|
||||
if (dto.dueAt !== undefined) data.dueAt = this.assertValidDueAt(dto.dueAt);
|
||||
return tenantPrisma.reminder.update({
|
||||
where: { id, tenantId, userId },
|
||||
data,
|
||||
select: REMINDER_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* „Spaeter erinnern“ (D-03): nur bei einer faelligen Erinnerung (409 sonst).
|
||||
* Setzt die neue Faelligkeit UND die Spuren des E-Mail-Planers zurueck, damit
|
||||
* die Benachrichtigungen wieder ausloesen und die E-Mail erneut verschickt
|
||||
* wird.
|
||||
*/
|
||||
async snooze(tenantId: string, userId: string, id: string, dto: SnoozeReminderDto) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
const row = await this.loadOwn(tenantPrisma, tenantId, userId, id);
|
||||
if (row.dueAt.getTime() > Date.now()) {
|
||||
throw new ConflictException('Die Erinnerung ist noch nicht fällig');
|
||||
}
|
||||
const dueAt = this.assertValidDueAt(dto.dueAt);
|
||||
return tenantPrisma.reminder.update({
|
||||
where: { id, tenantId, userId },
|
||||
data: { dueAt, emailSentAt: null, emailAttempts: 0 },
|
||||
select: REMINDER_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
/** Loescht die Zeile (E-02) — bedient „Loeschen“ vor und „Erledigt“ nach der Faelligkeit. */
|
||||
async remove(tenantId: string, userId: string, id: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId, userId);
|
||||
await this.loadOwn(tenantPrisma, tenantId, userId, id);
|
||||
await tenantPrisma.reminder.delete({ where: { id, tenantId, userId } });
|
||||
return { deleted: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Eigene Zeile laden. Unbekannt, fremder Mandant und fremder Benutzer sind
|
||||
* ununterscheidbar 404 (D-05, T-IF2-01) — nie ein Hinweis, dass es die
|
||||
* Kennung gibt.
|
||||
*/
|
||||
private async loadOwn(
|
||||
tenantPrisma: ReturnType<typeof forTenant>,
|
||||
tenantId: string,
|
||||
userId: string,
|
||||
id: string,
|
||||
) {
|
||||
const row = await tenantPrisma.reminder.findFirst({
|
||||
where: { id, tenantId, userId },
|
||||
select: { id: true, dueAt: true },
|
||||
});
|
||||
if (!row) {
|
||||
throw new NotFoundException('Erinnerung nicht gefunden');
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
|
||||
Reference in New Issue
Block a user