"use server" import { headers } from "next/headers" import { redirect } from "next/navigation" import { revalidatePath } from "next/cache" import { eq } from "drizzle-orm" import { z } from "zod" import { getAdminSession } from "@/lib/session" import { logAdminAction } from "@/lib/admin/audit" import { auth } from "@/lib/auth" import { db } from "@/lib/db" import { profiles, user as userTable } from "@/lib/db/schema" // ── gate ──────────────────────────────────────────────────────────────────── // Every server action re-verifies the caller is an admin. NEVER skip — these // mutate any user's data and bypass user_id scoping. async function guard() { const a = await getAdminSession() if (!a) throw new Error("Forbidden") return a } const planSchema = z.enum(["starter", "pro", "landlord", "lifetime"]) // ── change plan ───────────────────────────────────────────────────────────── export async function changeUserPlan(userId: string, plan: string) { const a = await guard() const nextPlan = planSchema.parse(plan) const existing = await db.query.profiles.findFirst({ where: eq(profiles.id, userId) }) const oldPlan = existing?.plan ?? null await db.update(profiles).set({ plan: nextPlan }).where(eq(profiles.id, userId)) await logAdminAction({ adminId: a.user.id, action: "plan_change", targetUserId: userId, metadata: { from: oldPlan, to: nextPlan }, }) revalidatePath(`/admin/users/${userId}`) return { ok: true } } // ── ban ───────────────────────────────────────────────────────────────────── export async function banUser(userId: string, reason?: string) { const a = await guard() if (userId === a.user.id) throw new Error("You cannot ban yourself") await auth.api.banUser({ body: { userId, banReason: reason || "Banned by admin" }, headers: await headers(), }) await logAdminAction({ adminId: a.user.id, action: "ban", targetUserId: userId, metadata: { reason: reason || "Banned by admin" }, }) revalidatePath(`/admin/users/${userId}`) return { ok: true } } // ── unban ─────────────────────────────────────────────────────────────────── export async function unbanUser(userId: string) { const a = await guard() await auth.api.unbanUser({ body: { userId }, headers: await headers(), }) await logAdminAction({ adminId: a.user.id, action: "unban", targetUserId: userId, }) revalidatePath(`/admin/users/${userId}`) return { ok: true } } // ── impersonate ───────────────────────────────────────────────────────────── export async function impersonateUser(userId: string) { const a = await guard() if (userId === a.user.id) throw new Error("You cannot impersonate yourself") await auth.api.impersonateUser({ body: { userId }, headers: await headers(), }) await logAdminAction({ adminId: a.user.id, action: "impersonate", targetUserId: userId, }) redirect("/dashboard") } // ── delete ────────────────────────────────────────────────────────────────── export async function deleteUser(userId: string) { const a = await guard() if (userId === a.user.id) throw new Error("You cannot delete yourself") await auth.api.removeUser({ body: { userId }, headers: await headers(), }) await logAdminAction({ adminId: a.user.id, action: "delete_user", targetUserId: userId, }) redirect("/admin/users") } // ── mark email verified ───────────────────────────────────────────────────── export async function markEmailVerified(userId: string) { const a = await guard() await db.update(userTable).set({ emailVerified: true }).where(eq(userTable.id, userId)) await logAdminAction({ adminId: a.user.id, action: "resend_verification", targetUserId: userId, metadata: { markedVerified: true }, }) revalidatePath(`/admin/users/${userId}`) return { ok: true } }