import { db, shoppingLists, shoppingListMembers, eq } from "@epicure/db"; import { sendPushNotification } from "@/lib/push"; import { getMessages, formatMessage } from "@/lib/i18n/server"; // formatMessage() is a plain {key} interpolator, not ICU-plural-aware, so // pluralization has to be resolved to a plain string before it's passed in. function itemsCountLabel(count: number, locale: string | null | undefined): string { if (locale === "fr") return count === 1 ? "1 article" : `${count} articles`; return count === 1 ? "1 item" : `${count} items`; } /** Other people with access to a shared shopping list — owner + members, excluding the actor. */ async function getOtherRecipients(listId: string, actorId: string) { const list = await db.query.shoppingLists.findFirst({ where: eq(shoppingLists.id, listId), columns: { userId: true }, }); const members = await db.query.shoppingListMembers.findMany({ where: eq(shoppingListMembers.listId, listId), columns: { userId: true }, }); const recipientIds = new Set(); if (list?.userId) recipientIds.add(list.userId); for (const m of members) recipientIds.add(m.userId); recipientIds.delete(actorId); if (recipientIds.size === 0) return []; return db.query.users.findMany({ where: (t, { inArray }) => inArray(t.id, [...recipientIds]), columns: { id: true, locale: true }, }); } /** * Push-notifies every other member of a shared shopping list about an item * change. No-op for solo (unshared) lists — getOtherRecipients returns empty. * Deliberately skips the `notifications` table (no in-app entry) since this * is meant as a lightweight real-time "someone's shopping too" ping, same * pattern as the leftover-expiry cron — not a persistent notification-center * event. */ export async function notifyShoppingListMembers( listId: string, actorId: string, actorName: string, event: { type: "checked" | "itemsAdded"; itemName?: string; count?: number; listName: string } ): Promise { const recipients = await getOtherRecipients(listId, actorId); if (recipients.length === 0) return; const url = `/shopping-lists/${listId}`; await Promise.all( recipients.map(async (recipient) => { const messages = getMessages(recipient.locale); const title = messages.notifications.pushTitle.shoppingListUpdate; const body = event.type === "checked" ? formatMessage(messages.notifications.detail.shoppingListChecked, { name: actorName, item: event.itemName ?? "", list: event.listName, }) : formatMessage(messages.notifications.detail.shoppingListItemsAdded, { name: actorName, countLabel: itemsCountLabel(event.count ?? 1, recipient.locale), list: event.listName, }); await sendPushNotification(recipient.id, { title, body, url }).catch((err) => { console.error("[shopping-list-notify] push failed", err); }); }) ); }