Files
kalendartool/app/api/calendars/[id]/invites/route.ts

98 lines
2.9 KiB
TypeScript

/**
* POST /api/calendars/[id]/invites - Invite-Link erstellen (nur Owner-Admin).
* GET /api/calendars/[id]/invites - Invite-Uebersicht (Aktiv/Abgelaufen/Verwendet).
*/
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/db/client';
import { requireCalendarContext } from '@/lib/api/context';
import { createInviteSchema } from '@/lib/validation';
import { canManageCalendar } from '@/lib/permissions/permissions';
import {
generateInviteToken,
hashInviteToken,
defaultInviteExpiry,
} from '@/lib/invites/tokens';
import { getConfig } from '@/lib/config';
import { errorResponse, forbiddenResponse } from '@/lib/api/responses';
interface RouteParams {
params: Promise<{ id: string }>;
}
export async function POST(
request: NextRequest,
{ params }: RouteParams,
): Promise<NextResponse> {
try {
const { id } = await params;
const context = await requireCalendarContext(id);
if (!canManageCalendar(context.user, context.calendar)) {
return forbiddenResponse();
}
const body = createInviteSchema.parse(await request.json());
const expiry = body.expiresInDays
? new Date(Date.now() + body.expiresInDays * 24 * 60 * 60 * 1000)
: defaultInviteExpiry();
// Klartext-Token wird genau einmal zurueckgegeben; in der DB
// liegt ausschliesslich der Hash (plan.md Abschnitt 9).
const token = generateInviteToken();
const invite = await prisma.invite.create({
data: {
calendarId: id,
tokenHash: hashInviteToken(token),
expiresAt: expiry,
},
select: { id: true, expiresAt: true, createdAt: true },
});
return NextResponse.json(
{
invite,
inviteUrl: `${getConfig().appUrl}/invite/${token}`,
},
{ status: 201 },
);
} catch (error) {
return errorResponse(error);
}
}
export async function GET(
_request: NextRequest,
{ params }: RouteParams,
): Promise<NextResponse> {
try {
const { id } = await params;
const context = await requireCalendarContext(id);
if (!canManageCalendar(context.user, context.calendar)) {
return forbiddenResponse();
}
const invites = await prisma.invite.findMany({
where: { calendarId: id },
orderBy: { createdAt: 'desc' },
select: { id: true, expiresAt: true, usedAt: true, createdAt: true },
});
const now = new Date();
const summary = {
active: invites.filter(
(invite) =>
invite.usedAt === null &&
(invite.expiresAt === null || invite.expiresAt > now),
).length,
expired: invites.filter(
(invite) => invite.usedAt === null && invite.expiresAt !== null && invite.expiresAt <= now,
).length,
used: invites.filter((invite) => invite.usedAt !== null).length,
};
return NextResponse.json({ invites, summary });
} catch (error) {
return errorResponse(error);
}
}