Merge branch 'dev' into qa
This commit is contained in:
		
						commit
						a245ec7d7b
					
				| @ -26,7 +26,7 @@ | |||||||
|       "port": 5001 |       "port": 5001 | ||||||
|     }, |     }, | ||||||
|     "firestore": { |     "firestore": { | ||||||
|       "port": 8084 |       "port": 8079 | ||||||
|     }, |     }, | ||||||
|     "storage": { |     "storage": { | ||||||
|       "port": 9199 |       "port": 9199 | ||||||
|  | |||||||
| @ -7,4 +7,5 @@ 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}# | ||||||
| 
 | GOOGLE_MAPS_API_KEY=#{GOOGLE_MAPS_API_KEY}# | ||||||
|  | FITLIENHOST=#{FITLIENHOST}# | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ import { Request } from "firebase-functions/v2/https"; | |||||||
| import * as admin from 'firebase-admin'; | import * as admin from 'firebase-admin'; | ||||||
| import * as express from "express"; | import * as express from "express"; | ||||||
| import * as logger from "firebase-functions/logger"; | import * as logger from "firebase-functions/logger"; | ||||||
| import { onDocumentCreated } from "firebase-functions/firestore"; |  | ||||||
| import * as os from 'os'; | 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'; | ||||||
| @ -11,6 +10,7 @@ import * as https from 'https'; | |||||||
| import cors from 'cors'; | import cors from 'cors'; | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| import { getStorage } from 'firebase-admin/storage'; | import { getStorage } from 'firebase-admin/storage'; | ||||||
|  | import { onDocumentCreated } from "firebase-functions/firestore"; | ||||||
| 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'); | ||||||
| @ -180,65 +180,146 @@ export const sendSMSMessage = onRequest({ | |||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| interface Invitation { | export const processNotificationOnCreate = onDocumentCreated({ | ||||||
|   email: string; |   region: '#{SERVICES_RGN}#', | ||||||
|   phoneNumber: string; |   document: 'notifications/{notificationId}' | ||||||
|   gymName: string; | }, async (event) => { | ||||||
|   invitedByName: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const notifyInvitation = onDocumentCreated({ |  | ||||||
|   document: 'notifications/{notificationId}', |  | ||||||
|   region: '#{SERVICES_RGN}#' |  | ||||||
| }, async (event: any) => { |  | ||||||
| 
 |  | ||||||
|   const invitation = event.data?.data() as Invitation; |  | ||||||
|   const invitationId = event.params.invitationId; |  | ||||||
| 
 |  | ||||||
|   if (!invitation) { |  | ||||||
|     console.error('Invitation data is missing.'); |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   try { |   try { | ||||||
|  |     const notification = event.data?.data(); | ||||||
|  |     const notificationId = event.params.notificationId; | ||||||
|  | 
 | ||||||
|  |     if (!notification) { | ||||||
|  |       logger.error(`No data found for notification ${notificationId}`); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (notification.notificationSent === true) { | ||||||
|  |       logger.info(`Notification ${notificationId} already sent, skipping.`); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let userId = null; | ||||||
|  |     let fcmToken = null; | ||||||
|  | 
 | ||||||
|  |     if (notification.userId) { | ||||||
|  |       userId = notification.userId; | ||||||
|  |       const userDoc = await admin.firestore().collection('users').doc(userId).get(); | ||||||
|  |       if (userDoc.exists) { | ||||||
|  |         fcmToken = userDoc.data()?.fcmToken; | ||||||
|  |       } | ||||||
|  |     } else if (notification.clientId) { | ||||||
|  |       userId = notification.clientId; | ||||||
|  |       const userDoc = await admin.firestore().collection('users').doc(userId).get(); | ||||||
|  |       if (userDoc.exists) { | ||||||
|  |         fcmToken = userDoc.data()?.fcmToken; | ||||||
|  |       } | ||||||
|  |     } else if (notification.invitorId) { | ||||||
|  |       userId = notification.invitorId; | ||||||
|  |       const userDoc = await admin.firestore().collection('users').doc(userId).get(); | ||||||
|  |       if (userDoc.exists) { | ||||||
|  |         fcmToken = userDoc.data()?.fcmToken; | ||||||
|  |       } | ||||||
|  |     } else if (notification.phoneNumber) { | ||||||
|       const userQuery = await admin |       const userQuery = await admin | ||||||
|         .firestore() |         .firestore() | ||||||
|         .collection('users') |         .collection('users') | ||||||
|       .where('email', '==', invitation.email) |         .where('phoneNumber', '==', notification.phoneNumber) | ||||||
|       .where('phoneNumber', '==', invitation.phoneNumber) |  | ||||||
|         .limit(1) |         .limit(1) | ||||||
|         .get(); |         .get(); | ||||||
| 
 | 
 | ||||||
|     if (userQuery.empty) { |       if (!userQuery.empty) { | ||||||
|       console.log( |         const userDoc = userQuery.docs[0]; | ||||||
|         `User not found for email: ${invitation.email} and phone: ${invitation.phoneNumber}.` |         userId = userDoc.id; | ||||||
|       ); |         fcmToken = userDoc.data()?.fcmToken; | ||||||
|       return null; |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const user = userQuery.docs[0].data(); |  | ||||||
|     const fcmToken = user.fcmToken; |  | ||||||
| 
 |  | ||||||
|     if (!fcmToken) { |     if (!fcmToken) { | ||||||
|       console.log(`FCM token not found for user: ${invitation.email}.`); |       logger.error(`FCM token not found for notification ${notificationId}`); | ||||||
|       return null; |       await admin.firestore().collection('notifications').doc(notificationId).update({ | ||||||
|  |         notificationError: 'FCM token not found for user', | ||||||
|  |         updatedAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() | ||||||
|  |       }); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let title = 'New Notification'; | ||||||
|  |     let body = notification.message || 'You have a new notification'; | ||||||
|  |     let data: Record<string, string> = { | ||||||
|  |       type: notification.type, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     switch (notification.type) { | ||||||
|  |       case 'day_pass_entry': | ||||||
|  |         const isAccepted = notification.status === 'ACCEPTED'; | ||||||
|  |         title = isAccepted ? 'Day Pass Approved' : 'Day Pass Denied'; | ||||||
|  |         body = notification.message || (isAccepted ? | ||||||
|  |           `Your day pass has been approved` : | ||||||
|  |           `Your day pass has been denied`); | ||||||
|  |         data.gymName = notification.gymName || ''; | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |       case 'trainer_assigned_to_client': | ||||||
|  |         title = 'Trainer Assigned'; | ||||||
|  |         body = notification.message || `${notification.trainerName} has been assigned as your trainer`; | ||||||
|  |         data.trainerName = notification.trainerName || ''; | ||||||
|  |         data.membershipId = notification.membershipId || ''; | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |       case 'client_invitations': | ||||||
|  |         if (notification.userId || notification.invitorId) { | ||||||
|  |           const isAccept = notification.status === 'ACCEPTED'; | ||||||
|  |           title = isAccept ? 'Invitation Accepted' : 'Invitation Rejected'; | ||||||
|  |           body = notification.message || (isAccept ? | ||||||
|  |             `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been accepted` : | ||||||
|  |             `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been rejected`); | ||||||
|  |         } else if (notification.phoneNumber) { | ||||||
|  |           let invitationStatus; | ||||||
|  |           if (notification.status === 'ACCEPTED') { | ||||||
|  |             invitationStatus = 'accepted'; | ||||||
|  |             title = 'Invitation Accepted'; | ||||||
|  |             body = notification.message || | ||||||
|  |               `You have accepted the invitation from ${notification.name}`; | ||||||
|  |           } else if (notification.status === 'REJECTED') { | ||||||
|  |             invitationStatus = 'rejected'; | ||||||
|  |             title = 'Invitation Rejected'; | ||||||
|  |             body = notification.message || | ||||||
|  |               `You have rejected the invitation from ${notification.name}`; | ||||||
|  |           } else if (notification.status === 'PENDING') { | ||||||
|  |             invitationStatus = 'pending'; | ||||||
|  |             title = 'New Invitation'; | ||||||
|  |             body = notification.message || | ||||||
|  |               `You have a new invitation pending from ${notification.name}`; | ||||||
|  |           } else { | ||||||
|  |             invitationStatus = 'unknown'; | ||||||
|  |             title = 'Invitation Update'; | ||||||
|  |             body = notification.message || 'There is an update to your invitation'; | ||||||
|  |           } | ||||||
|  |           data.status = invitationStatus; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         data.gymName = notification.gymName || ''; | ||||||
|  |         data.clientEmail = notification.clientEmail || ''; | ||||||
|  |         data.clientName = notification.name || ''; | ||||||
|  |         data.invitationId = notification.invitationId || ''; | ||||||
|  |         data.subscriptionName = notification.subscriptionName || ''; | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |       default: | ||||||
|  |         logger.info(`Using default handling for notification type: ${notification.type}`); | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const message: admin.messaging.Message = { |     const message: admin.messaging.Message = { | ||||||
|       notification: { |       notification: { | ||||||
|         title: 'New Gym Invitation', |         title: title, | ||||||
|         body: `${invitation.invitedByName} has invited you to join ${invitation.gymName}`, |         body: body, | ||||||
|       }, |  | ||||||
|       data: { |  | ||||||
|         type: 'invitation', |  | ||||||
|         invitationId: invitationId, |  | ||||||
|         gymName: invitation.gymName, |  | ||||||
|         senderName: invitation.invitedByName, |  | ||||||
|       }, |       }, | ||||||
|  |       data: data, | ||||||
|       android: { |       android: { | ||||||
|         priority: 'high', |         priority: 'high', | ||||||
|         notification: { |         notification: { | ||||||
|           channelId: 'invitations_channel', |           channelId: 'notifications_channel', | ||||||
|           priority: 'high', |           priority: 'high', | ||||||
|           defaultSound: true, |           defaultSound: true, | ||||||
|           defaultVibrateTimings: true, |           defaultVibrateTimings: true, | ||||||
| @ -246,15 +327,35 @@ export const notifyInvitation = onDocumentCreated({ | |||||||
|           clickAction: 'FLUTTER_NOTIFICATION_CLICK', |           clickAction: 'FLUTTER_NOTIFICATION_CLICK', | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|  |       apns: { | ||||||
|  |         payload: { | ||||||
|  |           aps: { | ||||||
|  |             sound: 'default', | ||||||
|  |             badge: 1, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|       token: fcmToken, |       token: fcmToken, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     await admin.messaging().send(message); |     try { | ||||||
|     console.log(`Invitation notification sent to ${invitation.email}.`); |       const fcmResponse = await admin.messaging().send(message); | ||||||
|     return null; |       logger.info(`FCM notification sent successfully: ${fcmResponse}`); | ||||||
|  | 
 | ||||||
|  |       await admin.firestore().collection('notifications').doc(notificationId).update({ | ||||||
|  |         notificationSent: true, | ||||||
|  |         sentAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() | ||||||
|  |       }); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|     console.error('Error sending invitation notification:', error); |       logger.error(`Error sending notification ${notificationId}:`, error); | ||||||
|     return null; | 
 | ||||||
|  |       await admin.firestore().collection('notifications').doc(notificationId).update({ | ||||||
|  |         notificationError: error instanceof Error ? error.message : String(error), | ||||||
|  |         updatedAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     logger.error('Error processing notification:', error); | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -271,6 +372,7 @@ export const createCashfreeOrder = onRequest({ | |||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const idToken = authHeader.split('Bearer ')[1]; |       const idToken = authHeader.split('Bearer ')[1]; | ||||||
|  |       try { | ||||||
|         const decodedToken = await admin.auth().verifyIdToken(idToken); |         const decodedToken = await admin.auth().verifyIdToken(idToken); | ||||||
|         const uid = decodedToken.uid; |         const uid = decodedToken.uid; | ||||||
| 
 | 
 | ||||||
| @ -279,7 +381,10 @@ export const createCashfreeOrder = onRequest({ | |||||||
|           customerName, |           customerName, | ||||||
|           customerEmail, |           customerEmail, | ||||||
|           customerPhone, |           customerPhone, | ||||||
|         productInfo |           productInfo, | ||||||
|  |           userId, | ||||||
|  |           gymId, | ||||||
|  |           orderId | ||||||
|         } = request.body; |         } = request.body; | ||||||
| 
 | 
 | ||||||
|         if (!amount || !customerEmail || !customerPhone) { |         if (!amount || !customerEmail || !customerPhone) { | ||||||
| @ -289,18 +394,25 @@ export const createCashfreeOrder = onRequest({ | |||||||
| 
 | 
 | ||||||
|         const clientId = process.env.CASHFREE_CLIENT_ID; |         const clientId = process.env.CASHFREE_CLIENT_ID; | ||||||
|         const clientSecret = process.env.CASHFREE_CLIENT_SECRET; |         const clientSecret = process.env.CASHFREE_CLIENT_SECRET; | ||||||
|       const isTest = true; |  | ||||||
| 
 | 
 | ||||||
|  |         if (!clientId || !clientSecret) { | ||||||
|  |           logger.error('Cashfree credentials not configured'); | ||||||
|  |           response.status(500).json({ error: 'Payment gateway configuration error' }); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const isTest = true; | ||||||
|  |         const hashKey = `hash_${Date.now()}_${uid.substring(0, 1)}_${orderId}`; | ||||||
|         const apiUrl = isTest |         const apiUrl = isTest | ||||||
|           ? 'https://sandbox.cashfree.com/pg/orders' |           ? 'https://sandbox.cashfree.com/pg/orders' | ||||||
|           : 'https://api.cashfree.com/pg/orders'; |           : 'https://api.cashfree.com/pg/orders'; | ||||||
| 
 | 
 | ||||||
|       const orderId = `order_${Date.now()}_${uid.substring(0, 6)}`; |         try { | ||||||
| 
 |  | ||||||
|           const cashfreeResponse = await axios.post( |           const cashfreeResponse = await axios.post( | ||||||
|             apiUrl, |             apiUrl, | ||||||
|             { |             { | ||||||
|               order_id: orderId, |               order_id: orderId, | ||||||
|  |               hash_key: hashKey, | ||||||
|               order_amount: amount, |               order_amount: amount, | ||||||
|               order_currency: 'INR', |               order_currency: 'INR', | ||||||
|               customer_details: { |               customer_details: { | ||||||
| @ -310,7 +422,7 @@ export const createCashfreeOrder = onRequest({ | |||||||
|                 customer_phone: customerPhone |                 customer_phone: customerPhone | ||||||
|               }, |               }, | ||||||
|               order_meta: { |               order_meta: { | ||||||
|             return_url: `https://fitlien.com/payment/status?order_id={order_id}`, |                 return_url: `https://${process.env.FITLIENHOST}/payment-bridge?order_id=${orderId}&hash_key=${hashKey}&user_id=${userId}&gym_id=${gymId}`, | ||||||
|                 // notify_url: `https://$filien.web.app/verifyCashfreePayment`
 |                 // notify_url: `https://$filien.web.app/verifyCashfreePayment`
 | ||||||
|               }, |               }, | ||||||
|               order_note: productInfo || 'Fitlien Membership' |               order_note: productInfo || 'Fitlien Membership' | ||||||
| @ -325,6 +437,7 @@ export const createCashfreeOrder = onRequest({ | |||||||
|             } |             } | ||||||
|           ); |           ); | ||||||
| 
 | 
 | ||||||
|  |           try { | ||||||
|             await admin.firestore().collection('payment_orders').doc(orderId).set({ |             await admin.firestore().collection('payment_orders').doc(orderId).set({ | ||||||
|               userId: uid, |               userId: uid, | ||||||
|               amount: amount, |               amount: amount, | ||||||
| @ -333,25 +446,51 @@ export const createCashfreeOrder = onRequest({ | |||||||
|               orderStatus: 'CREATED', |               orderStatus: 'CREATED', | ||||||
|               paymentGateway: 'Cashfree', |               paymentGateway: 'Cashfree', | ||||||
|               createdAt: new Date(), |               createdAt: new Date(), | ||||||
|  |               hashKey: hashKey, | ||||||
|  |               clientId: userId, | ||||||
|  |               gymId: gymId, | ||||||
|  |               orderId: orderId, | ||||||
|               ...cashfreeResponse.data |               ...cashfreeResponse.data | ||||||
|             }); |             }); | ||||||
|  |           } catch (firestoreError) { | ||||||
|  |             logger.error('Error storing order in Firestore:', firestoreError); | ||||||
|  |           } | ||||||
| 
 | 
 | ||||||
|           response.json({ |           response.json({ | ||||||
|  |             success: true, | ||||||
|             order_id: cashfreeResponse.data.order_id, |             order_id: cashfreeResponse.data.order_id, | ||||||
|             payment_session_id: cashfreeResponse.data.payment_session_id |             payment_session_id: cashfreeResponse.data.payment_session_id | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|           logger.info(`Cashfree order created: ${orderId}`); |           logger.info(`Cashfree order created: ${orderId}`); | ||||||
|  |         } catch (axiosError: any) { | ||||||
|  |           logger.error('Cashfree API error:', axiosError); | ||||||
|  |           response.status(axiosError.response?.status || 500).json({ | ||||||
|  |             success: false, | ||||||
|  |             error: 'Payment gateway error', | ||||||
|  |             details: axiosError.response?.data || axiosError.message, | ||||||
|  |             code: axiosError.code | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } catch (authError) { | ||||||
|  |         logger.error('Authentication error:', authError); | ||||||
|  |         response.status(401).json({ | ||||||
|  |           success: false, | ||||||
|  |           error: 'Invalid authentication token' | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|     } catch (error: any) { |     } catch (error: any) { | ||||||
|       logger.error('Cashfree order creation error:', error); |       logger.error('Cashfree order creation error:', error); | ||||||
|       response.status(500).json({ |       response.status(500).json({ | ||||||
|  |         success: false, | ||||||
|         error: 'Failed to create payment order', |         error: 'Failed to create payment order', | ||||||
|         details: error.response?.data || error.message |         details: error.message | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| export const verifyCashfreePayment = onRequest({ | export const verifyCashfreePayment = onRequest({ | ||||||
|   region: '#{SERVICES_RGN}#' |   region: '#{SERVICES_RGN}#' | ||||||
| }, async (request: Request, response: express.Response) => { | }, async (request: Request, response: express.Response) => { | ||||||
| @ -410,3 +549,102 @@ export const verifyCashfreePayment = onRequest({ | |||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user