feature/add-client #21

Merged
benoybose merged 30 commits from feature/add-client into dev 2025-04-21 09:19:04 +00:00
7 changed files with 141 additions and 12 deletions

View File

@ -0,0 +1,121 @@
import { onRequest } from "firebase-functions/v2/https";
import { getCorsHandler } from "../shared/middleware";
import { getAdmin, getLogger } from "../shared/config";
const corsHandler = getCorsHandler();
const admin = getAdmin();
const logger = getLogger();
export const registerClient = onRequest({
region: '#{SERVICES_RGN}#'
}, async (req, res) => {
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.phoneNumber) {
return res.status(400).json({ error: 'Phone number is required' });
}
const isdCode = gymUser.isdCode || '91';
const formattedPhoneNumber = gymUser.phoneNumber.startsWith('+')
? gymUser.phoneNumber
: `${isdCode}${gymUser.phoneNumber}`;
let clientUid;
try {
const userRecord = await admin.auth().getUserByPhoneNumber(formattedPhoneNumber)
.catch(() => null);
if (userRecord) {
clientUid = userRecord.uid;
} else {
const newUser = await admin.auth().createUser({
phoneNumber: formattedPhoneNumber,
displayName: gymUser.name || '',
email: gymUser.email || null,
});
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,
phoneNumber: formattedPhoneNumber,
};
await admin.firestore().collection('client_profile').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

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

View File

@ -1,13 +1,14 @@
import { onRequest } from "firebase-functions/v2/https"; import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https"; import { Request } from "firebase-functions/v2/https";
import { corsHandler } from "../shared/middleware"; import { getCorsHandler } from "../shared/middleware";
import { logger } from "../shared/config"; import { getLogger } from "../shared/config";
import formData from 'form-data'; import formData from 'form-data';
import Mailgun from 'mailgun.js'; import Mailgun from 'mailgun.js';
const { convert } = require('html-to-text'); const { convert } = require('html-to-text');
const mailgun = new Mailgun(formData); const mailgun = new Mailgun(formData);
const logger = getLogger();
const corsHandler = getCorsHandler();
export const sendEmailMessage = onRequest({ export const sendEmailMessage = onRequest({
region: '#{SERVICES_RGN}#' region: '#{SERVICES_RGN}#'
}, (request: Request, response) => { }, (request: Request, response) => {

View File

@ -4,14 +4,15 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as https from 'https'; import * as https from 'https';
import { corsHandler } from "../shared/middleware"; import { getCorsHandler } from "../shared/middleware";
import { logger } from "../shared/config"; import { getLogger } from "../shared/config";
import formData from 'form-data'; import formData from 'form-data';
import Mailgun from 'mailgun.js'; import Mailgun from 'mailgun.js';
const { convert } = require('html-to-text'); const { convert } = require('html-to-text');
const mailgun = new Mailgun(formData); const mailgun = new Mailgun(formData);
const logger = getLogger();
const corsHandler = getCorsHandler();
export const sendEmailWithAttachment = onRequest({ export const sendEmailWithAttachment = onRequest({
region: '#{SERVICES_RGN}#' region: '#{SERVICES_RGN}#'
}, async (request: Request, response) => { }, async (request: Request, response) => {

View File

@ -5,3 +5,4 @@ export { sendSMSMessage } from './sms';
export { processNotificationOnCreate } from './notifications'; export { processNotificationOnCreate } from './notifications';
export { createCashfreeLink, createCashfreeOrder, verifyCashfreePayment } from './payments'; export { createCashfreeLink, createCashfreeOrder, verifyCashfreePayment } from './payments';
export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { getPlaceDetails, getPlacesAutocomplete } from './places';
export { registerClient } from './clientRegistration';

View File

@ -1,9 +1,11 @@
import { onRequest } from "firebase-functions/v2/https"; import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https"; import { Request } from "firebase-functions/v2/https";
import { corsHandler } from "../../shared/middleware"; import { getCorsHandler } from "../../shared/middleware";
import { admin, logger } from "../../shared/config"; import { getAdmin, getLogger } from "../../shared/config";
import axios from "axios"; import axios from "axios";
const admin = getAdmin();
const logger = getLogger();
const corsHandler = getCorsHandler();
interface CashfreeOrderRequest { interface CashfreeOrderRequest {
amount: number; amount: number;
customerName?: string; customerName?: string;

View File

@ -1,9 +1,11 @@
import { onRequest } from "firebase-functions/v2/https"; import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https"; import { Request } from "firebase-functions/v2/https";
import { corsHandler } from "../../shared/middleware"; import { getCorsHandler } from "../../shared/middleware";
import { admin, logger } from "../../shared/config"; import { getAdmin, getLogger } from "../../shared/config";
import axios from "axios"; import axios from "axios";
const logger = getLogger();
const corsHandler = getCorsHandler();
const admin = getAdmin();
interface CashfreePaymentResponse { interface CashfreePaymentResponse {
order_status: string; order_status: string;
[key: string]: any; [key: string]: any;