package handler import ( "errors" "strconv" "accounting-app/internal/models" "accounting-app/internal/repository" "accounting-app/internal/service" "accounting-app/pkg/api" "github.com/gin-gonic/gin" ) // NotificationHandler handles HTTP requests for notification operations type NotificationHandler struct { notificationService *service.NotificationService userRepo *repository.UserRepository announcementRepo *repository.AnnouncementRepository } // NewNotificationHandler creates a new NotificationHandler instance func NewNotificationHandler( notificationService *service.NotificationService, userRepo *repository.UserRepository, announcementRepo *repository.AnnouncementRepository, ) *NotificationHandler { return &NotificationHandler{ notificationService: notificationService, userRepo: userRepo, announcementRepo: announcementRepo, } } // BroadcastNotification handles POST /api/v1/notifications/broadcast // Sends a notification to all users and records it in history func (h *NotificationHandler) BroadcastNotification(c *gin.Context) { // TODO: Add proper admin authorization check here when RBAC is implemented // For now, checks if user is authenticated at least adminID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } var input service.BroadcastInput if err := c.ShouldBindJSON(&input); err != nil { api.BadRequest(c, "Invalid request body: "+err.Error()) return } // Set admin ID from context input.AdminID = adminID.(uint) err := h.notificationService.Broadcast(input, h.userRepo, h.announcementRepo) if err != nil { api.InternalError(c, "Failed to broadcast notification: "+err.Error()) return } api.Success(c, gin.H{"message": "Broadcast sent successfully"}) } // GetAnnouncementHistory handles GET /api/v1/notifications/announcements // Returns the history of announcements func (h *NotificationHandler) GetAnnouncementHistory(c *gin.Context) { // TODO: Add admin check limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) result, err := h.notificationService.GetAnnouncementHistory(limit, offset, h.announcementRepo) if err != nil { api.InternalError(c, "Failed to get announcement history: "+err.Error()) return } api.Success(c, gin.H{ "announcements": result.Announcements, "total": result.Total, }) } // GetNotifications handles GET /api/v1/notifications // Returns a list of notifications with pagination and filtering func (h *NotificationHandler) GetNotifications(c *gin.Context) { // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } // Parse query parameters input := service.NotificationListInput{} // Parse type filter if typeStr := c.Query("type"); typeStr != "" { notifType := models.NotificationType(typeStr) input.Type = ¬ifType } // Parse is_read filter if isReadStr := c.Query("is_read"); isReadStr != "" { isRead := isReadStr == "true" input.IsRead = &isRead } // Parse pagination if offsetStr := c.Query("offset"); offsetStr != "" { offset, err := strconv.Atoi(offsetStr) if err != nil || offset < 0 { api.BadRequest(c, "Invalid offset") return } input.Offset = offset } if limitStr := c.Query("limit"); limitStr != "" { limit, err := strconv.Atoi(limitStr) if err != nil || limit < 0 { api.BadRequest(c, "Invalid limit") return } input.Limit = limit } result, err := h.notificationService.ListNotifications(userID.(uint), input) if err != nil { api.InternalError(c, "Failed to get notifications: "+err.Error()) return } // Calculate total pages totalPages := 0 if result.Limit > 0 { totalPages = int((result.Total + int64(result.Limit) - 1) / int64(result.Limit)) } // Calculate current page (1-indexed) currentPage := 1 if result.Limit > 0 { currentPage = (result.Offset / result.Limit) + 1 } api.SuccessWithMeta(c, result.Notifications, &api.Meta{ Page: currentPage, PageSize: result.Limit, TotalCount: result.Total, TotalPages: totalPages, }) } // GetUnreadCount handles GET /api/v1/notifications/unread-count // Returns the count of unread notifications func (h *NotificationHandler) GetUnreadCount(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } count, err := h.notificationService.GetUnreadCount(userID.(uint)) if err != nil { api.InternalError(c, "Failed to get unread count: "+err.Error()) return } api.Success(c, gin.H{"count": count}) } // MarkAsRead handles PUT /api/v1/notifications/:id/read // Marks a notification as read func (h *NotificationHandler) MarkAsRead(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid notification ID") return } err = h.notificationService.MarkAsRead(userID.(uint), uint(id)) if err != nil { if errors.Is(err, service.ErrNotificationNotFound) { api.NotFound(c, "Notification not found") return } api.InternalError(c, "Failed to mark notification as read: "+err.Error()) return } api.Success(c, gin.H{"message": "Notification marked as read"}) } // MarkAllAsRead handles POST /api/v1/notifications/read-all // Marks all notifications as read func (h *NotificationHandler) MarkAllAsRead(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } err := h.notificationService.MarkAllAsRead(userID.(uint)) if err != nil { api.InternalError(c, "Failed to mark all notifications as read: "+err.Error()) return } api.Success(c, gin.H{"message": "All notifications marked as read"}) } // DeleteNotification handles DELETE /api/v1/notifications/:id // Deletes a notification func (h *NotificationHandler) DeleteNotification(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid notification ID") return } err = h.notificationService.DeleteNotification(userID.(uint), uint(id)) if err != nil { if errors.Is(err, service.ErrNotificationNotFound) { api.NotFound(c, "Notification not found") return } api.InternalError(c, "Failed to delete notification: "+err.Error()) return } api.NoContent(c) } // CreateNotification handles POST /api/v1/notifications // Creates a new notification (Admin/System use) func (h *NotificationHandler) CreateNotification(c *gin.Context) { // TODO: Add admin authorization check var input service.CreateNotificationInput if err := c.ShouldBindJSON(&input); err != nil { api.BadRequest(c, "Invalid request body: "+err.Error()) return } notification, err := h.notificationService.CreateNotification(input) if err != nil { api.InternalError(c, "Failed to create notification: "+err.Error()) return } api.Created(c, notification) } // RegisterUserRoutes registers notification routes for regular users func (h *NotificationHandler) RegisterUserRoutes(rg *gin.RouterGroup) { notifications := rg.Group("/notifications") { notifications.GET("", h.GetNotifications) notifications.GET("/unread-count", h.GetUnreadCount) notifications.PUT("/:id/read", h.MarkAsRead) notifications.POST("/read-all", h.MarkAllAsRead) notifications.DELETE("/:id", h.DeleteNotification) } } // RegisterAdminRoutes registers notification routes for admin/system use func (h *NotificationHandler) RegisterAdminRoutes(rg *gin.RouterGroup) { notifications := rg.Group("/notifications") { notifications.POST("", h.CreateNotification) notifications.POST("/broadcast", h.BroadcastNotification) notifications.GET("/announcements", h.GetAnnouncementHistory) } }