import { redirect } from "next/navigation"; import { hash } from "bcryptjs"; import { prisma } from "@/lib/prisma"; import { SYSTEM_PERMISSIONS } from "@/lib/access"; import { setSession } from "@/lib/session"; import { hasAdminUser } from "@/lib/setup"; export const dynamic = "force-dynamic"; async function createFirstAdmin(formData: FormData) { "use server"; const username = String(formData.get("username") || "").trim().toLowerCase(); const password = String(formData.get("password") || ""); if (!username || password.length < 10) { throw new Error("Username is required and password must be at least 10 characters."); } const existingAdmin = await prisma.userRole.findFirst({ where: { role: { name: "admin" } }, select: { userId: true } }); if (existingAdmin) { redirect("/login"); } const passwordHash = await hash(password, 12); const user = await prisma.$transaction(async (tx) => { const permissions = await Promise.all( SYSTEM_PERMISSIONS.map((key) => tx.permission.upsert({ where: { key }, update: {}, create: { key, description: key } }) ) ); const adminRole = await tx.role.upsert({ where: { name: "admin" }, update: {}, create: { name: "admin", description: "Full system administrator" } }); await Promise.all( permissions.map((permission) => tx.rolePermission.upsert({ where: { roleId_permissionId: { roleId: adminRole.id, permissionId: permission.id } }, update: {}, create: { roleId: adminRole.id, permissionId: permission.id } }) ) ); const createdUser = await tx.user.create({ data: { username, displayName: username, passwordHash } }); await tx.userRole.create({ data: { userId: createdUser.id, roleId: adminRole.id } }); await tx.room.create({ data: { slug: `@${username}`, name: `${username}'s room`, ownerId: createdUser.id, visibility: "FRIENDS" } }); return createdUser; }); await setSession(user.id); redirect("/dashboard"); } export default async function SetupPage() { if (await hasAdminUser()) { redirect("/login"); } return (

WatchLink first setup

Create the first admin account. This screen locks after setup.

); }