Compare commits

...

3 Commits

Author SHA1 Message Date
04077d95f4 Adding pipeline 2025-03-13 13:13:35 +05:30
cff02bcc03 Merge pull request 'sendSMS function completed' (#1) from feature/fitlien-services-344 into dev
Reviewed-on: #1
Reviewed-by: Benoy Bose <benoybose@cosq.net>
2025-01-02 18:38:45 +00:00
Jipson George
c92befa9a4 sendSMS function completed 2024-12-31 13:27:25 +05:30
8 changed files with 2841 additions and 13 deletions

View File

@ -0,0 +1,83 @@
trigger:
- master
pool:
vmImage: "ubuntu-latest"
variables:
major: $(VERSION_MAJOR)
minor: $(VERSION_MINOR)
prefix: $[format('{0}.{1}', variables['major'], variables['minor'])]
patch: $[counter(variables['prefix'], 100)]
buildNumber: $(major).$(minor).$(patch)
steps:
- task: PowerShell@2
displayName: "Setting build version"
inputs:
targetType: "inline"
script: |
Write-Host "##vso[build.updatebuildnumber]${{ parameters.buildNumber }}"
- task: NodeTool@0
displayName: "Install Node"
inputs:
version: "20"
- task: Npm@1
displayName: "npm install"
inputs:
command: "install"
- task: PowerShell@2
displayName: "Set version in package.json"
inputs:
targetType: "inline"
script: |
$pkg = Get-Content -Path "$(System.DefaultWorkingDirectory)/functions/package.json" -Raw | ConvertFrom-Json
$pkg.version = "${{ parameters.buildNumber }}"
$pkg | ConvertTo-Json -Depth 100 | Set-Content -Path "$(System.DefaultWorkingDirectory)/functions/package.json"
- task: CmdLine@2
displayName: "Copy .env.example to .env"
inputs:
script: |
cp "$(System.DefaultWorkingDirectory)/functions/.env.example" "$(System.DefaultWorkingDirectory)/functions/.env"
- task: ReplaceTokens@3
displayName: "Replace tokens in .env file"
inputs:
targetFiles: "$(System.DefaultWorkingDirectory)/functions/.env"
tokenPrefix: "#{"
tokenSuffix: "}#"
- task: Npm@1
displayName: "npm run build"
inputs:
command: "custom"
workingDir: "$(System.DefaultWorkingDirectory)/functions"
customCommand: "run build"
- task: DeleteFiles@1
displayName: "Remove node_modules, *.log files, src directory from functions directory"
inputs:
SourceFolder: "$(System.DefaultWorkingDirectory)/functions"
Contents: |
node_modules/**
*.log
src/**
- task: ArchiveFiles@2
displayName: "Archive functions directory"
inputs:
rootFolderOrFile: "$(System.DefaultWorkingDirectory)/functions"
includeRootFolder: false
archiveFile: "$(System.DefaultWorkingDirectory)/fitlien-services-$(buildNumber).zip"
compression: "zip"
- task: CopyFiles@2
displayName: "Copy archive to staging directory"
inputs:
SourceFolder: "$(System.DefaultWorkingDirectory)"
Contents: "fitlien-services-$(buildNumber).zip"
TargetFolder: "$(System.ArtifactsDirectory)"

6
functions/.env.example Normal file
View File

@ -0,0 +1,6 @@
MAILGUN_API_KEY=#{MAILGUN_API_KEY}#
MAILGUN_SERVER=#{MAILGUN_SERVER}#
MAILGUN_FROM_ADDRESS=#{MAILGUN_FROM_ADDRESS}#
TWILIO_ACCOUNT_SID=AC5cfaae728ba68fb1aa6756d973b6e32b
TWILIO_AUTH_TOKEN=886ed704c7918078f361f5f88b42ffc0
TWILIO_PHONE_NUMBER=+12315005309

View File

@ -1,16 +1,20 @@
{ {
"name": "functions", "name": "functions",
"version": "0.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "functions", "name": "functions",
"version": "0.0.0",
"dependencies": { "dependencies": {
"firebase-admin": "^12.6.0", "firebase-admin": "^12.6.0",
"firebase-functions": "^6.0.1", "firebase-functions": "^6.0.1",
"form-data": "^4.0.1", "form-data": "^4.0.1",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"mailgun.js": "^10.3.0" "long": "4.0.0",
"mailgun.js": "^10.4.0",
"twilio": "^5.4.0"
}, },
"devDependencies": { "devDependencies": {
"firebase-functions-test": "^3.1.0", "firebase-functions-test": "^3.1.0",
@ -764,6 +768,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@grpc/proto-loader/node_modules/long": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
"integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==",
"optional": true
},
"node_modules/@istanbuljs/load-nyc-config": { "node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@ -2192,6 +2202,11 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
},
"node_modules/debug": { "node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -2737,9 +2752,9 @@
} }
}, },
"node_modules/firebase-functions": { "node_modules/firebase-functions": {
"version": "6.2.0", "version": "6.3.2",
"resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.2.0.tgz", "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.3.2.tgz",
"integrity": "sha512-vfyyVHS8elxplzEQ9To+NaINRPFUsDasQrasTa2eFJBYSPzdhkw6rwLmvwyYw622+ze+g4sDIb14VZym+afqXQ==", "integrity": "sha512-FC3A1/nhqt1ZzxRnj5HZLScQaozAcFSD/vSR8khqSoFNOfxuXgwJS6ZABTB7+v+iMD5z6Mmxw6OfqITUBuI7OQ==",
"dependencies": { "dependencies": {
"@types/cors": "^2.8.5", "@types/cors": "^2.8.5",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
@ -4419,9 +4434,9 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
}, },
"node_modules/long": { "node_modules/long": {
"version": "5.2.3", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
}, },
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
@ -4459,9 +4474,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}, },
"node_modules/mailgun.js": { "node_modules/mailgun.js": {
"version": "10.3.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-10.3.0.tgz", "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-10.4.0.tgz",
"integrity": "sha512-HJPmninRDGlzs8izNyfM/hbvefz6ol1gqeZ+doiumEHli7kGCrLlK6hURsq6oLjDoTNwgS37CUDhBPQy7x5PeQ==", "integrity": "sha512-YrdaZEAJwwjXGBTfZTNQ1LM7tmkdUaz2NpZEu7+zULcG4Wrlhd7cWSNZW0bxT3bP48k5N0mZWz8C2f9gc2+Geg==",
"dependencies": { "dependencies": {
"axios": "^1.7.4", "axios": "^1.7.4",
"base-64": "^1.0.0", "base-64": "^1.0.0",
@ -5029,6 +5044,11 @@
"node": ">=12.0.0" "node": ">=12.0.0"
} }
}, },
"node_modules/protobufjs/node_modules/long": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
"integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng=="
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -5230,6 +5250,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/scmp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
"integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="
},
"node_modules/selderee": { "node_modules/selderee": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
@ -5753,6 +5778,67 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
}, },
"node_modules/twilio": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/twilio/-/twilio-5.4.0.tgz",
"integrity": "sha512-kEmxzdOLTzXzUEXIkBVwT1Itxlbp+rtGrQogNfPtSE3EjoEsxrxB/9tdMIEbrsioL8CzTk/+fiKNJekAyHxjuQ==",
"dependencies": {
"axios": "^1.7.4",
"dayjs": "^1.11.9",
"https-proxy-agent": "^5.0.0",
"jsonwebtoken": "^9.0.2",
"qs": "^6.9.4",
"scmp": "^2.1.0",
"xmlbuilder": "^13.0.2"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/twilio/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/twilio/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/twilio/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/twilio/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/type-detect": { "node_modules/type-detect": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -5999,6 +6085,14 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/xmlbuilder": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz",
"integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==",
"engines": {
"node": ">=6.0"
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -1,5 +1,6 @@
{ {
"name": "functions", "name": "functions",
"version": "0.0.0",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"build:watch": "tsc --watch", "build:watch": "tsc --watch",
@ -18,7 +19,9 @@
"firebase-functions": "^6.0.1", "firebase-functions": "^6.0.1",
"form-data": "^4.0.1", "form-data": "^4.0.1",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"mailgun.js": "^10.3.0" "long": "4.0.0",
"mailgun.js": "^10.4.0",
"twilio": "^5.4.0"
}, },
"devDependencies": { "devDependencies": {
"firebase-functions-test": "^3.1.0", "firebase-functions-test": "^3.1.0",

View File

@ -1,10 +1,16 @@
import { onRequest } from "firebase-functions/v2/https"; import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https";
import { onDocumentCreated } from 'firebase-functions/v2/firestore';
import * as admin from 'firebase-admin';
import * as express from "express";
import * as logger from "firebase-functions/logger"; import * as logger from "firebase-functions/logger";
const formData = require('form-data'); const formData = require('form-data');
const Mailgun = require('mailgun.js'); const Mailgun = require('mailgun.js');
const { convert } = require('html-to-text'); const { convert } = require('html-to-text');
const twilio = require('twilio')
export const sendEmail = onRequest((request, response) => { export const sendEmail = onRequest((request: Request, response: express.Response) => {
const mailgun = new Mailgun(formData); const mailgun = new Mailgun(formData);
const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY });
@ -30,3 +36,100 @@ export const sendEmail = onRequest((request, response) => {
response.send(err); response.send(err);
}); });
}); });
export const sendSMS = onRequest((request: Request, response: express.Response) => {
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const { to, body } = request.body;
client.messages
.create({
body: body,
from: process.env.TWILIO_PHONE_NUMBER,
to: to
})
.then((message: any) => {
logger.info('SMS sent successfully:', message.sid);
response.json({ success: true, messageId: message.sid });
})
.catch((error: any) => {
logger.error('Error sending SMS:', error);
response.status(500).json({ success: false, error: error.message });
});
});
interface Invitation {
email: string;
phoneNumber: string;
gymName: string;
invitedByName: string;
}
export const sendInvitationNotification = onDocumentCreated(
'invitations/{invitationId}',
async (event) => {
const invitation = event.data?.data() as Invitation;
const invitationId = event.params.invitationId;
if (!invitation) {
console.error('Invitation data is missing.');
return null;
}
try {
const userQuery = await admin
.firestore()
.collection('users')
.where('email', '==', invitation.email)
.where('phoneNumber', '==', invitation.phoneNumber)
.limit(1)
.get();
if (userQuery.empty) {
console.log(
`User not found for email: ${invitation.email} and phone: ${invitation.phoneNumber}.`
);
return null;
}
const user = userQuery.docs[0].data();
const fcmToken = user.fcmToken;
if (!fcmToken) {
console.log(`FCM token not found for user: ${invitation.email}.`);
return null;
}
const message: admin.messaging.Message = {
notification: {
title: 'New Gym Invitation',
body: `${invitation.invitedByName} has invited you to join ${invitation.gymName}`,
},
data: {
type: 'invitation',
invitationId: invitationId,
gymName: invitation.gymName,
senderName: invitation.invitedByName,
},
android: {
priority: 'high',
notification: {
channelId: 'invitations_channel',
priority: 'high',
defaultSound: true,
defaultVibrateTimings: true,
icon: '@mipmap/ic_launcher',
clickAction: 'FLUTTER_NOTIFICATION_CLICK',
},
},
token: fcmToken,
};
await admin.messaging().send(message);
console.log(`Invitation notification sent to ${invitation.email}.`);
return null;
} catch (error) {
console.error('Error sending invitation notification:', error);
return null;
}
}
);

View File

@ -2,7 +2,8 @@
"compilerOptions": { "compilerOptions": {
"module": "NodeNext", "module": "NodeNext",
"esModuleInterop": true, "esModuleInterop": true,
"moduleResolution": "nodenext", "allowSyntheticDefaultImports": true,
"moduleResolution": "node16",
"noImplicitReturns": true, "noImplicitReturns": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"outDir": "lib", "outDir": "lib",

2530
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"devDependencies": {
"@types/node": "^22.10.2"
},
"dependencies": {
"firebase-functions": "^6.2.0"
}
}