"use server"; import { compare, hash } from "bcryptjs"; import { redirect } from "next/navigation"; import { Prisma, type User } from "@prisma/client"; import { prisma } from "./prisma"; import { clearSession, setSession } from "./session"; import { getAppSettings } from "./settings"; function normalizeUsername(value: FormDataEntryValue | null) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z0-9_-]/g, ""); } export async function registerUser(formData: FormData) { const settings = await getAppSettings(); if (settings.registrationMode === "DISABLED") { redirect("/register?error=closed"); } const username = normalizeUsername(formData.get("username")); const password = String(formData.get("password") || ""); const inviteCode = String(formData.get("inviteCode") || "").trim(); if (!username || password.length < 10) { redirect("/register?error=invalid"); } const invite = settings.registrationMode === "INVITE_ONLY" ? await prisma.invite.findUnique({ where: { code: inviteCode } }) : null; if ( settings.registrationMode === "INVITE_ONLY" && (!invite || invite.status !== "ACTIVE" || (invite.expiresAt && invite.expiresAt.getTime() < Date.now())) ) { redirect("/register?error=invite"); } const passwordHash = await hash(password, 12); let user: User; try { user = await prisma.$transaction(async (tx) => { const created = await tx.user.create({ data: { username, displayName: username, passwordHash, ownedRooms: { create: { slug: `@${username}`, name: `${username}'s room`, isPersonal: true, visibility: "FRIENDS" } } } }); if (invite) { await tx.invite.update({ where: { id: invite.id }, data: { status: "USED", usedById: created.id, usedAt: new Date() } }); if (invite.roomId) { await tx.roomMember.upsert({ where: { roomId_userId: { roomId: invite.roomId, userId: created.id } }, update: {}, create: { roomId: invite.roomId, userId: created.id } }); } } return created; }); } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") { redirect("/register?error=username"); } throw error; } await setSession(user.id); redirect("/dashboard"); } export async function loginUser(formData: FormData) { const username = normalizeUsername(formData.get("username")); const password = String(formData.get("password") || ""); const user = await prisma.user.findUnique({ where: { username } }); if (!user) { redirect("/login?error=credentials"); } if (user.disabledAt) { redirect("/login?error=disabled"); } const ok = await compare(password, user.passwordHash); if (!ok) { redirect("/login?error=credentials"); } await setSession(user.id); redirect("/dashboard"); } export async function logoutUser() { await clearSession(); redirect("/login"); }