Files
WatchLink/src/app/setup/page.tsx
MrSphay 035a255125
All checks were successful
Build / build (push) Successful in 9m18s
Template Compliance / compliance (push) Successful in 6s
Release Dry Run / release-dry-run (push) Successful in 1m28s
Gate setup and admin navigation
2026-05-15 17:32:26 +02:00

115 lines
3.1 KiB
TypeScript

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 (
<main className="auth-page">
<section className="auth-card">
<div className="title-block" style={{ marginBottom: 18 }}>
<h1>WatchLink first setup</h1>
<p>Create the first admin account. This screen locks after setup.</p>
</div>
<form className="form" action={createFirstAdmin}>
<label>
Username
<input className="input" name="username" autoComplete="username" required />
</label>
<label>
Password
<input className="input" name="password" type="password" autoComplete="new-password" minLength={10} required />
</label>
<button className="button primary" type="submit">
Create admin
</button>
</form>
</section>
</main>
);
}