99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
"use server";
|
|
|
|
import { randomBytes } from "node:crypto";
|
|
import { revalidatePath } from "next/cache";
|
|
import { redirect } from "next/navigation";
|
|
import { Prisma } from "@prisma/client";
|
|
import { prisma } from "./prisma";
|
|
import { requireCurrentUser, userIsAdmin } from "./session";
|
|
|
|
async function requireAdmin() {
|
|
const user = await requireCurrentUser();
|
|
if (!userIsAdmin(user)) redirect("/dashboard");
|
|
return user;
|
|
}
|
|
|
|
export async function disableUser(formData: FormData) {
|
|
const admin = await requireAdmin();
|
|
const userId = String(formData.get("userId") || "");
|
|
if (!userId || userId === admin.id) return;
|
|
await prisma.user.update({ where: { id: userId }, data: { disabledAt: new Date() } });
|
|
await audit(admin.id, "admin.user.disable", { userId });
|
|
revalidateAdmin();
|
|
}
|
|
|
|
export async function enableUser(formData: FormData) {
|
|
const admin = await requireAdmin();
|
|
const userId = String(formData.get("userId") || "");
|
|
if (!userId) return;
|
|
await prisma.user.update({ where: { id: userId }, data: { disabledAt: null } });
|
|
await audit(admin.id, "admin.user.enable", { userId });
|
|
revalidateAdmin();
|
|
}
|
|
|
|
export async function grantAdminRole(formData: FormData) {
|
|
const admin = await requireAdmin();
|
|
const userId = String(formData.get("userId") || "");
|
|
if (!userId) return;
|
|
const role = await prisma.role.upsert({
|
|
where: { name: "admin" },
|
|
update: {},
|
|
create: { name: "admin", description: "Full system administrator" }
|
|
});
|
|
await prisma.userRole.upsert({
|
|
where: { userId_roleId: { userId, roleId: role.id } },
|
|
update: {},
|
|
create: { userId, roleId: role.id }
|
|
});
|
|
await audit(admin.id, "admin.user.role.grant", { userId, role: "admin" });
|
|
revalidateAdmin();
|
|
}
|
|
|
|
export async function revokeAdminRole(formData: FormData) {
|
|
const admin = await requireAdmin();
|
|
const userId = String(formData.get("userId") || "");
|
|
if (!userId || userId === admin.id) return;
|
|
const role = await prisma.role.findUnique({ where: { name: "admin" } });
|
|
if (!role) return;
|
|
await prisma.userRole.deleteMany({ where: { userId, roleId: role.id } });
|
|
await audit(admin.id, "admin.user.role.revoke", { userId, role: "admin" });
|
|
revalidateAdmin();
|
|
}
|
|
|
|
export async function createInstanceInvite(formData: FormData) {
|
|
const admin = await requireAdmin();
|
|
const roomId = String(formData.get("roomId") || "") || null;
|
|
const expiresDays = Number(formData.get("expiresDays") || 0);
|
|
const expiresAt = Number.isFinite(expiresDays) && expiresDays > 0 ? new Date(Date.now() + expiresDays * 24 * 60 * 60 * 1000) : null;
|
|
const invite = await prisma.invite.create({
|
|
data: {
|
|
code: randomBytes(12).toString("base64url"),
|
|
creatorId: admin.id,
|
|
roomId,
|
|
expiresAt
|
|
}
|
|
});
|
|
await audit(admin.id, "admin.invite.create", { inviteId: invite.id, roomId });
|
|
revalidateAdmin();
|
|
redirect("/admin?tab=Invites&saved=1");
|
|
}
|
|
|
|
export async function revokeInvite(formData: FormData) {
|
|
const admin = await requireAdmin();
|
|
const inviteId = String(formData.get("inviteId") || "");
|
|
if (!inviteId) return;
|
|
await prisma.invite.update({ where: { id: inviteId }, data: { status: "REVOKED" } });
|
|
await audit(admin.id, "admin.invite.revoke", { inviteId });
|
|
revalidateAdmin();
|
|
}
|
|
|
|
async function audit(actorId: string, action: string, metadata: Prisma.InputJsonObject) {
|
|
await prisma.auditEvent.create({ data: { actorId, action, metadata } });
|
|
}
|
|
|
|
function revalidateAdmin() {
|
|
revalidatePath("/admin");
|
|
revalidatePath("/dashboard");
|
|
revalidatePath("/rooms");
|
|
}
|