export const dynamic = "force-dynamic"; import { z } from "zod"; import { requireAdmin } from "@/lib/auth/session"; import { handleAuthError, fail, ok } from "@/lib/api"; import { getSettings } from "@/lib/settings"; import { SETTING_KEYS } from "@/lib/constants"; import { randomToken } from "@/lib/utils"; import { createMeetingUrlWithConfig } from "@/lib/services/meeting-links"; import { getInstantMeetingBootstrap, resolveInstantMeetingSelection, updateInstantMeetingEmailCache } from "@/lib/services/instant-meeting"; import { sendInstantMeetingEmails } from "@/lib/email/mailer"; import { readJsonBody, validateMutationRequestOrigin } from "@/lib/security/request"; const sendSchema = z.object({ personId: z.string().min(1), additionalEmails: z.array(z.string().email()).max(100).default([]), customMessage: z.string().max(4000).optional(), subjectOverride: z.string().max(200).optional() }); export async function GET() { try { await requireAdmin(); const bootstrap = await getInstantMeetingBootstrap(); return ok(bootstrap); } catch (error) { return handleAuthError(error); } } export async function POST(req: Request) { try { const originError = validateMutationRequestOrigin(req); if (originError) return originError; const session = await requireAdmin(); const bodyResult = await readJsonBody(req, { maxBytes: 64 * 1024 }); if (!bodyResult.ok) return bodyResult.response; const parsed = sendSchema.safeParse(bodyResult.data); if (!parsed.success) { return fail("Ungültige Instant-Meeting Daten", 400, parsed.error.flatten()); } const selection = await resolveInstantMeetingSelection({ scopeType: "person", scopeId: parsed.data.personId, additionalEmails: parsed.data.additionalEmails }); if (!selection.ok) { return fail(selection.message, 400); } const settings = await getSettings([ SETTING_KEYS.COMPANY_NAME, SETTING_KEYS.JITSI_MEETING_MODE, SETTING_KEYS.JITSI_BASE_URL, SETTING_KEYS.JITSI_ROOM_PREFIX ]); const meetingUrl = createMeetingUrlWithConfig(randomToken(24), { mode: settings[SETTING_KEYS.JITSI_MEETING_MODE], baseUrl: settings[SETTING_KEYS.JITSI_BASE_URL], roomPrefix: settings[SETTING_KEYS.JITSI_ROOM_PREFIX] }); const companyName = (settings[SETTING_KEYS.COMPANY_NAME] || "CalBook").trim() || "CalBook"; const sendResult = await sendInstantMeetingEmails({ recipients: selection.recipients, meetingUrl, scopeLabel: selection.scopeLabel, initiatorName: session.user.name?.trim() || "Admin", companyName, customMessage: parsed.data.customMessage, subjectOverride: parsed.data.subjectOverride }); if (!sendResult.ok) { return fail(sendResult.message, 400); } await updateInstantMeetingEmailCache(selection.recipients); return ok({ message: "Instant Meeting erfolgreich versendet.", meetingUrl, sentCount: sendResult.sentCount, recipients: selection.recipients, scopeLabel: selection.scopeLabel }); } catch (error) { if (error instanceof Error && (error.message === "UNAUTHORIZED" || error.message === "FORBIDDEN")) { return handleAuthError(error); } const message = error instanceof Error ? error.message : "Instant Meeting konnte nicht versendet werden."; return fail(message, 500); } }