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>
This commit is contained in:
Claude
2026-06-18 10:02:05 +02:00
commit a4051ae132
74 changed files with 18317 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
import { NextResponse } from "next/server";
import { requireCurrentUser, UnauthorizedError } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { normalizePositiveInteger, normalizeTitle } from "@/lib/validation";
type RouteContext = {
params: Promise<{
id: string;
}>;
};
type NoteType = "note";
function normalizeNoteType(_value: unknown, _fallback: string): NoteType {
return "note";
}
function normalizeContent(value: unknown, fallback: string): string {
if (typeof value !== "string") {
return fallback;
}
return value.slice(0, 10000);
}
export async function PATCH(request: Request, context: RouteContext) {
try {
const user = await requireCurrentUser();
const params = await context.params;
const body = (await request.json().catch(() => null)) as {
type?: unknown;
title?: unknown;
content?: unknown;
x?: unknown;
y?: unknown;
w?: unknown;
h?: unknown;
} | null;
const currentNote = await prisma.noteBoardItem.findFirst({
where: {
id: params.id,
userId: user.id
}
});
if (!currentNote) {
return NextResponse.json({ error: "Notiz nicht gefunden." }, { status: 404 });
}
const hasTitle = body && Object.prototype.hasOwnProperty.call(body, "title");
const title = hasTitle ? normalizeTitle(body?.title) : currentNote.title;
if (!title) {
return NextResponse.json({ error: "Notiz-Titel ist ungültig." }, { status: 400 });
}
const note = await prisma.noteBoardItem.update({
where: {
id: currentNote.id
},
data: {
type:
body && Object.prototype.hasOwnProperty.call(body, "type")
? normalizeNoteType(body.type, currentNote.type)
: currentNote.type,
title,
content:
body && Object.prototype.hasOwnProperty.call(body, "content")
? normalizeContent(body.content, currentNote.content)
: currentNote.content,
x: normalizePositiveInteger(body?.x, currentNote.x, 0, 10000),
y: normalizePositiveInteger(body?.y, currentNote.y, 0, 10000),
w: normalizePositiveInteger(body?.w, currentNote.w, 1, 12),
h: normalizePositiveInteger(body?.h, currentNote.h, 1, 30)
}
});
return NextResponse.json({
note
});
} catch (error) {
if (error instanceof UnauthorizedError) {
return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
}
return NextResponse.json({ error: "Notiz konnte nicht gespeichert werden." }, { status: 500 });
}
}
export async function DELETE(_request: Request, context: RouteContext) {
try {
const user = await requireCurrentUser();
const params = await context.params;
const deleteResult = await prisma.noteBoardItem.deleteMany({
where: {
id: params.id,
userId: user.id
}
});
if (deleteResult.count === 0) {
return NextResponse.json({ error: "Notiz nicht gefunden." }, { status: 404 });
}
return NextResponse.json({
ok: true
});
} catch (error) {
if (error instanceof UnauthorizedError) {
return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
}
return NextResponse.json({ error: "Notiz konnte nicht gelöscht werden." }, { status: 500 });
}
}