package handlers import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "gitlab.com/patrickbritton3/sojorn/go-backend/internal/models" "gitlab.com/patrickbritton3/sojorn/go-backend/internal/repository" "gitlab.com/patrickbritton3/sojorn/go-backend/internal/services" "gitlab.com/patrickbritton3/sojorn/go-backend/pkg/utils" "github.com/rs/zerolog/log" ) type NotificationHandler struct { notifRepo *repository.NotificationRepository notifService *services.NotificationService } func NewNotificationHandler(notifRepo *repository.NotificationRepository, notifService *services.NotificationService) *NotificationHandler { return &NotificationHandler{ notifRepo: notifRepo, notifService: notifService, } } // GetNotifications retrieves paginated notifications for the user // GET /api/v1/notifications func (h *NotificationHandler) GetNotifications(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } limit := utils.GetQueryInt(c, "limit", 20) offset := utils.GetQueryInt(c, "offset", 0) grouped := c.Query("grouped") == "true" includeArchived := c.Query("include_archived") == "true" var notifications []models.Notification var err error if grouped { notifications, err = h.notifRepo.GetGroupedNotifications(c.Request.Context(), userIDStr.(string), limit, offset, includeArchived) } else { notifications, err = h.notifRepo.GetNotifications(c.Request.Context(), userIDStr.(string), limit, offset, includeArchived) } if err != nil { log.Error().Err(err).Msg("Failed to fetch notifications") c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch notifications"}) return } c.JSON(http.StatusOK, gin.H{"notifications": notifications}) } // GetUnreadCount returns the unread notification count // GET /api/v1/notifications/unread func (h *NotificationHandler) GetUnreadCount(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } count, err := h.notifRepo.GetUnreadCount(c.Request.Context(), userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch unread count"}) return } c.JSON(http.StatusOK, gin.H{"count": count}) } // GetBadgeCount returns the badge count for app icon badges // GET /api/v1/notifications/badge func (h *NotificationHandler) GetBadgeCount(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } badge, err := h.notifRepo.GetUnreadBadge(c.Request.Context(), userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch badge count"}) return } c.JSON(http.StatusOK, badge) } // MarkAsRead marks a single notification as read // PUT /api/v1/notifications/:id/read func (h *NotificationHandler) MarkAsRead(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } notificationID := c.Param("id") if _, err := uuid.Parse(notificationID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification ID"}) return } err := h.notifRepo.MarkAsRead(c.Request.Context(), notificationID, userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to mark notification as read"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // BulkMarkAsRead marks a list of notifications as read // POST /api/v1/notifications/read func (h *NotificationHandler) BulkMarkAsRead(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } var req struct { IDs []string `json:"ids" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err := h.notifRepo.MarkNotificationsAsRead(c.Request.Context(), req.IDs, userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to mark notifications as read"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // Archive archives a list of notifications // POST /api/v1/notifications/archive func (h *NotificationHandler) Archive(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } var req struct { IDs []string `json:"ids" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err := h.notifRepo.ArchiveNotifications(c.Request.Context(), req.IDs, userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to archive notifications"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // ArchiveAll archives all unarchived notifications // POST /api/v1/notifications/archive-all func (h *NotificationHandler) ArchiveAll(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } err := h.notifRepo.ArchiveAllNotifications(c.Request.Context(), userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to archive all notifications"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // MarkAllAsRead marks all notifications as read // PUT /api/v1/notifications/read-all func (h *NotificationHandler) MarkAllAsRead(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } err := h.notifRepo.MarkAllAsRead(c.Request.Context(), userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to mark all as read"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // DeleteNotification deletes a notification // DELETE /api/v1/notifications/:id func (h *NotificationHandler) DeleteNotification(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } notificationID := c.Param("id") if _, err := uuid.Parse(notificationID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification ID"}) return } err := h.notifRepo.DeleteNotification(c.Request.Context(), notificationID, userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete notification"}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } // GetNotificationPreferences returns the user's notification preferences // GET /api/v1/notifications/preferences func (h *NotificationHandler) GetNotificationPreferences(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } prefs, err := h.notifRepo.GetNotificationPreferences(c.Request.Context(), userIDStr.(string)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch preferences"}) return } c.JSON(http.StatusOK, prefs) } // UpdateNotificationPreferences updates the user's notification preferences // PUT /api/v1/notifications/preferences func (h *NotificationHandler) UpdateNotificationPreferences(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } userID, _ := uuid.Parse(userIDStr.(string)) var prefs models.NotificationPreferences if err := c.ShouldBindJSON(&prefs); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } prefs.UserID = userID if err := h.notifRepo.UpdateNotificationPreferences(c.Request.Context(), &prefs); err != nil { log.Error().Err(err).Msg("Failed to update notification preferences") c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update preferences"}) return } c.JSON(http.StatusOK, prefs) } // RegisterDevice registers an FCM token for push notifications // POST /api/v1/notifications/device func (h *NotificationHandler) RegisterDevice(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } userID, _ := uuid.Parse(userIDStr.(string)) var req models.UserFCMToken if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } req.UserID = userID if err := h.notifRepo.UpsertFCMToken(c.Request.Context(), &req); err != nil { log.Error().Err(err).Msg("Failed to register device") c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to register device"}) return } log.Info(). Str("user_id", userID.String()). Str("platform", req.Platform). Msg("FCM token registered") c.JSON(http.StatusOK, gin.H{"message": "Device registered"}) } // UnregisterDevice removes an FCM token // DELETE /api/v1/notifications/device func (h *NotificationHandler) UnregisterDevice(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } var req struct { FCMToken string `json:"fcm_token" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } if err := h.notifRepo.DeleteFCMToken(c.Request.Context(), userIDStr.(string), req.FCMToken); err != nil { log.Error().Err(err).Msg("Failed to unregister device") c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unregister device"}) return } log.Info(). Str("user_id", userIDStr.(string)). Msg("FCM token unregistered") c.JSON(http.StatusOK, gin.H{"message": "Device unregistered"}) } // UnregisterAllDevices removes all FCM tokens for the user (logout from all devices) // DELETE /api/v1/notifications/devices func (h *NotificationHandler) UnregisterAllDevices(c *gin.Context) { userIDStr, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } if err := h.notifRepo.DeleteAllFCMTokensForUser(c.Request.Context(), userIDStr.(string)); err != nil { log.Error().Err(err).Msg("Failed to unregister all devices") c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unregister devices"}) return } c.JSON(http.StatusOK, gin.H{"message": "All devices unregistered"}) }