feature/fitlien-add-cors #15
| @ -26,7 +26,7 @@ | ||||
|       "port": 5001 | ||||
|     }, | ||||
|     "firestore": { | ||||
|       "port": 8084 | ||||
|       "port": 8079 | ||||
|     }, | ||||
|     "storage": { | ||||
|       "port": 9199 | ||||
|  | ||||
| @ -3,7 +3,6 @@ import { Request } from "firebase-functions/v2/https"; | ||||
| import * as admin from 'firebase-admin'; | ||||
| import * as express from "express"; | ||||
| import * as logger from "firebase-functions/logger"; | ||||
| import { onDocumentCreated } from "firebase-functions/firestore"; | ||||
| import * as os from 'os'; | ||||
| import * as path from 'path'; | ||||
| import * as fs from 'fs'; | ||||
| @ -11,6 +10,7 @@ import * as https from 'https'; | ||||
| import cors from 'cors'; | ||||
| import axios from "axios"; | ||||
| import { getStorage } from 'firebase-admin/storage'; | ||||
| import { onDocumentCreated } from "firebase-functions/firestore"; | ||||
| const formData = require('form-data'); | ||||
| const Mailgun = require('mailgun.js'); | ||||
| const { convert } = require('html-to-text'); | ||||
| @ -24,7 +24,7 @@ if (!admin.apps.length) { | ||||
| const corsHandler = cors({ origin: true }); | ||||
| 
 | ||||
| export const sendEmailWithAttachment = onRequest({ | ||||
|   region: '#{SERVICES_RGN}#' | ||||
|   region: process.env.SERVICES_RGN | ||||
| }, async (request: Request, response: express.Response) => { | ||||
|   return corsHandler(request, response, async () => { | ||||
|     try { | ||||
| @ -157,7 +157,7 @@ export const sendEmailMessage = onRequest({ | ||||
| }); | ||||
| 
 | ||||
| export const sendSMSMessage = onRequest({ | ||||
|   region: '#{SERVICES_RGN}#' | ||||
|   region: process.env.SERVICES_RGN | ||||
| }, (request: Request, response: express.Response) => { | ||||
|   return corsHandler(request, response, async () => { | ||||
| 
 | ||||
| @ -180,65 +180,146 @@ export const sendSMSMessage = onRequest({ | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| interface Invitation { | ||||
|   email: string; | ||||
|   phoneNumber: string; | ||||
|   gymName: string; | ||||
|   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; | ||||
|   } | ||||
| 
 | ||||
| export const processNotificationOnCreate = onDocumentCreated({ | ||||
|   region: process.env.SERVICES_RGN, | ||||
|   document: 'notifications/{notificationId}' | ||||
| }, async (event) => { | ||||
|   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 | ||||
|         .firestore() | ||||
|         .collection('users') | ||||
|       .where('email', '==', invitation.email) | ||||
|       .where('phoneNumber', '==', invitation.phoneNumber) | ||||
|         .where('phoneNumber', '==', notification.phoneNumber) | ||||
|         .limit(1) | ||||
|         .get(); | ||||
|          | ||||
|     if (userQuery.empty) { | ||||
|       console.log( | ||||
|         `User not found for email: ${invitation.email} and phone: ${invitation.phoneNumber}.` | ||||
|       ); | ||||
|       return null; | ||||
|       if (!userQuery.empty) { | ||||
|         const userDoc = userQuery.docs[0]; | ||||
|         userId = userDoc.id; | ||||
|         fcmToken = userDoc.data()?.fcmToken; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     const user = userQuery.docs[0].data(); | ||||
|     const fcmToken = user.fcmToken; | ||||
| 
 | ||||
|     if (!fcmToken) { | ||||
|       console.log(`FCM token not found for user: ${invitation.email}.`); | ||||
|       return null; | ||||
|       logger.error(`FCM token not found for notification ${notificationId}`); | ||||
|       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 = { | ||||
|       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, | ||||
|         title: title, | ||||
|         body: body, | ||||
|       }, | ||||
|       data: data, | ||||
|       android: { | ||||
|         priority: 'high', | ||||
|         notification: { | ||||
|           channelId: 'invitations_channel', | ||||
|           channelId: 'notifications_channel', | ||||
|           priority: 'high', | ||||
|           defaultSound: true, | ||||
|           defaultVibrateTimings: true, | ||||
| @ -246,15 +327,35 @@ export const notifyInvitation = onDocumentCreated({ | ||||
|           clickAction: 'FLUTTER_NOTIFICATION_CLICK', | ||||
|         }, | ||||
|       }, | ||||
|       apns: { | ||||
|         payload: { | ||||
|           aps: { | ||||
|             sound: 'default', | ||||
|             badge: 1, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       token: fcmToken, | ||||
|     }; | ||||
|      | ||||
|     await admin.messaging().send(message); | ||||
|     console.log(`Invitation notification sent to ${invitation.email}.`); | ||||
|     return null; | ||||
|     try { | ||||
|       const fcmResponse = await admin.messaging().send(message); | ||||
|       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) { | ||||
|     console.error('Error sending invitation notification:', error); | ||||
|     return null; | ||||
|       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) { | ||||
|     logger.error('Error processing notification:', error); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user