SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
2.8 KB · 53 lines typescript
Raw Blame History
1"use server";23import { revalidatePath } from "next/cache";4import { z } from "zod";5import { getDb, users, organizations, eq } from "@fetcha/db";6import { getWorkspace, requestMeta, requireUser } from "@/lib/session";7import { writeAudit } from "@/lib/audit";8import type { ActionResult } from "./projects";910export async function completeOnboarding(): Promise<ActionResult> {11  const user = await requireUser();12  await getDb().update(users).set({ onboardingCompletedAt: new Date(), updatedAt: new Date() }).where(eq(users.id, user.id));13  revalidatePath("/dashboard", "layout");14  return { ok: true };15}1617const orgSchema = z.object({18  name: z.string().trim().min(2).max(80),19  softLimitUsd: z.coerce.number().min(0).optional().or(z.literal("")),20  hardLimitUsd: z.coerce.number().min(0).optional().or(z.literal("")),21});2223export async function updateOrganization(formData: FormData): Promise<ActionResult> {24  const ws = await getWorkspace();25  if (!["owner", "admin", "billing"].includes(ws.role)) return { ok: false, error: "Only owners and admins can change organization settings." };26  const parsed = orgSchema.safeParse(Object.fromEntries(formData));27  if (!parsed.success) return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };28  const d = parsed.data;29  const soft = d.softLimitUsd === "" || d.softLimitUsd === undefined ? null : d.softLimitUsd;30  const hard = d.hardLimitUsd === "" || d.hardLimitUsd === undefined ? null : d.hardLimitUsd;31  if (soft !== null && hard !== null && soft > hard) return { ok: false, error: "The soft limit must be lower than the hard limit." };32  await getDb().update(organizations).set({ name: d.name, softLimitUsd: soft, hardLimitUsd: hard, updatedAt: new Date() }).where(eq(organizations.id, ws.organization.id));33  const meta = await requestMeta();34  await writeAudit({ userId: ws.user.id, organizationId: ws.organization.id, action: "limits.updated", metadata: { soft, hard }, ipAddress: meta.ip, userAgent: meta.userAgent });35  revalidatePath("/dashboard", "layout");36  return { ok: true };37}3839export async function updateProfileName(name: string): Promise<ActionResult> {40  const user = await requireUser();41  const clean = name.trim().slice(0, 80);42  if (clean.length < 1) return { ok: false, error: "Name cannot be empty" };43  await getDb().update(users).set({ name: clean, updatedAt: new Date() }).where(eq(users.id, user.id));44  revalidatePath("/dashboard", "layout");45  return { ok: true };46}4748export async function recordAuditFromClient(action: "password.changed" | "email.changed" | "session.revoked" | "logout", metadata?: Record<string, unknown>): Promise<void> {49  const user = await requireUser();50  const meta = await requestMeta();51  await writeAudit({ userId: user.id, action, metadata, ipAddress: meta.ip, userAgent: meta.userAgent });52}53