Files
famapp/src/modules/bangs/server/actions.ts
T

139 lines
4.0 KiB
TypeScript

"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<typeof addBangInput> = {},
) {
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<typeof addBangInput> = {}) {
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<typeof updateBangInput>,
) {
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<typeof updateBangInput>) {
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<typeof deleteBangInput>) {
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");
}