From 10edbc9fcf352836ff7fb1dcb060dc33dfad026e Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:50:47 +0530 Subject: [PATCH] document change --- firebase.json | 2 +- functions/src/index.ts | 648 +++++++++-------------------------------- 2 files changed, 144 insertions(+), 506 deletions(-) diff --git a/firebase.json b/firebase.json index 076f09a..abf2c13 100644 --- a/firebase.json +++ b/firebase.json @@ -26,7 +26,7 @@ "port": 5001 }, "firestore": { - "port": 8084 + "port": 8079 }, "storage": { "port": 9199 diff --git a/functions/src/index.ts b/functions/src/index.ts index baaf43f..16c3edc 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import * as fs from 'fs'; import * as https from 'https'; import axios from "axios"; +import { onDocumentCreated } from "firebase-functions/firestore"; const formData = require('form-data'); const Mailgun = require('mailgun.js'); @@ -275,279 +276,122 @@ export const verifyCashfreePayment = onRequest({ } }); -export const sendFCMNotificationByType = onRequest({ - region: process.env.SERVICES_RGN -}, async (request: Request, response: express.Response) => { +export const processNotificationOnCreate = onDocumentCreated({ + region: process.env.SERVICES_RGN, + document: 'notifications/{notificationId}' +}, async (event) => { try { - const notificationType = request.body.type || request.query.type; - const userId = request.body.userId || request.body.clientId || request.query.userId || request.query.clientId || request.body.invitorId || request.query.invitorId; + const notification = event.data?.data(); + const notificationId = event.params.notificationId; - if (!notificationType) { - response.status(400).json({ error: 'Notification type is required' }); + if (!notification) { + logger.error(`No data found for notification ${notificationId}`); return; } - if (!userId) { - response.status(400).json({ error: 'User ID is required' }); + if (notification.notificationSent === true) { + logger.info(`Notification ${notificationId} already sent, skipping.`); return; } - const userDoc = await admin.firestore().collection('users').doc(userId).get(); + let userId = null; + let fcmToken = null; - if (!userDoc.exists) { - response.status(404).json({ error: `User not found for ID: ${userId}` }); - return; + 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; + } } - const user = userDoc.data(); - const fcmToken = user?.fcmToken; - if (!fcmToken) { - response.status(400).json({ error: `FCM token not found for user: ${userId}` }); - return; - } - - const userIdQuery = admin.firestore().collection('notifications') - .where('userId', '==', userId) - .where('type', '==', notificationType); - - const clientIdQuery = admin.firestore().collection('notifications') - .where('clientId', '==', userId) - .where('type', '==', notificationType); - - const invitorIdQuery = admin.firestore().collection('notifications') - .where('invitorId', '==', userId) - .where('type', '==', notificationType); - - const [userIdResults, clientIdResults, invitorIdResults] = await Promise.all([ - userIdQuery.get(), - clientIdQuery.get(), - invitorIdQuery.get() - ]); - - const notificationDocs = [...userIdResults.docs, ...clientIdResults.docs, ...invitorIdResults.docs] - .filter(doc => { - const data = doc.data(); - return data.notificationSent !== true; - }); - - if (notificationDocs.length === 0) { - response.status(404).json({ - error: `No unsent notifications of type '${notificationType}' found for user/client ${userId}` + 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; } - const results = []; + let title = 'New Notification'; + let body = notification.message || 'You have a new notification'; + let data: Record = { + type: notification.type, + }; - for (const doc of notificationDocs) { - const notification = doc.data(); - const docId = doc.id; - if (notification.notificationSent) { - logger.info(`Notification ${notificationType} already sent, skipping.`); - continue; - } - - let title = 'New Notification'; - let body = notification.message || 'You have a new notification'; - let data: Record = { - 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': - const isAccept = notification.status === 'ACCEPTED'; - title = isAccept ? 'Invitation Accepted' : 'Invitation Rejected'; - body = notification.message || (isAccept ? - `The invitation for ${notification.subcriptionName} you shared with ${notification.name} has been accepted` : - `The invitation for ${notification.subcriptionName} you shared with ${notification.name} has been rejected`); - data.gymName = notification.gymName || ''; - data.clientEmail = notification.clientEmail || ''; - data.clientName = notification.name || ''; - data.invitationId = notification.invitationId || ''; - data.subcriptionname= notification.subcriptionname || ''; - break; - - default: - logger.info(`Using default handling for notification type: ${notification.type}`); - break; - } - - const message: admin.messaging.Message = { - notification: { - title: title, - body: body, - }, - data: data, - android: { - priority: 'high', - notification: { - channelId: 'notifications_channel', - priority: 'high', - defaultSound: true, - defaultVibrateTimings: true, - icon: '@mipmap/ic_launcher', - clickAction: 'FLUTTER_NOTIFICATION_CLICK', - }, - }, - apns: { - payload: { - aps: { - sound: 'default', - badge: 1, - }, - }, - }, - token: fcmToken, - }; - - try { - const fcmResponse = await admin.messaging().send(message); - logger.info(`FCM notification sent to user ${userId} for 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; - await admin.firestore().collection('notifications').doc(docId).update({ - notificationSent: true, - sentAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() - }); + 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; - results.push({ - success: true, - messageId: fcmResponse - }); - } catch (error) { - logger.error(`Error sending notification ${notificationType}:`, error); - results.push({ - success: false, - type: notificationType, - error: error instanceof Error ? error.message : String(error) - }); - } - } - - response.json({ - success: true, - processed: results.length, - results: results - }); - - } catch (error) { - logger.error('Error processing notifications:', error); - response.status(500).json({ - success: false, - error: error instanceof Error ? error.message : String(error) - }); - } -}); - -export const sendFCMNotificationByPhone = onRequest({ - region: process.env.SERVICES_RGN -}, async (request: Request, response: express.Response) => { - try { - const notificationType = request.body.type || request.query.type; - const phoneNumber = request.body.phoneNumber || request.query.phoneNumber; - - if (!notificationType) { - response.status(400).json({ error: 'Notification type is required' }); - return; - } - - if (!phoneNumber) { - response.status(400).json({ error: 'Phone number is required' }); - return; - } - - const userQuery = await admin - .firestore() - .collection('users') - .where('phoneNumber', '==', phoneNumber) - .get(); - - if (userQuery.empty) { - response.status(404).json({ error: `User not found for phone number: ${phoneNumber}` }); - return; - } - - const userDoc = userQuery.docs[0]; - const user = userDoc.data(); - const fcmToken = user?.fcmToken; - - if (!fcmToken) { - response.status(400).json({ error: `FCM token not found for user with phone: ${phoneNumber}` }); - return; - } - - const invitorIdQuery = admin.firestore().collection('notifications') - .where('phoneNumber', '==', phoneNumber) - .where('type', '==', notificationType); - - const [userIdResults] = await Promise.all([ - invitorIdQuery.get() - ]); - - const notificationDocs = [...userIdResults.docs] - .filter(doc => { - const data = doc.data(); - return data.notificationSent !== true; - }); - - if (notificationDocs.length === 0) { - response.status(404).json({ - error: `No unsent notifications of type '${notificationType}' found for user with phone ${phoneNumber}` - }); - return; - } - - const results = []; - - for (const doc of notificationDocs) { - const notification = doc.data(); - const docId = doc.id; - - let title = 'New Notification'; - let body = notification.message || 'You have a new notification'; - let data: Record = { - type: notification.type, - }; - - switch (notification.type) { - - case 'client_invitations': - let invitationStatus; - if (notification.status === 'ACCEPTED') { - invitationStatus = 'accepted'; - title = 'Invitation Accepted'; - body = notification.message || - `You have rejected 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'; + 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 || ''; @@ -555,264 +399,58 @@ export const sendFCMNotificationByPhone = onRequest({ data.clientName = notification.name || ''; data.invitationId = notification.invitationId || ''; data.subscriptionName = notification.subscriptionName || ''; - data.status = invitationStatus; break; - - default: - logger.info(`Using default handling for notification type: ${notification.type}`); - break; - } - - const message: admin.messaging.Message = { + + default: + logger.info(`Using default handling for notification type: ${notification.type}`); + break; + } + + const message: admin.messaging.Message = { + notification: { + title: title, + body: body, + }, + data: data, + android: { + priority: 'high', notification: { - title: title, - body: body, - }, - data: data, - android: { + channelId: 'notifications_channel', priority: 'high', - notification: { - channelId: 'notifications_channel', - priority: 'high', - defaultSound: true, - defaultVibrateTimings: true, - icon: '@mipmap/ic_launcher', - clickAction: 'FLUTTER_NOTIFICATION_CLICK', + defaultSound: true, + defaultVibrateTimings: true, + icon: '@mipmap/ic_launcher', + clickAction: 'FLUTTER_NOTIFICATION_CLICK', + }, + }, + apns: { + payload: { + aps: { + sound: 'default', + badge: 1, }, }, - apns: { - payload: { - aps: { - sound: 'default', - badge: 1, - }, - }, - }, - token: fcmToken, - }; + }, + token: fcmToken, + }; + + try { + const fcmResponse = await admin.messaging().send(message); + logger.info(`FCM notification sent successfully: ${fcmResponse}`); - try { - const fcmResponse = await admin.messaging().send(message); - logger.info(`FCM notification sent to user with phone ${phoneNumber} for type: ${notification.type}`); - - await admin.firestore().collection('notifications').doc(docId).update({ - notificationSent: true, - sentAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() - }); - - results.push({ - success: true, - messageId: fcmResponse - }); - } catch (error) { - logger.error(`Error sending notification ${notification.type}:`, error); - results.push({ - success: false, - type: notification.type, - error: error instanceof Error ? error.message : String(error) - }); - } - } - - response.json({ - success: true, - processed: results.length, - results: results - }); - - } catch (error) { - logger.error('Error processing notifications by phone:', error); - response.status(500).json({ - success: false, - error: error instanceof Error ? error.message : String(error) - }); - } -}); - -export const sendTrainerAssignmentNotification = onRequest({ - region: process.env.SERVICES_RGN -}, async (request: Request, response: express.Response) => { - try { - const notificationType = request.body.type || request.query.type || 'trainer_assigned_to_client'; - const trainerName = request.body.trainerName || request.query.trainerName; - const clientId = request.body.clientId || request.query.clientId; - const gymId = request.body.gymId || request.query.gymId; - - if (!trainerName) { - response.status(400).json({ error: 'Trainer name is required' }); - return; - } - - if (!clientId) { - response.status(400).json({ error: 'Client ID is required' }); - return; - } - - if (!gymId) { - response.status(400).json({ error: 'Gym ID is required' }); - return; - } - - // Find the trainer by name - const trainerQuery = await admin - .firestore() - .collection('trainer_profiles') - .where('fullName', '==', trainerName) - .limit(1) - .get(); - - if (trainerQuery.empty) { - response.status(404).json({ error: `Trainer not found with name: ${trainerName}` }); - return; - } - - const trainerDoc = trainerQuery.docs[0]; - const trainer = trainerDoc.data(); - const trainerId = trainerDoc.id; - - if (!trainer.phoneNumber) { - response.status(400).json({ error: `Phone number not found for trainer: ${trainerName}` }); - return; - } - - // Get client name from client collection - const clientDoc = await admin.firestore().collection('clients').doc(clientId).get(); - if (!clientDoc.exists) { - response.status(404).json({ error: `Client not found with ID: ${clientId}` }); - return; - } - const client = clientDoc.data(); - const clientName = client?.name || client?.fullName || client?.displayName || 'A new client'; - - // Get gym name from gym ID - const gymDoc = await admin.firestore().collection('gyms').doc(gymId).get(); - if (!gymDoc.exists) { - response.status(404).json({ error: `Gym not found with ID: ${gymId}` }); - return; - } - const gym = gymDoc.data(); - const gymName = gym?.name || gym?.gymName || 'your gym'; - - // Find the user document for the trainer using their phone number - const userQuery = await admin - .firestore() - .collection('users') - .where('phoneNumber', '==', trainer.phoneNumber) - .limit(1) - .get(); - - if (userQuery.empty) { - response.status(404).json({ error: `User not found for trainer with phone: ${trainer.phoneNumber}` }); - return; - } - - const userDoc = userQuery.docs[0]; - const user = userDoc.data(); - const fcmToken = user?.fcmToken; - - if (!fcmToken) { - response.status(400).json({ error: `FCM token not found for trainer: ${trainerName}` }); - return; - } - - const notificationQuery = admin.firestore().collection('notifications') - .where('type', '==', notificationType) - .where('clientId', '==', clientId) - .where('gymId', '==', gymId) - .where('trainerName', '==', trainerName); - - const notificationSnapshot = await notificationQuery.get(); - - const unsentNotifications = notificationSnapshot.docs.filter(doc => { - const data = doc.data(); - return data.notificationSent !== true; - }); - - if (unsentNotifications.length === 0) { - response.status(404).json({ - error: `No unsent notifications found for trainer: ${trainerName}, client: ${clientId}, gym: ${gymId}` + 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() }); - return; } - - const results = []; - - for (const doc of unsentNotifications) { - const docId = doc.id; - - const title = 'New Client Assignment'; - const body = `You have been assigned to ${clientName} at ${gymName}`; - - const data: Record = { - type: notificationType, - trainerId: trainerId, - clientId: clientId, - clientName: clientName, - gymId: gymId, - gymName: gymName - }; - - const message: admin.messaging.Message = { - notification: { - title: title, - body: body, - }, - data: data, - android: { - priority: 'high', - notification: { - channelId: 'notifications_channel', - priority: 'high', - defaultSound: true, - defaultVibrateTimings: true, - icon: '@mipmap/ic_launcher', - clickAction: 'FLUTTER_NOTIFICATION_CLICK', - }, - }, - apns: { - payload: { - aps: { - sound: 'default', - badge: 1, - }, - }, - }, - token: fcmToken, - }; - - try { - const fcmResponse = await admin.messaging().send(message); - logger.info(`Trainer assignment notification sent to ${trainerName}`); - - await admin.firestore().collection('notifications').doc(docId).update({ - notificationSent: true, - sentAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() - }); - - results.push({ - success: true, - messageId: fcmResponse - }); - } catch (error) { - logger.error(`Error sending trainer assignment notification:`, error); - results.push({ - success: false, - type: notificationType, - error: error instanceof Error ? error.message : String(error) - }); - } - } - - response.json({ - success: true, - processed: results.length, - results: results - }); } catch (error) { - logger.error('Error processing trainer assignment notification:', error); - response.status(500).json({ - success: false, - error: error instanceof Error ? error.message : String(error) - }); + logger.error('Error processing notification:', error); } -}); +}); \ No newline at end of file