refactored the notification function

This commit is contained in:
josephgladwingeorge 2025-03-27 11:43:53 +05:30
parent d8686f3b8b
commit 883813a589
6 changed files with 289 additions and 109 deletions

View File

@ -1,6 +1,7 @@
{
"projects": {
"debug": "fitlien-dev",
"release": "fitlien"
"release": "fitlien",
"default": "fitlien-dev"
}
}

View File

@ -1,5 +1,6 @@
{
"firestore": {
"enabled": false,
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
@ -14,8 +15,7 @@
"firebase-debug.*.log",
"*.local"
],
"predeploy": [
]
"predeploy": []
}
],
"storage": {
@ -23,5 +23,20 @@
},
"remoteconfig": {
"template": "remoteconfig.template.json"
},
"emulators": {
"functions": {
"port": 5001
},
"firestore": {
"port": 8080
},
"storage": {
"port": 9199
},
"ui": {
"enabled": true
},
"singleProjectMode": true
}
}

View File

@ -0,0 +1,12 @@
{
"mailgun": {
"api_key": "a4540f0b68d40922a8ee203a44ec880c-623424ea-7d804677",
"server": "fitlien.cosqnet.com",
"from_address": "postmaster@fitlien.cosqnet.com"
},
"twilio": {
"account_sid": "AC5cfaae728ba68fb1aa6756d973b6e32b",
"auth_token": "886ed704c7918078f361f5f88b42ffc0",
"phone_number": "+12315005309"
}
}

View File

@ -8,6 +8,7 @@
"name": "functions",
"version": "0.0.0",
"dependencies": {
"dotenv": "^16.4.7",
"firebase-admin": "^12.6.0",
"firebase-functions": "^6.0.1",
"form-data": "^4.0.1",
@ -2341,6 +2342,17 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

View File

@ -15,6 +15,7 @@
},
"main": "lib/index.js",
"dependencies": {
"dotenv": "^16.4.7",
"firebase-admin": "^12.6.0",
"firebase-functions": "^6.0.1",
"form-data": "^4.0.1",

View File

@ -5,17 +5,23 @@ import { Message } from "firebase-admin/messaging";
import * as express from "express";
import * as logger from "firebase-functions/logger";
import { onDocumentCreated } from "firebase-functions/firestore";
// import * as dotenv from 'dotenv';
import * as functions from "firebase-functions";
// dotenv.config();
admin.initializeApp();
const formData = require('form-data');
const Mailgun = require('mailgun.js');
const { convert } = require('html-to-text');
const twilio = require('twilio')
export const sendEmailMessage = onRequest({
region: '#{SERVICES_RGN}#'
}, (request: Request, response: express.Response) => {
const mailgun = new Mailgun(formData);
const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY });
const mailGunClient = mailgun.client({ username: 'api', key: functions.config().MAILGUN_API_KEY });
const toAddress = request.body.toAddress;
const subject = request.body.subject;
@ -25,8 +31,8 @@ export const sendEmailMessage = onRequest({
};
const textMessage = convert(message, options);
mailGunClient.messages.create(process.env.MAILGUN_SERVER, {
from: process.env.MAILGUN_FROM_ADDRESS,
mailGunClient.messages.create(functions.config().MAILGUN_SERVER, {
from: functions.config().MAILGUN_FROM_ADDRESS,
to: toAddress,
subject: subject,
text: textMessage,
@ -43,12 +49,12 @@ export const sendEmailMessage = onRequest({
export const sendSMSMessage = onRequest({
region: '#{SERVICES_RGN}#'
}, (request: Request, response: express.Response) => {
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const client = twilio(functions.config().TWILIO_ACCOUNT_SID, functions.config().TWILIO_AUTH_TOKEN);
const { to, body } = request.body;
client.messages
.create({
body: body,
from: process.env.TWILIO_PHONE_NUMBER,
from: functions.config().TWILIO_PHONE_NUMBER,
to: to
})
.then((message: any) => {
@ -144,29 +150,35 @@ export const notifyTrainerUpdate = onRequest(
},
async (request: Request, response: express.Response) => {
try {
const { trainerId, section, trainerName, changedFields} = request.body;
const { trainerId, originalProfile,section,changedFields} = request.body;
if (!trainerId || !section || !trainerName || !changedFields) {
if (!trainerId || !originalProfile || !section || !changedFields) {
response.status(400).json({ success: false, error: 'Missing required parameters' });
return;
}
const trainerData = originalProfile;
const trainerName=trainerData["normalizedName"]
let changesTable = '<table border="1" cellpadding="5" style="border-collapse: collapse;">';
changesTable += '<tr><th>Field</th><th>New Value</th></tr>';
changesTable += '<tr><th>Field</th><th>Old Value</th><th>New Value</th></tr>';
if (changedFields && typeof changedFields === 'object') {
for (const [field, value] of Object.entries(changedFields)) {
for (const [field, newValue] of Object.entries(changedFields)) {
changesTable += '<tr>';
changesTable += `<td>${field}</td>`;
changesTable += `<td>${formatFieldName(field)}</td>`;
// Handle different types of values (strings, arrays, objects)
if (Array.isArray(value)) {
changesTable += `<td>${value.join(', ')}</td>`;
} else if (typeof value === 'object' && value !== null) {
changesTable += `<td>${JSON.stringify(value)}</td>`;
} else {
changesTable += `<td>${value}</td>`;
}
const oldValue = field.includes('.')
? field.split('.').reduce((obj, key) => obj && obj[key], trainerData)
: trainerData[field];
const formattedOldValue = formatValueForEmail(oldValue);
const formattedNewValue = formatValueForEmail(newValue);
changesTable += `<td>${formattedOldValue}</td>`;
changesTable += `<td>${formattedNewValue}</td>`;
changesTable += '</tr>';
}
@ -174,6 +186,21 @@ export const notifyTrainerUpdate = onRequest(
changesTable += '</table>';
const emailContent = `
<h2>Trainer Profile Update</h2>
<p>Your trainer ${trainerName} has updated their ${section}.</p>
<h3>Changes Made:</h3>
${changesTable}
`;
const mailgun = new Mailgun(formData);
const mailGunClient = mailgun.client({ username: 'api', key: functions.config().mailgun.api_key});
const options = { wordwrap: 130 };
const textMessage = convert(emailContent, options);
const memberships = await admin
.firestore()
.collection('memberships')
@ -219,56 +246,45 @@ export const notifyTrainerUpdate = onRequest(
const clientEmail = clientData.email;
if (fcmToken) {
const message : Message ={
notification: {
title: 'Trainer Profile Update',
body: `${trainerName} has updated their ${section}`,
},
data: {
type: 'trainer_profile_update',
trainerId: trainerId,
section: section,
trainerName: trainerName,
},
android: {
priority: 'high',
notification: {
channelId: 'trainer_updates_channel',
priority: 'high',
defaultSound: true,
defaultVibrateTimings: true,
icon: '@mipmap/ic_launcher',
clickAction: 'FLUTTER_NOTIFICATION_CLICK',
},
},
token: fcmToken,
};
// const message : Message ={
// notification: {
// title: 'Trainer Profile Update',
// body: `${trainerName} has updated their ${section}`,
// },
// data: {
// type: 'trainer_profile_update',
// trainerId: trainerId,
// section: section,
// trainerName: trainerName,
// },
// android: {
// priority: 'high',
// notification: {
// channelId: 'trainer_updates_channel',
// priority: 'high',
// defaultSound: true,
// defaultVibrateTimings: true,
// icon: '@mipmap/ic_launcher',
// clickAction: 'FLUTTER_NOTIFICATION_CLICK',
// },
// },
// token: fcmToken,
// };
const message = generateTrainerUpdateMessage(trainerId,section,trainerName,fcmToken);
notificationPromises.push(admin.messaging().send(message));
}
if (clientEmail) {
const emailContent = `
<h2>Trainer Profile Update</h2>
<p>Your trainer ${trainerName} has updated their ${section}.</p>
<h3>Changes Made:</h3>
${changesTable}
`;
// const emailPromise = mailGunClient.messages.create(functions.config().mailgun.server, {
// from: functions.config().mailgun.from_address,
// to: clientEmail,
// subject: `Your trainer ${trainerName} has updated their profile`,
// text: textMessage,
// html: emailContent
// });
const emailPromise = generateTrainerUpdateMail(mailGunClient,textMessage,emailContent,trainerName,clientEmail);
const mailgun = new Mailgun(formData);
const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY });
const options = { wordwrap: 130 };
const textMessage = convert(emailContent, options);
const emailPromise = mailGunClient.messages.create(process.env.MAILGUN_SERVER, {
from: process.env.MAILGUN_FROM_ADDRESS,
to: clientEmail,
subject: `Your trainer ${trainerName} has updated their profile`,
text: textMessage,
html: emailContent
});
emailPromises.push(emailPromise);
}
}
@ -293,55 +309,46 @@ export const notifyTrainerUpdate = onRequest(
const gymEmail = gymData.email;
if (fcmToken) {
const message :Message={
notification: {
title: 'Trainer Profile Update',
body: `${trainerName} has updated their ${section}`,
},
data: {
type: 'trainer_profile_update',
trainerId: trainerId,
section: section,
trainerName: trainerName,
},
android: {
priority: 'high',
notification: {
channelId: 'trainer_updates_channel',
priority: 'high',
defaultSound: true,
defaultVibrateTimings: true,
icon: '@mipmap/ic_launcher',
clickAction: 'FLUTTER_NOTIFICATION_CLICK',
},
},
token: fcmToken,
};
// const message :Message={
// notification: {
// title: 'Trainer Profile Update',
// body: `${trainerName} has updated their ${section}`,
// },
// data: {
// type: 'trainer_profile_update',
// trainerId: trainerId,
// section: section,
// trainerName: trainerName,
// },
// android: {
// priority: 'high',
// notification: {
// channelId: 'trainer_updates_channel',
// priority: 'high',
// defaultSound: true,
// defaultVibrateTimings: true,
// icon: '@mipmap/ic_launcher',
// clickAction: 'FLUTTER_NOTIFICATION_CLICK',
// },
// },
// token: fcmToken,
// };
const message = generateTrainerUpdateMessage(trainerId,section,trainerName,fcmToken);
notificationPromises.push(admin.messaging().send(message));
}
if (gymEmail) {
const emailContent = `
<h2>Trainer Profile Update</h2>
<p>Your trainer ${trainerName} has updated their ${section}.</p>
<h3>Changes Made:</h3>
${changesTable}
`;
// const emailPromise = mailGunClient.messages.create(functions.config().mailgun.server, {
// from: functions.config().mailgun.from_address,
// to: gymEmail,
// subject: `Your trainer ${trainerName} has updated their profile`,
// text: textMessage,
// html: emailContent
// });
const mailgun = new Mailgun(formData);
const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY });
const options = { wordwrap: 130 };
const textMessage = convert(emailContent, options);
const emailPromise = mailGunClient.messages.create(process.env.MAILGUN_SERVER, {
from: process.env.MAILGUN_FROM_ADDRESS,
to: gymEmail,
subject: `Your trainer ${trainerName} has updated their profile`,
text: textMessage,
html: emailContent
});
const emailPromise = generateTrainerUpdateMail(mailGunClient,textMessage,emailContent,trainerName,gymEmail);
emailPromises.push(emailPromise);
}
@ -361,3 +368,135 @@ export const notifyTrainerUpdate = onRequest(
}
}
);
function formatFieldName(field: string): string {
const formatted = field.replace(/([A-Z])/g, ' $1')
.replace(/^./, (str) => str.toUpperCase());
return formatted
.replace('Normalized ', '')
.replace('Work Experiences', 'Work Experience');
}
function formatValueForEmail(value: any): string {
if (value === undefined || value === null) {
return 'N/A';
}
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number' || typeof value === 'boolean') {
return value.toString();
}
if (value instanceof Date) {
return value.toLocaleDateString();
}
if (Array.isArray(value)) {
if (value.length > 0 && typeof value[0] === 'object') {
let table = '<table border="1" cellpadding="3" style="border-collapse: collapse; font-size: 0.9em;">';
if (value[0].hasOwnProperty('title') && value[0].hasOwnProperty('year')) {
table += '<tr><th>Title</th><th>Year</th></tr>';
value.forEach((item: any) => {
table += `<tr><td>${item.title}</td><td>${item.year}</td></tr>`;
});
} else if (value[0].hasOwnProperty('name') && value[0].hasOwnProperty('year')) {
table += '<tr><th>Name</th><th>Year</th><th>Validity</th><th>Country</th></tr>';
value.forEach((item: any) => {
table += `<tr><td>${item.name}</td><td>${item.year}</td><td>${item.validityYear}</td><td>${item.country}</td></tr>`;
});
} else if (value[0].hasOwnProperty('organization') && value[0].hasOwnProperty('role')) {
table += '<tr><th>Organization</th><th>Role</th><th>Duration</th></tr>';
value.forEach((item: any) => {
table += `<tr><td>${item.organization}</td><td>${item.role}</td><td>${item.duration}</td></tr>`;
});
} else {
const keys = Object.keys(value[0]);
table += '<tr>' + keys.map(k => `<th>${formatFieldName(k)}</th>`).join('') + '</tr>';
value.forEach((item: any) => {
table += '<tr>' + keys.map(k => `<td>${formatValueForEmail(item[k])}</td>`).join('') + '</tr>';
});
}
table += '</table>';
return table;
} else {
return value.join(', ');
}
}
if (typeof value === 'object') {
if (Object.keys(value).some(key => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].includes(key))) {
let scheduleTable = '<table border="1" cellpadding="3" style="border-collapse: collapse; font-size: 0.9em;">';
scheduleTable += '<tr><th>Day</th><th>Time Slots</th></tr>';
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
days.forEach(day => {
if (value[day] && Array.isArray(value[day])) {
const timeSlots = value[day].map((slot: string) => {
return slot.replace('TimeSlot.', '').replace(/^\w/, c => c.toUpperCase());
}).join(', ');
scheduleTable += `<tr><td>${day}</td><td>${timeSlots}</td></tr>`;
}
});
scheduleTable += '</table>';
return scheduleTable;
}
try {
return JSON.stringify(value, null, 2)
.replace(/[{}"]/g, '')
.replace(/,\n/g, '<br>')
.replace(/:/g, ': ');
} catch (e) {
return '[Complex Object]';
}
}
return String(value);
}
function generateTrainerUpdateMessage(trainerId: string, section: string, trainerName: string, fcmToken: string) : Message {
return {
notification: {
title: 'Trainer Profile Update',
body: `${trainerName} has updated their ${section}`,
},
data: {
type: 'trainer_profile_update',
trainerId: trainerId,
section: section,
trainerName: trainerName,
},
android: {
priority: 'high',
notification: {
channelId: 'trainer_updates_channel',
priority: 'high',
defaultSound: true,
defaultVibrateTimings: true,
icon: '@mipmap/ic_launcher',
clickAction: 'FLUTTER_NOTIFICATION_CLICK',
},
},
token: fcmToken,
};
}
function generateTrainerUpdateMail(mailGunClient:any,textMessage:any,emailContent:string,trainerName: string, clientEmail: string) : Promise<any> {
return mailGunClient.messages.create(functions.config().mailgun.server, {
from: functions.config().mailgun.from_address,
to: clientEmail,
subject: `Your trainer ${trainerName} has updated their profile`,
text: textMessage,
html: emailContent
});
}