restrict v6
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
// Copyright 2026 tamakyi. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ReportsStore 举报记录的读写。
|
||||
//
|
||||
// 举报只是「信号」,处置仍然由箱主(在 /user 面板删/设私密)或运维(CLI)完成 ——
|
||||
// 本项目没有管理后台,所以这里不提供「审核队列」之类的方法,只做写入与计数。
|
||||
type ReportsStore interface {
|
||||
// Create 写入一条举报。返回 created=false 表示该 IP 已举报过同一内容(唯一索引冲突),
|
||||
// 属于幂等成功:调用方不应据此提示用户「已举报过」,否则会成为内容存在性的探测接口。
|
||||
Create(ctx context.Context, opts CreateReportOptions) (bool, error)
|
||||
// QuestionIDsByUser 返回某箱主名下「被举报的提问 ID -> 待处理举报数」。
|
||||
// 箱主列表用它打标;已删除的提问不计入。
|
||||
QuestionIDsByUser(ctx context.Context, userID uint) (map[uint]int64, error)
|
||||
// CountByUserSince 统计某箱主自 since 起收到的待处理举报总数(邮件告警的触发依据)。
|
||||
CountByUserSince(ctx context.Context, userID uint, since time.Time) (int64, error)
|
||||
}
|
||||
|
||||
// Reports 举报记录全局 store,由 db.Init 装配。
|
||||
var Reports ReportsStore
|
||||
|
||||
var _ ReportsStore = (*reports)(nil)
|
||||
|
||||
// 举报理由白名单。做成固定枚举而不是自由文本:自由填写等于又开了一个可以写辱骂内容的输入框,
|
||||
// 而举报本身并不需要精确描述。
|
||||
const (
|
||||
ReportReasonHarassment = "harassment" // 人身攻击 / 辱骂
|
||||
ReportReasonDoxxing = "doxxing" // 曝光他人账号或作品
|
||||
ReportReasonDivisive = "divisive" // 挑起群体对立
|
||||
ReportReasonOther = "other" // 其他
|
||||
)
|
||||
|
||||
// IsValidReportReason 校验举报理由。
|
||||
func IsValidReportReason(reason string) bool {
|
||||
switch reason {
|
||||
case ReportReasonHarassment, ReportReasonDoxxing, ReportReasonDivisive, ReportReasonOther:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CreateReportOptions 写入举报的入参。
|
||||
type CreateReportOptions struct {
|
||||
QuestionID uint
|
||||
ReporterIP string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func NewReportsStore(db *gorm.DB) ReportsStore {
|
||||
return &reports{db: db}
|
||||
}
|
||||
|
||||
type reports struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (s *reports) Create(ctx context.Context, opts CreateReportOptions) (bool, error) {
|
||||
report := &ContentReport{
|
||||
QuestionID: opts.QuestionID,
|
||||
ReporterIP: opts.ReporterIP,
|
||||
Reason: opts.Reason,
|
||||
Status: ReportStatusPending,
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Create(report).Error; err != nil {
|
||||
// (question_id, reporter_ip) 唯一索引:同一个人对同一条内容只能报一次。
|
||||
// 静默视为成功,避免通过「已举报过」的提示反查内容是否存在。
|
||||
if isDuplicateKeyError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.Wrap(err, "create content report")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *reports) QuestionIDsByUser(ctx context.Context, userID uint) (map[uint]int64, error) {
|
||||
type row struct {
|
||||
QuestionID uint
|
||||
Count int64
|
||||
}
|
||||
var rows []*row
|
||||
if err := s.db.WithContext(ctx).
|
||||
Table("content_reports AS r").
|
||||
Select("r.question_id AS question_id, COUNT(r.id) AS count").
|
||||
Joins("JOIN questions q ON q.id = r.question_id").
|
||||
Where("q.user_id = ? AND q.deleted_at IS NULL", userID).
|
||||
Where("r.status = ?", ReportStatusPending).
|
||||
Group("r.question_id").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "count reports by user")
|
||||
}
|
||||
|
||||
counts := make(map[uint]int64, len(rows))
|
||||
for _, r := range rows {
|
||||
counts[r.QuestionID] = r.Count
|
||||
}
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
func (s *reports) CountByUserSince(ctx context.Context, userID uint, since time.Time) (int64, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Table("content_reports AS r").
|
||||
Joins("JOIN questions q ON q.id = r.question_id").
|
||||
Where("q.user_id = ? AND q.deleted_at IS NULL", userID).
|
||||
Where("r.status = ?", ReportStatusPending).
|
||||
Where("r.created_at >= ?", since).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, errors.Wrap(err, "count reports since")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
Reference in New Issue
Block a user