Files
Grow/src/app/api/cron/milk-expiry/route.ts
T
arnaudne e9c8d7e97e feat: daily digest, photo timeline, milk expiry cron, batch delete, duration chart
- /api/cron/daily-digest: last feed/diaper, 24h sleep total, next med due
- /photos: photo grid grouped by day, full-screen lightbox with prev/next
- /api/cron/milk-expiry: push alert for lots expiring within 24h
- Timeline: select mode with checkboxes, confirm + bulk DELETE
- Stats: avg NAP/NIGHT_SLEEP duration trend dual-line chart (30d)
- Nav: Photos link added under "Vie du bébé" drawer group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:31:47 +02:00

84 lines
2.6 KiB
TypeScript

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { sendPushover } from "@/lib/push-notify";
// Call daily with:
// POST /api/cron/milk-expiry
// Authorization: Bearer <CRON_SECRET>
async function getCronSecret(): Promise<string> {
const row = await prisma.systemConfig.findUnique({ where: { key: "cron_secret" } });
return row?.value || process.env.CRON_SECRET || "";
}
export async function POST(req: Request) {
const secret = await getCronSecret();
if (secret) {
const authHeader = req.headers.get("authorization") ?? "";
if (authHeader !== `Bearer ${secret}`) {
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
}
}
const windowEnd = new Date(Date.now() + 24 * 3600 * 1000);
const expiringLots = await prisma.milkStock.findMany({
where: {
used: false,
expiresAt: { lte: windowEnd },
},
include: {
baby: {
include: {
family: {
include: {
users: {
where: { pushoverUserKey: { not: null } },
select: { pushoverUserKey: true },
},
},
},
},
},
},
orderBy: { expiresAt: "asc" },
});
if (expiringLots.length === 0) {
return NextResponse.json({ ok: true, sent: 0 });
}
// Group by family
const byFamily = new Map<string, { users: { pushoverUserKey: string | null }[]; lines: string[]; babyName: string }>();
for (const lot of expiringLots) {
const familyId = lot.baby.family.id;
if (!byFamily.has(familyId)) {
byFamily.set(familyId, { users: lot.baby.family.users, lines: [], babyName: lot.baby.name });
}
const entry = byFamily.get(familyId)!;
const expiresIn = lot.expiresAt.getTime() - Date.now();
const hoursLeft = Math.max(0, Math.floor(expiresIn / 3600000));
const loc = lot.location === "fridge" ? "frigo" : lot.location === "freezer" ? "congélateur" : "ambiant";
const alreadyExpired = expiresIn <= 0;
entry.lines.push(
alreadyExpired
? `⚠️ ${lot.volume} mL (${loc}) — expiré`
: `🥛 ${lot.volume} mL (${loc}) — expire dans ${hoursLeft}h`
);
}
let sent = 0;
for (const { users, lines, babyName } of byFamily.values()) {
const message = [`🥛 Stock lait — ${babyName}`, "", ...lines].join("\n");
for (const user of users) {
if (user.pushoverUserKey) {
await sendPushover(user.pushoverUserKey, message, `🥛 Alerte stock lait`);
sent++;
}
}
}
return NextResponse.json({ ok: true, sent, lots: expiringLots.length });
}