feat(digest): add weekly capacity digest email cron

Sends a Monday digest to all ADMIN + MANAGER users with:
- Team utilization % for the next 4 weeks
- Overbooked resource count
- Open demand count
- Upcoming vacation count
- Top 5 most utilized resources

Route: GET /api/cron/weekly-digest (secured by CRON_SECRET).
HTML template and plain-text fallback included.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 13:33:12 +02:00
parent 607af1a857
commit ab4ec91e02
4 changed files with 363 additions and 0 deletions
@@ -0,0 +1,40 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { sendWeeklyDigest } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { verifyCronSecret } from "~/lib/cron-auth.js";
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
/**
* GET /api/cron/weekly-digest
*
* Sends a weekly capacity digest email to all ADMIN and MANAGER users.
* Contains team utilization, overbooking count, open demand count, and
* upcoming vacations for the next 4 weeks.
*
* Secure with CRON_SECRET: requests must include `Authorization: Bearer <secret>`.
* Schedule: run every Monday morning (e.g. `0 7 * * 1` in cron syntax).
*/
export async function GET(request: Request) {
const deny = verifyCronSecret(request);
if (deny) return deny;
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await sendWeeklyDigest(prisma as any);
return NextResponse.json({
ok: true,
...result,
sentAt: new Date().toISOString(),
});
} catch (error) {
logger.error({ error, route: "/api/cron/weekly-digest" }, "Weekly digest cron failed");
return NextResponse.json(
{ ok: false, error: "Internal error" },
{ status: 500 },
);
}
}