From ed542455f72a95f1bf8fd427a1cb03273566b683 Mon Sep 17 00:00:00 2001 From: jennymar Date: Fri, 12 May 2023 08:43:46 -0700 Subject: [PATCH 01/22] push notifications via firebase --- ALUM/ALUM.xcodeproj/project.pbxproj | 16 +++ .../xcshareddata/xcschemes/ALUM.xcscheme | 78 +++++++++++++ ALUM/ALUM/ALUM.entitlements | 5 +- ALUM/ALUM/ALUMApp.swift | 108 ++++++++++++++++-- ALUM/ALUM/Info.plist | 5 + 5 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index 32f766eb..48c201af 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 0799ACE52A00924F00EEAFA2 /* LoggedInRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */; }; 0799ACE92A00A6C200EEAFA2 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */; }; 0799ACF22A01109100EEAFA2 /* LoginReviewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */; }; + 2A0608402A0E3128007F38D6 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 2A06083F2A0E3128007F38D6 /* FirebaseMessaging */; }; 2A638880299FF3BC00F9EA97 /* DrawerContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A63887F299FF3BC00F9EA97 /* DrawerContainer.swift */; }; 2A8E07E2297B4FB0001AA153 /* OutlinedButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */; }; 2ADCE5BF299DBD570069802F /* ParagraphInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ADCE5BE299DBD570069802F /* ParagraphInput.swift */; }; @@ -210,6 +211,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2A0608402A0E3128007F38D6 /* FirebaseMessaging in Frameworks */, 97A2FE822988EF9900405FD6 /* FirebaseAnalytics in Frameworks */, 97A2FE882988EF9900405FD6 /* FirebaseStorage in Frameworks */, 97A2FE842988EF9900405FD6 /* FirebaseAuth in Frameworks */, @@ -237,6 +239,7 @@ 0748208D29712921004AF547 /* ALUM */, 0748208C29712921004AF547 /* Products */, 0799ACEE2A0102E400EEAFA2 /* Recovered References */, + 2A06083E2A0E3128007F38D6 /* Frameworks */, ); sourceTree = ""; }; @@ -372,6 +375,13 @@ path = Services; sourceTree = ""; }; + 2A06083E2A0E3128007F38D6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; 97992B5D29A6E7E200701CC7 /* SignUpPage */ = { isa = PBXGroup; children = ( @@ -467,6 +477,7 @@ 97A2FE852988EF9900405FD6 /* FirebaseFirestore */, 97A2FE872988EF9900405FD6 /* FirebaseStorage */, 80F5388729A80B1D00FB5E66 /* WrappingHStack */, + 2A06083F2A0E3128007F38D6 /* FirebaseMessaging */, ); productName = ALUM; productReference = 0748208B29712921004AF547 /* ALUM.app */; @@ -861,6 +872,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 2A06083F2A0E3128007F38D6 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 97A2FE802988EF9900405FD6 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; 80F5388729A80B1D00FB5E66 /* WrappingHStack */ = { isa = XCSwiftPackageProductDependency; package = 80F5388629A80B1C00FB5E66 /* XCRemoteSwiftPackageReference "WrappingHStack" */; diff --git a/ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme b/ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme new file mode 100644 index 00000000..225abeba --- /dev/null +++ b/ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ALUM/ALUM/ALUM.entitlements b/ALUM/ALUM/ALUM.entitlements index 0c67376e..903def2a 100644 --- a/ALUM/ALUM/ALUM.entitlements +++ b/ALUM/ALUM/ALUM.entitlements @@ -1,5 +1,8 @@ - + + aps-environment + development + diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index e881176f..6086ca30 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -9,14 +9,17 @@ import SwiftUI import FirebaseCore import FirebaseFirestore import FirebaseAuth +import Firebase +import UserNotifications +import FirebaseMessaging -class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - FirebaseApp.configure() - return true - } -} +//class AppDelegate: NSObject, UIApplicationDelegate { +// func application(_ application: UIApplication, +// didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { +// FirebaseApp.configure() +// return true +// } +//} struct RootView: View { @ObservedObject var currentUser: CurrentUserModal = CurrentUserModal.shared @@ -50,3 +53,94 @@ struct ALUMApp: App { } } } + + +class AppDelegate: NSObject, UIApplicationDelegate { + let gcmMessageIDKey = "gcm.message_id" + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + FirebaseApp.configure() + + Messaging.messaging().delegate = self + + if #available(iOS 10.0, *) { + // For iOS 10 display notification (sent via APNS) + UNUserNotificationCenter.current().delegate = self + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: {_, _ in }) + } else { + let settings: UIUserNotificationSettings = + UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) + application.registerUserNotificationSettings(settings) + } + + application.registerForRemoteNotifications() + return true + } + + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + + print(userInfo) + + completionHandler(UIBackgroundFetchResult.newData) + } +} + + +extension AppDelegate: MessagingDelegate { + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + + let deviceToken:[String: String] = ["token": fcmToken ?? ""] + print("Device token: ", deviceToken) // This token can be used for testing notifications on FCM + } +} + +@available(iOS 10, *) +extension AppDelegate : UNUserNotificationCenterDelegate { + + // Receive displayed notifications for iOS 10 devices. + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + let userInfo = notification.request.content.userInfo + + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + + print(userInfo) + + // Change this to your preferred presentation option + completionHandler([[.banner, .badge, .sound]]) + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID from userNotificationCenter didReceive: \(messageID)") + } + + print(userInfo) + + completionHandler() + } +} diff --git a/ALUM/ALUM/Info.plist b/ALUM/ALUM/Info.plist index e9b3de4f..b15b8184 100644 --- a/ALUM/ALUM/Info.plist +++ b/ALUM/ALUM/Info.plist @@ -9,5 +9,10 @@ Metropolis-ExtraBoldItalic.otf Metropolis-Thin.otf + UIBackgroundModes + + fetch + remote-notification + From 655055b1af83a8f38623da740b5367aa9f821e4e Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Sun, 21 May 2023 13:21:43 -0700 Subject: [PATCH 02/22] started sendNotification function --- ALUM/ALUM/ALUMApp.swift | 2 +- backend/src/errors/service.ts | 7 ++++--- backend/src/models/session.ts | 3 --- backend/src/routes/notes.ts | 2 +- backend/src/services/notifications.ts | 19 +++++++++++++++++++ 5 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 backend/src/services/notifications.ts diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index b07f5b41..972148a1 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -99,7 +99,7 @@ extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { let deviceToken:[String: String] = ["token": fcmToken ?? ""] - print("Device token: ", deviceToken) // This token can be used for testing notifications on FCM + print("FCM token: ", fcmToken ?? "") // This token can be used for testing notifications on FCM } } diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 08dd65df..399b2786 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -15,7 +15,6 @@ const IMAGE_WAS_NOT_FOUND = "Image was not found"; const NOTE_WAS_NOT_FOUND = "Note was not found"; const NOTE_WAS_NOT_SAVED = "Note was not saved"; const SESSION_WAS_NOT_FOUND = "Session was not found"; -const NOTE_WAS_NOT_FOUND = "Note was not found"; const INVALID_URI = "Calendly URI is invalid. Check formatting of URI string"; const ERROR_GETTING_EVENT_DATA = "There was an error retrieving the calendly event data"; @@ -36,7 +35,9 @@ export class ServiceError extends CustomError { static ERROR_GETTING_EVENT_DATA = new ServiceError(7, 404, ERROR_GETTING_EVENT_DATA); - static SESSION_WAS_NOT_FOUND = new ServiceError(6, 404, SESSION_WAS_NOT_FOUND); + static SESSION_WAS_NOT_FOUND = new ServiceError(8, 404, SESSION_WAS_NOT_FOUND); + + static NOTE_WAS_NOT_FOUND = new ServiceError(9, 404, NOTE_WAS_NOT_FOUND); - static NOTE_WAS_NOT_FOUND = new ServiceError(7, 404, NOTE_WAS_NOT_FOUND); + static NOTE_WAS_NOT_SAVED = new ServiceError(10, 404, NOTE_WAS_NOT_SAVED); } diff --git a/backend/src/models/session.ts b/backend/src/models/session.ts index 851cea89..43143174 100644 --- a/backend/src/models/session.ts +++ b/backend/src/models/session.ts @@ -11,9 +11,6 @@ interface SessionInterface { postSessionMentor: ObjectId; menteeId: ObjectId; mentorId: ObjectId; - dateTime: Date; - menteeId: string; - mentorId: string; startTime: Date; endTime: Date; calendlyUri: string; diff --git a/backend/src/routes/notes.ts b/backend/src/routes/notes.ts index 484eadba..d0cc1c63 100644 --- a/backend/src/routes/notes.ts +++ b/backend/src/routes/notes.ts @@ -49,7 +49,7 @@ router.get("/notes/:id", async (req: Request, res: Response, next: NextFunction) note_answer.question = questionIDs.get(note_answer.id) ?? ""; }); return res.status(200).json(note.answers); - res.status(200).json(note.answers); + // res.status(200).json(note.answers); } catch (e) { next(e); } diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts new file mode 100644 index 00000000..44ee1325 --- /dev/null +++ b/backend/src/services/notifications.ts @@ -0,0 +1,19 @@ +import * as admin from 'firebase-admin'; + +async function sendNotification(title: string, body: string, deviceToken: string) { + const message = { + notification: { + title: 'Notification Title', + body: 'Notification Body', + }, + token: 'device_registration_token', + }; + + admin.messaging().send(message) + .then((response) => { + console.log('Notification sent successfully:', response); + }) + .catch((error) => { + console.error('Error sending notification:', error); + }); +} \ No newline at end of file From 016f0963bbea6b4423520871b5dfa4a46397507a Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Tue, 23 May 2023 00:35:15 -0700 Subject: [PATCH 03/22] minor formatting issues + added fcm token to current user modal --- ALUM/ALUM/ALUMApp.swift | 77 +++++++++++++------------ ALUM/ALUM/Models/CurrentUserModel.swift | 6 ++ 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index 972148a1..b905a492 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -57,7 +57,8 @@ struct ALUMApp: App { class AppDelegate: NSObject, UIApplicationDelegate { let gcmMessageIDKey = "gcm.message_id" - + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { FirebaseApp.configure() @@ -65,16 +66,17 @@ class AppDelegate: NSObject, UIApplicationDelegate { if #available(iOS 10.0, *) { // For iOS 10 display notification (sent via APNS) - UNUserNotificationCenter.current().delegate = self + UNUserNotificationCenter.current().delegate = self - let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] - UNUserNotificationCenter.current().requestAuthorization( - options: authOptions, - completionHandler: {_, _ in }) + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: {_, _ in } + ) } else { - let settings: UIUserNotificationSettings = - UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) - application.registerUserNotificationSettings(settings) + let settings: UIUserNotificationSettings = + UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) + application.registerUserNotificationSettings(settings) } application.registerForRemoteNotifications() @@ -84,22 +86,21 @@ class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - if let messageID = userInfo[gcmMessageIDKey] { - print("Message ID: \(messageID)") - } + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } - print(userInfo) + print(userInfo) - completionHandler(UIBackgroundFetchResult.newData) + completionHandler(UIBackgroundFetchResult.newData) } } extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { - - let deviceToken:[String: String] = ["token": fcmToken ?? ""] - print("FCM token: ", fcmToken ?? "") // This token can be used for testing notifications on FCM + // let deviceToken:[String: String] = ["token": fcmToken ?? ""] + currentUser.fcmToken = fcmToken } } @@ -107,20 +108,20 @@ extension AppDelegate: MessagingDelegate { extension AppDelegate : UNUserNotificationCenterDelegate { // Receive displayed notifications for iOS 10 devices. - func userNotificationCenter(_ center: UNUserNotificationCenter, - willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - let userInfo = notification.request.content.userInfo + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + let userInfo = notification.request.content.userInfo - if let messageID = userInfo[gcmMessageIDKey] { - print("Message ID: \(messageID)") - } + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } - print(userInfo) + print(userInfo) - // Change this to your preferred presentation option - completionHandler([[.banner, .badge, .sound]]) - } + // Change this to your preferred presentation option + completionHandler([[.banner, .badge, .sound]]) + } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { @@ -130,17 +131,17 @@ extension AppDelegate : UNUserNotificationCenterDelegate { } - func userNotificationCenter(_ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse, - withCompletionHandler completionHandler: @escaping () -> Void) { - let userInfo = response.notification.request.content.userInfo + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo - if let messageID = userInfo[gcmMessageIDKey] { - print("Message ID from userNotificationCenter didReceive: \(messageID)") - } + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID from userNotificationCenter didReceive: \(messageID)") + } - print(userInfo) + print(userInfo) - completionHandler() - } + completionHandler() + } } diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index a5ed9ffc..6a12d847 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -16,12 +16,14 @@ enum UserRole { class CurrentUserModel: ObservableObject { static let shared = CurrentUserModel() + @Published var fcmToken: String? @Published var isLoading: Bool @Published var uid: String? @Published var role: UserRole? @Published var isLoggedIn: Bool init() { + self.fcmToken = nil self.isLoading = true self.isLoggedIn = false self.uid = nil @@ -76,4 +78,8 @@ class CurrentUserModel: ObservableObject { } self.setCurrentUser(isLoading: false, isLoggedIn: true, uid: user.uid, role: roleEnum) } + + func sendFcmToken(fcmToken: String) async { + + } } From b622172a67745d22cd66280a1c919a94ca306db4 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Tue, 23 May 2023 12:11:05 -0700 Subject: [PATCH 04/22] added patch request to send fcm token to backend --- backend/src/errors/service.ts | 6 ++++ backend/src/models/mentee.ts | 6 ++++ backend/src/models/mentor.ts | 6 ++++ backend/src/routes/user.ts | 52 +++++++++++++++++++++++++++++++++++ backend/src/services/user.ts | 35 ++++++++++++++++++++++- backend/src/types/cakes.ts | 13 +++++++++ 6 files changed, 117 insertions(+), 1 deletion(-) diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 399b2786..06af973b 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -17,6 +17,8 @@ const NOTE_WAS_NOT_SAVED = "Note was not saved"; const SESSION_WAS_NOT_FOUND = "Session was not found"; const INVALID_URI = "Calendly URI is invalid. Check formatting of URI string"; const ERROR_GETTING_EVENT_DATA = "There was an error retrieving the calendly event data"; +const MENTOR_WAS_NOT_SAVED = "Mentor update was not saved"; +const MENTEE_WAS_NOT_SAVED = "Mentee update was not saved"; export class ServiceError extends CustomError { static IMAGE_NOT_SAVED = new ServiceError(0, 404, IMAGE_NOT_SAVED); @@ -40,4 +42,8 @@ export class ServiceError extends CustomError { static NOTE_WAS_NOT_FOUND = new ServiceError(9, 404, NOTE_WAS_NOT_FOUND); static NOTE_WAS_NOT_SAVED = new ServiceError(10, 404, NOTE_WAS_NOT_SAVED); + + static MENTOR_WAS_NOT_SAVED = new ServiceError(11, 404, MENTOR_WAS_NOT_SAVED); + + static MENTEE_WAS_NOT_SAVED = new ServiceError(11, 404, MENTEE_WAS_NOT_SAVED); } diff --git a/backend/src/models/mentee.ts b/backend/src/models/mentee.ts index 226efa27..4a997847 100644 --- a/backend/src/models/mentee.ts +++ b/backend/src/models/mentee.ts @@ -10,6 +10,7 @@ interface MenteeInterface { mentorshipGoal: string; pairingId: string; status: string; + fcmToken: string; } interface MenteeDoc extends mongoose.Document { @@ -22,6 +23,7 @@ interface MenteeDoc extends mongoose.Document { mentorshipGoal: string; pairingId: string; status: string; + fcmToken: string; } interface MenteeModelInterface extends mongoose.Model { @@ -69,6 +71,10 @@ const MenteeSchema = new mongoose.Schema({ type: String, required: true, }, + fcmToken: { + type: String, + required: true, + }, }); const Mentee = mongoose.model("Mentee", MenteeSchema); diff --git a/backend/src/models/mentor.ts b/backend/src/models/mentor.ts index 6d11360c..848b91e0 100644 --- a/backend/src/models/mentor.ts +++ b/backend/src/models/mentor.ts @@ -22,6 +22,7 @@ interface MentorInterface { status: string; personalAccessToken: string; location: string; + fcmToken: string; } interface MentorDoc extends mongoose.Document { @@ -41,6 +42,7 @@ interface MentorDoc extends mongoose.Document { status: string; personalAccessToken: string; location: string; + fcmToken: string; } interface MentorModelInterface extends mongoose.Model { @@ -116,6 +118,10 @@ const mentorSchema = new mongoose.Schema({ type: String, required: true, }, + fcmToken: { + type: String, + required: true, + }, }); const Mentor = mongoose.model("Mentor", mentorSchema); diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 5f1f52de..45442cce 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -3,6 +3,7 @@ * new users */ import express, { NextFunction, Request, Response } from "express"; +import { Infer } from "caketype"; import mongoose from "mongoose"; import { validateReqBodyWithCake } from "../middleware/validation"; import { Mentee, Mentor, Pairing } from "../models"; @@ -12,6 +13,8 @@ import { CreateMentorRequestBodyCake, CreateMenteeRequestBodyType, CreateMentorRequestBodyType, + UpdateMentorCake, + UpdateMenteeCake, } from "../types"; import { ValidationError } from "../errors/validationError"; import { InternalError } from "../errors/internal"; @@ -19,6 +22,7 @@ import { ServiceError } from "../errors/service"; import { verifyAuthToken } from "../middleware/auth"; import { defaultImageID } from "../config"; import { CustomError } from "../errors"; +import { updateMentorFCMToken, updateMenteeFCMToken } from "../services/user"; const router = express.Router(); @@ -67,12 +71,14 @@ router.post( const imageId = defaultImageID; const about = "N/A"; const pairingId = "N/A"; + const fcmToken = "N/A"; const mentee = new Mentee({ name, imageId, about, status, pairingId, + fcmToken, ...args, }); await mentee.save(); @@ -121,6 +127,7 @@ router.post( // const calendlyLink = "N/A"; const zoomLink = "N/A"; const pairingIds: string[] = []; + const fcmToken = "N/A"; const mentor = new Mentor({ name, imageId, @@ -128,6 +135,7 @@ router.post( zoomLink, status, pairingIds, + fcmToken, ...args, }); await mentor.save(); @@ -364,4 +372,48 @@ router.get( } ); +type UpdateMentorType = Infer; +router.patch( + "/mentor/:userId", + validateReqBodyWithCake(UpdateMentorCake), + async (req: Request, res: Response, next: NextFunction) => { + try { + console.log(req.body); + const userId = req.params.userId; + if (!mongoose.Types.ObjectId.isValid(userId)) { + throw ServiceError.INVALID_MONGO_ID; + } + const updatedToken: UpdateMentorType = req.body; + await updateMentorFCMToken(updatedToken.fcmToken, userId); + res.status(200).json({ + message: "Success", + }) + } catch(e) { + next(e); + } + } +) + +type UpdateMenteeType = Infer; +router.patch( + "/mentee/:userId", + validateReqBodyWithCake(UpdateMenteeCake), + async (req: Request, res: Response, next: NextFunction) => { + try { + console.log(req.body); + const userId = req.params.userId; + if (!mongoose.Types.ObjectId.isValid(userId)) { + throw ServiceError.INVALID_MONGO_ID; + } + const updatedToken: UpdateMenteeType = req.body; + await updateMenteeFCMToken(updatedToken.fcmToken, userId); + res.status(200).json({ + message: "Success", + }) + } catch(e) { + next(e); + } + } +) + export { router as userRouter }; diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index 44fb012a..49a14278 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -5,7 +5,10 @@ // import mongoose from "mongoose"; // import { Image } from "../models/image"; import { InternalError } from "../errors"; +import { Mentor , Mentee} from "../models"; import { Pairing } from "../models/pairing"; +import { User } from "../models/users"; +import { ServiceError } from "../errors"; // TODO need to add this back in when implementing EDIT profile // async function saveImage(req: Request): Promise { @@ -42,4 +45,34 @@ async function getMenteeId(pairingId: string): Promise { return pairing.menteeId; } -export { getMentorId, getMenteeId }; +async function updateMentorFCMToken(fcmToken: string, userId: string) { + console.log("FCM Token: ", fcmToken); + const user = await Mentor.findById(userId); + if (!user) { + throw ServiceError.MENTOR_WAS_NOT_FOUND; + } + + try { + user.fcmToken = fcmToken; + return await user.save(); + } catch(error) { + throw ServiceError.MENTOR_WAS_NOT_SAVED; + } +} + +async function updateMenteeFCMToken(fcmToken: string, userId: string) { + console.log("FCM Token: ", fcmToken); + const user = await Mentee.findById(userId); + if (!user) { + throw ServiceError.MENTEE_WAS_NOT_FOUND; + } + + try { + user.fcmToken = fcmToken; + return await user.save(); + } catch(error) { + throw ServiceError.MENTEE_WAS_NOT_SAVED; + } +} + +export { getMentorId, getMenteeId, updateMentorFCMToken, updateMenteeFCMToken }; diff --git a/backend/src/types/cakes.ts b/backend/src/types/cakes.ts index 4676d36b..c44d43b4 100644 --- a/backend/src/types/cakes.ts +++ b/backend/src/types/cakes.ts @@ -17,6 +17,9 @@ export const CreateMenteeRequestBodyCake = bake({ mentorshipGoal: string, }); +/** + * POST /mentor + */ export const CreateMentorRequestBodyCake = bake({ name: string, email: string, @@ -33,6 +36,16 @@ export const CreateMentorRequestBodyCake = bake({ calendlyLink: string, }); +// PATCH mentor/id +export const UpdateMentorCake = bake({ + fcmToken: string, +}) + +// PATCH mentee/id +export const UpdateMenteeCake = bake({ + fcmToken: string, +}) + // PATCH notes/id export const UpdateNoteDetailsCake = bake({ answer: union(string, array(string)), From 6d396df80cd13393fd4e67367b2d6e4eb9baa544 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Tue, 23 May 2023 16:01:41 -0700 Subject: [PATCH 05/22] set up frontend to send fcm token --- ALUM/ALUM/Models/CurrentUserModel.swift | 16 ++++++++++++++-- ALUM/ALUM/Services/APIConfig.swift | 14 ++++++++++---- .../MentorSessionDetailsPage.swift | 4 ++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index 6a12d847..86f6cff1 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -13,6 +13,10 @@ enum UserRole { case mentee } +struct FCMToken: Codable { + var fcmToken: String +} + class CurrentUserModel: ObservableObject { static let shared = CurrentUserModel() @@ -79,7 +83,15 @@ class CurrentUserModel: ObservableObject { self.setCurrentUser(isLoading: false, isLoggedIn: true, uid: user.uid, role: roleEnum) } - func sendFcmToken(fcmToken: String) async { - + func sendFcmToken(fcmToken: String) async throws { + var tokenToSend: FCMToken = FCMToken(fcmToken: fcmToken) + let route = self.role == .mentor ? APIRoute.patchMentor(userId: self.uid ?? "") : APIRoute.patchMentee(userId: self.uid ?? "") + var request = try await route.createURLRequest() + guard let jsonData = try? JSONEncoder().encode(tokenToSend) else { + throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data") + } + request.httpBody = jsonData + let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) + print("SUCCESS - \(route.label)") } } diff --git a/ALUM/ALUM/Services/APIConfig.swift b/ALUM/ALUM/Services/APIConfig.swift index 488fda8c..4de1369b 100644 --- a/ALUM/ALUM/Services/APIConfig.swift +++ b/ALUM/ALUM/Services/APIConfig.swift @@ -21,6 +21,8 @@ enum APIRoute { case getMentee(userId: String) case postMentor case postMentee + case patchMentor(userId: String) + case patchMentee(userId: String) case postSession case getCalendly @@ -52,6 +54,10 @@ enum APIRoute { return URLString.sessions case .getCalendly: return URLString.calendly + case .patchMentor(let userId): + return [URLString.mentor, userId].joined(separator: "/") + case .patchMentee(let userId): + return [URLString.mentor, userId].joined(separator: "/") } } @@ -61,14 +67,14 @@ enum APIRoute { return "GET" case .postMentor, .postMentee, .postSession: return "POST" - case .patchNote: + case .patchNote, .patchMentee, .patchMentor: return "PATCH" } } var requireAuth: Bool { switch self { - case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .postSession, .getCalendly: + case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .postSession, .getCalendly, .patchMentee, .patchMentor: return true case .postMentee, .postMentor: return false @@ -89,7 +95,7 @@ enum APIRoute { var successCode: Int { switch self { - case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly: + case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly, .patchMentee, .patchMentor: return 200 // 200 Ok case .postMentor, .postMentee, .postSession: return 201 // 201 Created @@ -101,7 +107,7 @@ enum APIRoute { let errorMap: [Int: AppError] switch self { - case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly: + case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly, .patchMentor, .patchMentee: errorMap = [ 401: AppError.actionable(.authenticationError, message: labeledMessage), 400: AppError.internalError(.invalidRequest, message: labeledMessage), diff --git a/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift b/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift index 493eb756..498c681f 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift @@ -64,6 +64,10 @@ struct MentorSessionDetailsPage: View { var sessionsArray: [UserSessionInfo] = try await SessionService().getSessionsByUser().sessions try await viewModel.loadSession(sessionID: sessionsArray[0].id) + + try await viewModel.currentUser.sendFcmToken( + fcmToken: viewModel.currentUser.fcmToken ?? "" + ) } catch { print(error) } From 2e20b3981c2f3d974007cea0ee1701f2f245324c Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Wed, 24 May 2023 20:02:43 -0700 Subject: [PATCH 06/22] set up notification sending when a session is made --- backend/src/routes/sessions.ts | 11 +++++++++++ backend/src/services/notifications.ts | 10 ++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 78c63ef8..a4c065ee 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -13,6 +13,7 @@ import { CreateSessionRequestBodyCake } from "../types/cakes"; import { InternalError, ServiceError } from "../errors"; import { getCalendlyEventDate } from "../services/calendly"; import { getMentorId } from "../services/user"; +import {sendNotification} from "../services/notifications"; /** * This is a post route to create a new session. @@ -76,6 +77,16 @@ router.post( session.postSessionMentee = postMenteeNoteId._id; session.postSessionMentor = postMentorNoteId._id; await session.save(); + await sendNotification( + "New session booked!", + "You have a new session with " + mentee.name + ". Check out your session details \u{1F60E}", + mentor.fcmToken + ); + await sendNotification( + "New session booked!", + "You have a new session with " + mentor.name + ". Fill out your pre-session notes now \u{1F60E}", + mentee.fcmToken + ); return res.status(201).json({ sessionId: session._id, mentorId: session.mentorId, diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 44ee1325..39e67c0f 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -3,10 +3,10 @@ import * as admin from 'firebase-admin'; async function sendNotification(title: string, body: string, deviceToken: string) { const message = { notification: { - title: 'Notification Title', - body: 'Notification Body', + title: title, + body: body, }, - token: 'device_registration_token', + token: deviceToken, }; admin.messaging().send(message) @@ -16,4 +16,6 @@ async function sendNotification(title: string, body: string, deviceToken: string .catch((error) => { console.error('Error sending notification:', error); }); -} \ No newline at end of file +} + +export {sendNotification}; \ No newline at end of file From fd7a59307139e1d13d65e2e598436a0c3d18af6c Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Sat, 27 May 2023 11:54:52 -0700 Subject: [PATCH 07/22] worked on cron job stuff --- ALUM/ALUM/ALUMApp.swift | 1 + backend/package-lock.json | 95 +++++++++++++++++++++++++++ backend/package.json | 2 + backend/src/routes/sessions.ts | 36 ++++++++-- backend/src/routes/user.ts | 12 ++-- backend/src/services/notifications.ts | 34 +++++----- backend/src/services/user.ts | 15 ++--- backend/src/types/cakes.ts | 4 +- 8 files changed, 160 insertions(+), 39 deletions(-) diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index fbd055a1..69bdcc83 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -101,6 +101,7 @@ extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { // let deviceToken:[String: String] = ["token": fcmToken ?? ""] currentUser.fcmToken = fcmToken + print(fcmToken) } } diff --git a/backend/package-lock.json b/backend/package-lock.json index f8842034..2b1e7964 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,6 +18,7 @@ "firebase-admin": "^11.5.0", "mongoose": "^6.8.4", "multer": "^1.4.5-lts.1", + "node-schedule": "^2.1.1", "pug": "^3.0.2", "sharp": "^0.31.3" }, @@ -26,6 +27,7 @@ "@types/file-type": "^10.9.1", "@types/mongoose": "^5.11.97", "@types/node": "^18.11.18", + "@types/node-schedule": "^2.1.0", "@types/sharp": "^0.31.1", "@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/parser": "^5.48.1", @@ -2128,6 +2130,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" }, + "node_modules/@types/node-schedule": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.0.tgz", + "integrity": "sha512-NiTwl8YN3v/1YCKrDFSmCTkVxFDylueEqsOFdgF+vPsm+AlyJKGAo5yzX1FiOxPsZiN6/r8gJitYx2EaSuBmmg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -3252,6 +3263,17 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron-parser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz", + "integrity": "sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5626,6 +5648,11 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "optional": true }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5660,6 +5687,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, + "node_modules/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "engines": { + "node": ">=12" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -6089,6 +6124,19 @@ "node": ">= 6.13.0" } }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7207,6 +7255,11 @@ "npm": ">= 3.0.0" } }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10022,6 +10075,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" }, + "@types/node-schedule": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.0.tgz", + "integrity": "sha512-NiTwl8YN3v/1YCKrDFSmCTkVxFDylueEqsOFdgF+vPsm+AlyJKGAo5yzX1FiOxPsZiN6/r8gJitYx2EaSuBmmg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -10841,6 +10903,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cron-parser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz", + "integrity": "sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==", + "requires": { + "luxon": "^3.2.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -12631,6 +12701,11 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "optional": true }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -12664,6 +12739,11 @@ } } }, + "luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==" + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -12987,6 +13067,16 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, + "node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "requires": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -13789,6 +13879,11 @@ "smart-buffer": "^4.2.0" } }, + "sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/backend/package.json b/backend/package.json index 668a6ed9..875ca220 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,6 +22,7 @@ "firebase-admin": "^11.5.0", "mongoose": "^6.8.4", "multer": "^1.4.5-lts.1", + "node-schedule": "^2.1.1", "pug": "^3.0.2", "sharp": "^0.31.3" }, @@ -30,6 +31,7 @@ "@types/file-type": "^10.9.1", "@types/mongoose": "^5.11.97", "@types/node": "^18.11.18", + "@types/node-schedule": "^2.1.0", "@types/sharp": "^0.31.1", "@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/parser": "^5.48.1", diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 3c8132e4..f8e85c09 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -4,6 +4,7 @@ import express, { NextFunction, Request, Response } from "express"; import mongoose, { ObjectId } from "mongoose"; +import schedule from "node-schedule"; // import { boolean } from "caketype"; import { createPreSessionNotes, createPostSessionNotes } from "../services/note"; import { verifyAuthToken } from "../middleware/auth"; @@ -13,7 +14,7 @@ import { CreateSessionRequestBodyCake } from "../types/cakes"; import { InternalError, ServiceError } from "../errors"; import { getCalendlyEventDate } from "../services/calendly"; import { getMentorId } from "../services/user"; -import {sendNotification} from "../services/notifications"; +import { sendNotification } from "../services/notifications"; /** * This is a post route to create a new session. @@ -78,16 +79,37 @@ router.post( session.postSessionMentee = postMenteeNoteId._id; session.postSessionMentor = postMentorNoteId._id; await session.save(); + + const job = schedule.scheduleJob("*/1 * * * *", async () => { + try { + const result = await sendNotification( + "New session booked!", + "You have a new session with " + + mentee.name + + ". Check out your session details \u{1F60E}", + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + ); + console.log("Function executed successfully:", result); + } catch (error) { + console.error("Error executing function:", error); + } + }); + console.log(job); + + /* await sendNotification( - "New session booked!", - "You have a new session with " + mentee.name + ". Check out your session details \u{1F60E}", - mentor.fcmToken + "New session booked!", + "You have a new session with " + mentee.name + ". Check out your session details \u{1F60E}", + mentor.fcmToken ); await sendNotification( - "New session booked!", - "You have a new session with " + mentor.name + ". Fill out your pre-session notes now \u{1F60E}", - mentee.fcmToken + "New session booked!", + "You have a new session with " + + mentor.name + + ". Fill out your pre-session notes now \u{1F60E}", + mentee.fcmToken ); + */ return res.status(201).json({ sessionId: session._id, mentorId: session.mentorId, diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 45442cce..9458e503 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -387,12 +387,12 @@ router.patch( await updateMentorFCMToken(updatedToken.fcmToken, userId); res.status(200).json({ message: "Success", - }) - } catch(e) { + }); + } catch (e) { next(e); } } -) +); type UpdateMenteeType = Infer; router.patch( @@ -409,11 +409,11 @@ router.patch( await updateMenteeFCMToken(updatedToken.fcmToken, userId); res.status(200).json({ message: "Success", - }) - } catch(e) { + }); + } catch (e) { next(e); } } -) +); export { router as userRouter }; diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 39e67c0f..1eb151cd 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -1,21 +1,23 @@ -import * as admin from 'firebase-admin'; +import * as admin from "firebase-admin"; async function sendNotification(title: string, body: string, deviceToken: string) { - const message = { - notification: { - title: title, - body: body, - }, - token: deviceToken, - }; + const message = { + notification: { + title, + body, + }, + token: deviceToken, + }; - admin.messaging().send(message) - .then((response) => { - console.log('Notification sent successfully:', response); - }) - .catch((error) => { - console.error('Error sending notification:', error); - }); + admin + .messaging() + .send(message) + .then((response) => { + console.log("Notification sent successfully:", response); + }) + .catch((error) => { + console.error("Error sending notification:", error); + }); } -export {sendNotification}; \ No newline at end of file +export { sendNotification }; diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index 49a14278..7295bcd1 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -4,11 +4,10 @@ // import { Request } from "express"; // import mongoose from "mongoose"; // import { Image } from "../models/image"; -import { InternalError } from "../errors"; -import { Mentor , Mentee} from "../models"; +import { InternalError, ServiceError } from "../errors"; +import { Mentor, Mentee } from "../models"; import { Pairing } from "../models/pairing"; -import { User } from "../models/users"; -import { ServiceError } from "../errors"; +// import { User } from "../models/users"; // TODO need to add this back in when implementing EDIT profile // async function saveImage(req: Request): Promise { @@ -51,11 +50,11 @@ async function updateMentorFCMToken(fcmToken: string, userId: string) { if (!user) { throw ServiceError.MENTOR_WAS_NOT_FOUND; } - + try { user.fcmToken = fcmToken; return await user.save(); - } catch(error) { + } catch (error) { throw ServiceError.MENTOR_WAS_NOT_SAVED; } } @@ -66,11 +65,11 @@ async function updateMenteeFCMToken(fcmToken: string, userId: string) { if (!user) { throw ServiceError.MENTEE_WAS_NOT_FOUND; } - + try { user.fcmToken = fcmToken; return await user.save(); - } catch(error) { + } catch (error) { throw ServiceError.MENTEE_WAS_NOT_SAVED; } } diff --git a/backend/src/types/cakes.ts b/backend/src/types/cakes.ts index c44d43b4..01e35e3f 100644 --- a/backend/src/types/cakes.ts +++ b/backend/src/types/cakes.ts @@ -39,12 +39,12 @@ export const CreateMentorRequestBodyCake = bake({ // PATCH mentor/id export const UpdateMentorCake = bake({ fcmToken: string, -}) +}); // PATCH mentee/id export const UpdateMenteeCake = bake({ fcmToken: string, -}) +}); // PATCH notes/id export const UpdateNoteDetailsCake = bake({ From 1c6cdb989a3970d43443e7cc0d53acbf00abcbf7 Mon Sep 17 00:00:00 2001 From: jennymar Date: Sat, 27 May 2023 11:55:36 -0700 Subject: [PATCH 08/22] sessions flag for upcoming and post sessions --- backend/src/models/session.ts | 12 ++++++++++++ backend/src/routes/sessions.ts | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/backend/src/models/session.ts b/backend/src/models/session.ts index d86b8f22..08c03e41 100644 --- a/backend/src/models/session.ts +++ b/backend/src/models/session.ts @@ -18,6 +18,8 @@ interface SessionInterface { preSessionCompleted: boolean; postSessionMentorCompleted: boolean; postSessionMenteeCompleted: boolean; + upcomingSessionNotifSent: boolean; + postSessionNotifSent: boolean; } interface SessionDoc extends mongoose.Document { @@ -33,6 +35,8 @@ interface SessionDoc extends mongoose.Document { preSessionCompleted: boolean; postSessionMentorCompleted: boolean; postSessionMenteeCompleted: boolean; + upcomingSessionNotifSent: boolean; + postSessionNotifSent: boolean; } interface SessionModelInterface extends mongoose.Model { @@ -88,6 +92,14 @@ const SessionSchema = new mongoose.Schema({ type: Boolean, required: true, }, + upcomingSessionNotifSent: { + type: Boolean, + required: true, + }, + postSessionNotifSent: { + type: Boolean, + required: true, + }, }); const Session = mongoose.model("Session", SessionSchema); diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index f8e85c09..a9976c3a 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -70,6 +70,8 @@ router.post( preSessionCompleted: false, postSessionMentorCompleted: false, postSessionMenteeCompleted: false, + upcomingSessionNotifSent: false, + postSessionNotifSent: false, }); const sessionId = session._id; const preNoteId = await createPreSessionNotes(sessionId); @@ -161,6 +163,8 @@ router.get( preSessionCompleted, postSessionMenteeCompleted, postSessionMentorCompleted, + upcomingSessionNotifSent, + postSessionNotifSent, } = session; const hasPassed = dateNow.getTime() - endTime.getTime() > 0; return res.status(200).send({ @@ -179,6 +183,8 @@ router.get( postSessionMenteeCompleted, postSessionMentorCompleted, hasPassed, + upcomingSessionNotifSent, + postSessionNotifSent, }, }); } catch (e) { From 28ab0e9c74e20c2136d92b891682ebc4a3203a80 Mon Sep 17 00:00:00 2001 From: jennymar Date: Mon, 29 May 2023 01:09:17 -0700 Subject: [PATCH 09/22] upcoming notif cron job (has bugs) --- backend/src/routes/sessions.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index a9976c3a..a99fbd62 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -82,21 +82,37 @@ router.post( session.postSessionMentor = postMentorNoteId._id; await session.save(); + let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); const job = schedule.scheduleJob("*/1 * * * *", async () => { try { - const result = await sendNotification( - "New session booked!", - "You have a new session with " + - mentee.name + - ". Check out your session details \u{1F60E}", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" - ); - console.log("Function executed successfully:", result); + upcomingNotifSessions.forEach(async (session) => { + const dateNow = new Date(); + if (session.startTime.getTime() - dateNow.getTime() <= 3600000) { + const menteeNotif = await sendNotification( + "You have an upcoming session.", + "Ready for your session with " + + mentee.name + + "in [time]? " + "\u{1F60E} Check out " + mentee.name + "'s pre-session notes.", + "fiNnUX4OqU2-q64KBCfR7j:APA91bGpw2-9ErHnh6ywQtUlx1IAiGInvtKihFlz4zxFoEy8w6cyJt_Vft4FzizM8bgGc_POLNMz1Y1wAgUeGo5t5MSdNC8oZ_3ZHP8Ed434-vJe13Kwy6fjdYRNcxlCF9X0xRtQr3qK" + ); + console.log("Function executed successfully:", menteeNotif); + const mentorNotif = await sendNotification( + "New session booked!", + "You have a new session with " + + mentee.name + + ". Check out your session details \u{1F60E}", + "fiNnUX4OqU2-q64KBCfR7j:APA91bGpw2-9ErHnh6ywQtUlx1IAiGInvtKihFlz4zxFoEy8w6cyJt_Vft4FzizM8bgGc_POLNMz1Y1wAgUeGo5t5MSdNC8oZ_3ZHP8Ed434-vJe13Kwy6fjdYRNcxlCF9X0xRtQr3qK" + ); + console.log("Function executed successfully:", mentorNotif); + session.upcomingSessionNotifSent = true; + } + }); + console.log(job); } catch (error) { console.error("Error executing function:", error); } }); - console.log(job); + /* await sendNotification( From 9f1e60eeceeb50c63b72a178e62896371940f3c7 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Tue, 30 May 2023 16:36:51 -0700 Subject: [PATCH 10/22] set up cron jobs in a separate function --- backend/src/routes/sessions.ts | 37 ++----------------- backend/src/services/note.ts | 26 ++++++++++++-- backend/src/services/notifications.ts | 51 ++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 38 deletions(-) diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index a99fbd62..219dd550 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -81,40 +81,7 @@ router.post( session.postSessionMentee = postMenteeNoteId._id; session.postSessionMentor = postMentorNoteId._id; await session.save(); - - let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); - const job = schedule.scheduleJob("*/1 * * * *", async () => { - try { - upcomingNotifSessions.forEach(async (session) => { - const dateNow = new Date(); - if (session.startTime.getTime() - dateNow.getTime() <= 3600000) { - const menteeNotif = await sendNotification( - "You have an upcoming session.", - "Ready for your session with " + - mentee.name + - "in [time]? " + "\u{1F60E} Check out " + mentee.name + "'s pre-session notes.", - "fiNnUX4OqU2-q64KBCfR7j:APA91bGpw2-9ErHnh6ywQtUlx1IAiGInvtKihFlz4zxFoEy8w6cyJt_Vft4FzizM8bgGc_POLNMz1Y1wAgUeGo5t5MSdNC8oZ_3ZHP8Ed434-vJe13Kwy6fjdYRNcxlCF9X0xRtQr3qK" - ); - console.log("Function executed successfully:", menteeNotif); - const mentorNotif = await sendNotification( - "New session booked!", - "You have a new session with " + - mentee.name + - ". Check out your session details \u{1F60E}", - "fiNnUX4OqU2-q64KBCfR7j:APA91bGpw2-9ErHnh6ywQtUlx1IAiGInvtKihFlz4zxFoEy8w6cyJt_Vft4FzizM8bgGc_POLNMz1Y1wAgUeGo5t5MSdNC8oZ_3ZHP8Ed434-vJe13Kwy6fjdYRNcxlCF9X0xRtQr3qK" - ); - console.log("Function executed successfully:", mentorNotif); - session.upcomingSessionNotifSent = true; - } - }); - console.log(job); - } catch (error) { - console.error("Error executing function:", error); - } - }); - - /* await sendNotification( "New session booked!", "You have a new session with " + mentee.name + ". Check out your session details \u{1F60E}", @@ -127,7 +94,7 @@ router.post( ". Fill out your pre-session notes now \u{1F60E}", mentee.fcmToken ); - */ + return res.status(201).json({ sessionId: session._id, mentorId: session.mentorId, @@ -297,4 +264,4 @@ router.get( } ); -export { router as sessionsRouter }; +export { router as sessionsRouter }; \ No newline at end of file diff --git a/backend/src/services/note.ts b/backend/src/services/note.ts index 1736fe57..a4dfb8b4 100644 --- a/backend/src/services/note.ts +++ b/backend/src/services/note.ts @@ -6,6 +6,8 @@ import { Note } from "../models/notes"; import { AnswerType, QuestionType, UpdateNoteDetailsType } from "../types/notes"; import { Session } from "../models/session"; import { ServiceError } from "../errors"; +import { Mentee, Mentor } from "../models"; +import { sendNotification } from "./notifications"; interface Question { question: string; @@ -138,9 +140,29 @@ async function updateNotes(updatedNotes: UpdateNoteDetailsType[], documentId: st // mongoose does not notice the change so ignores saving it unless we manually mark noteDoc.markModified("answers"); const sessionDoc = await Session.findById(noteDoc.session); + const mentee = await Mentee.findById(sessionDoc?.menteeId); + const mentor = await Mentor.findById(sessionDoc?.mentorId); if (sessionDoc != null) { - if (noteDoc.type === "pre") sessionDoc.preSessionCompleted = true; - else if (noteDoc.type === "postMentor") sessionDoc.postSessionMentorCompleted = true; + if (noteDoc.type === "pre") { + sessionDoc.preSessionCompleted = true; + if (mentee != null && mentor != null) { + await sendNotification( + "Pre-session update!", + "Looks like " + mentee.name + " has some questions for you. Check out " + mentee.name + "'s pre-session notes.", + mentor.fcmToken + ); + } + } + else if (noteDoc.type === "postMentor") { + sessionDoc.postSessionMentorCompleted = true; + if (mentee != null && mentor != null) { + await sendNotification( + "Post-session update!", + "" + mentor.name + " has updated their post-session notes. Check out what they had to say!", + mentee.fcmToken + ); + } + } else if (noteDoc.type === "postMentee") sessionDoc.postSessionMenteeCompleted = true; if (missedNote && sessionDoc.missedSessionReason == null) sessionDoc.missedSessionReason = missedReason; diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 1eb151cd..5818acbc 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -1,4 +1,6 @@ import * as admin from "firebase-admin"; +import schedule from "node-schedule"; +import { Mentee, Mentor, Session } from "../models"; async function sendNotification(title: string, body: string, deviceToken: string) { const message = { @@ -20,4 +22,51 @@ async function sendNotification(title: string, body: string, deviceToken: string }); } -export { sendNotification }; +async function startCronJob() { + let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); + const job = schedule.scheduleJob("*/5 * * * *", async () => { + try { + upcomingNotifSessions.forEach(async (session) => { + const dateNow = new Date(); + const mentee = await Mentee.findById(session.menteeId); + const mentor = await Mentor.findById(session.mentorId); + if (session.startTime.getTime() - dateNow.getTime() <= 86400 && mentee != null && mentor != null) { + if (session.preSessionCompleted) { + const menteeNotif = await sendNotification( + "You have an upcoming session.", + "Ready for your session with " + mentor.name + "in 24 hours? " + "\u{1F60E}", + mentee.fcmToken + ); + console.log("Function executed successfully:", menteeNotif); + const mentorNotif = await sendNotification( + "You have an upcoming session.", + "Ready for your session with " + mentee.name + "in 24 hours? " + "\u{1F60E}. Check out " + mentee.name + "'s pre-session notes.", + mentor.fcmToken + ); + console.log("Function executed successfully:", mentorNotif); + } else { + const menteeNotif = await sendNotification( + "You have an upcoming session.", + "Ready for your session with " + mentor.name + "in 24 hours? " + "\u{1F60E}. Fill out your pre-session notes now!", + mentee.fcmToken + ); + console.log("Function executed successfully:", menteeNotif); + const mentorNotif = await sendNotification( + "You have an upcoming session.", + "Ready for your session with " + mentee.name + "in 24 hours? " + "\u{1F60E}", + mentor.fcmToken + ); + console.log("Function executed successfully:", mentorNotif); + } + + session.upcomingSessionNotifSent = true; + } + }); + console.log(job); + } catch (error) { + console.error("Error executing function:", error); + } + }); +} + +export { sendNotification, startCronJob }; From 065ac5d9f0d1a1be8797ed6482be3bf630ebd069 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Wed, 31 May 2023 12:05:33 -0700 Subject: [PATCH 11/22] just need to fix setting the flag to true --- backend/src/app.ts | 3 +++ backend/src/routes/sessions.ts | 12 ++++++++---- backend/src/services/notifications.ts | 28 +++++++++++++++------------ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 2dd5393a..960e6915 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -11,6 +11,7 @@ import { errorHandler } from "./errors/handler"; import { Pairing } from "./models/pairing"; import { calendlyPage } from "./routes/calendlyPage"; +import { startCronJob } from "./services/notifications"; /** * Express server application class. * @description Will later contain the routing system. @@ -45,3 +46,5 @@ server.app.use(errorHandler); // This handler is reached whenever there is some // make server listen on some port server.app.listen(port, () => console.log(`> Listening on port ${port}`)); // eslint-disable-line no-console + +startCronJob(); \ No newline at end of file diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 219dd550..83d8c1a8 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -57,6 +57,9 @@ router.post( } const accessToken = mentor.personalAccessToken; const data = await getCalendlyEventDate(req.body.calendlyURI, accessToken); + const startDate = new Date(2023, 0O5, 0O5, 17, 0, 0, 0); + const endDate = new Date(2023, 0O5, 0O5, 18, 0, 0, 0); + const session = new Session({ preSession: null, postSessionMentee: null, @@ -64,8 +67,8 @@ router.post( menteeId: uid, mentorId, missedSessionReason: null, - startTime: data.resource.start_time, - endTime: data.resource.end_time, + startTime: data.resource?.start_time ?? startDate, + endTime: data.resource?.end_time ?? endDate, calendlyUri: req.body.calendlyURI, preSessionCompleted: false, postSessionMentorCompleted: false, @@ -85,14 +88,14 @@ router.post( await sendNotification( "New session booked!", "You have a new session with " + mentee.name + ". Check out your session details \u{1F60E}", - mentor.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken ); await sendNotification( "New session booked!", "You have a new session with " + mentor.name + ". Fill out your pre-session notes now \u{1F60E}", - mentee.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken ); return res.status(201).json({ @@ -102,6 +105,7 @@ router.post( }); } catch (e) { next(); + console.log(e); return res.status(400).json({ error: e, }); diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 5818acbc..2631d233 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -23,46 +23,50 @@ async function sendNotification(title: string, body: string, deviceToken: string } async function startCronJob() { + console.log("inside cron job function"); let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); - const job = schedule.scheduleJob("*/5 * * * *", async () => { + const job = schedule.scheduleJob("*/1 * * * *", async () => { try { upcomingNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); const mentor = await Mentor.findById(session.mentorId); - if (session.startTime.getTime() - dateNow.getTime() <= 86400 && mentee != null && mentor != null) { + if (session.startTime.getTime() - dateNow.getTime() <= 86400000 && mentee != null && mentor != null) { if (session.preSessionCompleted) { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + "in 24 hours? " + "\u{1F60E}", - mentee.fcmToken + "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}", + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + "in 24 hours? " + "\u{1F60E}. Check out " + mentee.name + "'s pre-session notes.", - mentor.fcmToken + "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}. Check out " + mentee.name + "'s pre-session notes.", + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } else { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + "in 24 hours? " + "\u{1F60E}. Fill out your pre-session notes now!", - mentee.fcmToken + "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}. Fill out your pre-session notes now!", + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + "in 24 hours? " + "\u{1F60E}", - mentor.fcmToken + "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}", + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + //mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } - - session.upcomingSessionNotifSent = true; } + session.upcomingSessionNotifSent = true; + console.log("should have updated session flag"); }); console.log(job); + } catch (error) { console.error("Error executing function:", error); } From 2e5a71657ceeceb433c5202aba5bf857901a985c Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Wed, 31 May 2023 14:09:50 -0700 Subject: [PATCH 12/22] finished upcoming notification sending --- backend/src/services/notifications.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 2631d233..59b184c7 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -35,34 +35,35 @@ async function startCronJob() { if (session.preSessionCompleted) { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}", + "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}. Check out " + mentee.name + "'s pre-session notes.", + "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}. Check out " + mentee.name + "'s pre-session notes.", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } else { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}. Fill out your pre-session notes now!", + "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}. Fill out your pre-session notes now!", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}", + "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } + session.upcomingSessionNotifSent = true; + await session.save(); } - session.upcomingSessionNotifSent = true; console.log("should have updated session flag"); }); console.log(job); From bbe452a0be08b99a97c6def109b9501eb970ebee Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Wed, 31 May 2023 19:36:44 -0700 Subject: [PATCH 13/22] set up notifications for after session is completed --- backend/src/app.ts | 5 ++- backend/src/services/notifications.ts | 58 ++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 960e6915..47ba107d 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -11,7 +11,7 @@ import { errorHandler } from "./errors/handler"; import { Pairing } from "./models/pairing"; import { calendlyPage } from "./routes/calendlyPage"; -import { startCronJob } from "./services/notifications"; +import { startUpcomingSessionCronJob, startPostSessionCronJob } from "./services/notifications"; /** * Express server application class. * @description Will later contain the routing system. @@ -47,4 +47,5 @@ server.app.use(errorHandler); // This handler is reached whenever there is some // make server listen on some port server.app.listen(port, () => console.log(`> Listening on port ${port}`)); // eslint-disable-line no-console -startCronJob(); \ No newline at end of file +startUpcomingSessionCronJob(); +startPostSessionCronJob(); \ No newline at end of file diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 59b184c7..3278bea7 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -2,6 +2,16 @@ import * as admin from "firebase-admin"; import schedule from "node-schedule"; import { Mentee, Mentor, Session } from "../models"; +/** + * Unicode notes: + * eyes - 1F440 + * cool with glasses - 1F60E + * + * To find emojis and their unicode value, use https://apps.timwhitlock.info/emoji/tables/unicode + * The unicode is in format "U+1234"; in code, use "\u{1234}" + */ + + async function sendNotification(title: string, body: string, deviceToken: string) { const message = { notification: { @@ -22,10 +32,10 @@ async function sendNotification(title: string, body: string, deviceToken: string }); } -async function startCronJob() { - console.log("inside cron job function"); + +async function startUpcomingSessionCronJob() { let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); - const job = schedule.scheduleJob("*/1 * * * *", async () => { + const job = schedule.scheduleJob("*/5 * * * *", async () => { try { upcomingNotifSessions.forEach(async (session) => { const dateNow = new Date(); @@ -35,27 +45,27 @@ async function startCronJob() { if (session.preSessionCompleted) { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}", + "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}. Check out " + mentee.name + "'s pre-session notes.", + "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}. Check out " + mentee.name + "'s pre-session notes.", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } else { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F60E}. Fill out your pre-session notes now!", + "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}. Fill out your pre-session notes now!", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F60E}", + "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken ); @@ -64,14 +74,42 @@ async function startCronJob() { session.upcomingSessionNotifSent = true; await session.save(); } - console.log("should have updated session flag"); }); - console.log(job); + } catch (error) { + console.error("Error executing function:", error); + } + }); +} +async function startPostSessionCronJob() { + let postNotifSessions = await Session.find({ postSessionNotifSent: { $eq: false } }); + const job = schedule.scheduleJob("*/5 * * * *", async () => { + try { + postNotifSessions.forEach(async (session) => { + const dateNow = new Date(); + const mentee = await Mentee.findById(session.menteeId); + const mentor = await Mentor.findById(session.mentorId); + if (dateNow.getTime() - session.endTime.getTime() >= 0 && mentee != null && mentor != null) { + // mentee notification + await sendNotification( + "\u{2705} Session complete!", + "How did your session with " + mentor.name + " go? Jot it down in your post-session notes.", + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken + ); + // mentor notification + await sendNotification( + "\u{2705} Session complete!", + "How did your session with " + mentee.name + " go? Jot it down in your post-session notes.", + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken + ); + session.postSessionNotifSent = true; + await session.save(); + } + }); } catch (error) { console.error("Error executing function:", error); } }); } -export { sendNotification, startCronJob }; +export { sendNotification, startUpcomingSessionCronJob, startPostSessionCronJob }; From 9d449ccd603a7507143fa62ff834e5d82eb688d5 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Thu, 1 Jun 2023 15:01:01 -0700 Subject: [PATCH 14/22] minor changes with error handling --- backend/src/routes/sessions.ts | 20 ++++++++++++-- backend/src/services/note.ts | 40 +++++++++++++++------------ backend/src/services/notifications.ts | 29 +++++++++++++------ 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 346eb82f..df2876de 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -82,14 +82,14 @@ router.post( await sendNotification( "New session booked!", "You have a new session with " + mentee.name + ". Check out your session details \u{1F60E}", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken + mentor.fcmToken ); await sendNotification( "New session booked!", "You have a new session with " + mentor.name + ". Fill out your pre-session notes now \u{1F60E}", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken + mentee.fcmToken ); return res.status(201).json({ @@ -307,6 +307,10 @@ router.patch( if (!mentor) { throw InternalError.ERROR_GETTING_MENTOR; } + const mentee = await Mentee.findById(currSession.menteeId); + if (!mentee) { + throw InternalError.ERROR_GETTING_MENTEE; + } const personalAccessToken = mentor.personalAccessToken; await deleteCalendlyEvent(oldCalendlyURI, personalAccessToken); const newEventData = await getCalendlyEventDate(newCalendlyURI, personalAccessToken); @@ -316,6 +320,16 @@ router.patch( calendlyUri: newCalendlyURI, }; await Session.findByIdAndUpdate(sessionId, { $set: updates }, { new: true }); + await sendNotification( + "A session has been rescheduled", + "Your upcoming session with " + mentor.name + " has been rescheduled! Check out your new session details.", + mentee.fcmToken + ) + await sendNotification( + "A session has been rescheduled", + "" + mentee.name + " has rescheduled your upcoming session! Check out your session details.", + mentor.fcmToken + ) return res.status(200).json({ message: "Successfuly updated the session!", }); @@ -343,6 +357,8 @@ router.delete( const mentor = await Mentor.findById(session.mentorId); if (!mentor) throw ServiceError.MENTOR_WAS_NOT_FOUND; const personalAccessToken = mentor.personalAccessToken; + const mentee = await Mentee.findById(session.menteeId); + if (!mentee) throw ServiceError.MENTEE_WAS_NOT_FOUND; try { deleteCalendlyEvent(uri, personalAccessToken); await deleteNotes(session.preSession, session.postSessionMentee, session.postSessionMentor); diff --git a/backend/src/services/note.ts b/backend/src/services/note.ts index 8da708a6..f9d13daf 100644 --- a/backend/src/services/note.ts +++ b/backend/src/services/note.ts @@ -5,7 +5,7 @@ import postSessionQuestions from "../models/postQuestionsList.json"; import { Note } from "../models/notes"; import { AnswerType, QuestionType, UpdateNoteDetailsType } from "../types/notes"; import { Session } from "../models/session"; -import { ServiceError } from "../errors"; +import { InternalError, ServiceError } from "../errors"; import { Mentee, Mentor } from "../models"; import { sendNotification } from "./notifications"; @@ -138,35 +138,39 @@ async function updateNotes(updatedNotes: UpdateNoteDetailsType[], documentId: st // mongoose does not notice the change so ignores saving it unless we manually mark noteDoc.markModified("answers"); const sessionDoc = await Session.findById(noteDoc.session); - const mentee = await Mentee.findById(sessionDoc?.menteeId); - const mentor = await Mentor.findById(sessionDoc?.mentorId); + if (!sessionDoc) { + throw InternalError.ERROR_GETTING_SESSION; + } + const mentee = await Mentee.findById(sessionDoc.menteeId); + if (!mentee) { + throw InternalError.ERROR_GETTING_MENTEE; + } + const mentor = await Mentor.findById(sessionDoc.mentorId); + if (!mentor) { + throw InternalError.ERROR_GETTING_MENTOR; + } if (sessionDoc != null) { if (noteDoc.type === "pre") { sessionDoc.preSessionCompleted = true; - if (mentee != null && mentor != null) { - await sendNotification( - "Pre-session update!", - "Looks like " + mentee.name + " has some questions for you. Check out " + mentee.name + "'s pre-session notes.", - mentor.fcmToken - ); - } + await sendNotification( + "Pre-session update!", + "Looks like " + mentee.name + " has some questions for you. Check out " + mentee.name + "'s pre-session notes.", + mentor.fcmToken + ); } else if (noteDoc.type === "postMentor") { sessionDoc.postSessionMentorCompleted = true; - if (mentee != null && mentor != null) { - await sendNotification( - "Post-session update!", - "" + mentor.name + " has updated their post-session notes. Check out what they had to say!", - mentee.fcmToken - ); - } + await sendNotification( + "Post-session update!", + "" + mentor.name + " has updated their post-session notes. Check out what they had to say!", + mentee.fcmToken + ); } else if (noteDoc.type === "postMentee") sessionDoc.postSessionMenteeCompleted = true; if (missedNote && sessionDoc.missedSessionReason == null) sessionDoc.missedSessionReason = missedReason; await sessionDoc.save(); } - console.log(noteDoc); return await noteDoc.save(); } catch (error) { throw ServiceError.NOTE_WAS_NOT_SAVED; diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 3278bea7..2ff1dbca 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -1,6 +1,7 @@ import * as admin from "firebase-admin"; import schedule from "node-schedule"; import { Mentee, Mentor, Session } from "../models"; +import { InternalError } from "../errors"; /** * Unicode notes: @@ -40,33 +41,39 @@ async function startUpcomingSessionCronJob() { upcomingNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); + if (!mentee) { + throw InternalError.ERROR_GETTING_MENTEE; + } const mentor = await Mentor.findById(session.mentorId); - if (session.startTime.getTime() - dateNow.getTime() <= 86400000 && mentee != null && mentor != null) { + if (!mentor) { + throw InternalError.ERROR_GETTING_MENTOR; + } + if (session.startTime.getTime() - dateNow.getTime() <= 86400000) { if (session.preSessionCompleted) { const menteeNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken + mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}. Check out " + mentee.name + "'s pre-session notes.", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken + mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } else { const menteeNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}. Fill out your pre-session notes now!", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + mentee.fcmToken //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + mentor.fcmToken //mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); @@ -88,19 +95,25 @@ async function startPostSessionCronJob() { postNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); + if (!mentee) { + throw InternalError.ERROR_GETTING_MENTEE; + } const mentor = await Mentor.findById(session.mentorId); - if (dateNow.getTime() - session.endTime.getTime() >= 0 && mentee != null && mentor != null) { + if (!mentor) { + throw InternalError.ERROR_GETTING_MENTOR; + } + if (dateNow.getTime() - session.endTime.getTime() >= 0) { // mentee notification await sendNotification( "\u{2705} Session complete!", "How did your session with " + mentor.name + " go? Jot it down in your post-session notes.", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken + mentee.fcmToken ); // mentor notification await sendNotification( "\u{2705} Session complete!", "How did your session with " + mentee.name + " go? Jot it down in your post-session notes.", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken + mentor.fcmToken ); session.postSessionNotifSent = true; await session.save(); From fc033bfbccedfd716085bc2d75d0a261910cdd19 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Thu, 1 Jun 2023 22:54:57 -0700 Subject: [PATCH 15/22] updated fcm token route + added notification instances for deleted session --- ALUM/ALUM/Models/CurrentUserModel.swift | 2 +- ALUM/ALUM/Services/APIConfig.swift | 24 +++++++------- backend/src/routes/sessions.ts | 24 ++++++++++++++ backend/src/routes/user.ts | 42 ++++++++----------------- backend/src/services/notifications.ts | 22 +++++++------ backend/src/types/cakes.ts | 9 ++---- 6 files changed, 63 insertions(+), 60 deletions(-) diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index c74c7ef0..029cb402 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -145,7 +145,7 @@ class CurrentUserModel: ObservableObject { func sendFcmToken(fcmToken: String) async throws { var tokenToSend: FCMToken = FCMToken(fcmToken: fcmToken) - let route = self.role == .mentor ? APIRoute.patchMentor(userId: self.uid ?? "") : APIRoute.patchMentee(userId: self.uid ?? "") + let route = APIRoute.patchUser(userId: self.uid ?? "") var request = try await route.createURLRequest() guard let jsonData = try? JSONEncoder().encode(tokenToSend) else { throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data") diff --git a/ALUM/ALUM/Services/APIConfig.swift b/ALUM/ALUM/Services/APIConfig.swift index a548c370..5ba9a59d 100644 --- a/ALUM/ALUM/Services/APIConfig.swift +++ b/ALUM/ALUM/Services/APIConfig.swift @@ -23,8 +23,7 @@ enum APIRoute { case getMentee(userId: String) case postMentor case postMentee - case patchMentor(userId: String) - case patchMentee(userId: String) + case patchUser(userId: String) case getCalendly case getNote(noteId: String) @@ -60,10 +59,8 @@ enum APIRoute { return URLString.sessions case .getCalendly: return URLString.calendly - case .patchMentor(let userId): - return [URLString.mentor, userId].joined(separator: "/") - case .patchMentee(let userId): - return [URLString.mentor, userId].joined(separator: "/") + case .patchUser(let userId): + return [URLString.user, userId].joined(separator: "/") case .deleteSession(sessionId: let sessionId): return [URLString.sessions, sessionId].joined(separator: "/") case .patchSession(sessionId: let sessionId): @@ -77,7 +74,7 @@ enum APIRoute { return "GET" case .postMentor, .postMentee, .postSession: return "POST" - case .patchNote, .patchMentee, .patchMentor, .patchSession: + case .patchNote, .patchUser, .patchSession: return "PATCH" case .deleteSession: return "DELETE" @@ -96,8 +93,7 @@ enum APIRoute { .getSessions, .postSession, .getCalendly, - .patchMentee, - .patchMentor, + .patchUser, .patchSession, .deleteSession: return true @@ -121,8 +117,7 @@ enum APIRoute { var successCode: Int { switch self { case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, - .getSession, .getSessions, .getCalendly, .patchMentee, - .patchMentor, .deleteSession, .patchSession: + .getSession, .getSessions, .getCalendly, .patchUser, .deleteSession, .patchSession: return 200 // 200 Ok case .postMentor, .postMentee, .postSession: return 201 // 201 Created @@ -135,8 +130,8 @@ enum APIRoute { switch self { case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, - .getSession, .getSessions, .getCalendly, .patchMentor, - .patchMentee, .deleteSession, .patchSession: + .getSession, .getSessions, .getCalendly, .patchUser, + .deleteSession, .patchSession: errorMap = [ 401: AppError.actionable(.authenticationError, message: labeledMessage), 400: AppError.internalError(.invalidRequest, message: labeledMessage), @@ -147,5 +142,8 @@ enum APIRoute { 400: AppError.internalError(.invalidRequest, message: labeledMessage) ] } + + let error = errorMap[statusCode] ?? AppError.internalError(.unknownError, message: labeledMessage) + return error } } diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index df2876de..1609b93f 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -350,6 +350,7 @@ router.delete( [verifyAuthToken], async (req: Request, res: Response, next: NextFunction) => { console.log("Deleting a session"); + const role = req.body.role; const sessionId = req.params.sessionId; const session = await Session.findById(sessionId); if (!session) throw ServiceError.SESSION_WAS_NOT_FOUND; @@ -363,6 +364,29 @@ router.delete( deleteCalendlyEvent(uri, personalAccessToken); await deleteNotes(session.preSession, session.postSessionMentee, session.postSessionMentor); await Session.findByIdAndDelete(sessionId); + if (role === "mentee") { + await sendNotification( + "A session has been cancelled.", + "Your session with " + mentor.name + " has been cancelled.", + mentee.fcmToken + ) + await sendNotification( + "A session has been cancelled.", + "" + mentee.name + " has cancelled your upcoming session.", + mentor.fcmToken + ) + } else if (role === "mentor") { + await sendNotification( + "A session has been cancelled.", + "Your session with " + mentee.name + " has been cancelled.", + mentor.fcmToken + ) + await sendNotification ( + "A session has been cancelled.", + "" + mentor.name + " has cancelled your upcoming session. \u{1F494} Reschedule to save your pre-session notes.", + mentee.fcmToken + ) + } return res.status(200).json({ message: "calendly successfully cancelled, notes deleted, session deleted.", }); diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 034264f4..0a8cdcbd 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -14,8 +14,7 @@ import { CreateMentorRequestBodyCake, CreateMenteeRequestBodyType, CreateMentorRequestBodyType, - UpdateMentorCake, - UpdateMenteeCake, + UpdateUserCake, } from "../types"; import { ValidationError } from "../errors/validationError"; import { InternalError } from "../errors/internal"; @@ -374,10 +373,11 @@ router.get( } ); -type UpdateMentorType = Infer; +type UpdateUserType = Infer router.patch( - "/mentor/:userId", - validateReqBodyWithCake(UpdateMentorCake), + "/user/:userId", + [verifyAuthToken], + validateReqBodyWithCake(UpdateUserCake), async (req: Request, res: Response, next: NextFunction) => { try { console.log(req.body); @@ -385,30 +385,13 @@ router.patch( if (!mongoose.Types.ObjectId.isValid(userId)) { throw ServiceError.INVALID_MONGO_ID; } - const updatedToken: UpdateMentorType = req.body; - await updateMentorFCMToken(updatedToken.fcmToken, userId); - res.status(200).json({ - message: "Success", - }); - } catch (e) { - next(e); - } - } -); - -type UpdateMenteeType = Infer; -router.patch( - "/mentee/:userId", - validateReqBodyWithCake(UpdateMenteeCake), - async (req: Request, res: Response, next: NextFunction) => { - try { - console.log(req.body); - const userId = req.params.userId; - if (!mongoose.Types.ObjectId.isValid(userId)) { - throw ServiceError.INVALID_MONGO_ID; + const role = req.body.role; + const updatedToken = req.body.fcmToken; + if (role === "mentee") { + await updateMenteeFCMToken(updatedToken, userId); + } else if (role === "mentor") { + await updateMentorFCMToken(updatedToken, userId); } - const updatedToken: UpdateMenteeType = req.body; - await updateMenteeFCMToken(updatedToken.fcmToken, userId); res.status(200).json({ message: "Success", }); @@ -416,7 +399,8 @@ router.patch( next(e); } } -); +) + /** * Route to setup mobile app for any logged in user (mentor or mentee) * diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 2ff1dbca..1ef6a028 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -7,6 +7,8 @@ import { InternalError } from "../errors"; * Unicode notes: * eyes - 1F440 * cool with glasses - 1F60E + * check mark - 2705 + * broken heart - 1F494 * * To find emojis and their unicode value, use https://apps.timwhitlock.info/emoji/tables/unicode * The unicode is in format "U+1234"; in code, use "\u{1234}" @@ -35,9 +37,9 @@ async function sendNotification(title: string, body: string, deviceToken: string async function startUpcomingSessionCronJob() { - let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); - const job = schedule.scheduleJob("*/5 * * * *", async () => { + const job = schedule.scheduleJob("*/1 * * * *", async () => { try { + let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); upcomingNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); @@ -53,27 +55,27 @@ async function startUpcomingSessionCronJob() { const menteeNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}", - mentee.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}. Check out " + mentee.name + "'s pre-session notes.", - mentor.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); console.log("Function executed successfully:", mentorNotif); } else { const menteeNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}. Fill out your pre-session notes now!", - mentee.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}", - mentor.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" //mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); @@ -89,9 +91,9 @@ async function startUpcomingSessionCronJob() { } async function startPostSessionCronJob() { - let postNotifSessions = await Session.find({ postSessionNotifSent: { $eq: false } }); - const job = schedule.scheduleJob("*/5 * * * *", async () => { + const job = schedule.scheduleJob("*/1 * * * *", async () => { try { + let postNotifSessions = await Session.find({ postSessionNotifSent: { $eq: false } }); postNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); @@ -107,13 +109,13 @@ async function startPostSessionCronJob() { await sendNotification( "\u{2705} Session complete!", "How did your session with " + mentor.name + " go? Jot it down in your post-session notes.", - mentee.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); // mentor notification await sendNotification( "\u{2705} Session complete!", "How did your session with " + mentee.name + " go? Jot it down in your post-session notes.", - mentor.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); session.postSessionNotifSent = true; await session.save(); diff --git a/backend/src/types/cakes.ts b/backend/src/types/cakes.ts index 01e35e3f..dd0b1a8c 100644 --- a/backend/src/types/cakes.ts +++ b/backend/src/types/cakes.ts @@ -36,13 +36,8 @@ export const CreateMentorRequestBodyCake = bake({ calendlyLink: string, }); -// PATCH mentor/id -export const UpdateMentorCake = bake({ - fcmToken: string, -}); - -// PATCH mentee/id -export const UpdateMenteeCake = bake({ +// PATCH user/id +export const UpdateUserCake = bake({ fcmToken: string, }); From c66e2467f2d92f4abf82c234928681b3a4ead2b9 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:21:29 -0700 Subject: [PATCH 16/22] temp commit for notification system - still need to work on this --- backend/src/errors/service.ts | 2 -- backend/src/routes/sessions.ts | 2 +- backend/src/routes/user.ts | 7 +++---- backend/src/services/notifications.ts | 14 +++++++------- backend/src/services/user.ts | 4 ++-- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 2764080c..63c59334 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -19,8 +19,6 @@ const NOTE_WAS_NOT_FOUND = "Note was not found"; const NOTE_WAS_NOT_SAVED = "Note was not saved"; const INVALID_URI = "Calendly URI is invalid. Check formatting of URI string"; const ERROR_GETTING_EVENT_DATA = "There was an error retrieving the calendly event data"; -const MENTOR_WAS_NOT_SAVED = "Mentor update was not saved"; -const MENTEE_WAS_NOT_SAVED = "Mentee update was not saved"; const ERROR_DELETING_EVENT = "There was an error deleting a calendly event"; const INVALID_ROLE_WAS_FOUND = "Allowed user roles for this context is mentor and mentee only"; diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 1609b93f..4fc64e73 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -4,7 +4,7 @@ import express, { NextFunction, Request, Response } from "express"; import mongoose, { ObjectId } from "mongoose"; -import schedule from "node-schedule"; +// import schedule from "node-schedule"; // import { boolean } from "caketype"; import { validateReqBodyWithCake } from "../middleware/validation"; import { Mentor, Mentee, Session } from "../models"; diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index b0fc3567..fb8c6830 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -3,12 +3,12 @@ * new users */ import express, { NextFunction, Request, Response } from "express"; -import { Infer } from "caketype"; +// import { Infer } from "caketype"; import mongoose from "mongoose"; import { validateReqBodyWithCake } from "../middleware/validation"; import { Mentee, Mentor, Pairing } from "../models"; import { createUser } from "../services/auth"; -import { getMenteeId, getMentorId, updateMentor, updateMentee } from "../services/user"; +import { getMenteeId, getMentorId, updateMentor, updateMentee, updateMentorFCMToken, updateMenteeFCMToken } from "../services/user"; import { CreateMenteeRequestBodyCake, CreateMentorRequestBodyCake, @@ -26,7 +26,6 @@ import { ServiceError } from "../errors/service"; import { verifyAuthToken } from "../middleware/auth"; import { defaultImageID } from "../config"; import { CustomError } from "../errors"; -import { updateMentorFCMToken, updateMenteeFCMToken } from "../services/user"; import { AuthError } from "../errors/auth"; import { getUpcomingSession, getLastSession } from "../services/session"; @@ -378,7 +377,7 @@ router.get( } ); -type UpdateUserType = Infer +// type UpdateUserType = Infer router.patch( "/user/:userId", [verifyAuthToken], diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 1ef6a028..95912576 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -37,9 +37,9 @@ async function sendNotification(title: string, body: string, deviceToken: string async function startUpcomingSessionCronJob() { - const job = schedule.scheduleJob("*/1 * * * *", async () => { + schedule.scheduleJob("*/1 * * * *", async () => { try { - let upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); + const upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); upcomingNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); @@ -54,7 +54,7 @@ async function startUpcomingSessionCronJob() { if (session.preSessionCompleted) { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}", + "Ready for your session with ${mentor.name} in 24 hours? \u{1F440}", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); console.log("Function executed successfully:", menteeNotif); @@ -69,14 +69,14 @@ async function startUpcomingSessionCronJob() { "You have an upcoming session.", "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}. Fill out your pre-session notes now!", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" - //mentee.fcmToken + // mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}", "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" - //mentor.fcmToken + // mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } @@ -91,9 +91,9 @@ async function startUpcomingSessionCronJob() { } async function startPostSessionCronJob() { - const job = schedule.scheduleJob("*/1 * * * *", async () => { + schedule.scheduleJob("*/1 * * * *", async () => { try { - let postNotifSessions = await Session.find({ postSessionNotifSent: { $eq: false } }); + const postNotifSessions = await Session.find({ postSessionNotifSent: { $eq: false } }); postNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index 612faea0..e3fd640b 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -4,12 +4,12 @@ // import { Request } from "express"; // import mongoose from "mongoose"; // import { Image } from "../models/image"; +import { Request } from "express"; +import mongoose from "mongoose"; import { InternalError, ServiceError } from "../errors"; import { Mentor, Mentee } from "../models"; import { Pairing } from "../models/pairing"; // import { User } from "../models/users"; -import { Request } from "express"; -import mongoose from "mongoose"; import { Image } from "../models/image"; import { UpdateMenteeRequestBodyType, UpdateMentorRequestBodyType } from "../types"; import { validateCalendlyAccessToken, validateCalendlyLink } from "./calendly"; From 6c95d61ea36e49a0d0a1cb43c5263729c2f4a5fd Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Thu, 19 Oct 2023 22:23:38 -0700 Subject: [PATCH 17/22] sending FCM token to back end works --- ALUM/ALUM/ALUMApp.swift | 3 ++- ALUM/ALUM/Models/CurrentUserModel.swift | 6 ++++++ backend/src/routes/user.ts | 5 ++++- backend/src/services/notifications.ts | 11 +++++++---- backend/src/types/cakes.ts | 2 ++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index 08ac68b5..f88bb3c6 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -89,6 +89,7 @@ extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { // let deviceToken:[String: String] = ["token": fcmToken ?? ""] currentUser.fcmToken = fcmToken + // currentUser.fcmToken = Messaging.messaging().fcmToken print(fcmToken) } } @@ -113,7 +114,7 @@ extension AppDelegate : UNUserNotificationCenterDelegate { } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - + Messaging.messaging().apnsToken = deviceToken } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index d5f599a1..18030c8e 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -67,6 +67,7 @@ class CurrentUserModel: ObservableObject { self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil) } else { try await self.setFromFirebaseUser(user: user!) + try await sendFcmToken(fcmToken: fcmToken!) } } catch { // in case setFromFirebaseUser fails, just make the user login again @@ -161,10 +162,15 @@ class CurrentUserModel: ObservableObject { } func sendFcmToken(fcmToken: String) async throws { + print(fcmToken) + print(self.uid) var tokenToSend: FCMToken = FCMToken(fcmToken: fcmToken) let route = APIRoute.patchUser(userId: self.uid ?? "") var request = try await route.createURLRequest() guard let jsonData = try? JSONEncoder().encode(tokenToSend) else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data") } request.httpBody = jsonData diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index ee1f80ef..82a295de 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -435,13 +435,15 @@ router.patch( validateReqBodyWithCake(UpdateUserCake), async (req: Request, res: Response, next: NextFunction) => { try { - console.log(req.body); + console.log("Starting patch"); const userId = req.params.userId; if (!mongoose.Types.ObjectId.isValid(userId)) { throw ServiceError.INVALID_MONGO_ID; } + console.log("user id is valid"); const role = req.body.role; const updatedToken = req.body.fcmToken; + console.log("got information"); if (role === "mentee") { await updateMenteeFCMToken(updatedToken, userId); } else if (role === "mentor") { @@ -451,6 +453,7 @@ router.patch( message: "Success", }); } catch (e) { + console.log("error"); next(e); } } diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 95912576..2517b0f4 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -55,12 +55,14 @@ async function startUpcomingSessionCronJob() { const menteeNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with ${mentor.name} in 24 hours? \u{1F440}", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + mentee.fcmToken + // "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}. Check out " + mentee.name + "'s pre-session notes.", + // mentor.fcmToken "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); console.log("Function executed successfully:", mentorNotif); @@ -68,15 +70,16 @@ async function startUpcomingSessionCronJob() { const menteeNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}. Fill out your pre-session notes now!", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" - // mentee.fcmToken + mentee.fcmToken + // "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + // ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" // mentor.fcmToken + "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); console.log("Function executed successfully:", mentorNotif); } diff --git a/backend/src/types/cakes.ts b/backend/src/types/cakes.ts index 177efab1..50d2ead0 100644 --- a/backend/src/types/cakes.ts +++ b/backend/src/types/cakes.ts @@ -39,6 +39,8 @@ export const CreateMentorRequestBodyCake = bake({ // PATCH user/id export const UpdateUserCake = bake({ fcmToken: string, + uid: string, + role: string }); export const UpdateMentorRequestBodyCake = bake({ name: string, From 84771c00ebc4c0efb90781539586fa8381a28007 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:24:07 -0700 Subject: [PATCH 18/22] last bugs with fcm token --- ALUM/ALUM/Models/CurrentUserModel.swift | 28 ++++++++++++++++++++++++- ALUM/ALUM/Services/APIConfig.swift | 4 +++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index 18030c8e..690f2733 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -67,7 +67,23 @@ class CurrentUserModel: ObservableObject { self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil) } else { try await self.setFromFirebaseUser(user: user!) - try await sendFcmToken(fcmToken: fcmToken!) + // try await sendFcmToken(fcmToken: fcmToken!) + Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in + if let token = self.fcmToken { + /* + self.sendFcmTokenHelper(fcmToken: token) + timer.invalidate() + */ + Task { + do { + try await self.sendFcmToken(fcmToken: token) + } catch { + print("Error in sending FCM Token") + } + } + timer.invalidate() + } + } } } catch { // in case setFromFirebaseUser fails, just make the user login again @@ -161,6 +177,16 @@ class CurrentUserModel: ObservableObject { return userStatus } + func sendFcmTokenHelper(fcmToken: String) { + Task { + do { + try await sendFcmToken(fcmToken: fcmToken) + } catch { + print("Error in sending FCM Token") + } + } + } + func sendFcmToken(fcmToken: String) async throws { print(fcmToken) print(self.uid) diff --git a/ALUM/ALUM/Services/APIConfig.swift b/ALUM/ALUM/Services/APIConfig.swift index 86387350..9e9169fb 100644 --- a/ALUM/ALUM/Services/APIConfig.swift +++ b/ALUM/ALUM/Services/APIConfig.swift @@ -11,12 +11,14 @@ let developmentMode = false // Firebase URL will not be updated very frequently because // so your changes to backend will only reflect on localhost until deployed +/* #if DEBUG let baseURL = "http://127.0.0.1:3000" #else let baseURL = "https://firebaseapp-ozybc5bsma-uc.a.run.app" #endif - +*/ +let baseURL = "http://100.115.53.55:3000" struct URLString { static let user = "\(baseURL)/user" static let mentor = "\(baseURL)/mentor" From 87a3bc0cacaae9dae4fd5b58a68b4e86371370a4 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Fri, 27 Oct 2023 07:21:33 -0700 Subject: [PATCH 19/22] sending FCM token works --- ALUM/ALUM/ALUMApp.swift | 1 + ALUM/ALUM/Models/CurrentUserModel.swift | 18 +----------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index f88bb3c6..8dc1368c 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -87,6 +87,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + print("did receive FCM Token") // let deviceToken:[String: String] = ["token": fcmToken ?? ""] currentUser.fcmToken = fcmToken // currentUser.fcmToken = Messaging.messaging().fcmToken diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index 690f2733..881bec49 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -67,23 +67,7 @@ class CurrentUserModel: ObservableObject { self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil) } else { try await self.setFromFirebaseUser(user: user!) - // try await sendFcmToken(fcmToken: fcmToken!) - Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in - if let token = self.fcmToken { - /* - self.sendFcmTokenHelper(fcmToken: token) - timer.invalidate() - */ - Task { - do { - try await self.sendFcmToken(fcmToken: token) - } catch { - print("Error in sending FCM Token") - } - } - timer.invalidate() - } - } + try await sendFcmToken(fcmToken: fcmToken!) } } catch { // in case setFromFirebaseUser fails, just make the user login again From 36656e7ff9404ad5df7360d95556a3c323f14330 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Fri, 27 Oct 2023 07:36:20 -0700 Subject: [PATCH 20/22] notification system working - next steps: make sure all users in the database have fcm tokens, and all sessions have flags for notifSent --- backend/src/services/notifications.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 2517b0f4..6dcfcd98 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -56,14 +56,12 @@ async function startUpcomingSessionCronJob() { "You have an upcoming session.", "Ready for your session with ${mentor.name} in 24 hours? \u{1F440}", mentee.fcmToken - // "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}. Check out " + mentee.name + "'s pre-session notes.", - // mentor.fcmToken - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } else { @@ -71,15 +69,12 @@ async function startUpcomingSessionCronJob() { "You have an upcoming session.", "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}. Fill out your pre-session notes now!", mentee.fcmToken - // "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" - // ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}", - // mentor.fcmToken - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } @@ -112,13 +107,13 @@ async function startPostSessionCronJob() { await sendNotification( "\u{2705} Session complete!", "How did your session with " + mentor.name + " go? Jot it down in your post-session notes.", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + mentee.fcmToken ); // mentor notification await sendNotification( "\u{2705} Session complete!", "How did your session with " + mentee.name + " go? Jot it down in your post-session notes.", - "dm8czbE_cUXvn3oQSveO2X:APA91bFXOMa7M-BcZpxShpUYm8XtfMUgN9IsnKA3uirE-yo3S3IvwsXWoYc-MgsvwZG3N4LQiw7LASZCA9F4iTIQkUKtA34vx3wMvBE2PbfVm0ZDX93VAaYqTjdFVbmyUhhCkf2fIY9M" + mentor.fcmToken ); session.postSessionNotifSent = true; await session.save(); From be32c74ff89ccd165366389c2499a13e86b6b989 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Sun, 29 Oct 2023 10:04:30 -0700 Subject: [PATCH 21/22] linting --- ALUM/ALUM/ALUMApp.swift | 18 ++-- ALUM/ALUM/Models/CurrentUserModel.swift | 4 +- ALUM/ALUM/Services/APIConfig.swift | 114 ++++++++++++------------ backend/src/errors/service.ts | 2 +- backend/src/routes/sessions.ts | 36 ++++---- backend/src/routes/user.ts | 11 ++- backend/src/services/note.ts | 16 ++-- backend/src/services/notifications.ts | 24 ++--- backend/src/services/user.ts | 10 ++- backend/src/types/cakes.ts | 2 +- 10 files changed, 132 insertions(+), 105 deletions(-) diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index 8dc1368c..3cc17704 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -13,13 +13,13 @@ import Firebase import UserNotifications import FirebaseMessaging -//class AppDelegate: NSObject, UIApplicationDelegate { +// class AppDelegate: NSObject, UIApplicationDelegate { // func application(_ application: UIApplication, -// didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { +// didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // FirebaseApp.configure() // return true // } -//} +// } extension View { func dismissKeyboardOnDrag() -> some View { @@ -42,12 +42,12 @@ struct ALUMApp: App { } } - class AppDelegate: NSObject, UIApplicationDelegate { let gcmMessageIDKey = "gcm.message_id" @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions + launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { FirebaseApp.configure() Messaging.messaging().delegate = self @@ -84,7 +84,6 @@ class AppDelegate: NSObject, UIApplicationDelegate { } } - extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { print("did receive FCM Token") @@ -96,12 +95,13 @@ extension AppDelegate: MessagingDelegate { } @available(iOS 10, *) -extension AppDelegate : UNUserNotificationCenterDelegate { +extension AppDelegate: UNUserNotificationCenterDelegate { // Receive displayed notifications for iOS 10 devices. func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + withCompletionHandler completionHandler: + @escaping (UNNotificationPresentationOptions) -> Void) { let userInfo = notification.request.content.userInfo if let messageID = userInfo[gcmMessageIDKey] { diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index 881bec49..4243209d 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -160,7 +160,7 @@ class CurrentUserModel: ObservableObject { } return userStatus } - + func sendFcmTokenHelper(fcmToken: String) { Task { do { @@ -170,7 +170,7 @@ class CurrentUserModel: ObservableObject { } } } - + func sendFcmToken(fcmToken: String) async throws { print(fcmToken) print(self.uid) diff --git a/ALUM/ALUM/Services/APIConfig.swift b/ALUM/ALUM/Services/APIConfig.swift index 9e9169fb..f8721b4a 100644 --- a/ALUM/ALUM/Services/APIConfig.swift +++ b/ALUM/ALUM/Services/APIConfig.swift @@ -11,14 +11,12 @@ let developmentMode = false // Firebase URL will not be updated very frequently because // so your changes to backend will only reflect on localhost until deployed -/* #if DEBUG let baseURL = "http://127.0.0.1:3000" #else let baseURL = "https://firebaseapp-ozybc5bsma-uc.a.run.app" #endif -*/ -let baseURL = "http://100.115.53.55:3000" + struct URLString { static let user = "\(baseURL)/user" static let mentor = "\(baseURL)/mentor" @@ -48,68 +46,68 @@ enum APIRoute { var url: String { switch self { - case .getSelf: - return [URLString.user, "me"].joined(separator: "/") - case .getMentor(let userId): - return [URLString.mentor, userId].joined(separator: "/") - case .getMentee(let userId): - return [URLString.mentee, userId].joined(separator: "/") - case .postMentor: - return URLString.mentor - case .postMentee: - return URLString.mentee - case .getNote(noteId: let noteId): - return [URLString.notes, noteId].joined(separator: "/") - case .patchNote(noteId: let noteId): - return [URLString.notes, noteId].joined(separator: "/") - case .getSession(sessionId: let sessionId): - return [URLString.sessions, sessionId].joined(separator: "/") - case .getSessions: - return URLString.sessions - case .postSession: - return URLString.sessions - case .getCalendly: - return URLString.calendly - case .patchUser(let userId): - return [URLString.user, userId].joined(separator: "/") - case .deleteSession(sessionId: let sessionId): - return [URLString.sessions, sessionId].joined(separator: "/") - case .patchSession(sessionId: let sessionId): - return [URLString.sessions, sessionId].joined(separator: "/") + case .getSelf: + return [URLString.user, "me"].joined(separator: "/") + case .getMentor(let userId): + return [URLString.mentor, userId].joined(separator: "/") + case .getMentee(let userId): + return [URLString.mentee, userId].joined(separator: "/") + case .postMentor: + return URLString.mentor + case .postMentee: + return URLString.mentee + case .getNote(noteId: let noteId): + return [URLString.notes, noteId].joined(separator: "/") + case .patchNote(noteId: let noteId): + return [URLString.notes, noteId].joined(separator: "/") + case .getSession(sessionId: let sessionId): + return [URLString.sessions, sessionId].joined(separator: "/") + case .getSessions: + return URLString.sessions + case .postSession: + return URLString.sessions + case .getCalendly: + return URLString.calendly + case .patchUser(let userId): + return [URLString.user, userId].joined(separator: "/") + case .deleteSession(sessionId: let sessionId): + return [URLString.sessions, sessionId].joined(separator: "/") + case .patchSession(sessionId: let sessionId): + return [URLString.sessions, sessionId].joined(separator: "/") } } var method: String { switch self { - case .getSelf, .getMentee, .getMentor, .getNote, .getSession, .getSessions, .getCalendly: - return "GET" - case .postMentor, .postMentee, .postSession: - return "POST" - case .patchNote, .patchUser, .patchSession: - return "PATCH" - case .deleteSession: - return "DELETE" + case .getSelf, .getMentee, .getMentor, .getNote, .getSession, .getSessions, .getCalendly: + return "GET" + case .postMentor, .postMentee, .postSession: + return "POST" + case .patchNote, .patchUser, .patchSession: + return "PATCH" + case .deleteSession: + return "DELETE" } } var requireAuth: Bool { switch self { - case - .getSelf, - .getMentor, - .getMentee, - .getNote, - .patchNote, - .getSession, - .getSessions, - .postSession, - .getCalendly, - .patchUser, - .patchSession, - .deleteSession: - return true - case .postMentee, .postMentor: - return false + case + .getSelf, + .getMentor, + .getMentee, + .getNote, + .patchNote, + .getSession, + .getSessions, + .postSession, + .getCalendly, + .patchUser, + .patchSession, + .deleteSession: + return true + case .postMentee, .postMentor: + return false } } @@ -127,7 +125,7 @@ enum APIRoute { var successCode: Int { switch self { - case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, + case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly, .patchUser, .deleteSession, .patchSession: return 200 // 200 Ok case .postMentor, .postMentee, .postSession: @@ -140,7 +138,7 @@ enum APIRoute { let errorMap: [Int: AppError] switch self { - case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, + case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly, .patchUser, .deleteSession, .patchSession: errorMap = [ @@ -157,7 +155,7 @@ enum APIRoute { 400: AppError.internalError(.invalidRequest, message: message) ] } - + let error = errorMap[statusCode] ?? AppError.internalError(.unknownError, message: labeledMessage) return error } diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 124893cd..2742e3c7 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -49,7 +49,7 @@ export class ServiceError extends CustomError { static MENTOR_WAS_NOT_SAVED = new ServiceError(11, 404, MENTOR_WAS_NOT_SAVED); static MENTEE_WAS_NOT_SAVED = new ServiceError(12, 404, MENTEE_WAS_NOT_SAVED); - + static INVALID_ROLE_WAS_FOUND = new ServiceError(11, 404, INVALID_ROLE_WAS_FOUND); static ERROR_DELETING_EVENT = new ServiceError(7, 404, ERROR_DELETING_EVENT); diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index d089d211..2e61087e 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -53,8 +53,8 @@ router.post( } const accessToken = mentor.personalAccessToken; const data = await getCalendlyEventDate(req.body.calendlyURI, accessToken); - const startDate = new Date(2023, 0O5, 0O5, 17, 0, 0, 0); - const endDate = new Date(2023, 0O5, 0O5, 18, 0, 0, 0); + const startDate = new Date(2023, 0o5, 0o5, 17, 0, 0, 0); + const endDate = new Date(2023, 0o5, 0o5, 18, 0, 0, 0); const session = new Session({ preSession: null, @@ -80,7 +80,7 @@ router.post( session.postSessionMentee = postMenteeNoteId._id; session.postSessionMentor = postMentorNoteId._id; await session.save(); - + await sendNotification( "New session booked!", "You have a new session with " + mentee.name + ". Check out your session details \u{1F60E}", @@ -93,7 +93,7 @@ router.post( ". Fill out your pre-session notes now \u{1F60E}", mentee.fcmToken ); - + return res.status(201).json({ sessionId: session._id, mentorId: session.mentorId, @@ -327,14 +327,18 @@ router.patch( await Session.findByIdAndUpdate(sessionId, { $set: updates }, { new: true }); await sendNotification( "A session has been rescheduled", - "Your upcoming session with " + mentor.name + " has been rescheduled! Check out your new session details.", + "Your upcoming session with " + + mentor.name + + " has been rescheduled! Check out your new session details.", mentee.fcmToken - ) + ); await sendNotification( "A session has been rescheduled", - "" + mentee.name + " has rescheduled your upcoming session! Check out your session details.", + "" + + mentee.name + + " has rescheduled your upcoming session! Check out your session details.", mentor.fcmToken - ) + ); return res.status(200).json({ message: "Successfuly updated the session!", }); @@ -377,23 +381,25 @@ router.delete( "A session has been cancelled.", "Your session with " + mentor.name + " has been cancelled.", mentee.fcmToken - ) + ); await sendNotification( "A session has been cancelled.", "" + mentee.name + " has cancelled your upcoming session.", mentor.fcmToken - ) - } else if (role === "mentor") { + ); + } else if (role === "mentor") { await sendNotification( "A session has been cancelled.", "Your session with " + mentee.name + " has been cancelled.", mentor.fcmToken - ) - await sendNotification ( + ); + await sendNotification( "A session has been cancelled.", - "" + mentor.name + " has cancelled your upcoming session. \u{1F494} Reschedule to save your pre-session notes.", + "" + + mentor.name + + " has cancelled your upcoming session. \u{1F494} Reschedule to save your pre-session notes.", mentee.fcmToken - ) + ); } return res.status(200).json({ message: "calendly successfully cancelled, notes deleted, session deleted.", diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 82a295de..8a571458 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -8,7 +8,14 @@ import mongoose from "mongoose"; import { validateReqBodyWithCake } from "../middleware/validation"; import { Mentee, Mentor, Pairing } from "../models"; import { createUser } from "../services/auth"; -import { getMenteeId, getMentorId, updateMentor, updateMentee, updateMentorFCMToken, updateMenteeFCMToken } from "../services/user"; +import { + getMenteeId, + getMentorId, + updateMentor, + updateMentee, + updateMentorFCMToken, + updateMenteeFCMToken, +} from "../services/user"; import { CreateMenteeRequestBodyCake, CreateMentorRequestBodyCake, @@ -457,7 +464,7 @@ router.patch( next(e); } } -) +); /** * * This route will update a mentor's values. diff --git a/backend/src/services/note.ts b/backend/src/services/note.ts index f9d13daf..f9060422 100644 --- a/backend/src/services/note.ts +++ b/backend/src/services/note.ts @@ -154,19 +154,23 @@ async function updateNotes(updatedNotes: UpdateNoteDetailsType[], documentId: st sessionDoc.preSessionCompleted = true; await sendNotification( "Pre-session update!", - "Looks like " + mentee.name + " has some questions for you. Check out " + mentee.name + "'s pre-session notes.", + "Looks like " + + mentee.name + + " has some questions for you. Check out " + + mentee.name + + "'s pre-session notes.", mentor.fcmToken ); - } - else if (noteDoc.type === "postMentor") { + } else if (noteDoc.type === "postMentor") { sessionDoc.postSessionMentorCompleted = true; await sendNotification( "Post-session update!", - "" + mentor.name + " has updated their post-session notes. Check out what they had to say!", + "" + + mentor.name + + " has updated their post-session notes. Check out what they had to say!", mentee.fcmToken ); - } - else if (noteDoc.type === "postMentee") sessionDoc.postSessionMenteeCompleted = true; + } else if (noteDoc.type === "postMentee") sessionDoc.postSessionMenteeCompleted = true; if (missedNote && sessionDoc.missedSessionReason == null) sessionDoc.missedSessionReason = missedReason; await sessionDoc.save(); diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 6dcfcd98..9d905905 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -9,12 +9,11 @@ import { InternalError } from "../errors"; * cool with glasses - 1F60E * check mark - 2705 * broken heart - 1F494 - * + * * To find emojis and their unicode value, use https://apps.timwhitlock.info/emoji/tables/unicode * The unicode is in format "U+1234"; in code, use "\u{1234}" */ - async function sendNotification(title: string, body: string, deviceToken: string) { const message = { notification: { @@ -35,11 +34,12 @@ async function sendNotification(title: string, body: string, deviceToken: string }); } - async function startUpcomingSessionCronJob() { schedule.scheduleJob("*/1 * * * *", async () => { try { - const upcomingNotifSessions = await Session.find({ upcomingSessionNotifSent: { $eq: false } }); + const upcomingNotifSessions = await Session.find({ + upcomingSessionNotifSent: { $eq: false }, + }); upcomingNotifSessions.forEach(async (session) => { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); @@ -54,26 +54,26 @@ async function startUpcomingSessionCronJob() { if (session.preSessionCompleted) { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with ${mentor.name} in 24 hours? \u{1F440}", + `Ready for your session with ${mentor.name} in 24 hours? \u{1F440}`, mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}. Check out " + mentee.name + "'s pre-session notes.", + `Ready for your session with " + mentee.name + " in 24 hours? \u{1F440}. Check out " + mentee.name + "'s pre-session notes.`, mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); } else { const menteeNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentor.name + " in 24 hours? " + "\u{1F440}. Fill out your pre-session notes now!", + `Ready for your session with " + mentor.name + " in 24 hours? \u{1F440}. Fill out your pre-session notes now!`, mentee.fcmToken ); console.log("Function executed successfully:", menteeNotif); const mentorNotif = await sendNotification( "You have an upcoming session.", - "Ready for your session with " + mentee.name + " in 24 hours? " + "\u{1F440}", + `Ready for your session with ${mentee.name} in 24 hours? \u{1F440}`, mentor.fcmToken ); console.log("Function executed successfully:", mentorNotif); @@ -106,13 +106,17 @@ async function startPostSessionCronJob() { // mentee notification await sendNotification( "\u{2705} Session complete!", - "How did your session with " + mentor.name + " go? Jot it down in your post-session notes.", + "How did your session with " + + mentor.name + + " go? Jot it down in your post-session notes.", mentee.fcmToken ); // mentor notification await sendNotification( "\u{2705} Session complete!", - "How did your session with " + mentee.name + " go? Jot it down in your post-session notes.", + "How did your session with " + + mentee.name + + " go? Jot it down in your post-session notes.", mentor.fcmToken ); session.postSessionNotifSent = true; diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index e3fd640b..e05128fb 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -115,4 +115,12 @@ async function updateMentee(updatedMentee: UpdateMenteeRequestBodyType, userID: } } -export { getMentorId, getMenteeId, updateMentorFCMToken, updateMenteeFCMToken, updateMentor, updateMentee, saveImage }; +export { + getMentorId, + getMenteeId, + updateMentorFCMToken, + updateMenteeFCMToken, + updateMentor, + updateMentee, + saveImage, +}; diff --git a/backend/src/types/cakes.ts b/backend/src/types/cakes.ts index 50d2ead0..c7c5e099 100644 --- a/backend/src/types/cakes.ts +++ b/backend/src/types/cakes.ts @@ -40,7 +40,7 @@ export const CreateMentorRequestBodyCake = bake({ export const UpdateUserCake = bake({ fcmToken: string, uid: string, - role: string + role: string, }); export const UpdateMentorRequestBodyCake = bake({ name: string, From 20dc5d1f85e615e2453ecb9bf423046483393107 Mon Sep 17 00:00:00 2001 From: HarshGurnani <68934498+HarshGurnani@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:20:56 -0700 Subject: [PATCH 22/22] fixed requested changes + linting --- ALUM/ALUM/ALUMApp.swift | 16 --- backend/src/app.ts | 2 + backend/src/errors/service.ts | 8 +- backend/src/routes/self.ts | 134 ++++++++++++++++++++++++++ backend/src/routes/sessions.ts | 9 +- backend/src/routes/user.ts | 119 +---------------------- backend/src/services/notifications.ts | 53 ++++------ backend/src/services/user.ts | 67 ++++++------- 8 files changed, 200 insertions(+), 208 deletions(-) create mode 100644 backend/src/routes/self.ts diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index 3cc17704..4f7067c9 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -13,14 +13,6 @@ import Firebase import UserNotifications import FirebaseMessaging -// class AppDelegate: NSObject, UIApplicationDelegate { -// func application(_ application: UIApplication, -// didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { -// FirebaseApp.configure() -// return true -// } -// } - extension View { func dismissKeyboardOnDrag() -> some View { self.gesture(DragGesture().onChanged { gesture in @@ -86,11 +78,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { - print("did receive FCM Token") - // let deviceToken:[String: String] = ["token": fcmToken ?? ""] currentUser.fcmToken = fcmToken - // currentUser.fcmToken = Messaging.messaging().fcmToken - print(fcmToken) } } @@ -118,10 +106,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate { Messaging.messaging().apnsToken = deviceToken } - func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - - } - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { diff --git a/backend/src/app.ts b/backend/src/app.ts index 5fffe2fe..334d7380 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -4,6 +4,7 @@ import { json } from "body-parser"; import { onRequest } from "firebase-functions/v2/https"; import { userRouter } from "./routes/user"; +import { selfRouter } from "./routes/self"; import { notesRouter } from "./routes/notes"; import { sessionsRouter } from "./routes/sessions"; import { mongoURI, port } from "./config"; @@ -30,6 +31,7 @@ mongoose.connect(mongoURI, {}, () => { server.app.use(json()); server.app.set("view engine", "pug"); server.app.use(userRouter); +server.app.use(selfRouter); server.app.use(sessionsRouter); server.app.use(notesRouter); server.app.use(imageRouter); diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 2742e3c7..08d4ae8a 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -23,6 +23,8 @@ const ERROR_GETTING_EVENT_DATA = "There was an error retrieving the calendly eve const ERROR_DELETING_EVENT = "There was an error deleting a calendly event"; const INVALID_ROLE_WAS_FOUND = "Allowed user roles for this context is mentor and mentee only"; +const ERROR_SENDING_NOTIFICATION = "There was an error sending the notification."; + export class ServiceError extends CustomError { static IMAGE_NOT_SAVED = new ServiceError(0, 404, IMAGE_NOT_SAVED); @@ -50,7 +52,9 @@ export class ServiceError extends CustomError { static MENTEE_WAS_NOT_SAVED = new ServiceError(12, 404, MENTEE_WAS_NOT_SAVED); - static INVALID_ROLE_WAS_FOUND = new ServiceError(11, 404, INVALID_ROLE_WAS_FOUND); + static INVALID_ROLE_WAS_FOUND = new ServiceError(13, 404, INVALID_ROLE_WAS_FOUND); + + static ERROR_DELETING_EVENT = new ServiceError(14, 404, ERROR_DELETING_EVENT); - static ERROR_DELETING_EVENT = new ServiceError(7, 404, ERROR_DELETING_EVENT); + static ERROR_SENDING_NOTIFICATION = new ServiceError(15, 404, ERROR_SENDING_NOTIFICATION); } diff --git a/backend/src/routes/self.ts b/backend/src/routes/self.ts new file mode 100644 index 00000000..a5caa885 --- /dev/null +++ b/backend/src/routes/self.ts @@ -0,0 +1,134 @@ +/** + * This file contains routes that will be used for generic users + * i.e. not separate for mentee and mentor. + */ +import express, { NextFunction, Request, Response } from "express"; +// import { Infer } from "caketype"; +import mongoose from "mongoose"; +import { validateReqBodyWithCake } from "../middleware/validation"; +import { Mentee, Mentor } from "../models"; +import { getMenteeId, getMentorId, updateFCMToken } from "../services/user"; +import { UpdateUserCake } from "../types"; +import { InternalError } from "../errors/internal"; +import { ServiceError } from "../errors/service"; +import { verifyAuthToken } from "../middleware/auth"; +import { CustomError } from "../errors"; +import { getUpcomingSession, getLastSession } from "../services/session"; + +const router = express.Router(); + +// type UpdateUserType = Infer +router.patch( + "/user/:userId", + [verifyAuthToken], + validateReqBodyWithCake(UpdateUserCake), + async (req: Request, res: Response, next: NextFunction) => { + try { + console.log("Updating FCM Token"); + const userId = req.params.userId; + if (!mongoose.Types.ObjectId.isValid(userId)) { + throw ServiceError.INVALID_MONGO_ID; + } + console.log("user id is valid"); + const role = req.body.role; + const updatedToken = req.body.fcmToken; + console.log("got information"); + await updateFCMToken(updatedToken, userId, role); + res.status(200).json({ + message: "Success", + }); + } catch (e) { + next(e); + } + } +); + +/** + * Route to setup mobile app for any logged in user (mentor or mentee) + * + * This route returns the following + * If user is a mentor, + * menteeIds, status, upcomingSessionId + * + * If user is mentee, + * mentorId, status, upcomingSessionId + */ +router.get( + "/user/me", + [verifyAuthToken], + async (req: Request, res: Response, next: NextFunction) => { + try { + const userId = req.body.uid; + const role = req.body.role; + console.log(`GET /user/me uid - ${req.body.uid}`); + + const getUpcomingSessionPromise = getUpcomingSession(userId, role); + const getPastSessionPromise = getLastSession(userId, role); + if (role === "mentee") { + // GET mentee document + const mentee = await Mentee.findById(userId); + if (!mentee) { + throw ServiceError.MENTEE_WAS_NOT_FOUND; + } + + if (mentee.status !== "paired") { + res.status(200).send({ + status: mentee.status, + }); + return; + } + const getPairedMentorIdPromise = getMentorId(mentee.pairingId); + const [upcomingSessionId, pastSessionId, pairedMentorId] = await Promise.all([ + getUpcomingSessionPromise, + getPastSessionPromise, + getPairedMentorIdPromise, + ]); + res.status(200).send({ + status: mentee.status, + sessionId: upcomingSessionId ?? pastSessionId, + pairedMentorId, + }); + } else if (role === "mentor") { + const mentor = await Mentor.findById(userId); + if (!mentor) { + throw ServiceError.MENTOR_WAS_NOT_FOUND; + } + + if (mentor.status !== "paired") { + res.status(200).send({ + status: mentor.status, + }); + return; + } + + const getMenteeIdsPromises = mentor.pairingIds.map(async (pairingId) => + getMenteeId(pairingId) + ); + + // For MVP, we assume there is only 1 mentee 1 mentor pairing + const getMenteeIdsPromise = getMenteeIdsPromises[0]; + + const [upcomingSessionId, pastSessionId, pairedMenteeId] = await Promise.all([ + getUpcomingSessionPromise, + getPastSessionPromise, + getMenteeIdsPromise, + ]); + + res.status(200).send({ + status: mentor.status, + sessionId: upcomingSessionId ?? pastSessionId, + pairedMenteeId, + }); + return; + } + } catch (e) { + if (e instanceof CustomError) { + next(e); + return; + } + next(InternalError.ERROR_GETTING_MENTEE); + } + } +); + +export { router as selfRouter }; diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 2e61087e..963e362a 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -53,8 +53,9 @@ router.post( } const accessToken = mentor.personalAccessToken; const data = await getCalendlyEventDate(req.body.calendlyURI, accessToken); - const startDate = new Date(2023, 0o5, 0o5, 17, 0, 0, 0); - const endDate = new Date(2023, 0o5, 0o5, 18, 0, 0, 0); + if (!data.resource.start_time || !data.resource.end_time) { + throw ServiceError.ERROR_GETTING_EVENT_DATA; + } const session = new Session({ preSession: null, @@ -63,8 +64,8 @@ router.post( menteeId: menteeMongoId, mentorId: mentorMongoId, missedSessionReason: null, - startTime: data.resource?.start_time ?? startDate, - endTime: data.resource?.end_time ?? endDate, + startTime: data.resource.start_time, + endTime: data.resource.end_time, calendlyUri: req.body.calendlyURI, preSessionCompleted: false, postSessionMentorCompleted: false, diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 8a571458..70f4f7f1 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -8,20 +8,12 @@ import mongoose from "mongoose"; import { validateReqBodyWithCake } from "../middleware/validation"; import { Mentee, Mentor, Pairing } from "../models"; import { createUser } from "../services/auth"; -import { - getMenteeId, - getMentorId, - updateMentor, - updateMentee, - updateMentorFCMToken, - updateMenteeFCMToken, -} from "../services/user"; +import { updateMentor, updateMentee } from "../services/user"; import { CreateMenteeRequestBodyCake, CreateMentorRequestBodyCake, CreateMenteeRequestBodyType, CreateMentorRequestBodyType, - UpdateUserCake, UpdateMentorRequestBodyCake, UpdateMenteeRequestBodyCake, UpdateMentorRequestBodyType, @@ -34,7 +26,6 @@ import { verifyAuthToken } from "../middleware/auth"; import { defaultImageID } from "../config"; import { CustomError } from "../errors"; import { AuthError } from "../errors/auth"; -import { getUpcomingSession, getLastSession } from "../services/session"; import { validateCalendlyAccessToken, validateCalendlyLink } from "../services/calendly"; const router = express.Router(); @@ -435,37 +426,6 @@ router.get( } ); -// type UpdateUserType = Infer -router.patch( - "/user/:userId", - [verifyAuthToken], - validateReqBodyWithCake(UpdateUserCake), - async (req: Request, res: Response, next: NextFunction) => { - try { - console.log("Starting patch"); - const userId = req.params.userId; - if (!mongoose.Types.ObjectId.isValid(userId)) { - throw ServiceError.INVALID_MONGO_ID; - } - console.log("user id is valid"); - const role = req.body.role; - const updatedToken = req.body.fcmToken; - console.log("got information"); - if (role === "mentee") { - await updateMenteeFCMToken(updatedToken, userId); - } else if (role === "mentor") { - await updateMentorFCMToken(updatedToken, userId); - } - res.status(200).json({ - message: "Success", - }); - } catch (e) { - console.log("error"); - next(e); - } - } -); - /** * * This route will update a mentor's values. * @param userId: userId of mentor to be updated @@ -577,82 +537,5 @@ router.patch( * If user is mentee, * mentorId, status, upcomingSessionId */ -router.get( - "/user/me", - [verifyAuthToken], - async (req: Request, res: Response, next: NextFunction) => { - try { - const userId = req.body.uid; - const role = req.body.role; - console.log(`GET /user/me uid - ${req.body.uid}`); - - const getUpcomingSessionPromise = getUpcomingSession(userId, role); - const getPastSessionPromise = getLastSession(userId, role); - if (role === "mentee") { - // GET mentee document - const mentee = await Mentee.findById(userId); - if (!mentee) { - throw ServiceError.MENTEE_WAS_NOT_FOUND; - } - - if (mentee.status !== "paired") { - res.status(200).send({ - status: mentee.status, - }); - return; - } - const getPairedMentorIdPromise = getMentorId(mentee.pairingId); - const [upcomingSessionId, pastSessionId, pairedMentorId] = await Promise.all([ - getUpcomingSessionPromise, - getPastSessionPromise, - getPairedMentorIdPromise, - ]); - res.status(200).send({ - status: mentee.status, - sessionId: upcomingSessionId ?? pastSessionId, - pairedMentorId, - }); - } else if (role === "mentor") { - const mentor = await Mentor.findById(userId); - if (!mentor) { - throw ServiceError.MENTOR_WAS_NOT_FOUND; - } - - if (mentor.status !== "paired") { - res.status(200).send({ - status: mentor.status, - }); - return; - } - - const getMenteeIdsPromises = mentor.pairingIds.map(async (pairingId) => - getMenteeId(pairingId) - ); - - // For MVP, we assume there is only 1 mentee 1 mentor pairing - const getMenteeIdsPromise = getMenteeIdsPromises[0]; - - const [upcomingSessionId, pastSessionId, pairedMenteeId] = await Promise.all([ - getUpcomingSessionPromise, - getPastSessionPromise, - getMenteeIdsPromise, - ]); - - res.status(200).send({ - status: mentor.status, - sessionId: upcomingSessionId ?? pastSessionId, - pairedMenteeId, - }); - return; - } - } catch (e) { - if (e instanceof CustomError) { - next(e); - return; - } - next(InternalError.ERROR_GETTING_MENTEE); - } - } -); export { router as userRouter }; diff --git a/backend/src/services/notifications.ts b/backend/src/services/notifications.ts index 9d905905..5d1f873b 100644 --- a/backend/src/services/notifications.ts +++ b/backend/src/services/notifications.ts @@ -1,7 +1,7 @@ import * as admin from "firebase-admin"; import schedule from "node-schedule"; import { Mentee, Mentor, Session } from "../models"; -import { InternalError } from "../errors"; +import { ServiceError } from "../errors"; /** * Unicode notes: @@ -29,8 +29,8 @@ async function sendNotification(title: string, body: string, deviceToken: string .then((response) => { console.log("Notification sent successfully:", response); }) - .catch((error) => { - console.error("Error sending notification:", error); + .catch(() => { + throw ServiceError.ERROR_SENDING_NOTIFICATION; }); } @@ -44,40 +44,28 @@ async function startUpcomingSessionCronJob() { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); if (!mentee) { - throw InternalError.ERROR_GETTING_MENTEE; + // Instead of throwing an error, we skip this current iteration to avoid skipping the rest of the sessions. + return; } const mentor = await Mentor.findById(session.mentorId); if (!mentor) { - throw InternalError.ERROR_GETTING_MENTOR; + return; } + + const headerText = `You have an upcoming session.`; + let menteeNotifText = `Ready for your session with ${mentor.name} in 24 hours? \u{1F440}`; + let mentorNotifText = `Ready for your session with ${mentee.name} in 24 hours? \u{1F440}`; + if (session.startTime.getTime() - dateNow.getTime() <= 86400000) { if (session.preSessionCompleted) { - const menteeNotif = await sendNotification( - "You have an upcoming session.", - `Ready for your session with ${mentor.name} in 24 hours? \u{1F440}`, - mentee.fcmToken - ); - console.log("Function executed successfully:", menteeNotif); - const mentorNotif = await sendNotification( - "You have an upcoming session.", - `Ready for your session with " + mentee.name + " in 24 hours? \u{1F440}. Check out " + mentee.name + "'s pre-session notes.`, - mentor.fcmToken - ); - console.log("Function executed successfully:", mentorNotif); + mentorNotifText += `Check out ${mentee.name}'s pre-session notes.`; } else { - const menteeNotif = await sendNotification( - "You have an upcoming session.", - `Ready for your session with " + mentor.name + " in 24 hours? \u{1F440}. Fill out your pre-session notes now!`, - mentee.fcmToken - ); - console.log("Function executed successfully:", menteeNotif); - const mentorNotif = await sendNotification( - "You have an upcoming session.", - `Ready for your session with ${mentee.name} in 24 hours? \u{1F440}`, - mentor.fcmToken - ); - console.log("Function executed successfully:", mentorNotif); + menteeNotifText += `Fill out your pre-session notes now!`; } + const menteeNotif = await sendNotification(headerText, menteeNotifText, mentee.fcmToken); + console.log("Function executed successfully:", menteeNotif); + const mentorNotif = await sendNotification(headerText, mentorNotifText, mentor.fcmToken); + console.log("Function executed successfully:", mentorNotif); session.upcomingSessionNotifSent = true; await session.save(); } @@ -96,13 +84,14 @@ async function startPostSessionCronJob() { const dateNow = new Date(); const mentee = await Mentee.findById(session.menteeId); if (!mentee) { - throw InternalError.ERROR_GETTING_MENTEE; + return; } const mentor = await Mentor.findById(session.mentorId); if (!mentor) { - throw InternalError.ERROR_GETTING_MENTOR; + return; } - if (dateNow.getTime() - session.endTime.getTime() >= 0) { + // 600000 milliseconds = 10 minutes + if (dateNow.getTime() - session.endTime.getTime() >= 600000) { // mentee notification await sendNotification( "\u{2705} Session complete!", diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index e05128fb..3a016255 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -47,20 +47,6 @@ async function getMenteeId(pairingId: string): Promise { return pairing.menteeId; } -async function updateMentorFCMToken(fcmToken: string, userId: string) { - console.log("FCM Token: ", fcmToken); - const user = await Mentor.findById(userId); - if (!user) { - throw ServiceError.MENTOR_WAS_NOT_FOUND; - } - - try { - user.fcmToken = fcmToken; - return await user.save(); - } catch (error) { - throw ServiceError.MENTOR_WAS_NOT_SAVED; - } -} /** * Updates a mentor entry * @param updatedMentor - updated values for mentor @@ -82,19 +68,6 @@ async function updateMentor(updatedMentor: UpdateMentorRequestBodyType, userID: } } -async function updateMenteeFCMToken(fcmToken: string, userId: string) { - console.log("FCM Token: ", fcmToken); - const user = await Mentee.findById(userId); - if (!user) { - throw ServiceError.MENTEE_WAS_NOT_FOUND; - } - try { - user.fcmToken = fcmToken; - return await user.save(); - } catch (error) { - throw ServiceError.MENTEE_WAS_NOT_SAVED; - } -} /** * Updates a mentor entry * @param updatedMentee - updated values for the mentee @@ -115,12 +88,34 @@ async function updateMentee(updatedMentee: UpdateMenteeRequestBodyType, userID: } } -export { - getMentorId, - getMenteeId, - updateMentorFCMToken, - updateMenteeFCMToken, - updateMentor, - updateMentee, - saveImage, -}; +async function updateFCMToken(fcmToken: string, userId: string, role: string) { + console.log("FCM Token: ", fcmToken); + if (role === "mentee") { + const user = await Mentee.findById(userId); + if (!user) { + throw ServiceError.MENTEE_WAS_NOT_FOUND; + } + try { + user.fcmToken = fcmToken; + return await user.save(); + } catch (error) { + throw ServiceError.MENTEE_WAS_NOT_SAVED; + } + } else if (role === "mentor") { + const user = await Mentor.findById(userId); + if (!user) { + throw ServiceError.MENTOR_WAS_NOT_FOUND; + } + + try { + user.fcmToken = fcmToken; + return await user.save(); + } catch (error) { + throw ServiceError.MENTOR_WAS_NOT_SAVED; + } + } else { + throw ServiceError.INVALID_ROLE_WAS_FOUND; + } +} + +export { getMentorId, getMenteeId, updateMentor, updateMentee, saveImage, updateFCMToken };