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)
154 lines
4.7 KiB
Go
154 lines
4.7 KiB
Go
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 ¬ificationRepository{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(¬ification, "id = ?", id).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return ¬ification, 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(¬ifications).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(¬ifications).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
|
||
}
|