diff --git a/.firebaserc b/.firebaserc
index 008864b..7a602e8 100644
--- a/.firebaserc
+++ b/.firebaserc
@@ -1,6 +1,7 @@
{
"projects": {
"debug": "fitlien-dev",
- "release": "fitlien"
+ "release": "fitlien",
+ "default": "fitlien-dev"
}
}
diff --git a/firebase.json b/firebase.json
index da6f4cb..301c4ec 100644
--- a/firebase.json
+++ b/firebase.json
@@ -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
}
}
diff --git a/functions/.runtimeconfig.json b/functions/.runtimeconfig.json
new file mode 100644
index 0000000..d87d508
--- /dev/null
+++ b/functions/.runtimeconfig.json
@@ -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"
+ }
+}
diff --git a/functions/package-lock.json b/functions/package-lock.json
index a2c4591..2cd1512 100644
--- a/functions/package-lock.json
+++ b/functions/package-lock.json
@@ -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",
diff --git a/functions/package.json b/functions/package.json
index d7c10c4..041fb56 100644
--- a/functions/package.json
+++ b/functions/package.json
@@ -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",
diff --git a/functions/src/index.ts b/functions/src/index.ts
index 6945fb9..fa69917 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -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 = '
';
- changesTable += '| Field | New Value |
';
+ changesTable += '| Field | Old Value | New Value |
';
if (changedFields && typeof changedFields === 'object') {
- for (const [field, value] of Object.entries(changedFields)) {
+ for (const [field, newValue] of Object.entries(changedFields)) {
changesTable += '';
- changesTable += `| ${field} | `;
+ changesTable += `${formatFieldName(field)} | `;
- // Handle different types of values (strings, arrays, objects)
- if (Array.isArray(value)) {
- changesTable += `${value.join(', ')} | `;
- } else if (typeof value === 'object' && value !== null) {
- changesTable += `${JSON.stringify(value)} | `;
- } else {
- changesTable += `${value} | `;
- }
+ const oldValue = field.includes('.')
+ ? field.split('.').reduce((obj, key) => obj && obj[key], trainerData)
+ : trainerData[field];
+
+ const formattedOldValue = formatValueForEmail(oldValue);
+ const formattedNewValue = formatValueForEmail(newValue);
+
+ changesTable += `${formattedOldValue} | `;
+ changesTable += `${formattedNewValue} | `;
changesTable += '
';
}
@@ -174,6 +186,21 @@ export const notifyTrainerUpdate = onRequest(
changesTable += '
';
+ const emailContent = `
+ Trainer Profile Update
+ Your trainer ${trainerName} has updated their ${section}.
+ Changes Made:
+ ${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 = `
- Trainer Profile Update
- Your trainer ${trainerName} has updated their ${section}.
- Changes Made:
- ${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 = `
- Trainer Profile Update
- Your trainer ${trainerName} has updated their ${section}.
- Changes Made:
- ${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 = '';
+
+ if (value[0].hasOwnProperty('title') && value[0].hasOwnProperty('year')) {
+ table += '| Title | Year |
';
+ value.forEach((item: any) => {
+ table += `| ${item.title} | ${item.year} |
`;
+ });
+ } else if (value[0].hasOwnProperty('name') && value[0].hasOwnProperty('year')) {
+ table += '| Name | Year | Validity | Country |
';
+ value.forEach((item: any) => {
+ table += `| ${item.name} | ${item.year} | ${item.validityYear} | ${item.country} |
`;
+ });
+ } else if (value[0].hasOwnProperty('organization') && value[0].hasOwnProperty('role')) {
+ table += '| Organization | Role | Duration |
';
+ value.forEach((item: any) => {
+ table += `| ${item.organization} | ${item.role} | ${item.duration} |
`;
+ });
+ } else {
+ const keys = Object.keys(value[0]);
+ table += '' + keys.map(k => `| ${formatFieldName(k)} | `).join('') + '
';
+ value.forEach((item: any) => {
+ table += '' + keys.map(k => `| ${formatValueForEmail(item[k])} | `).join('') + '
';
+ });
+ }
+
+ 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 = '';
+ scheduleTable += '| Day | Time Slots |
';
+
+ 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 += `| ${day} | ${timeSlots} |
`;
+ }
+ });
+
+ scheduleTable += '
';
+ return scheduleTable;
+ }
+
+ try {
+ return JSON.stringify(value, null, 2)
+ .replace(/[{}"]/g, '')
+ .replace(/,\n/g, '
')
+ .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 {
+ 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
+ });
+
+}
\ No newline at end of file