Initial commit: Kalendartool (Next.js, Prisma, Docker)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kühn
2026-10-08 14:38:04 +02:00
commit bc49c3074e
94 changed files with 12812 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
/**
* Konfliktpruefung fuer Reservierungen (plan.md Abschnitt 6).
*
* Zwei Zeitraume kollidieren, wenn sie sich ueberlappen:
* existing.start < new.end UND new.start < existing.end
*
* Die Pruefung MUSS serverseitig innerhalb einer Transaktion mit
* konsistentem Isolation-Level erfolgen, damit gleichzeitige Buchungen
* zweier Benutzer nicht zu einer Doppelbuchung fuehren koennen.
*/
import type { Reservation } from '@prisma/client';
/** Fehler, der bei einer Zeitkollision geworfen wird. */
export class ReservationConflictError extends Error {
constructor() {
super('Der gewuenschte Zeitraum ist bereits belegt.');
this.name = 'ReservationConflictError';
}
}
/** Fehler, wenn der Zeitraum ungueltig ist (Vergangenheit, Start>=Ende). */
export class ReservationValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ReservationValidationError';
}
}
/** Prueft rein funktional, ob sich zwei Zeitraume ueberlappen. */
export function intervalsOverlap(
aStart: Date,
aEnd: Date,
bStart: Date,
bEnd: Date,
): boolean {
return aStart < bEnd && bStart < aEnd;
}
/**
* Findet kollidierende Reservierungen eines Kalenders im Zeitraum.
* Rein funktional und damit gut testbar; die Datenbankabfrage wird
* als Parameter injiziert.
*/
export function findConflicts(
existing: Pick<Reservation, 'id' | 'startAt' | 'endAt'>[],
newStart: Date,
newEnd: Date,
ignoreReservationId?: string,
): Pick<Reservation, 'id' | 'startAt' | 'endAt'>[] {
return existing.filter(
(reservation) =>
reservation.id !== ignoreReservationId &&
intervalsOverlap(reservation.startAt, reservation.endAt, newStart, newEnd),
);
}
/**
* Validiert, dass Start vor Ende liegt und der Zeitraum nicht in der
* Vergangenheit beginnt. Wirft sonst eine aussagekraeftige Fehlermeldung.
*/
export function validateReservationPeriod(
startAt: Date,
endAt: Date,
now: Date = new Date(),
): void {
if (Number.isNaN(startAt.getTime()) || Number.isNaN(endAt.getTime())) {
throw new ReservationValidationError(
'Start- und Endzeit muessen gueltige Zeitpunkte sein.',
);
}
if (startAt >= endAt) {
throw new ReservationValidationError(
'Die Startzeit muss vor der Endzeit liegen.',
);
}
if (startAt < now) {
throw new ReservationValidationError(
'Der Zeitraum liegt in der Vergangenheit.',
);
}
}