feat: initialize CalBook project with comprehensive scheduling, admin, and deployment infrastructure
This commit is contained in:
105
app/api/admin/instant-meeting/route.ts
Normal file
105
app/api/admin/instant-meeting/route.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user