增加站内信未读提醒和后台通知中心+txt 校验
This commit is contained in:
@@ -17,3 +17,18 @@ type Notification struct {
|
||||
func (Notification) TableName() string {
|
||||
return "notifications"
|
||||
}
|
||||
|
||||
type AdminNotification struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
AdminUserID uint64 `gorm:"not null;index" json:"admin_user_id"`
|
||||
Type string `gorm:"size:32;not null" json:"type"`
|
||||
Title string `gorm:"size:128;not null" json:"title"`
|
||||
Content string `json:"content"`
|
||||
IsRead bool `gorm:"not null;default:false" json:"is_read"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (AdminNotification) TableName() string {
|
||||
return "admin_notifications"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package adminnotification
|
||||
|
||||
import "time"
|
||||
|
||||
type Query struct {
|
||||
AdminUserID uint64
|
||||
OnlyUnread bool
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
type PaginatedResult struct {
|
||||
Items interface{} `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type NotificationDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
AdminUserID uint64 `json:"admin_user_id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
IsRead bool `json:"is_read"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UnreadCountDTO struct {
|
||||
UnreadCount int64 `json:"unread_count"`
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package adminnotification
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"hfb_sys/backend/internal/middleware"
|
||||
"hfb_sys/backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
func parsePagination(c *gin.Context) (int, int) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
func (h *Handler) List(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
page, pageSize := parsePagination(c)
|
||||
result, err := h.service.List(c.Request.Context(), Query{
|
||||
AdminUserID: adminID,
|
||||
OnlyUnread: c.Query("read") == "unread",
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
})
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) UnreadCount(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
result, err := h.service.UnreadCount(c.Request.Context(), adminID)
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) MarkRead(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "ID 不正确")
|
||||
return
|
||||
}
|
||||
if err := h.service.MarkRead(c.Request.Context(), adminID, id); err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"read": true})
|
||||
}
|
||||
|
||||
func (h *Handler) MarkAllRead(c *gin.Context) {
|
||||
adminID, ok := currentAdminID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
return
|
||||
}
|
||||
count, err := h.service.MarkAllRead(c.Request.Context(), adminID)
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"read_count": count})
|
||||
}
|
||||
|
||||
func currentAdminID(c *gin.Context) (uint64, bool) {
|
||||
value, ok := c.Get(middleware.ContextAdminID)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
adminID, ok := value.(uint64)
|
||||
return adminID, ok
|
||||
}
|
||||
|
||||
func writeNotificationError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
case errors.Is(err, ErrNotificationNotFound):
|
||||
response.NotFound(c, "通知不存在")
|
||||
default:
|
||||
response.Error(c, http.StatusInternalServerError, "internal_error", "通知服务暂时不可用")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package adminnotification
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
|
||||
func (r *Repository) List(ctx context.Context, query Query) (*PaginatedResult, error) {
|
||||
db := r.db.WithContext(ctx).Model(&model.AdminNotification{}).
|
||||
Where("admin_user_id = ?", query.AdminUserID)
|
||||
countDB := r.db.WithContext(ctx).Model(&model.AdminNotification{}).
|
||||
Where("admin_user_id = ?", query.AdminUserID)
|
||||
if query.OnlyUnread {
|
||||
db = db.Where("is_read = ?", false)
|
||||
countDB = countDB.Where("is_read = ?", false)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := countDB.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
var rows []model.AdminNotification
|
||||
if err := db.Order("id DESC").Offset(offset).Limit(query.PageSize).Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]NotificationDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, toDTO(row))
|
||||
}
|
||||
return &PaginatedResult{Items: items, Total: total, Page: query.Page, PageSize: query.PageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UnreadCount(ctx context.Context, adminUserID uint64) (int64, error) {
|
||||
var total int64
|
||||
err := r.db.WithContext(ctx).Model(&model.AdminNotification{}).
|
||||
Where("admin_user_id = ? AND is_read = ?", adminUserID, false).
|
||||
Count(&total).Error
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (r *Repository) MarkRead(ctx context.Context, adminUserID uint64, id uint64) error {
|
||||
result := r.db.WithContext(ctx).Model(&model.AdminNotification{}).
|
||||
Where("id = ? AND admin_user_id = ?", id, adminUserID).
|
||||
Update("is_read", true)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
var total int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.AdminNotification{}).
|
||||
Where("id = ? AND admin_user_id = ?", id, adminUserID).
|
||||
Count(&total).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if total == 0 {
|
||||
return ErrNotificationNotFound
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) MarkAllRead(ctx context.Context, adminUserID uint64) (int64, error) {
|
||||
result := r.db.WithContext(ctx).Model(&model.AdminNotification{}).
|
||||
Where("admin_user_id = ? AND is_read = ?", adminUserID, false).
|
||||
Update("is_read", true)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// Entry 描述一条待写入的管理员通知。
|
||||
type Entry struct {
|
||||
AdminUserID uint64
|
||||
Type string
|
||||
Title string
|
||||
Content string
|
||||
}
|
||||
|
||||
// Append 在事务中批量写入管理员通知。
|
||||
// 典型用法:在业务事务内调用 adminnotification.Append(tx, entries...),
|
||||
// 确保通知与业务数据在同一事务中提交或回滚。
|
||||
func Append(tx *gorm.DB, entries ...Entry) error {
|
||||
for _, entry := range entries {
|
||||
if entry.AdminUserID == 0 || entry.Title == "" {
|
||||
continue
|
||||
}
|
||||
row := model.AdminNotification{
|
||||
AdminUserID: entry.AdminUserID,
|
||||
Type: entry.Type,
|
||||
Title: entry.Title,
|
||||
Content: entry.Content,
|
||||
}
|
||||
if row.Type == "" {
|
||||
row.Type = "system"
|
||||
}
|
||||
if err := tx.Create(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toDTO(row model.AdminNotification) NotificationDTO {
|
||||
return NotificationDTO{
|
||||
ID: row.ID,
|
||||
AdminUserID: row.AdminUserID,
|
||||
Type: row.Type,
|
||||
Title: row.Title,
|
||||
Content: row.Content,
|
||||
IsRead: row.IsRead,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package adminnotification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"hfb_sys/backend/internal/database"
|
||||
"hfb_sys/backend/internal/model"
|
||||
)
|
||||
|
||||
func TestRepositoryUnreadCountAndMarkRead(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := db.AutoMigrate(&model.AdminNotification{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
repo := NewRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := db.Create(&model.AdminNotification{
|
||||
AdminUserID: 7,
|
||||
Type: "system",
|
||||
Title: "库存预警",
|
||||
Content: "二维码库存不足",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("Create() error = %v", err)
|
||||
}
|
||||
|
||||
count, err := repo.UnreadCount(ctx, 7)
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadCount() error = %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("UnreadCount() = %d, want 1", count)
|
||||
}
|
||||
|
||||
if err := repo.MarkRead(ctx, 7, 1); err != nil {
|
||||
t.Fatalf("MarkRead() error = %v", err)
|
||||
}
|
||||
if err := repo.MarkRead(ctx, 7, 1); err != nil {
|
||||
t.Fatalf("MarkRead() repeat error = %v", err)
|
||||
}
|
||||
|
||||
count, err = repo.UnreadCount(ctx, 7)
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadCount() after read error = %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("UnreadCount() after read = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryMarkAllRead(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := db.AutoMigrate(&model.AdminNotification{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
repo := NewRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
rows := []model.AdminNotification{
|
||||
{AdminUserID: 7, Type: "system", Title: "一", Content: "一"},
|
||||
{AdminUserID: 7, Type: "system", Title: "二", Content: "二"},
|
||||
{AdminUserID: 8, Type: "system", Title: "三", Content: "三"},
|
||||
}
|
||||
if err := db.Create(&rows).Error; err != nil {
|
||||
t.Fatalf("Create() error = %v", err)
|
||||
}
|
||||
|
||||
affected, err := repo.MarkAllRead(ctx, 7)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkAllRead() error = %v", err)
|
||||
}
|
||||
if affected != 2 {
|
||||
t.Fatalf("MarkAllRead() affected = %d, want 2", affected)
|
||||
}
|
||||
affected, err = repo.MarkAllRead(ctx, 7)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkAllRead() repeat error = %v", err)
|
||||
}
|
||||
if affected != 0 {
|
||||
t.Fatalf("MarkAllRead() repeat affected = %d, want 0", affected)
|
||||
}
|
||||
|
||||
count, err := repo.UnreadCount(ctx, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadCount() error = %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("other admin UnreadCount() = %d, want 1", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryMarkReadNotFound(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := db.AutoMigrate(&model.AdminNotification{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
repo := NewRepository(db)
|
||||
|
||||
err := repo.MarkRead(context.Background(), 7, 99)
|
||||
if !errors.Is(err, ErrNotificationNotFound) {
|
||||
t.Fatalf("MarkRead() error = %v, want ErrNotificationNotFound", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package adminnotification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||
var ErrNotificationNotFound = errors.New("admin notification not found")
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
}
|
||||
|
||||
func NewService(repo *Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, query Query) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.List(ctx, query)
|
||||
}
|
||||
|
||||
func (s *Service) UnreadCount(ctx context.Context, adminUserID uint64) (*UnreadCountDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
count, err := s.repo.UnreadCount(ctx, adminUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UnreadCountDTO{UnreadCount: count}, nil
|
||||
}
|
||||
|
||||
func (s *Service) MarkRead(ctx context.Context, adminUserID uint64, id uint64) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.MarkRead(ctx, adminUserID, id)
|
||||
}
|
||||
|
||||
func (s *Service) MarkAllRead(ctx context.Context, adminUserID uint64) (int64, error) {
|
||||
if s.repo == nil {
|
||||
return 0, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.MarkAllRead(ctx, adminUserID)
|
||||
}
|
||||
@@ -20,3 +20,7 @@ type PaginatedResult struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type UnreadCountDTO struct {
|
||||
UnreadCount int64 `json:"unread_count"`
|
||||
}
|
||||
|
||||
@@ -48,6 +48,34 @@ func (h *Handler) List(c *gin.Context) {
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) UnreadCount(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
result, err := h.service.UnreadCount(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) MarkAllRead(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
count, err := h.service.MarkAllRead(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"read_count": count})
|
||||
}
|
||||
|
||||
func (h *Handler) MarkRead(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
@@ -79,6 +107,8 @@ func writeNotificationError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
case errors.Is(err, ErrNotificationNotFound):
|
||||
response.NotFound(c, "通知不存在")
|
||||
default:
|
||||
response.ServiceUnavailable(c, "通知服务暂时不可用")
|
||||
}
|
||||
|
||||
@@ -45,9 +45,40 @@ func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int
|
||||
|
||||
func (r *Repository) MarkRead(ctx context.Context, userID uint64, id uint64) error {
|
||||
now := time.Now()
|
||||
return r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
result := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Update("read_at", now).Error
|
||||
Update("read_at", now)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
var total int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Count(&total).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if total == 0 {
|
||||
return ErrNotificationNotFound
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) UnreadCount(ctx context.Context, userID uint64) (int64, error) {
|
||||
var total int64
|
||||
err := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("user_id = ? AND read_at IS NULL", userID).
|
||||
Count(&total).Error
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (r *Repository) MarkAllRead(ctx context.Context, userID uint64) (int64, error) {
|
||||
now := time.Now()
|
||||
result := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("user_id = ? AND read_at IS NULL", userID).
|
||||
Update("read_at", now)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
func Append(tx *gorm.DB, entries ...Entry) error {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"hfb_sys/backend/internal/database"
|
||||
"hfb_sys/backend/internal/model"
|
||||
)
|
||||
|
||||
func TestRepositoryUnreadCountAndMarkRead(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := db.AutoMigrate(&model.Notification{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
repo := NewRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := db.Create(&model.Notification{
|
||||
UserID: 10,
|
||||
Type: "order",
|
||||
Title: "订单通知",
|
||||
Content: "请处理订单",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("Create() error = %v", err)
|
||||
}
|
||||
|
||||
count, err := repo.UnreadCount(ctx, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadCount() error = %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("UnreadCount() = %d, want 1", count)
|
||||
}
|
||||
|
||||
if err := repo.MarkRead(ctx, 10, 1); err != nil {
|
||||
t.Fatalf("MarkRead() error = %v", err)
|
||||
}
|
||||
// 重复标记应保持幂等,不应误报不存在。
|
||||
if err := repo.MarkRead(ctx, 10, 1); err != nil {
|
||||
t.Fatalf("MarkRead() repeat error = %v", err)
|
||||
}
|
||||
|
||||
count, err = repo.UnreadCount(ctx, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadCount() after read error = %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("UnreadCount() after read = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryMarkReadNotFound(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := db.AutoMigrate(&model.Notification{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
repo := NewRepository(db)
|
||||
|
||||
err := repo.MarkRead(context.Background(), 10, 99)
|
||||
if !errors.Is(err, ErrNotificationNotFound) {
|
||||
t.Fatalf("MarkRead() error = %v, want ErrNotificationNotFound", err)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
var ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||
var ErrNotificationNotFound = errors.New("notification not found")
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
@@ -28,3 +29,21 @@ func (s *Service) MarkRead(ctx context.Context, userID uint64, id uint64) error
|
||||
}
|
||||
return s.repo.MarkRead(ctx, userID, id)
|
||||
}
|
||||
|
||||
func (s *Service) UnreadCount(ctx context.Context, userID uint64) (*UnreadCountDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
count, err := s.repo.UnreadCount(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UnreadCountDTO{UnreadCount: count}, nil
|
||||
}
|
||||
|
||||
func (s *Service) MarkAllRead(ctx context.Context, userID uint64) (int64, error) {
|
||||
if s.repo == nil {
|
||||
return 0, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.MarkAllRead(ctx, userID)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"hfb_sys/backend/internal/modules/admindashboard"
|
||||
"hfb_sys/backend/internal/modules/adminfinance"
|
||||
"hfb_sys/backend/internal/modules/adminmgr"
|
||||
"hfb_sys/backend/internal/modules/adminnotification"
|
||||
"hfb_sys/backend/internal/modules/adminrole"
|
||||
"hfb_sys/backend/internal/modules/adminuser"
|
||||
"hfb_sys/backend/internal/modules/announcement"
|
||||
@@ -145,6 +146,12 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
}
|
||||
adminAuditService := adminaudit.NewService(adminAuditRepo)
|
||||
adminAuditHandler := adminaudit.NewHandler(adminAuditService)
|
||||
var adminNotificationRepo *adminnotification.Repository
|
||||
if deps.DB != nil {
|
||||
adminNotificationRepo = adminnotification.NewRepository(deps.DB)
|
||||
}
|
||||
adminNotificationService := adminnotification.NewService(adminNotificationRepo)
|
||||
adminNotificationHandler := adminnotification.NewHandler(adminNotificationService)
|
||||
userHandler := user.NewHandler(userRepo)
|
||||
var realnameRepo *realname.Repository
|
||||
if deps.DB != nil {
|
||||
@@ -447,6 +454,8 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
notificationRoutes := api.Group("/notifications", requireAuth)
|
||||
{
|
||||
notificationRoutes.GET("", notificationHandler.List)
|
||||
notificationRoutes.GET("/unread-count", notificationHandler.UnreadCount)
|
||||
notificationRoutes.PUT("/read-all", notificationHandler.MarkAllRead)
|
||||
notificationRoutes.POST("/:id/read", notificationHandler.MarkRead)
|
||||
}
|
||||
|
||||
@@ -542,6 +551,10 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
|
||||
adminRoutes.GET("/system-configs", requirePerm("system_config:view"), systemConfigHandler.List)
|
||||
adminRoutes.PUT("/system-configs/:key", requirePerm("system_config:update"), systemConfigHandler.Update)
|
||||
adminRoutes.GET("/notifications", requirePerm("notification:view"), adminNotificationHandler.List)
|
||||
adminRoutes.GET("/notifications/unread-count", requirePerm("notification:view"), adminNotificationHandler.UnreadCount)
|
||||
adminRoutes.PUT("/notifications/read-all", requirePerm("notification:view"), adminNotificationHandler.MarkAllRead)
|
||||
adminRoutes.POST("/notifications/:id/read", requirePerm("notification:view"), adminNotificationHandler.MarkRead)
|
||||
adminRoutes.GET("/audit-logs", requirePerm("audit_log:view"), adminAuditHandler.List)
|
||||
if chatHubHandler != nil {
|
||||
adminRoutes.GET("/chats/events", requirePerm("chat:view"), chatHubHandler.AdminEvents)
|
||||
|
||||
Reference in New Issue
Block a user