Files
backend/internal/repository/notification_repo.go
lafay 98b8abd26a
All checks were successful
Build Backend / build (push) Successful in 12m54s
Build Backend / build-docker (push) Successful in 1m41s
feat(config): add sensitive word filtering system with database support
Implement sensitive word filtering feature with configurable database and Redis
support. Add security enhancements including WebSocket origin validation, improved
password strength requirements, timing attack prevention, and production JWT secret
validation. Add batch operation validation limits and optimize user blocking
queries with subqueries.

BREAKING CHANGE: Password validation now requires minimum 8 characters with uppercase,
lowercase, and digit (was 6-50 characters without complexity requirements)
2026-04-07 00:07:40 +08:00

154 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/cursor"
"context"
"gorm.io/gorm"
)
// NotificationRepository 通知仓储接口
type NotificationRepository interface {
Create(notification *model.Notification) error
GetByID(id string) (*model.Notification, error)
GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error)
MarkAsRead(id string) error
MarkAllAsRead(userID string) error
Delete(id string) error
GetUnreadCount(userID string) (int64, error)
DeleteAllByUserID(userID string) error
GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error)
}
// notificationRepository 通知仓储实现
type notificationRepository struct {
db *gorm.DB
}
// NewNotificationRepository 创建通知仓储
func NewNotificationRepository(db *gorm.DB) NotificationRepository {
return &notificationRepository{db: db}
}
// Create 创建通知
func (r *notificationRepository) Create(notification *model.Notification) error {
return r.db.Create(notification).Error
}
// GetByID 根据ID获取通知
func (r *notificationRepository) GetByID(id string) (*model.Notification, error) {
var notification model.Notification
err := r.db.First(&notification, "id = ?", id).Error
if err != nil {
return nil, err
}
return &notification, nil
}
// GetByUserID 获取用户通知
func (r *notificationRepository) GetByUserID(userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) {
var notifications []*model.Notification
var total int64
query := r.db.Model(&model.Notification{}).Where("user_id = ?", userID)
if unreadOnly {
query = query.Where("is_read = ?", false)
}
query.Count(&total)
offset := (page - 1) * pageSize
err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&notifications).Error
return notifications, total, err
}
// MarkAsRead 标记为已读
func (r *notificationRepository) MarkAsRead(id string) error {
return r.db.Model(&model.Notification{}).Where("id = ?", id).Update("is_read", true).Error
}
// MarkAllAsRead 标记所有为已读
func (r *notificationRepository) MarkAllAsRead(userID string) error {
return r.db.Model(&model.Notification{}).Where("user_id = ?", userID).Update("is_read", true).Error
}
// Delete 删除通知
func (r *notificationRepository) Delete(id string) error {
return r.db.Delete(&model.Notification{}, "id = ?", id).Error
}
// GetUnreadCount 获取未读数量
func (r *notificationRepository) GetUnreadCount(userID string) (int64, error) {
var count int64
err := r.db.Model(&model.Notification{}).Where("user_id = ? AND is_read = ?", userID, false).Count(&count).Error
return count, err
}
// DeleteAllByUserID 删除用户所有通知
func (r *notificationRepository) DeleteAllByUserID(userID string) error {
return r.db.Where("user_id = ?", userID).Delete(&model.Notification{}).Error
}
// ========== Cursor Pagination Methods ==========
// GetNotificationsByCursor 游标分页获取用户通知
// 排序方式created_at DESC
func (r *notificationRepository) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) {
// 构建基础查询
query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ?", userID)
if unreadOnly {
query = query.Where("is_read = ?", false)
}
// 使用游标构建器
builder := cursor.NewBuilder(query, cursor.SortByCreatedAtDesc).
WithCursor(req.Cursor, req.Direction).
WithPageSize(req.PageSize)
if builder.Error() != nil {
// 无效游标,返回空列表
return cursor.NewCursorPageResult([]*model.Notification{}, "", "", false), nil
}
// 执行查询
var notifications []*model.Notification
query = builder.Build()
if err := query.Find(&notifications).Error; err != nil {
return nil, err
}
// 构建响应
pageSize := builder.GetPageSize()
hasMore := cursor.HasMore(len(notifications), pageSize)
if hasMore {
notifications = notifications[:pageSize]
}
// 生成游标
var nextCursor, prevCursor string
if len(notifications) > 0 {
// 下一页游标
if hasMore {
lastNotification := notifications[len(notifications)-1]
nextCursor = cursor.NewCursor(
cursor.FormatTime(lastNotification.CreatedAt),
lastNotification.ID,
cursor.SortByCreatedAtDesc,
).Encode()
}
// 上一页游标
firstNotification := notifications[0]
prevCursor = cursor.NewCursor(
cursor.FormatTime(firstNotification.CreatedAt),
firstNotification.ID,
cursor.SortByCreatedAtDesc,
).Encode()
}
return cursor.NewCursorPageResult(notifications, nextCursor, prevCursor, hasMore), nil
}