"use server"; import { revalidatePath } from "next/cache"; import { currentUserId, requireUserId } from "@/lib/auth/session"; import { withUser } from "@/lib/db"; import type { ActionResult } from "@/lib/db/rpc"; /** * Den Entwurf für mich belegen — beim Öffnen und danach im Takt. * * Die Regel hire_drafts_update lässt die Zeile nur durch, wenn die Sperre * frei, meine oder abgelaufen ist. Trifft das Schreiben keine Zeile, hat sie * jemand anderes offen; das ist kein Fehler, sondern die Auskunft. * * Dieselbe Funktion frischt die Sperre auf: sie schreibt locked_at neu, * solange sie mir gehört. Der Assistent ruft sie darum im Takt, sonst liefe * die Frist einem langen Ausfüllen davon. * * updated_at bleibt unberührt. Eine Sperre ist keine Änderung am Entwurf — * bewegte sie den Zeitstempel, sortierte sich die Liste um und „Gespeichert * am" logge. */ export async function entwurfSperren(id: string): Promise { const userId = await requireUserId(); try { const ergebnis = await withUser(userId, (tx) => tx .updateTable("hire_drafts") .set({ locked_by: userId, locked_at: new Date().toISOString() }) .where("id", "=", id) .executeTakeFirst() ); if (ergebnis.numUpdatedRows === 0n) { return { success: false, error: "Dieser Entwurf wird gerade von jemand anderem bearbeitet." }; } } catch (err) { return { success: false, error: err instanceof Error ? err.message : "Unbekannter Fehler." }; } revalidatePath("/"); return { success: true }; } /** * Die Sperre zurückgeben. * * Die Bedingung auf locked_by steht hier und nicht nur in der Regel: die * Regel liesse auch das Freigeben einer *abgelaufenen fremden* Sperre zu, * und das wäre ein stiller Griff in die Arbeit von jemandem, der gerade * wieder aufgefrischt hat. * * Ein Scheitern bleibt ohne Meldung: die Frist räumt die Sperre ohnehin ab, * und es gäbe nichts, was die Person daraufhin tun könnte. */ export async function entwurfFreigeben(id: string): Promise { const userId = await requireUserId(); try { await withUser(userId, (tx) => tx .updateTable("hire_drafts") .set({ locked_by: null, locked_at: null }) .where("id", "=", id) .where("locked_by", "=", userId) .execute() ); } catch (err) { return { success: false, error: err instanceof Error ? err.message : "Unbekannter Fehler." }; } revalidatePath("/"); return { success: true }; } export async function saveHireDraft(payload: { id?: string; step: number; data: Record; }): Promise { const userId = await currentUserId(); if (!userId) return { success: false, error: "Nicht angemeldet." }; try { const id = await withUser(userId, async (tx) => { if (payload.id) { // Wer schreiben darf, entscheidet die Regel hire_drafts_update: // der Entwurf muss meiner oder von einer hinzugewählten Person sein, // **und** die Sperre muss mir gehören. // // Die Zahl der geänderten Zeilen wird trotzdem gelesen: eine Regel // weist ein UPDATE nicht mit einem Fehler ab, sie lässt es ins Leere // laufen. Ohne diese Prüfung meldete die Anwendung „gespeichert", und // gespeichert wäre nichts — der ärgerlichste Fall überhaupt, weil die // Person den Assistenten daraufhin beruhigt zumacht. const ergebnis = await tx .updateTable("hire_drafts") .set({ step: payload.step, payload: payload.data, updated_at: new Date().toISOString() }) .where("id", "=", payload.id) .executeTakeFirst(); if (ergebnis.numUpdatedRows === 0n) { throw new Error( "Der Entwurf liess sich nicht speichern: er wird gerade von jemand anderem bearbeitet oder wurde inzwischen gelöscht." ); } return payload.id; } const row = await tx .insertInto("hire_drafts") .values({ created_by: userId, step: payload.step, payload: payload.data }) .returning("id") .executeTakeFirstOrThrow(); return row.id; }); revalidatePath("/"); return { success: true, id }; } catch (err) { return { success: false, error: err instanceof Error ? err.message : "Unbekannter Fehler." }; } } export async function deleteHireDraft(id: string): Promise { try { // Auch hier die Zeilenzahl: die Regel hire_drafts_delete lässt ein // Löschen gegen eine fremde Sperre leer laufen statt es abzuweisen. // „Entwurf gelöscht" über einem Entwurf, der noch dasteht, wäre die // schlechtere Auskunft. await withUser(await currentUserId(), async (tx) => { const ergebnis = await tx.deleteFrom("hire_drafts").where("id", "=", id).executeTakeFirst(); if (ergebnis.numDeletedRows === 0n) { throw new Error( "Der Entwurf liess sich nicht löschen: er wird gerade von jemand anderem bearbeitet oder war schon weg." ); } }); revalidatePath("/"); return { success: true }; } catch (err) { return { success: false, error: err instanceof Error ? err.message : "Unbekannter Fehler." }; } }