Complete WatchLink V1 realtime features
Some checks failed
Template Compliance / compliance (push) Successful in 7s
Release Dry Run / release-dry-run (push) Failing after 1m8s
Build / build (push) Failing after 1m15s

This commit is contained in:
MrSphay
2026-05-15 23:27:18 +02:00
parent 04d75c386f
commit c1ac6e4142
25 changed files with 1775 additions and 253 deletions

97
src/lib/admin-actions.ts Normal file
View File

@@ -0,0 +1,97 @@
"use server";
import { randomBytes } from "node:crypto";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
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: Record<string, unknown>) {
await prisma.auditEvent.create({ data: { actorId, action, metadata } });
}
function revalidateAdmin() {
revalidatePath("/admin");
revalidatePath("/dashboard");
revalidatePath("/rooms");
}