a4051ae132
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>
198 lines
4.5 KiB
TypeScript
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 });
|
|
}
|
|
}
|