|
1 | | -// const cron = require("node-cron"); |
2 | | -// const { subDays, startOfDay, endOfDay } = require("date-fns") |
3 | | -// const ConnectionRequest = require("../models/connectionRequest") |
4 | | -// const sendEmail = require("./sendEmail") |
5 | | - |
6 | | -// const yesterDay = subDays(new Date(), 0); |
7 | | -// const start = startOfDay(yesterDay); |
8 | | -// const end = endOfDay(yesterDay); |
9 | | - |
10 | | -// cron.schedule("0 8 1 1 1 ", async () => { |
11 | | -// try { |
12 | | -// const pendingRequests =await ConnectionRequest.find({ |
13 | | -// status: "interested", |
14 | | -// createdAt: { |
15 | | -// $gte: start, |
16 | | -// $lt: end |
17 | | -// } |
18 | | -// }).populate("fromUserId toUserId") |
19 | | - |
20 | | -// const emails = [...new Set(pendingRequests.map((req) => req.toUserId.email))] |
21 | | -// console.log(emails); |
22 | | - |
23 | | -// for (const email of emails) { |
24 | | -// try { |
25 | | -// const res = await sendEmail(email, "Pending Requests", "You have connection requests pending so please review them", "<h1>Review Requests</h1>"); |
26 | | -// console.log(res); |
27 | | -// } catch (error) { |
28 | | -// console.log(error) |
29 | | -// } |
30 | | -// } |
31 | | -// } catch (error) { |
32 | | -// console.error(error); |
33 | | -// } |
34 | | -// }) |
| 1 | +const cron = require("node-cron"); |
| 2 | +const mongoose = require("mongoose"); |
| 3 | +const { subDays, startOfDay, endOfDay, format } = require("date-fns"); |
| 4 | +const ConnectionRequest = require("../models/connectionRequest"); |
| 5 | +const sendEmail = require("../services/emailService"); |
| 6 | +const { escapeHtml, button } = require("../services/emailTemplates"); |
| 7 | + |
| 8 | +const CRON_EXPRESSION = "0 8 * * *"; // Every day at 08:00 |
| 9 | +const CRON_TIMEZONE = process.env.CRON_TIMEZONE; // optional, e.g. "Asia/Kolkata" |
| 10 | + |
| 11 | +const groupBy = (items, getKey) => { |
| 12 | + return items.reduce((acc, item) => { |
| 13 | + const key = getKey(item); |
| 14 | + if (!acc[key]) acc[key] = []; |
| 15 | + acc[key].push(item); |
| 16 | + return acc; |
| 17 | + }, {}); |
| 18 | +}; |
| 19 | + |
| 20 | +cron.schedule( |
| 21 | + CRON_EXPRESSION, |
| 22 | + async () => { |
| 23 | + try { |
| 24 | + if (mongoose.connection.readyState !== 1) { |
| 25 | + console.warn("[cron] DB not connected; skipping daily request digest"); |
| 26 | + return; |
| 27 | + } |
| 28 | + |
| 29 | + const yesterday = subDays(new Date(), 1); |
| 30 | + const start = startOfDay(yesterday); |
| 31 | + const end = endOfDay(yesterday); |
| 32 | + |
| 33 | + const requests = await ConnectionRequest.find({ |
| 34 | + status: "interested", |
| 35 | + createdAt: { $gte: start, $lt: end }, |
| 36 | + }) |
| 37 | + .populate("fromUserId", "firstName lastName") |
| 38 | + .populate("toUserId", "firstName lastName email"); |
| 39 | + |
| 40 | + if (!requests.length) return; |
| 41 | + |
| 42 | + const byRecipient = groupBy( |
| 43 | + requests.filter(r => r?.toUserId?.email), |
| 44 | + (r) => String(r.toUserId._id) |
| 45 | + ); |
| 46 | + |
| 47 | + const dateLabel = format(yesterday, "do MMM yyyy"); |
| 48 | + const appUrl = (process.env.FRONTEND_URL || "").replace(/\/$/, ""); |
| 49 | + const cta = appUrl ? button(appUrl, "Open DevSync") : ""; |
| 50 | + |
| 51 | + for (const recipientId of Object.keys(byRecipient)) { |
| 52 | + const recipientRequests = byRecipient[recipientId]; |
| 53 | + const recipient = recipientRequests[0].toUserId; |
| 54 | + const to = recipient.email; |
| 55 | + |
| 56 | + const listItems = recipientRequests |
| 57 | + .map((r) => { |
| 58 | + const from = r.fromUserId; |
| 59 | + const fromName = escapeHtml( |
| 60 | + [from?.firstName, from?.lastName].filter(Boolean).join(" ") || "Someone" |
| 61 | + ); |
| 62 | + return `<li style="margin: 6px 0;">${fromName} sent you a connection request</li>`; |
| 63 | + }) |
| 64 | + .join(""); |
| 65 | + |
| 66 | + const count = recipientRequests.length; |
| 67 | + const subject = `You have ${count} new connection request${count === 1 ? "" : "s"} on DevSync`; |
| 68 | + |
| 69 | + const recipientName = escapeHtml(recipient.firstName || "there"); |
| 70 | + |
| 71 | + const html = ` |
| 72 | + <p style="margin: 0 0 12px;">Hi ${recipientName},</p> |
| 73 | + <p style="margin: 0 0 12px;">Here’s a summary of the connection requests you received on <b>${escapeHtml(dateLabel)}</b>:</p> |
| 74 | + <ul style="padding-left: 18px; margin: 0 0 12px;">${listItems}</ul> |
| 75 | + <p style="margin: 0 0 12px;">Please review and respond when you’re ready.</p> |
| 76 | + ${cta} |
| 77 | + <p style="margin: 12px 0 0; color: #6b7280; font-size: 12px;">If the button doesn’t work, open: ${escapeHtml(appUrl || "DevSync")}</p> |
| 78 | + `; |
| 79 | + |
| 80 | + try { |
| 81 | + await sendEmail({ |
| 82 | + to, |
| 83 | + subject, |
| 84 | + preheader: `You received ${count} new request${count === 1 ? "" : "s"} yesterday.`, |
| 85 | + html, |
| 86 | + }); |
| 87 | + } catch (error) { |
| 88 | + console.error("[cron] Failed to send digest to", to, error?.message || error); |
| 89 | + } |
| 90 | + } |
| 91 | + } catch (error) { |
| 92 | + console.error("[cron] Daily request digest failed:", error?.message || error); |
| 93 | + } |
| 94 | + }, |
| 95 | + CRON_TIMEZONE ? { timezone: CRON_TIMEZONE } : undefined |
| 96 | +); |
| 97 | + |
35 | 98 |
|
36 | 99 |
|
0 commit comments