TypeScript 97.5%
SQL 1.4%
Python 0.8%
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