fitlien-service-881 Removed the moved functions and the sendEmailSMS, sendSMSMessage

This commit is contained in:
Dhansh A S 2025-10-20 16:20:37 +05:30
parent a69ed7078a
commit b0aaab0e81
11 changed files with 0 additions and 533 deletions

View File

@ -1,6 +1,3 @@
TWILIO_ACCOUNT_SID=#{TWILIO_ACCOUNT_SID}#
TWILIO_AUTH_TOKEN=#{TWILIO_AUTH_TOKEN}#
TWILIO_PHONE_NUMBER=#{TWILIO_PHONE_NUMBER}#
SERVICES_RGN=#{SERVICES_RGN}# SERVICES_RGN=#{SERVICES_RGN}#
CASHFREE_CLIENT_ID=#{CASHFREE_CLIENT_ID}# CASHFREE_CLIENT_ID=#{CASHFREE_CLIENT_ID}#
CASHFREE_CLIENT_SECRET=#{CASHFREE_CLIENT_SECRET}# CASHFREE_CLIENT_SECRET=#{CASHFREE_CLIENT_SECRET}#

View File

@ -1 +0,0 @@
export { sendEmailSES } from './sendEmailSES';

View File

@ -1,209 +0,0 @@
import { getLogger } from "../shared/config";
import { getCorsHandler } from "../shared/middleware";
import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https";
import { Response } from "express";
import { SESClient } from "@aws-sdk/client-ses";
import { SendEmailCommand, SendRawEmailCommand } from "@aws-sdk/client-ses";
import { HttpsError } from "firebase-functions/v2/https";
import * as mime from 'mime-types';
import axios from 'axios';
const logger = getLogger();
const corsHandler = getCorsHandler();
interface EmailRequest {
to: string | string[];
subject: string;
html: string;
text?: string;
from: string;
replyTo?: string;
attachments?: Attachment[];
fileUrl?: string;
fileName?: string;
}
interface Attachment {
filename: string;
content: string | Buffer;
contentType?: string;
}
const stripHtml = (html: string): string => {
if (!html) return '';
return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
}
async function sendSimpleEmail(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
}
});
const command = new SendEmailCommand({
Source: data.from,
Destination: { ToAddresses: recipients },
Message: {
Subject: { Data: data.subject },
Body: {
Html: { Data: data.html },
Text: { Data: data.text || stripHtml(data.html) }
}
},
ReplyToAddresses: data.replyTo ? [data.replyTo] : undefined,
});
const result = await ses.send(command);
return { messageId: result.MessageId };
}
async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
}
});
const boundary = `boundary_${Math.random().toString(16).substr(2)}`;
let rawMessage = `From: ${data.from}\n`;
rawMessage += `To: ${recipients.join(', ')}\n`;
rawMessage += `Subject: ${data.subject}\n`;
rawMessage += `MIME-Version: 1.0\n`;
rawMessage += `Content-Type: multipart/mixed; boundary="${boundary}"\n\n`;
// Add email body (multipart/alternative)
rawMessage += `--${boundary}\n`;
rawMessage += `Content-Type: multipart/alternative; boundary="alt_${boundary}"\n\n`;
// Text part
if (data.text) {
rawMessage += `--alt_${boundary}\n`;
rawMessage += `Content-Type: text/plain; charset=UTF-8\n\n`;
rawMessage += `${data.text}\n\n`;
}
// HTML part
rawMessage += `--alt_${boundary}\n`;
rawMessage += `Content-Type: text/html; charset=UTF-8\n\n`;
rawMessage += `${data.html}\n\n`;
// Close alternative part
rawMessage += `--alt_${boundary}--\n\n`;
// Add attachments
for (const attachment of data.attachments || []) {
const contentType = attachment.contentType ||
mime.lookup(attachment.filename) ||
'application/octet-stream';
rawMessage += `--${boundary}\n`;
rawMessage += `Content-Type: ${contentType}; name="${attachment.filename}"\n`;
rawMessage += `Content-Disposition: attachment; filename="${attachment.filename}"\n`;
rawMessage += `Content-Transfer-Encoding: base64\n\n`;
const contentBuffer = typeof attachment.content === 'string'
? Buffer.from(attachment.content, 'base64')
: attachment.content;
rawMessage += contentBuffer.toString('base64') + '\n\n';
}
// Close message
rawMessage += `--${boundary}--`;
const command = new SendRawEmailCommand({
RawMessage: { Data: Buffer.from(rawMessage) }
});
const result = await ses.send(command);
return { messageId: result.MessageId };
}
async function downloadFileFromUrl(url: string): Promise<Buffer> {
try {
const response = await axios.get(url, { responseType: 'arraybuffer' });
return Buffer.from(response.data);
} catch (error) {
logger.error(`Error downloading file from URL: ${error}`);
throw new Error(`Failed to download file: ${error}`);
}
}
export const sendEmailSES = onRequest({
region: '#{SERVICES_RGN}#'
}, (request: Request, response: Response) => {
return corsHandler(request, response, async () => {
try {
const toAddress = request.body.toAddress;
const subject = request.body.subject;
const message = request.body.message;
// Initialize data with basic fields
const data: EmailRequest = {
to: toAddress,
html: message,
subject: subject,
text: stripHtml(message),
from: process.env.SES_FROM_EMAIL || 'support@fitlien.com',
replyTo: process.env.SES_REPLY_TO_EMAIL || 'support@fitlien.com',
attachments: request.body.attachments as Attachment[] || []
};
// Handle file URL if provided
if (request.body.fileUrl && request.body.fileName) {
logger.info(`Downloading attachment from URL: ${request.body.fileUrl}`);
try {
const fileContent = await downloadFileFromUrl(request.body.fileUrl);
// If attachments array doesn't exist, create it
if (!data.attachments) {
data.attachments = [];
}
// Add the downloaded file as an attachment
data.attachments.push({
filename: request.body.fileName,
content: fileContent,
contentType: mime.lookup(request.body.fileName) || 'application/octet-stream'
});
logger.info(`Successfully downloaded attachment: ${request.body.fileName}`);
} catch (downloadError) {
logger.error(`Failed to download attachment: ${downloadError}`);
throw new Error(`Failed to process attachment: ${downloadError}`);
}
}
if (!data.to || !data.subject || !data.html || !data.from) {
throw new HttpsError(
'invalid-argument',
'Missing required email fields'
);
}
logger.info(`Sending Email '${data.subject}' to '${data.to}' from '${data.from}'`);
const recipients = Array.isArray(data.to) ? data.to : [data.to];
if (data.attachments && data.attachments.length > 0) {
const messageResult = await sendEmailWithAttachments(data, recipients);
response.status(200).json(messageResult);
} else {
const messageResult = await sendSimpleEmail(data, recipients);
response.status(200).json(messageResult);
}
} catch (e) {
logger.error(`Error while sending E-mail. Error: ${e}`);
console.error(`Error while sending E-mail. Error: ${e}`);
response.status(500).json({
success: false,
error: 'Error while sending E-mail'
});
}
});
});

View File

@ -10,16 +10,12 @@ setGlobalOptions({
}); });
export * from "./shared/config"; export * from "./shared/config";
export { sendEmailSES } from "./email";
export { sendSMSMessage } from "./sms";
export { accessFile } from "./storage"; export { accessFile } from "./storage";
export { export {
processNotificationOnCreate, processNotificationOnCreate,
checkExpiredMemberships, checkExpiredMemberships,
} from "./notifications"; } from "./notifications";
export * from "./payments"; export * from "./payments";
export { getPlaceDetails, getPlacesAutocomplete } from "./places";
export { registerClient } from "./users";
export { export {
esslGetUserDetails, esslGetUserDetails,
esslUpdateUser, esslUpdateUser,

View File

@ -1,67 +0,0 @@
import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https";
import * as express from "express";
import { getLogger } from "../shared/config";
import { getCorsHandler } from "../shared/middleware";
import axios from "axios";
const logger = getLogger();
const corsHandler = getCorsHandler();
export const getPlacesAutocomplete = onRequest({
region: '#{SERVICES_RGN}#'
}, async (request: Request, response: express.Response) => {
return corsHandler(request, response, async () => {
try {
const { input, location, radius, types, components, sessiontoken } = request.query;
if (!input) {
response.status(400).json({
error: 'Input parameter is required for autocomplete'
});
return;
}
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
if (!apiKey) {
logger.error('Google Places API key is not configured');
response.status(500).json({ error: 'Server configuration error' });
return;
}
const url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json';
const params: any = {
key: apiKey,
input: input
};
if (location && radius) {
params.location = location;
params.radius = radius;
}
if (types) {
params.types = types;
}
if (components) {
params.components = components;
}
if (sessiontoken) {
params.sessiontoken = sessiontoken;
}
const result = await axios.get(url, { params });
logger.info('Google Places Autocomplete API request completed successfully');
response.json(result.data);
} catch (error) {
logger.error('Error fetching place autocomplete suggestions:', error);
response.status(500).json({
success: false,
error: error instanceof Error ? error.message : String(error)
});
}
});
});

View File

@ -1,51 +0,0 @@
import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https";
import * as express from "express";
import axios from "axios";
const { getCorsHandler } = require('../shared/middleware');
const corsHandler = getCorsHandler();
const { getLogger } = require('../shared/config');
const logger = getLogger();
export const getPlaceDetails = onRequest({
region: '#{SERVICES_RGN}#'
}, async (request: Request, response: express.Response) => {
return corsHandler(request, response, async () => {
try {
const { place_id, fields } = request.query;
if (!place_id) {
response.status(400).json({
error: 'place_id parameter is required'
});
return;
}
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
if (!apiKey) {
logger.error('Google Places API key is not configured');
response.status(500).json({ error: 'Server configuration error' });
return;
}
const url = 'https://maps.googleapis.com/maps/api/place/details/json';
const params: any = {
key: apiKey,
place_id: place_id,
fields: fields || 'geometry'
};
const result = await axios.get(url, { params });
logger.info('Google Places Details API request completed successfully');
response.json(result.data);
} catch (error) {
logger.error('Error fetching place details:', error);
response.status(500).json({
success: false,
error: error instanceof Error ? error.message : String(error)
});
}
});
});

View File

@ -1,2 +0,0 @@
export { getPlaceDetails } from './details';
export { getPlacesAutocomplete } from './autocomplete';

View File

@ -1 +0,0 @@
export { sendSMSMessage } from './sendSMS';

View File

@ -1,77 +0,0 @@
import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https";
import { getCorsHandler } from "../shared/middleware";
import { getLogger } from "../shared/config";
import twilio from 'twilio';
const corsHandler = getCorsHandler();
const logger = getLogger();
// Initialize Twilio client
const twilioClient = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
interface SMSRequest {
to: string;
body: string;
}
export const sendSMSMessage = onRequest({
region: '#{SERVICES_RGN}#'
}, async (request: Request, response) => {
return corsHandler(request, response, async () => {
try {
const { to, body } = request.body as SMSRequest;
// Input validation
if (!to || !body) {
logger.error('Missing required SMS parameters');
response.status(400).json({
success: false,
error: 'Both "to" and "body" parameters are required'
});
return;
}
// Validate phone number format (basic check)
if (!/^\+?[1-9]\d{1,14}$/.test(to)) {
logger.error('Invalid phone number format', { to });
response.status(400).json({
success: false,
error: 'Invalid phone number format'
});
return;
}
// Send SMS
const message = await twilioClient.messages.create({
body: body,
from: process.env.TWILIO_PHONE_NUMBER,
to: to
});
logger.info('SMS sent successfully', {
messageId: message.sid,
to: to,
length: body.length
});
response.json({
success: true,
messageId: message.sid,
timestamp: message.dateCreated
});
} catch (error: any) {
logger.error('Error sending SMS:', error);
const statusCode = error.status === 401 ? 401 : 500;
response.status(statusCode).json({
success: false,
error: error.message,
code: error.code,
moreInfo: error.moreInfo
});
}
});
});

View File

@ -1,117 +0,0 @@
import { onRequest } from "firebase-functions/v2/https";
import { getCorsHandler } from "../shared/middleware";
import { getAdmin, getLogger } from "../shared/config";
import { Request } from "firebase-functions/v2/https";
import { Response } from "express";
const corsHandler = getCorsHandler();
const admin = getAdmin();
const logger = getLogger();
export const registerClient = onRequest({
region: '#{SERVICES_RGN}#'
}, async (req: Request, res: Response) => {
return corsHandler(req, res, async () => {
try {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed. Please use POST.' });
}
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized. Missing or invalid authorization header.' });
}
const idToken = authHeader.split('Bearer ')[1];
try {
const decodedToken = await admin.auth().verifyIdToken(idToken);
const uid = decodedToken.uid;
const userDoc = await admin.firestore().collection('users').doc(uid).get();
if (!userDoc.exists) {
return res.status(403).json({ error: 'Forbidden. User not found.' });
}
const userData = userDoc.data();
if (!userData || !userData.roles || !userData.roles.includes('gym_owner')) {
return res.status(403).json({ error: 'Forbidden. Only gym owners can register clients.' });
}
const gymUser = req.body;
if (!gymUser.fields["phone-number"]) {
return res.status(400).json({ error: 'Phone number is required' });
}
let clientUid;
try {
const userRecord = await admin.auth().getUserByPhoneNumber(gymUser.fields["phone-number"])
.catch(() => null);
if (userRecord) {
clientUid = userRecord.uid;
} else {
const newUser = await admin.auth().createUser({
phoneNumber: gymUser.fields["phone-number"],
displayName: gymUser.fields["first-name"] || '',
});
clientUid = newUser.uid;
}
} catch (error) {
logger.error('Error creating authentication user:', error);
return res.status(500).json({
error: 'Failed to create authentication user',
details: error
});
}
try {
gymUser.uid = clientUid;
gymUser.registeredBy = uid;
if (gymUser.name) {
gymUser.normalizedName = gymUser.name.toLowerCase();
}
if (gymUser.dateOfBirth && !(typeof gymUser.dateOfBirth === 'string')) {
gymUser.dateOfBirth = new Date(gymUser.dateOfBirth).toISOString();
}
const clientData = {
...gymUser,
};
await admin.firestore().collection('client_profiles').doc(clientUid).set(clientData);
return res.status(201).json({
success: true,
message: 'Client registered successfully',
clientId: clientUid
});
} catch (error) {
logger.error('Error creating client profile:', error);
try {
if (!gymUser.uid) {
await admin.auth().deleteUser(clientUid);
}
} catch (deleteError) {
logger.error('Error deleting auth user after failed profile creation:', deleteError);
}
return res.status(500).json({
error: 'Failed to create client profile',
details: error
});
}
} catch (authError) {
logger.error('Authentication error:', authError);
return res.status(401).json({ error: 'Unauthorized. Invalid token.' });
}
} catch (error) {
logger.error('Unexpected error in client registration:', error);
return res.status(500).json({
error: 'Internal server error',
details: error
});
}
});
});

View File

@ -1 +0,0 @@
export { registerClient } from './clientRegistration';