Files
EMI-Backend/notifications.js
T
2025-02-27 23:11:53 -05:00

495 lines
19 KiB
JavaScript

const nodemailer = require("nodemailer");
const DBGetter = require("./mongoDB.js");
const { Expo } = require('expo-server-sdk');
const webPush = require('web-push');
const sendWebNotification = async (subscription, title, body) => {
try {
const payload = JSON.stringify({
title,
body
});
webPush.sendNotification(subscription, payload);
} catch (error) {
console.error('Error sending web notification', error);
}
}
// Expo API to send push notifications, this code was a provided snipped
const sendPushNotification = async (profileToken, body, data) => {
if (!profileToken) return 0;
let expo = new Expo();
if(!Array.isArray(profileToken)) return 0;
let pushTokens = [];
profileToken.forEach((token) => {
if (!Expo.isExpoPushToken(token)) {
console.error(`Push token ${token} is not a valid Expo push token`);
return 0;
}
pushTokens.push(token);
})
// Create the messages that you want to send to clients
let messages = [];
// Construct a message (see https://docs.expo.io/push-notifications/sending-notifications/)
messages.push({
to: pushTokens,
sound: 'default',
body: body,
data: data,
})
let chunks = expo.chunkPushNotifications(messages);
let tickets = [];
(async () => {
for (let chunk of chunks) {
try {
let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
console.log(ticketChunk);
tickets.push(...ticketChunk);
// NOTE: If a ticket contains an error code in ticket.details.error, you
// must handle it appropriately. The error codes are listed in the Expo
// documentation:
// https://docs.expo.io/push-notifications/sending-notifications/#individual-errors
} catch (error) {
console.error(error);
}
}
})();
let receiptIds = [];
for (let ticket of tickets) {
// NOTE: Not all tickets have IDs; for example, tickets for notifications
// that could not be enqueued will have error information and no receipt ID.
if (ticket.id) {
receiptIds.push(ticket.id);
}
}
let receiptIdChunks = expo.chunkPushNotificationReceiptIds(receiptIds);
(async () => {
for (let chunk of receiptIdChunks) {
try {
let receipts = await expo.getPushNotificationReceiptsAsync(chunk);
console.log(receipts);
// The receipts specify whether Apple or Google successfully received the
// notification and information about an error, if one occurred.
for (let receiptId in receipts) {
let { status, message, details } = receipts[receiptId];
if (status === 'ok') {
continue;
} else if (status === 'error') {
console.error(
`There was an error sending a notification: ${message}`
);
if (details && details.error) {
// The error codes are listed in the Expo documentation:
// https://docs.expo.io/push-notifications/sending-notifications/#individual-errors
// You must handle the errors appropriately.
console.error(`The error code is ${details.error}`);
}
}
}
} catch (error) {
console.error(error);
}
}
})();
}
// API to send emails from specific domain.
// TODO: Replace by a generic function, and specifics should be provided
// via enviroment variables.
const sendEmail = async (to, subject, html) => {
let transporter = nodemailer.createTransport({
host: "mail.emmint.com",
port: 465,
secure: true,
auth: {
user: "noreply@emmint.com",
pass: process.env.EMAILPASS,
},
});
let info = await transporter.sendMail({
from: '"EMI Social" <noreply@emmint.com',
to,
subject,
html,
}).catch(console.error);
if (info && info.messageId) console.log("Email sent: %s", info.messageId);
};
// Specific Notification templates
// TODO: This should be replaced by a system of tempaltes.
const yourBookmarkedPostGotACommentTemplate = (post, userEmail, postProfile, senderProfile, bookedProfile, message) => {
let subject = senderProfile.profile.firstName + " commented on the post you follow";
let html = `
<p>Hello ${bookedProfile.profile.firstName},</p>
<p>One of the post you bookmarked has a new comment:</p>
<blockquote cite="https://social.emmint.com/">
<p>${post.content}</p>
</blockquote>
<figcaption>— ${postProfile.profile.firstName} ${postProfile.profile.lastName}</figcaption>
<p>Comment:</p>
<blockquote cite="https://social.emmint.com/">
<p>${message}</p>
</blockquote>
<figcaption>— ${senderProfile.profile.firstName} ${senderProfile.profile.lastName}</figcaption>
<p><a href="https://social.emmint.com/">Check it on the site</a></p>
<p>Blessings</p>
`;
sendEmail(userEmail, subject, html);
};
const youGotANewPostCommentTemplate = (post, userEmail, profile, senderProfile, message) => {
let subject = senderProfile.profile.firstName + " comment on your post";
let html = `
<p>Hello ${profile.profile.firstName},</p>
<p>You got a comment on your post:</p>
<blockquote cite="https://social.emmint.com/">
<p>${post.content}</p>
</blockquote>
<figcaption>— You</figcaption>
<p>Comment:</p>
<blockquote cite="https://social.emmint.com/">
<p>${message}</p>
</blockquote>
<figcaption>— ${senderProfile.profile.firstName} ${senderProfile.profile.lastName}</figcaption>
<p><a href="https://social.emmint.com/">Check it on the site</a></p>
<p>Blessings</p>
`;
return sendEmail(userEmail, subject, html);
};
const yourGroupGotANewPostTemplate = (groupProfile, userEmail, profile, senderProfile, message) => {
let subject = senderProfile.profile.firstName + " posted on one of the groups you follow";
let html = `
<p>Hello ${profile.profile.firstName},</p>
<p>${groupProfile.profile.firstName} ${groupProfile.profile.lastName} have new post:</p>
<blockquote cite="https://social.emmint.com/">
<p>${message}</p>
</blockquote>
<figcaption>— ${senderProfile.profile.firstName} ${senderProfile.profile.lastName}</figcaption>
<p><a href="https://social.emmint.com/">Check it on the site</a></p>
<p>Blessings</p>
`;
return sendEmail(userEmail, subject, html);
};
const youGotANewPostTemplate = (profile, userEmail, senderProfile, message) => {
let subject = senderProfile.profile.firstName + " post on your profile";
let html = `
<p>Hello ${profile.profile.firstName},</p>
<p>You got a new post:</p>
<blockquote cite="https://social.emmint.com/">
<p>${message}</p>
</blockquote>
<figcaption>— ${senderProfile.profile.firstName} ${senderProfile.profile.lastName}</figcaption>
<p><a href="https://social.emmint.com/">Check it on the site</a></p>
<p>Blessings</p>
`;
sendEmail(userEmail, subject, html)
}
const youHaveAnInvitation = (invitedName, invitedEmail, senderProfile) => {
let subject = senderProfile.profile.firstName + " post on your profile";
let html = `
<p>Hello ${invitedName},</p>
<p>You have been invited to be part of the new efforts to be connected through our website.</p>
<p>The social part of our site is a place to be in contact with the espiritual family, read what is new for all the members and have access to the most recent teachings, as well as our catalog of courses.</p>
<p><a href="https://social.emmint.com/">Register to make an account at https://social.emmint.com/</a></p>
<p> Invitation sent by:
${senderProfile.profile.firstName} ${senderProfile.profile.lastName}
</p>
<p>Blessings</p>
<p>Emmanuel International Ministries</p>
`;
sendEmail(invitedEmail, subject, html)
}
const yourGroupHasARequestTemplate = (groupProfile, ownerEmail, senderProfile) => {
let subject = senderProfile.profile.firstName + " wants to join " + groupProfile.profile.firstName + groupProfile.profile.lastName;
let html = `
<p>Hello Admin of ${groupProfile.profile.firstName} ${groupProfile.profile.lastName},</p>
<p>Your group, has a new request from ${senderProfile.profile.firstName} ${senderProfile.profile.lastName}</p>
<p>To respond to this request <a href="https://social.emmint.com/">go to the group</a></p>
<p>Blessings</p>
<p>Emmanuel International Ministries</p>
`;
sendEmail(ownerEmail, subject, html)
}
const yourGroupRequestAcceptedTemplate = (groupProfile, acceptedEmail, acceptedProfile) => {
let subject = "Welcome to the " + groupProfile.profile.firstName + " " + groupProfile.profile.lastName + " group";
let html = `
<p>Hello ${acceptedProfile.profile.firstName} ${acceptedProfile.profile.lastName},</p>
<p>Your request to the group ${groupProfile.profile.firstName + groupProfile.profile.lastName} has been accepted.</p>
<p>You can now <a href="https://social.emmint.com/feed/${groupProfile._id}">go to the group</a></p>
<p>Blessings</p>
<p>Emmanuel International Ministries</p>
`;
sendEmail(acceptedEmail, subject, html)
}
const convertLinks = (input) => {
let text = input;
const linksFound = text.match(/(?:www|https?)[^\s]+/g);
const aLink = [];
if (linksFound != null) {
for (let i = 0; i < linksFound.length; i++) {
let replace = linksFound[i];
if (!(linksFound[i].match(/(http(s?)):\/\//))) { replace = 'http://' + linksFound[i] }
let linkText = replace.split('/')[2];
if (linkText.substring(0, 3) == 'www') { linkText = linkText.replace('www.', '') }
if (linkText.match(/youtu/)) {
let youtubeID = replace.split('/').slice(-1)[0];
aLink.push('<div class="video-wrapper"><iframe src="https://www.youtube.com/embed/' + youtubeID + '" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>')
}
else if (linkText.match(/vimeo/)) {
let vimeoID = replace.split('/').slice(-1)[0];
aLink.push('<div class="video-wrapper"><iframe src="https://player.vimeo.com/video/' + vimeoID + '" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>')
}
else {
aLink.push('<a href="' + replace + '" target="_blank">' + linkText + '</a>');
}
text = text.split(linksFound[i]).map(item => { return aLink[i].includes('iframe') ? item.trim() : item }).join(aLink[i]);
}
return text;
}
else {
return input;
}
}
const broadcastNews = async (post, users) => {
let subject = "We have news for you!";
let filteredContent = convertLinks(post.content);
let html = `
<p>Hello beloved,</p>
<p>We have news for you:</p>
<blockquote cite="https://social.emmint.com/">
<p>${filteredContent}</p>
</blockquote>
<figcaption>— Emmanuel International Ministries</figcaption>
<p>Check more updates <a href="https://social.emmint.com/">on the website</a></p>
<p>Blessings</p>
`;
for (const user of users) {
console.log(user, 'Sending news')
await sendEmail(user.username, subject, html);
}
};
const Notifications = {
sendEmail,
async yourBookmarkedPostGotAComment(post, postProfile, senderProfile, message) {
const DB = await DBGetter.getDB;
const subscribedPromise = post.bookmarks.map((profileid) => {
return DB.getProfileCache(profileid);
});
const subscribed = await Promise.all(subscribedPromise);
const usersPromise = subscribed.map((profile) => {
return DB.getUsernameByIdCache(profile.userid);
});
const usersEmails = await Promise.all(usersPromise);
usersEmails.forEach((userEmail, index) => {
const bookedProfile = subscribed[index];
if (bookedProfile._id == senderProfile._id) return 0;
const notifBody = `${senderProfile.profile.firstName} commented in a post you follow`;
sendPushNotification(bookedProfile.token, notifBody, {post_id: post._id});
DB.addNotification(bookedProfile._id, notifBody, post._id, post.comments.length - 1, senderProfile._id);
yourBookmarkedPostGotACommentTemplate(post, userEmail, postProfile, senderProfile, bookedProfile, message);
});
},
async youGotANewPostComment(postId, whoPostedId, message) {
const DB = await DBGetter.getDB;
const post = await DB.getPost(postId);
const postProfile = await DB.getProfileCache(post.profileid);
const senderProfile = await DB.getProfileCache(whoPostedId);
const userEmail = await DB.getUsernameByIdCache(postProfile.userid);
if (post.bookmarks) {
this.yourBookmarkedPostGotAComment(post, postProfile, senderProfile, message)
}
if (postProfile.isCourse || senderProfile._id == postProfile._id) return 0; //Course owners do not need to receive notifs
const notifBody = `${senderProfile.profile.firstName} commented in your post`;
sendPushNotification(postProfile.token, notifBody, {post_id: postId, comment: message});
DB.addNotification(post.profileid, notifBody, postId, post.comments.length - 1, senderProfile._id);
return youGotANewPostCommentTemplate(post, userEmail, postProfile, senderProfile, message);
},
async yourBookmarkedPostGotAReactiom(post, postProfile, senderProfile) {
const DB = await DBGetter.getDB;
const subscribedPromise = post.bookmarks.map((profileid) => {
return DB.getProfileCache(profileid);
});
const subscribed = await Promise.all(subscribedPromise);
subscribed.forEach((bookedProfile) => {
if (bookedProfile._id == senderProfile._id) return 0;
const notifBody = `${senderProfile.profile.firstName} liked a post you follow`;
sendPushNotification(bookedProfile.token, notifBody, {post_id: post._id});
DB.addNotification(bookedProfile._id, notifBody, post._id, null, senderProfile._id);
});
},
async youGotANewReaction(postId, whoReactedId, reactionType){
const DB = await DBGetter.getDB;
const post = await DB.getPost(postId);
const postProfile = await DB.getProfileCache(post.profileid);
const senderProfile = await DB.getProfileCache(whoReactedId);
const userEmail = await DB.getUsernameByIdCache(postProfile.userid);
if (post.bookmarks) {
this.yourBookmarkedPostGotAReactiom(post, postProfile, senderProfile)
}
if (postProfile.isCourse || senderProfile._id == postProfile._id) return 0; //Course owners do not need to receive notifs
const notifBody = `${senderProfile.profile.firstName} liked your post`;
sendPushNotification(postProfile.token, notifBody, {post_id: post._id});
DB.addNotification(post.profileid, notifBody, postId, null, senderProfile._id);
return 0;
},
async yourGroupGotANewPost(groupProfile, senderProfile, message, post) {
const DB = await DBGetter.getDB;
if(!groupProfile.subscribed) return 0;
let subscribedPromise = Object.keys(groupProfile.subscribed).map((profileid) => {
return DB.getProfileCache(profileid);
});
let subscribed = await Promise.all(subscribedPromise);
let usersPromise = subscribed.map((profile) => {
return DB.getUsernameByIdCache(profile.userid);
});
let users = await Promise.all(usersPromise);
users.forEach((userEmail, index) => {
let userProfile = subscribed[index]; //who is this email sending to
if (userProfile._id == senderProfile._id) return 0; //avoid sending self notifications
if(groupProfile._id == senderProfile._id){
const notifBody = `${groupProfile.profile.firstName} ${groupProfile.profile.lastName} has a new post!`;
sendPushNotification(userProfile.token, notifBody, {post_id: post._id, profile_id: groupProfile._id});
return DB.addNotification(userProfile._id, notifBody, post._id, null, senderProfile._id);
}
const notifBody = `${senderProfile.profile.firstName} post in the group ${groupProfile.profile.firstName} ${groupProfile.profile.lastName}`;
sendPushNotification(userProfile.token, notifBody, {post_id: post._id, profile_id: groupProfile._id});
DB.addNotification(userProfile._id, notifBody, post._id, null, senderProfile._id);
// Disabling email notifications for now, until settings are implemented
// yourGroupGotANewPostTemplate(groupProfile, userEmail, userProfile, senderProfile, message);
});
},
async youGotANewPost(post) {
const toProfileId = post.toProfile;
const whoPostedId = post.profileid;
const message = post.content;
const DB = await DBGetter.getDB;
const profile = await DB.getProfileCache(toProfileId);
const user = await DB.getUserById(profile.userid);
const senderProfile = await DB.getProfileCache(whoPostedId);
if (profile.isGroup) {
return this.yourGroupGotANewPost(profile, senderProfile, message, post);
}
if (post.nonOrganicType == 'News') {
const emails = await DB.getAllEmails();
return this.broadcastNews(post, emails);
}
const notifBody = `${senderProfile.profile.firstName} post in your profile`;
sendPushNotification(profile.token, notifBody, {post_id: post._id, profile_id: post.profileid});
// sendWebNotification(profile.webSubscription, notifBody, message);
DB.addNotification(toProfileId, notifBody, post._id, null, senderProfile._id);
return youGotANewPostTemplate(profile, user.username, senderProfile, message);
},
async yourGroupMakeANewPost(post) {
const whoPostedId = post.profileid;
const message = post.content;
const DB = await DBGetter.getDB;
const profile = await DB.getProfileCache(whoPostedId);
if (profile.isGroup) {
return this.yourGroupGotANewPost(profile, profile, message, post);
}
},
async yourSubscriptionProfileHasNewPost(post) {
const whoPostedId = post.profileid;
const message = post.content;
const DB = await DBGetter.getDB;
const profile = await DB.getProfileCache(whoPostedId);
const subscribed_profiles = await DB.getFollowingTheProfile(whoPostedId);
subscribed_profiles.forEach((subscribed_id, index) => {
const userProfile = subscribed_profiles[index];
if (userProfile._id == whoPostedId._id) return 0;
const notifBody = `${profile.profile.firstName} posted: ${message.substring(0, 50)}...`;
sendPushNotification(userProfile.token, notifBody, {profile_id: whoPostedId});
//sendWebNotification(userProfile.webSubscription, notifBody, message);
DB.addNotification(userProfile._id, notifBody, post._id, null, profile._id);
});
},
youHaveAnInvitation,
broadcastNews,
async yourGroupHasARequest(requesterProfileId, groupId) {
const DB = await DBGetter.getDB;
const requesterProfile = await DB.getProfileCache(requesterProfileId);
const groupProfile = await DB.getProfileCache(groupId);
const user = await DB.getUserById(groupProfile.userid);
yourGroupHasARequestTemplate(groupProfile, user.username, requesterProfile)
const notifBody = `${requesterProfile.profile.firstName} wants to join your group ${groupProfile.profile.firstName} ${groupProfile.profile.lastName}`;
//sendPushNotification(profile.token, notifBody, {});
//sendWebNotification(profile.webSubscription, notifBody, message);
},
async yourGroupRequestAccepted(requesterProfileId, groupId) {
const DB = await DBGetter.getDB;
const requesterProfile = await DB.getProfileCache(requesterProfileId);
const groupProfile = await DB.getProfileCache(groupId);
const user = await DB.getUserById(requesterProfile.userid);
yourGroupRequestAcceptedTemplate(groupProfile, user.username, requesterProfile);
const notifBody = `You were added to the group ${groupProfile.profile.firstName} ${groupProfile.profile.lastName}`;
sendPushNotification(requesterProfile.token, notifBody, {});
// sendWebNotification(requesterProfile.webSubscription, notifBody);
DB.addNotification(requesterProfile, notifBody, null, null, groupProfile._id);
},
}
module.exports = Notifications