import { slugify } from "./slugify"; /** * Oeffentliche Adresse einer Ernte (/ernten/). * * Eine Imkerei erntet dieselbe Sorte mehrmals im Jahr (z.B. zwei * Schleuderungen Bluetenhonig im Juni und Juli). Sorte und Jahr allein * unterscheiden Ernten deshalb nicht — eindeutig ist nur der Slug. * * Vorschlaege in absteigender Lesbarkeit: * 1. "bluetenhonig-2026" — die erste Ernte einer Sorte im Jahr * 2. "bluetenhonig-2026-07-14" — jede weitere, am Erntedatum erkennbar * 3. "bluetenhonig-2026-07-14-2", "-3", … — gleicher Tag (selten, aber moeglich) * * Das Datum wird in UTC gelesen: ApiCore und das Admin-Formular liefern * reine Kalendertage als Mitternacht UTC. */ export const MAX_SLUG_SUFFIX = 20; export function harvestSlugCandidates(type: string, harvestDate: Date): string[] { const base = slugify(type) || "ernte"; const year = harvestDate.getUTCFullYear(); const day = harvestDate.toISOString().slice(0, 10); const withDay = `${base}-${day}`; const numbered = Array.from({ length: MAX_SLUG_SUFFIX - 1 }, (_, i) => `${withDay}-${i + 2}`); return [`${base}-${year}`, withDay, ...numbered]; } /** Prisma-Fehler "Unique constraint failed" — ohne Bindung an EINEN der beiden generierten Clients. */ export function isUniqueConstraintError(error: unknown): boolean { return typeof error === "object" && error !== null && "code" in error && error.code === "P2002"; } /** * Fuehrt `write` mit dem ersten noch freien Slug aus. * * Bewusst KEIN "erst pruefen, dann schreiben": zwischen beiden Schritten * koennte ein zweiter Admin-Tab oder ein paralleler Sync denselben Slug * belegen. Stattdessen entscheidet der Unique-Index der Datenbank — schlaegt * das Schreiben daran fehl, wird der naechste Vorschlag versucht. * * Voraussetzung: der einzige Unique-Index, den `write` verletzen kann, ist * der auf `slug`. Andere Fehler werden unveraendert weitergeworfen. * * `findTaken` filtert die schon bekannten Belegungen vorab heraus. Das ist * nur Komfort: Prisma protokolliert jeden abgefangenen Konflikt trotzdem als * "prisma:error", und ein Log voller scheinbarer Fehler fuehrt in die Irre. * Die Sicherheit kommt weiterhin allein vom Unique-Index. */ export async function writeWithFreeSlug( candidates: string[], write: (slug: string) => Promise, findTaken: (candidates: string[]) => Promise = async () => [] ): Promise { const taken = new Set(await findTaken(candidates)); const free = candidates.filter((slug) => !taken.has(slug)); for (const slug of free) { try { return await write(slug); } catch (error: unknown) { if (!isUniqueConstraintError(error)) throw error; } } throw new Error(`Alle ${candidates.length} Adressvorschläge sind belegt (zuletzt "${candidates.at(-1)}").`); }