first commit

This commit is contained in:
2026-01-13 18:08:59 +01:00
commit 5d2630a02f
41 changed files with 1632 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
import NextAuth from "next-auth";
import { authOptions } from "../../../../lib/auth";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View File

@@ -0,0 +1,28 @@
import { NextResponse } from "next/server";
import { prisma } from "../../../../lib/prisma";
import { isAdminSession, requireSession } from "../../../../lib/auth-helpers";
export async function PATCH(request: Request, context: { params: { id: string } }) {
const { session } = await requireSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!isAdminSession(session)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await request.json();
const { status } = body || {};
if (!status || !["APPROVED", "REJECTED"].includes(status)) {
return NextResponse.json({ error: "Status ungueltig." }, { status: 400 });
}
const event = await prisma.event.update({
where: { id: context.params.id },
data: { status }
});
return NextResponse.json(event);
}

63
app/api/events/route.ts Normal file
View File

@@ -0,0 +1,63 @@
import { NextResponse } from "next/server";
import { prisma } from "../../../lib/prisma";
import { isAdminSession, requireSession } from "../../../lib/auth-helpers";
export async function GET(request: Request) {
const { session } = await requireSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const status = searchParams.get("status");
const isAdmin = isAdminSession(session);
const where = isAdmin
? status
? { status }
: {}
: {
OR: [
{ status: "APPROVED" },
{ createdBy: { email: session.user?.email || "" } }
]
};
const events = await prisma.event.findMany({
where,
orderBy: { startAt: "asc" }
});
return NextResponse.json(events);
}
export async function POST(request: Request) {
const { session } = await requireSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { title, description, location, startAt, endAt } = body || {};
if (!title || !startAt || !endAt) {
return NextResponse.json(
{ error: "Titel, Start und Ende sind erforderlich." },
{ status: 400 }
);
}
const event = await prisma.event.create({
data: {
title,
description: description || null,
location: location || null,
startAt: new Date(startAt),
endAt: new Date(endAt),
status: isAdminSession(session) ? "APPROVED" : "PENDING",
createdBy: { connect: { email: session.user?.email || "" } }
}
});
return NextResponse.json(event, { status: 201 });
}

View File

@@ -0,0 +1,42 @@
import ical from "ical-generator";
import { NextResponse } from "next/server";
import { prisma } from "../../../../lib/prisma";
export async function GET(
_request: Request,
context: { params: { token: string } }
) {
const view = await prisma.userView.findUnique({
where: { token: context.params.token },
include: { items: { include: { event: true } }, user: true }
});
if (!view) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const calendar = ical({
name: `Vereinskalender - ${view.name}`,
timezone: "Europe/Berlin"
});
view.items
.map((item) => item.event)
.filter((event) => event.status === "APPROVED")
.forEach((event) => {
calendar.createEvent({
id: event.id,
summary: event.title,
description: event.description || undefined,
location: event.location || undefined,
start: event.startAt,
end: event.endAt
});
});
return new NextResponse(calendar.toString(), {
headers: {
"Content-Type": "text/calendar; charset=utf-8"
}
});
}

30
app/api/register/route.ts Normal file
View File

@@ -0,0 +1,30 @@
import bcrypt from "bcryptjs";
import { NextResponse } from "next/server";
import { prisma } from "../../../lib/prisma";
import { isAdminEmail } from "../../../lib/auth";
export async function POST(request: Request) {
const body = await request.json();
const { email, name, password } = body || {};
if (!email || !password) {
return NextResponse.json({ error: "Email und Passwort sind erforderlich." }, { status: 400 });
}
const existing = await prisma.user.findUnique({ where: { email } });
if (existing) {
return NextResponse.json({ error: "Account existiert bereits." }, { status: 409 });
}
const passwordHash = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: {
email,
name: name || null,
passwordHash,
role: isAdminEmail(email) ? "ADMIN" : "USER"
}
});
return NextResponse.json({ id: user.id, email: user.email });
}

View File

@@ -0,0 +1,60 @@
import { NextResponse } from "next/server";
import { prisma } from "../../../../../lib/prisma";
import { requireSession } from "../../../../../lib/auth-helpers";
async function ensureOwner(viewId: string, email: string) {
const view = await prisma.userView.findFirst({
where: { id: viewId, user: { email } }
});
return view;
}
export async function POST(request: Request, context: { params: { id: string } }) {
const { session } = await requireSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const email = session.user?.email || "";
const view = await ensureOwner(context.params.id, email);
if (!view) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const body = await request.json();
const { eventId } = body || {};
if (!eventId) {
return NextResponse.json({ error: "Event erforderlich." }, { status: 400 });
}
await prisma.userViewItem.create({
data: { viewId: view.id, eventId }
});
return NextResponse.json({ ok: true }, { status: 201 });
}
export async function DELETE(request: Request, context: { params: { id: string } }) {
const { session } = await requireSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const email = session.user?.email || "";
const view = await ensureOwner(context.params.id, email);
if (!view) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const body = await request.json();
const { eventId } = body || {};
if (!eventId) {
return NextResponse.json({ error: "Event erforderlich." }, { status: 400 });
}
await prisma.userViewItem.deleteMany({
where: { viewId: view.id, eventId }
});
return NextResponse.json({ ok: true });
}

43
app/api/views/route.ts Normal file
View File

@@ -0,0 +1,43 @@
import { randomUUID } from "crypto";
import { NextResponse } from "next/server";
import { prisma } from "../../../lib/prisma";
import { requireSession } from "../../../lib/auth-helpers";
export async function GET() {
const { session } = await requireSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const views = await prisma.userView.findMany({
where: { user: { email: session.user?.email || "" } },
include: { items: { include: { event: true } } },
orderBy: { createdAt: "desc" }
});
return NextResponse.json(views);
}
export async function POST(request: Request) {
const { session } = await requireSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { name } = body || {};
if (!name) {
return NextResponse.json({ error: "Name erforderlich." }, { status: 400 });
}
const view = await prisma.userView.create({
data: {
name,
token: randomUUID(),
user: { connect: { email: session.user?.email || "" } }
}
});
return NextResponse.json(view, { status: 201 });
}