70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
export const dynamic = "force-dynamic";
|
|
|
|
import { calculateSlotsForDisplayDate } from "@/lib/services/availability";
|
|
import { slotsQuerySchema } from "@/lib/validators/public";
|
|
import { fail, ok } from "@/lib/api";
|
|
import { enforceRateLimit } from "@/lib/rate-limit";
|
|
import { resolveTimeZone } from "@/lib/date";
|
|
|
|
type DaySlotsCacheEntry = {
|
|
slots: Awaited<ReturnType<typeof calculateSlotsForDisplayDate>>;
|
|
expiresAt: number;
|
|
};
|
|
|
|
declare global {
|
|
// eslint-disable-next-line no-var
|
|
var calbookDaySlotsCache: Map<string, DaySlotsCacheEntry> | undefined;
|
|
}
|
|
|
|
const daySlotsCache = global.calbookDaySlotsCache ?? new Map<string, DaySlotsCacheEntry>();
|
|
if (!global.calbookDaySlotsCache) {
|
|
global.calbookDaySlotsCache = daySlotsCache;
|
|
}
|
|
|
|
const DAY_SLOTS_CACHE_TTL_MS = Number(process.env.SLOTS_DAY_CACHE_TTL_MS ?? "6000");
|
|
|
|
export async function GET(req: Request) {
|
|
const limit = enforceRateLimit({
|
|
req,
|
|
scope: "public-slots",
|
|
limit: 120,
|
|
windowMs: 60_000
|
|
});
|
|
|
|
if (!limit.ok) {
|
|
return fail("Zu viele Anfragen. Bitte kurz warten.", 429, {
|
|
retryAfterSeconds: limit.retryAfterSeconds
|
|
});
|
|
}
|
|
|
|
const url = new URL(req.url);
|
|
const parsed = slotsQuerySchema.safeParse({
|
|
mitarbeiterId: url.searchParams.get("mitarbeiterId") ?? undefined,
|
|
datum: url.searchParams.get("datum"),
|
|
timezone: url.searchParams.get("timezone") ?? undefined
|
|
});
|
|
|
|
if (!parsed.success) {
|
|
return fail("Ungültige Parameter", 400, parsed.error.flatten());
|
|
}
|
|
|
|
const timezone = resolveTimeZone(parsed.data.timezone);
|
|
const cacheKey = `${parsed.data.mitarbeiterId ?? "all"}|${parsed.data.datum}|${timezone}`;
|
|
const cached = daySlotsCache.get(cacheKey);
|
|
if (cached && cached.expiresAt > Date.now()) {
|
|
return ok({ slots: cached.slots });
|
|
}
|
|
|
|
const results = await calculateSlotsForDisplayDate(
|
|
parsed.data.mitarbeiterId,
|
|
parsed.data.datum,
|
|
{ displayTimezone: timezone }
|
|
);
|
|
daySlotsCache.set(cacheKey, {
|
|
slots: results,
|
|
expiresAt: Date.now() + DAY_SLOTS_CACHE_TTL_MS
|
|
});
|
|
|
|
return ok({ slots: results });
|
|
}
|