Files
personal-dashboard/src/app/api/users/route.ts
T
Claude a4051ae132 Initial commit: Personal Dashboard
Next.js 16 dashboard with configurable widgets (favorites, notes, calendar,
clock, calculator, search, domain-check), multi-tab support, user auth,
dark mode, and Docker deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:02:05 +02:00

198 lines
4.5 KiB
TypeScript

import { NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import { requireCurrentUser, UnauthorizedError } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import {
normalizeDisplayName,
normalizeEmail,
normalizePassword,
normalizeUserRole
} from "@/lib/validation";
export async function GET() {
try {
const currentUser = await requireCurrentUser();
if (currentUser.role !== "ADMIN") {
return NextResponse.json({ error: "Keine Berechtigung." }, { status: 403 });
}
const users = await prisma.user.findMany({
orderBy: {
createdAt: "asc"
},
select: {
id: true,
email: true,
displayName: true,
role: true,
createdAt: true
}
});
return NextResponse.json({
users
});
} catch (error) {
if (error instanceof UnauthorizedError) {
return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
}
return NextResponse.json({ error: "Nutzer konnten nicht geladen werden." }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const currentUser = await requireCurrentUser();
if (currentUser.role !== "ADMIN") {
return NextResponse.json({ error: "Keine Berechtigung." }, { status: 403 });
}
const body = (await request.json().catch(() => null)) as {
email?: unknown;
password?: unknown;
displayName?: unknown;
role?: unknown;
} | null;
const email = normalizeEmail(body?.email);
const password = normalizePassword(body?.password);
const displayName = normalizeDisplayName(body?.displayName);
const role = normalizeUserRole(body?.role) ?? "USER";
if (!email || !password) {
return NextResponse.json(
{
error: "E-Mail oder Passwort ungültig. Das Passwort muss 10 bis 128 Zeichen lang sein."
},
{ status: 400 }
);
}
const existingUser = await prisma.user.findUnique({
where: {
email
},
select: {
id: true
}
});
if (existingUser) {
return NextResponse.json(
{
error: "Ein Nutzer mit dieser E-Mail existiert bereits."
},
{ status: 409 }
);
}
const passwordHash = await bcrypt.hash(password, 12);
const user = await prisma.user.create({
data: {
email,
passwordHash,
displayName,
role,
settings: {
create: {
darkMode: false,
calendarMaxEvents: 8,
calendarLookaheadDays: 60,
dashboardTitle: "Personal Dashboard",
logoUrl: "/logo.svg",
primaryColor: "#2563eb",
secondaryColor: "#dbeafe"
}
},
widgets: {
create: [
{
type: "favorites",
title: "Links/Favoriten",
position: 0,
x: 0,
y: 0,
w: 3,
h: 6
},
{
type: "note",
title: "Notiz",
position: 1,
x: 3,
y: 0,
w: 3,
h: 4
},
{
type: "search",
title: "Suche",
position: 3,
x: 9,
y: 0,
w: 3,
h: 2
},
{
type: "calendar",
title: "Kalender",
position: 4,
x: 9,
y: 2,
w: 3,
h: 7
}
]
}
},
select: {
id: true,
email: true,
displayName: true,
role: true,
createdAt: true
}
});
const createdWidgets = await prisma.widget.findMany({
where: {
userId: user.id,
type: {
in: ["note"]
}
}
});
await Promise.all(
createdWidgets.map((widget) =>
prisma.noteBoardItem.create({
data: {
id: widget.id,
userId: user.id,
type: widget.type,
title: widget.title,
content: ""
}
})
)
);
return NextResponse.json(
{
user
},
{ status: 201 }
);
} catch (error) {
if (error instanceof UnauthorizedError) {
return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
}
return NextResponse.json({ error: "Nutzer konnte nicht erstellt werden." }, { status: 500 });
}
}