"use server"; import { and, eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import { db } from "@/lib/db"; import { getCurrentSession } from "@/lib/session"; import { logActivity } from "@/modules/_core/activity"; import { bangEvents } from "../schema"; const dateString = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Must be YYYY-MM-DD"); const addBangInput = z.object({ occurredOn: dateString.optional(), }); const updateBangInput = z.object({ id: z.string().uuid(), occurredOn: dateString, }); const deleteBangInput = z.object({ id: z.string().uuid(), }); function todayString(): string { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; } export async function addBang(input: z.input = {}) { const parsed = addBangInput.parse(input); const { user, household } = await getCurrentSession(); const occurredOn = parsed.occurredOn ?? todayString(); const [bang] = await db .insert(bangEvents) .values({ householdId: household.id, recordedBy: user.id, occurredOn, }) .returning(); if (!bang) throw new Error("Bang was not recorded"); await logActivity({ entityType: "bangs.event", entityId: bang.id, action: "create", payload: { occurredOn }, }); revalidatePath("/"); revalidatePath("/d/[slug]", "page"); return bang; } export async function updateBang(input: z.input) { const parsed = updateBangInput.parse(input); const { household } = await getCurrentSession(); await assertCanAccessBang(parsed.id, household.id); const [bang] = await db .update(bangEvents) .set({ occurredOn: parsed.occurredOn }) .where(eq(bangEvents.id, parsed.id)) .returning(); if (!bang) throw new Error("Bang was not updated"); await logActivity({ entityType: "bangs.event", entityId: bang.id, action: "update", payload: { occurredOn: parsed.occurredOn }, }); revalidatePath("/"); revalidatePath("/d/[slug]", "page"); return bang; } export async function deleteBang(input: z.input) { const parsed = deleteBangInput.parse(input); const { household } = await getCurrentSession(); await assertCanAccessBang(parsed.id, household.id); const [existing] = await db .select({ occurredOn: bangEvents.occurredOn }) .from(bangEvents) .where(eq(bangEvents.id, parsed.id)) .limit(1); await logActivity({ entityType: "bangs.event", entityId: parsed.id, action: "delete", payload: existing ? { occurredOn: existing.occurredOn } : undefined, }); await db.delete(bangEvents).where(eq(bangEvents.id, parsed.id)); revalidatePath("/"); revalidatePath("/d/[slug]", "page"); } async function assertCanAccessBang(bangId: string, householdId: string) { const [bang] = await db .select({ id: bangEvents.id }) .from(bangEvents) .where(and(eq(bangEvents.id, bangId), eq(bangEvents.householdId, householdId))) .limit(1); if (!bang) throw new Error("Forbidden"); }