|
|
|
|
@ -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,55 +246,44 @@ 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 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
|
|
|
|
|
});
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|