Files
Novault-backend/internal/handler/notification_handler.go

287 lines
7.9 KiB
Go
Raw Permalink Normal View History

2026-01-27 18:42:35 +08:00
package handler
import (
"errors"
"strconv"
"accounting-app/internal/models"
"accounting-app/internal/repository"
2026-01-27 18:42:35 +08:00
"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
2026-01-27 18:42:35 +08:00
}
// NewNotificationHandler creates a new NotificationHandler instance
func NewNotificationHandler(
notificationService *service.NotificationService,
userRepo *repository.UserRepository,
announcementRepo *repository.AnnouncementRepository,
) *NotificationHandler {
2026-01-27 18:42:35 +08:00
return &NotificationHandler{
notificationService: notificationService,
userRepo: userRepo,
announcementRepo: announcementRepo,
2026-01-27 18:42:35 +08:00
}
}
// 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,
})
}
2026-01-27 18:42:35 +08:00
// 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 = &notifType
}
// 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) {
2026-01-27 18:42:35 +08:00
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)
}
}