Files
Calbook/app/api/admin/instant-meeting/route.ts

106 lines
3.4 KiB
TypeScript

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);
}
}