106 lines
3.4 KiB
TypeScript
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);
|
|
}
|
|
}
|