e9c8d7e97e
- /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>
84 lines
2.6 KiB
TypeScript
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 });
|
|
}
|