Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MailChimp Replacement Backend #1080

Open
wants to merge 11 commits into
base: development
Choose a base branch
from
74 changes: 74 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"babel-plugin-module-resolver": "^5.0.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.3",
"cheerio": "^1.0.0-rc.12",
"cors": "^2.8.4",
"cron": "^1.8.2",
"dotenv": "^5.0.1",
Expand Down
142 changes: 101 additions & 41 deletions src/controllers/emailController.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
// emailController.js
const nodemailer = require('nodemailer');
// const nodemailer = require('nodemailer');
const jwt = require('jsonwebtoken');
const cheerio = require('cheerio');
const emailSender = require('../utilities/emailSender');
const EmailSubcriptionList = require('../models/emailSubcriptionList');
const userProfile = require('../models/userProfile');
const { hasPermission } = require('../utilities/permissions');

const frontEndUrl = process.env.FRONT_END_URL || 'http://localhost:3000';
const jwtSecret = process.env.JWT_SECRET || 'EmailSecret';

const handleContentToOC = (htmlContent) =>
`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
${htmlContent}
</body>
</html>`;

const handleContentToNonOC = (htmlContent, email) =>
`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
${htmlContent}
<p> Thank you for subscribing to our email updates!</p>
<p> If you would like to unsubscribe, please click <a href="${frontEndUrl}/email-unsubscribe?email=${email}">here</a></p>
</body>
</html>`;

function extractImagesAndCreateAttachments(html) {
const $ = cheerio.load(html);
const attachments = [];

$('img').each((i, img) => {
const src = $(img).attr('src');
if (src.startsWith('data:image')) {
const base64Data = src.split(',')[1];
const _cid = `image-${i}`;
attachments.push({
filename: `image-${i}.png`,
content: Buffer.from(base64Data, 'base64'),
cid: _cid,
});
$(img).attr('src', `cid:${_cid}`);
}
});
return {
html: $.html(),
attachments,
};
}

const sendEmail = async (req, res) => {
const canSendEmail = await hasPermission(req.body.requestor, 'sendEmails');
if (!canSendEmail) {
Expand All @@ -16,10 +65,27 @@ const sendEmail = async (req, res) => {
}
try {
const { to, subject, html } = req.body;
// Validate required fields
if (!subject || !html || !to) {
const missingFields = [];
if (!subject) missingFields.push('Subject');
if (!html) missingFields.push('HTML content');
if (!to) missingFields.push('Recipient email');
console.log('missingFields', missingFields);
return res
.status(400)
.send(`${missingFields.join(' and ')} ${missingFields.length > 1 ? 'are' : 'is'} required`);
}

// Extract images and create attachments
const { html: processedHtml, attachments } = extractImagesAndCreateAttachments(html);

// Log recipient for debugging
console.log('Recipient:', to);

console.log('to', to);
// Send email
emailSender(to, subject, handleContentToOC(processedHtml), attachments);

emailSender(to, subject, html);
return res.status(200).send('Email sent successfully');
} catch (error) {
console.error('Error sending email:', error);
Expand All @@ -35,46 +101,44 @@ const sendEmailToAll = async (req, res) => {
}
try {
const { subject, html } = req.body;
if (!subject || !html) {
return res.status(400).send('Subject and HTML content are required');
}

const { html: processedHtml, attachments } = extractImagesAndCreateAttachments(html);

const users = await userProfile.find({
firstName: 'Haoji',
firstName: '',
email: { $ne: null },
isActive: true,
emailSubscriptions: true,
});
let to = '';
const emailContent = ` <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
if (users.length === 0) {
return res.status(404).send('No users found');
}

const recipientEmails = users.map((user) => user.email);
console.log('# sendEmailToAll to', recipientEmails.join(','));
if (recipientEmails.length === 0) {
throw new Error('No recipients defined');
}

<body>
${html}
</body>
</html>`;
users.forEach((user) => {
to += `${user.email},`;
});
emailSender(to, subject, emailContent);
const emailList = await EmailSubcriptionList.find({ email: { $ne: null } });
emailList.forEach((emailObject) => {
const { email } = emailObject;
const emailContent = ` <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
const emailContentToOCmembers = handleContentToOC(processedHtml);
await Promise.all(
recipientEmails.map((email) =>
emailSender(email, subject, emailContentToOCmembers, attachments),
),
);

const emailSubscribers = await EmailSubcriptionList.find({ email: { $exists: true, $ne: '' } });
console.log('# sendEmailToAll emailSubscribers', emailSubscribers.length);
await Promise.all(
emailSubscribers.map(({ email }) => {
const emailContentToNonOCmembers = handleContentToNonOC(processedHtml, email);
return emailSender(email, subject, emailContentToNonOCmembers, attachments);
}),
);

<body>
${html}
<p> Thank you for subscribing to our email updates!</p>
<p> If you would like to unsubscribe, please click <a href="${frontEndUrl}/email-unsubscribe?email=${email}">here</a></p>
</body>
</html>`;
emailSender(email, subject, emailContent);
});
return res.status(200).send('Email sent successfully');
} catch (error) {
console.error('Error sending email:', error);
Expand Down Expand Up @@ -112,13 +176,9 @@ const addNonHgnEmailSubscription = async (req, res) => {
}
const payload = { email };

const token = jwt.sign(
payload,
jwtSecret,
{
expiresIn: 360,
},
);
const token = jwt.sign(payload, jwtSecret, {
expiresIn: 360,
});
const emailContent = ` <!DOCTYPE html>
<html>
<head>
Expand Down
Loading
Loading