import type { NextRequest } from "next/server"; import { z } from "zod"; import { getActor } from "@/lib/auth/actor"; import { describePasswordRules, hashPassword, MIN_PASSWORD_LENGTH, } from "@/lib/auth/password"; import { requirePermission } from "@/lib/auth/policy"; import { prisma } from "@/lib/db"; import { errorResponse, firstZodMessage, jsonError } from "@/lib/http"; import { QrRequestError } from "@/lib/qr/type-module"; export const dynamic = "force-dynamic"; const createSchema = z.object({ email: z .string() .trim() .toLowerCase() .min(1, { error: "Bitte gib eine E-Mail-Adresse ein." }) .max(254, { error: "Die Adresse ist zu lang." }) .refine((value) => /^[^\s@]+@[^\s@.]+\.[^\s@]+$/.test(value), { error: "Das ist keine gültige E-Mail-Adresse.", }), name: z.string().trim().max(120).optional(), password: z.string().min(MIN_PASSWORD_LENGTH, { error: `Das Passwort ist zu kurz. ${describePasswordRules()}`, }), isInstanceAdmin: z.boolean().default(false), }); export async function GET(): Promise { try { requirePermission(await getActor(), "instance:manage"); const users = await prisma.user.findMany({ orderBy: [{ isInstanceAdmin: "desc" }, { email: "asc" }], include: { memberships: { include: { group: { select: { id: true, name: true } } } }, _count: { select: { sessions: true } }, }, }); return Response.json({ users: users.map((user) => ({ id: user.id, email: user.email, name: user.name, isInstanceAdmin: user.isInstanceAdmin, isActive: user.isActive, isLocked: Boolean( user.lockedUntil && user.lockedUntil.getTime() > Date.now(), ), lastLoginAt: user.lastLoginAt?.toISOString() ?? null, createdAt: user.createdAt.toISOString(), activeSessions: user._count.sessions, memberships: user.memberships.map((m) => ({ groupId: m.group.id, groupName: m.group.name, role: m.role, })), })), }); } catch (error) { return errorResponse(error); } } export async function POST(request: NextRequest): Promise { try { requirePermission(await getActor(), "instance:manage"); const parsed = createSchema.safeParse(await request.json()); if (!parsed.success) { return jsonError(firstZodMessage(parsed.error)); } const existing = await prisma.user.findUnique({ where: { email: parsed.data.email }, select: { id: true }, }); if (existing) { throw new QrRequestError( "Diese E-Mail-Adresse ist bereits vergeben.", 409, ); } const user = await prisma.user.create({ data: { email: parsed.data.email, name: parsed.data.name || null, passwordHash: await hashPassword(parsed.data.password), isInstanceAdmin: parsed.data.isInstanceAdmin, }, }); return Response.json( { user: { id: user.id, email: user.email } }, { status: 201 }, ); } catch (error) { return errorResponse(error); } }