144 lines
3.3 KiB
TypeScript
144 lines
3.3 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { parseICS } from "node-ical";
|
|
import { isAdminSession, requireSession } from "../../../../lib/auth-helpers";
|
|
import { prisma } from "../../../../lib/prisma";
|
|
|
|
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
|
|
const asText = (value: unknown) => {
|
|
if (value === null || value === undefined) return "";
|
|
if (typeof value === "string") return value.trim();
|
|
return String(value).trim();
|
|
};
|
|
|
|
export async function POST(request: Request) {
|
|
const { session } = await requireSession();
|
|
if (!session) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
if (!isAdminSession(session)) {
|
|
return NextResponse.json({ error: "Nur für Admins." }, { status: 403 });
|
|
}
|
|
|
|
const formData = await request.formData();
|
|
const file = formData.get("file");
|
|
const categoryId = asText(formData.get("categoryId"));
|
|
|
|
if (!(file instanceof File)) {
|
|
return NextResponse.json(
|
|
{ error: "Bitte eine iCal-Datei hochladen." },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (!categoryId) {
|
|
return NextResponse.json(
|
|
{ error: "Bitte eine Kategorie auswählen." },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const category = await prisma.category.findUnique({
|
|
where: { id: categoryId }
|
|
});
|
|
if (!category) {
|
|
return NextResponse.json(
|
|
{ error: "Kategorie nicht gefunden." },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
return NextResponse.json(
|
|
{ error: "Datei ist zu groß (max. 5 MB)." },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
let parsed: Record<string, any>;
|
|
try {
|
|
const raw = await file.text();
|
|
parsed = parseICS(raw);
|
|
} catch (err) {
|
|
return NextResponse.json(
|
|
{ error: "iCal-Datei konnte nicht gelesen werden." },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const entries = Object.values(parsed).filter(
|
|
(entry) => entry && entry.type === "VEVENT"
|
|
);
|
|
|
|
if (entries.length === 0) {
|
|
return NextResponse.json(
|
|
{ error: "Keine Termine in der iCal-Datei gefunden." },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
let created = 0;
|
|
let duplicates = 0;
|
|
let skipped = 0;
|
|
let recurringSkipped = 0;
|
|
|
|
const creatorEmail = session.user?.email || "";
|
|
|
|
for (const entry of entries) {
|
|
if (entry.rrule) {
|
|
recurringSkipped += 1;
|
|
continue;
|
|
}
|
|
|
|
const title = asText(entry.summary);
|
|
const start = entry.start instanceof Date ? entry.start : null;
|
|
if (!title || !start || Number.isNaN(start.getTime())) {
|
|
skipped += 1;
|
|
continue;
|
|
}
|
|
|
|
const end =
|
|
entry.end instanceof Date && !Number.isNaN(entry.end.getTime())
|
|
? entry.end
|
|
: new Date(start.getTime() + 3 * 60 * 60 * 1000);
|
|
const location = asText(entry.location) || null;
|
|
const description = asText(entry.description) || null;
|
|
|
|
const existing = await prisma.event.findFirst({
|
|
where: {
|
|
title,
|
|
startAt: start,
|
|
location,
|
|
categoryId
|
|
}
|
|
});
|
|
|
|
if (existing) {
|
|
duplicates += 1;
|
|
continue;
|
|
}
|
|
|
|
await prisma.event.create({
|
|
data: {
|
|
title,
|
|
description,
|
|
location,
|
|
startAt: start,
|
|
endAt: end,
|
|
status: "APPROVED",
|
|
createdBy: { connect: { email: creatorEmail } },
|
|
category: { connect: { id: categoryId } }
|
|
}
|
|
});
|
|
|
|
created += 1;
|
|
}
|
|
|
|
return NextResponse.json({
|
|
created,
|
|
duplicates,
|
|
skipped,
|
|
recurringSkipped
|
|
});
|
|
}
|