- Rename module path from github.com/patbritton/sojorn-backend to gitlab.com/patrickbritton3/sojorn/go-backend - Updated 78 references across 41 files - Matches new GitLab repository structure
353 lines
11 KiB
Go
353 lines
11 KiB
Go
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"})
|
|
}
|