feature/fitlien-add-cors #18
| @ -26,7 +26,7 @@ | |||||||
|       "port": 5001 |       "port": 5001 | ||||||
|     }, |     }, | ||||||
|     "firestore": { |     "firestore": { | ||||||
|       "port": 8084 |       "port": 8079 | ||||||
|     }, |     }, | ||||||
|     "storage": { |     "storage": { | ||||||
|       "port": 9199 |       "port": 9199 | ||||||
|  | |||||||
| @ -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'); | ||||||
| @ -24,7 +24,7 @@ if (!admin.apps.length) { | |||||||
| const corsHandler = cors({ origin: true }); | const corsHandler = cors({ origin: true }); | ||||||
| 
 | 
 | ||||||
| export const sendEmailWithAttachment = onRequest({ | export const sendEmailWithAttachment = onRequest({ | ||||||
|   region: '#{SERVICES_RGN}#' |   region: process.env.SERVICES_RGN | ||||||
| }, async (request: Request, response: express.Response) => { | }, async (request: Request, response: express.Response) => { | ||||||
|   return corsHandler(request, response, async () => { |   return corsHandler(request, response, async () => { | ||||||
|     try { |     try { | ||||||
| @ -157,7 +157,7 @@ export const sendEmailMessage = onRequest({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export const sendSMSMessage = onRequest({ | export const sendSMSMessage = onRequest({ | ||||||
|   region: '#{SERVICES_RGN}#' |   region: process.env.SERVICES_RGN | ||||||
| }, (request: Request, response: express.Response) => { | }, (request: Request, response: express.Response) => { | ||||||
|   return corsHandler(request, response, async () => { |   return corsHandler(request, response, async () => { | ||||||
| 
 | 
 | ||||||
| @ -180,65 +180,146 @@ export const sendSMSMessage = onRequest({ | |||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| interface Invitation { | export const processNotificationOnCreate = onDocumentCreated({ | ||||||
|   email: string; |   region: process.env.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 userQuery = await admin |     const notification = event.data?.data(); | ||||||
|       .firestore() |     const notificationId = event.params.notificationId; | ||||||
|       .collection('users') |      | ||||||
|       .where('email', '==', invitation.email) |     if (!notification) { | ||||||
|       .where('phoneNumber', '==', invitation.phoneNumber) |       logger.error(`No data found for notification ${notificationId}`); | ||||||
|       .limit(1) |       return; | ||||||
|       .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(); |     if (notification.notificationSent === true) { | ||||||
|     const fcmToken = user.fcmToken; |       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 | ||||||
|  |         .firestore() | ||||||
|  |         .collection('users') | ||||||
|  |         .where('phoneNumber', '==', notification.phoneNumber) | ||||||
|  |         .limit(1) | ||||||
|  |         .get(); | ||||||
|  |          | ||||||
|  |       if (!userQuery.empty) { | ||||||
|  |         const userDoc = userQuery.docs[0]; | ||||||
|  |         userId = userDoc.id; | ||||||
|  |         fcmToken = userDoc.data()?.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) { | ||||||
|  |       logger.error(`Error sending notification ${notificationId}:`, error); | ||||||
|  |        | ||||||
|  |       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) { |   } catch (error) { | ||||||
|     console.error('Error sending invitation notification:', error); |     logger.error('Error processing notification:', error); | ||||||
|     return null; |  | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user