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>
124 lines
3.6 KiB
TypeScript
124 lines
3.6 KiB
TypeScript
import { mkdir, stat, writeFile } from "fs/promises";
|
|
import path from "path";
|
|
import { randomUUID } from "crypto";
|
|
import { NextResponse } from "next/server";
|
|
import { requireCurrentUser, UnauthorizedError } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
const uploadRoot = "/data/uploads";
|
|
const maxFileSizeBytes = 2 * 1024 * 1024;
|
|
|
|
const allowedMimeTypes: Record<string, string> = {
|
|
"image/png": "png",
|
|
"image/svg+xml": "svg",
|
|
"image/x-icon": "ico",
|
|
"image/vnd.microsoft.icon": "ico"
|
|
};
|
|
|
|
function getExtension(file: File): string | null {
|
|
return allowedMimeTypes[file.type] ?? null;
|
|
}
|
|
|
|
async function ensureSettings(userId: string, email: string) {
|
|
const existingSettings = await prisma.settings.findUnique({
|
|
where: { userId }
|
|
});
|
|
|
|
if (existingSettings) {
|
|
return existingSettings;
|
|
}
|
|
|
|
return prisma.settings.create({
|
|
data: {
|
|
userId,
|
|
darkMode: false,
|
|
calendarIcsUrl: null,
|
|
calendarMaxEvents: 8,
|
|
calendarLookaheadDays: 60,
|
|
dashboardTitle: "Personal Dashboard",
|
|
dashboardSubtitle: email,
|
|
logoUrl: "/logo.svg",
|
|
faviconUrl: "/favicon.ico",
|
|
backgroundImageUrl: "/background-fancy.svg",
|
|
backgroundImageOpacity: 32,
|
|
primaryColor: "#2563eb",
|
|
secondaryColor: "#dbeafe",
|
|
customCss: ""
|
|
}
|
|
});
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const user = await requireCurrentUser();
|
|
await ensureSettings(user.id, user.email);
|
|
|
|
const formData = await request.formData();
|
|
const file = formData.get("file");
|
|
|
|
if (!(file instanceof File)) {
|
|
return NextResponse.json({ error: "Keine Datei hochgeladen." }, { status: 400 });
|
|
}
|
|
|
|
if (file.size <= 0 || file.size > maxFileSizeBytes) {
|
|
return NextResponse.json({ error: "Datei ist leer oder größer als 2 MB." }, { status: 400 });
|
|
}
|
|
|
|
const extension = getExtension(file);
|
|
|
|
if (!extension) {
|
|
return NextResponse.json({ error: "Nur ICO, PNG oder SVG sind erlaubt." }, { status: 400 });
|
|
}
|
|
|
|
const userUploadDirectory = path.join(uploadRoot, "users", user.id);
|
|
await mkdir(userUploadDirectory, { recursive: true });
|
|
|
|
const fileName = `favicon-${Date.now()}-${randomUUID()}.${extension}`;
|
|
const filePath = path.join(userUploadDirectory, fileName);
|
|
const bytes = await file.arrayBuffer();
|
|
|
|
await writeFile(filePath, Buffer.from(bytes));
|
|
|
|
const writtenFile = await stat(filePath);
|
|
|
|
if (!writtenFile.isFile() || writtenFile.size <= 0) {
|
|
return NextResponse.json({ error: "Favicon wurde nicht korrekt gespeichert." }, { status: 500 });
|
|
}
|
|
|
|
const faviconUrl = `/api/uploads/users/${user.id}/${fileName}`;
|
|
|
|
const settings = await prisma.settings.update({
|
|
where: { userId: user.id },
|
|
data: { faviconUrl }
|
|
});
|
|
|
|
return NextResponse.json({ settings });
|
|
} catch (error) {
|
|
if (error instanceof UnauthorizedError) {
|
|
return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
|
|
}
|
|
|
|
return NextResponse.json({ error: "Favicon konnte nicht hochgeladen werden." }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function DELETE() {
|
|
try {
|
|
const user = await requireCurrentUser();
|
|
await ensureSettings(user.id, user.email);
|
|
|
|
const settings = await prisma.settings.update({
|
|
where: { userId: user.id },
|
|
data: { faviconUrl: "/favicon.ico" }
|
|
});
|
|
|
|
return NextResponse.json({ settings });
|
|
} catch (error) {
|
|
if (error instanceof UnauthorizedError) {
|
|
return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 });
|
|
}
|
|
|
|
return NextResponse.json({ error: "Favicon konnte nicht zurückgesetzt werden." }, { status: 500 });
|
|
}
|
|
}
|