diff --git a/frontend/occupi-web/src/components/bookingComponent/BookingComponent.tsx b/frontend/occupi-web/src/components/bookingComponent/BookingComponent.tsx index 737d6fd5..af6f27ae 100644 --- a/frontend/occupi-web/src/components/bookingComponent/BookingComponent.tsx +++ b/frontend/occupi-web/src/components/bookingComponent/BookingComponent.tsx @@ -39,10 +39,10 @@ const statusColorMap: Record = { }; // type BookingComponentProps = { -// roleColumnName: string; // Add other props as needed +// positionColumnName: string; // Add other props as needed // }; -const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; +const INITIAL_VISIBLE_COLUMNS = ["name", "position", "status", "role", "actions"]; type User = (typeof users)[0]; @@ -131,7 +131,7 @@ export default function App() { {user.email} ); - case "role": + case "position": return (

@@ -153,7 +153,26 @@ export default function App() { {cellValue} ); - + case "role": + return ( + + + + + alert(key)} + > + basic + admin + + + ) case "actions": diff --git a/occupi-backend/pkg/handlers/api_handlers.go b/occupi-backend/pkg/handlers/api_handlers.go index cc955a29..edcba821 100644 --- a/occupi-backend/pkg/handlers/api_handlers.go +++ b/occupi-backend/pkg/handlers/api_handlers.go @@ -1431,7 +1431,7 @@ func AddIP(ctx *gin.Context, appsession *models.AppSession) { } // Add the IP to the database - err := database.AddIP(ctx, appsession, request) + ipInfo, err := database.AddIP(ctx, appsession, request) if err != nil { configs.CaptureError(ctx, err) ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse( @@ -1443,6 +1443,39 @@ func AddIP(ctx *gin.Context, appsession *models.AppSession) { return } + // get logged users email from ctx + email, errv := AttemptToGetEmail(ctx, appsession) + if errv != nil { + configs.CaptureError(ctx, errv) + logrus.Error("Failed to get logged users email because: ", errv) + // we are more focused on theadding of the ip address so we will not return an error + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully toggled admin status!", nil)) + return + } + + if err := CreateAndSendNotificationLogic( + ctx, + appsession, + email, + request.Emails, + "IP address added", + fmt.Sprintf("%s has added %s to the list of allowed IP addresses for you. Check your email for more details.", email, request.IP), + fmt.Sprintf("You have successfully added %s to the list of allowed IP addresses for %s.", request.IP, utils.ConvertArrayToCommaDelimitedString(request.Emails)), + ); err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to send notification because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return + } + + subject := "IP address added" + body := utils.FormatIPAddressAddedEmailBody(ipInfo, email) + + if err := mail.SendBulkEmailWithBCC(request.Emails, subject, body, appsession); err != nil { + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return + } + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully added IP!", nil)) } @@ -1467,7 +1500,7 @@ func RemoveIP(ctx *gin.Context, appsession *models.AppSession) { } // Remove the IP from the database - err := database.RemoveIP(ctx, appsession, request) + ipInfo, err := database.RemoveIP(ctx, appsession, request) if err != nil { configs.CaptureError(ctx, err) ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse( @@ -1479,6 +1512,39 @@ func RemoveIP(ctx *gin.Context, appsession *models.AppSession) { return } + // get logged users email from ctx + email, errv := AttemptToGetEmail(ctx, appsession) + if errv != nil { + configs.CaptureError(ctx, errv) + logrus.Error("Failed to get logged users email because: ", errv) + // we are more focused on the removing the ip address so we will not return an error + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully toggled admin status!", nil)) + return + } + + if err := CreateAndSendNotificationLogic( + ctx, + appsession, + email, + request.Emails, + "IP address removed", + fmt.Sprintf("%s has removed %s from the list of allowed IP addresses for you. Check your email for more details.", email, request.IP), + fmt.Sprintf("You have successfully removed %s from the list of allowed IP addresses for %s.", request.IP, utils.ConvertArrayToCommaDelimitedString(request.Emails)), + ); err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to send notification because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return + } + + subject := "IP address removed" + body := utils.FormatIPAddressRemovedEmailBody(ipInfo, email) + + if err := mail.SendBulkEmailWithBCC(request.Emails, subject, body, appsession); err != nil { + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return + } + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully removed IP!", nil)) } @@ -1503,15 +1569,48 @@ func ToggleAllowAnonymousIP(ctx *gin.Context, appsession *models.AppSession) { } // Toggle the allow anonymous IP status - err := database.ToggleAllowAnonymousIP(ctx, appsession, request) - - if err != nil { + if err := database.ToggleAllowAnonymousIP(ctx, appsession, request); err != nil { configs.CaptureError(ctx, err) logrus.Error("Failed to toggle allow anonymous IP because: ", err) ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) return } + // get logged users email from ctx + email, errv := AttemptToGetEmail(ctx, appsession) + if errv != nil { + configs.CaptureError(ctx, errv) + logrus.Error("Failed to get logged users email because: ", errv) + // we are more focused on the removing the ip address so we will not return an error + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully toggled admin status!", nil)) + return + } + + var receiverAllow string + var senderAllow string + if request.BlockAnonymousIPAddress { + receiverAllow = fmt.Sprintf(("%s has allowed anonymous IP addresses for you."), email) + senderAllow = fmt.Sprintf("You have successfully allowed anonymous IP addresses for %s.", utils.ConvertArrayToCommaDelimitedString(request.Emails)) + } else { + receiverAllow = fmt.Sprintf(("%s has disallowed anonymous IP addresses for you."), email) + senderAllow = fmt.Sprintf("You have successfully disallowed anonymous IP addresses for %s.", utils.ConvertArrayToCommaDelimitedString(request.Emails)) + } + + if err := CreateAndSendNotificationLogic( + ctx, + appsession, + email, + request.Emails, + "Allow anonymous IP address toggled", + receiverAllow, + senderAllow, + ); err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to send notification because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return + } + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully toggled allow anonymous IP status!", nil)) } @@ -1632,13 +1731,6 @@ func ToggleAdminStatus(ctx *gin.Context, appsession *models.AppSession) { return } - // ensure email exists - if !database.EmailExists(ctx, appsession, request.Email) { - configs.CaptureError(ctx, errors.New("email does not exist")) - ctx.JSON(http.StatusBadRequest, utils.ErrorResponse(http.StatusBadRequest, "Invalid request payload", constants.InvalidRequestPayloadCode, "Email does not exist", nil)) - return - } - // Toggle the admin status err := database.ToggleAdminStatus(ctx, appsession, request) if err != nil { @@ -1648,5 +1740,82 @@ func ToggleAdminStatus(ctx *gin.Context, appsession *models.AppSession) { return } - ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully toggled admin status!", nil)) + email, err := AttemptToGetEmail(ctx, appsession) + if err != nil { + configs.CaptureError(ctx, err) + ctx.JSON(http.StatusBadRequest, utils.ErrorResponse( + http.StatusBadRequest, + "Invalid request payload", + constants.InvalidRequestPayloadCode, + "Email must be provided", + nil)) + return + } + + if err := CreateAndSendNotificationLogic( + ctx, + appsession, + email, + []string{request.Email}, + "Role status changed", + fmt.Sprintf("%s has changed your role status to %s", email, request.Role), + fmt.Sprintf("You have changed %s's role status to %s", request.Email, request.Role), + ); err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to send notification because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return + } + + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully toggled admin status and sent notifications!", nil)) +} + +func SendDownloadReportNotification(ctx *gin.Context, appsession *models.AppSession) { + var request models.RequestEmail + if err := ctx.ShouldBindJSON(&request); err != nil { + configs.CaptureError(ctx, err) + ctx.JSON(http.StatusBadRequest, utils.ErrorResponse( + http.StatusBadRequest, + "Invalid request payload", + constants.InvalidRequestPayloadCode, + "Invalid JSON payload", + nil)) + return + } + + // valdidate the email + if !utils.ValidateEmail(request.Email) { + configs.CaptureError(ctx, errors.New("invalid email address")) + ctx.JSON(http.StatusBadRequest, utils.ErrorResponse(http.StatusBadRequest, "Invalid request payload", constants.InvalidRequestPayloadCode, "Invalid email address", nil)) + return + } + + email, err := AttemptToGetEmail(ctx, appsession) + if err != nil { + configs.CaptureError(ctx, err) + ctx.JSON(http.StatusBadRequest, utils.ErrorResponse( + http.StatusBadRequest, + "Invalid request payload", + constants.InvalidRequestPayloadCode, + "Email must be provided", + nil)) + return + } + + if err := CreateAndSendNotificationLogic( + ctx, + appsession, + email, + []string{request.Email}, + "Report Downloaded", + fmt.Sprintf("%s has downloaded your report", email), + fmt.Sprintf("You have downloaded %s's report", request.Email), + ); err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to send notification because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return + } + + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully sent notification!", nil)) } diff --git a/occupi-backend/pkg/handlers/api_helpers.go b/occupi-backend/pkg/handlers/api_helpers.go index d1c51c04..2c4e1892 100644 --- a/occupi-backend/pkg/handlers/api_helpers.go +++ b/occupi-backend/pkg/handlers/api_helpers.go @@ -10,11 +10,14 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/COS301-SE-2024/occupi/occupi-backend/configs" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants" + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/database" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models" + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/receiver" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/utils" "github.com/ccoveille/go-safecast" "github.com/gin-gonic/gin" @@ -249,3 +252,70 @@ func DefaultFemalePFP(race ...string) string { func DefaultNBPFP() string { return "default_nb1.jpg" } + +func CreateAndSendNotificationLogic(ctx *gin.Context, appsession *models.AppSession, senderEmail string, receiverEmails []string, + title string, messageReciever string, messageSender string) error { + // get the users expo push token + tokens, err := database.GetUsersPushTokens(ctx, appsession, receiverEmails) + if err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to get users push tokens because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return err + } + + tokenArr, err := utils.ConvertTokensToStringArray(tokens, "expoPushToken") + if err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to convert tokens to string array because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return err + } + + notificationReciever := models.ScheduledNotification{ + NotiID: utils.GenerateUUID(), + Title: title, + Message: messageReciever, + Sent: false, + SendTime: time.Now(), + Emails: receiverEmails, + UnsentExpoPushTokens: tokenArr, + UnreadEmails: receiverEmails, + } + + notificationSender := models.ScheduledNotification{ + NotiID: utils.GenerateUUID(), + Title: title, + Message: messageSender, + Sent: true, + SendTime: time.Now(), + Emails: []string{senderEmail}, + UnsentExpoPushTokens: []string{}, + UnreadEmails: []string{senderEmail}, + } + + // Save the notifications to the database + if saved, err := database.AddNotification(ctx, appsession, notificationReciever, false); err != nil || !saved { + configs.CaptureError(ctx, err) + logrus.Error("Failed to save notification to the database because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return err + } + + if saved, err := database.AddNotification(ctx, appsession, notificationSender, true); err != nil || !saved { + configs.CaptureError(ctx, err) + logrus.Error("Failed to save notification to the database because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return err + } + + // send the notification + if err := receiver.SendPushNotification(notificationReciever, appsession); err != nil { + configs.CaptureError(ctx, err) + logrus.Error("Failed to send notification because: ", err) + ctx.JSON(http.StatusInternalServerError, utils.InternalServerError()) + return err + } + + return nil +} diff --git a/occupi-backend/pkg/receiver/recieve.go b/occupi-backend/pkg/receiver/recieve.go index 7b89ccf3..47c11ee4 100644 --- a/occupi-backend/pkg/receiver/recieve.go +++ b/occupi-backend/pkg/receiver/recieve.go @@ -37,7 +37,7 @@ func StartConsumeMessage(appsession *models.AppSession) { go func() { for _, notification := range notifications { - notificationSendingLogic(notification, appsession) + NotificationSendingLogic(notification, appsession) } }() @@ -64,12 +64,12 @@ func StartConsumeMessage(appsession *models.AppSession) { UnreadEmails: utils.ConvertCommaDelimitedStringToArray(unreadEmails), } - notificationSendingLogic(notification, appsession) + NotificationSendingLogic(notification, appsession) } }() } -func notificationSendingLogic(notification models.ScheduledNotification, appsession *models.AppSession) { +func NotificationSendingLogic(notification models.ScheduledNotification, appsession *models.AppSession) { // to account for discrepancies in time, we should allow for a range of 5 seconds before and after the scheduled time // whereby we can send the notification, after that, we should discard the notification, else if there // is still more than 5 seconds before the scheduled time, we should wait until the time is right @@ -77,14 +77,14 @@ func notificationSendingLogic(notification models.ScheduledNotification, appsess switch { case now.After(notification.SendTime.Add(-5*time.Second)) && now.Before(notification.SendTime.Add(5*time.Second)): - err := sendPushNotification(notification, appsession) + err := SendPushNotification(notification, appsession) if err != nil { logrus.Error("Failed to send push notification: ", err) } case now.Before(notification.SendTime.Add(-5 * time.Second)): // wait until the time is right time.Sleep(time.Until(notification.SendTime)) - err := sendPushNotification(notification, appsession) + err := SendPushNotification(notification, appsession) if err != nil { logrus.Error("Failed to send push notification: ", err) } @@ -97,7 +97,7 @@ func notificationSendingLogic(notification models.ScheduledNotification, appsess } } -func sendPushNotification(notification models.ScheduledNotification, appsession *models.AppSession) error { +func SendPushNotification(notification models.ScheduledNotification, appsession *models.AppSession) error { for _, token := range notification.UnsentExpoPushTokens { // To check the token is valid pushToken, err := expo.NewExponentPushToken(token) @@ -106,11 +106,8 @@ func sendPushNotification(notification models.ScheduledNotification, appsession continue } - // Create a new Expo SDK client - client := expo.NewPushClient(nil) - // Publish message - response, err := client.Publish( + response, err := appsession.ExpoClient.Publish( &expo.PushMessage{ To: []expo.ExponentPushToken{pushToken}, Body: notification.Message, diff --git a/occupi-backend/pkg/router/router.go b/occupi-backend/pkg/router/router.go index 1e2084eb..478cd858 100644 --- a/occupi-backend/pkg/router/router.go +++ b/occupi-backend/pkg/router/router.go @@ -67,6 +67,7 @@ func OccupiRouter(router *gin.Engine, appsession *models.AppSession) { api.DELETE("/remove-ip", middleware.ProtectedRoute, func(ctx *gin.Context) { middleware.VerifyMobileUser(ctx, appsession) }, middleware.AdminRoute, func(ctx *gin.Context) { handlers.RemoveIP(ctx, appsession) }) api.PUT("/toggle-allow-anonymous-ip", middleware.ProtectedRoute, func(ctx *gin.Context) { middleware.VerifyMobileUser(ctx, appsession) }, middleware.AdminRoute, func(ctx *gin.Context) { handlers.ToggleAllowAnonymousIP(ctx, appsession) }) api.PUT("/toggle-admin-status", middleware.ProtectedRoute, func(ctx *gin.Context) { middleware.VerifyMobileUser(ctx, appsession) }, middleware.AdminRoute, func(ctx *gin.Context) { handlers.ToggleAdminStatus(ctx, appsession) }) + api.PUT("/notify-report-download", middleware.ProtectedRoute, func(ctx *gin.Context) { middleware.VerifyMobileUser(ctx, appsession) }, middleware.AdminRoute, func(ctx *gin.Context) { handlers.SendDownloadReportNotification(ctx, appsession) }) } analytics := router.Group("/analytics") {