"use server"; import { and, eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import type { ApiAuthContext } from "@/lib/api-auth"; import { db } from "@/lib/db"; import { getCurrentSession } from "@/lib/session"; import { logActivityForScope } from "@/modules/_core/activity"; import { bangEvents } from "../schema"; import { addBangInput, updateBangInput } from "./schemas"; const deleteBangInput = z.object({ id: z.string().uuid(), }); function toScope(ctx: ApiAuthContext) { return { householdId: ctx.householdId, userId: ctx.userId }; } 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 addBangForScope( scope: ApiAuthContext, input: z.input = {}, ) { const parsed = addBangInput.parse(input); const occurredOn = parsed.occurredOn ?? todayString(); const [bang] = await db .insert(bangEvents) .values({ householdId: scope.householdId, recordedBy: scope.userId, occurredOn, }) .returning(); if (!bang) throw new Error("Bang was not recorded"); await logActivityForScope(toScope(scope), { entityType: "bangs.event", entityId: bang.id, action: "create", payload: { occurredOn }, }); return bang; } export async function addBang(input: z.input = {}) { const { user, household } = await getCurrentSession(); const bang = await addBangForScope( { householdId: household.id, userId: user.id, role: null }, input, ); revalidatePath("/"); revalidatePath("/d/[slug]", "page"); return bang; } export async function updateBangForScope( scope: ApiAuthContext, input: { id: string } & z.input, ) { const parsed = z.object({ id: z.string().uuid() }).and(updateBangInput).parse(input); await assertCanAccessBang(parsed.id, scope.householdId); 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 logActivityForScope(toScope(scope), { entityType: "bangs.event", entityId: bang.id, action: "update", payload: { occurredOn: parsed.occurredOn }, }); return bang; } export async function updateBang(input: { id: string } & z.input) { const parsed = z.object({ id: z.string().uuid() }).and(updateBangInput).parse(input); const { household, user } = await getCurrentSession(); const bang = await updateBangForScope( { householdId: household.id, userId: user.id, role: null }, parsed, ); revalidatePath("/"); revalidatePath("/d/[slug]", "page"); return bang; } export async function deleteBangForScope(scope: ApiAuthContext, input: { id: string }) { const parsed = deleteBangInput.parse(input); await assertCanAccessBang(parsed.id, scope.householdId); const [existing] = await db .select({ occurredOn: bangEvents.occurredOn }) .from(bangEvents) .where(eq(bangEvents.id, parsed.id)) .limit(1); await logActivityForScope(toScope(scope), { entityType: "bangs.event", entityId: parsed.id, action: "delete", payload: existing ? { occurredOn: existing.occurredOn } : undefined, }); await db.delete(bangEvents).where(eq(bangEvents.id, parsed.id)); } export async function deleteBang(input: z.input) { const parsed = deleteBangInput.parse(input); const { household, user } = await getCurrentSession(); await deleteBangForScope({ householdId: household.id, userId: user.id, role: null }, parsed); 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"); }