veza/veza-backend-api/internal/services/report_service.go

112 lines
3 KiB
Go

package services
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"go.uber.org/zap"
"gorm.io/gorm"
"veza-backend-api/internal/models"
)
// ReportService handles moderation reports (v0.803 ADM1)
type ReportService struct {
db *gorm.DB
logger *zap.Logger
}
// NewReportService creates a new ReportService
func NewReportService(db *gorm.DB, logger *zap.Logger) *ReportService {
return &ReportService{db: db, logger: logger}
}
// CreateReportRequest is the request for creating a report
type CreateReportRequest struct {
ContentType string `json:"content_type" binding:"required"`
ContentID *uuid.UUID `json:"content_id"`
Reason string `json:"reason" binding:"required"`
ReportedUserID *uuid.UUID `json:"reported_user_id"`
}
// ResolveReportRequest is the request for resolving a report
type ResolveReportRequest struct {
Action string `json:"action" binding:"required"` // dismiss, warn, ban
Note string `json:"note"`
}
// ListReportsParams are the params for listing reports
type ListReportsParams struct {
Status string
Limit int
Offset int
}
// List returns paginated reports
func (s *ReportService) List(ctx context.Context, params ListReportsParams) ([]models.Report, int64, error) {
var reports []models.Report
query := s.db.WithContext(ctx).Model(&models.Report{})
if params.Status != "" && params.Status != "all" {
query = query.Where("status = ?", params.Status)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("failed to count reports: %w", err)
}
limit := params.Limit
if limit <= 0 {
limit = 20
}
if limit > 100 {
limit = 100
}
if err := query.Order("created_at DESC").Offset(params.Offset).Limit(limit).Find(&reports).Error; err != nil {
return nil, 0, fmt.Errorf("failed to list reports: %w", err)
}
return reports, total, nil
}
// Create creates a new report
func (s *ReportService) Create(ctx context.Context, reporterID uuid.UUID, req CreateReportRequest) (*models.Report, error) {
report := &models.Report{
ReporterID: reporterID,
ReportedUserID: req.ReportedUserID,
ContentType: req.ContentType,
ContentID: req.ContentID,
Reason: req.Reason,
Status: "pending",
}
if err := s.db.WithContext(ctx).Create(report).Error; err != nil {
return nil, fmt.Errorf("failed to create report: %w", err)
}
return report, nil
}
// Resolve resolves a report (dismiss, warn, ban)
func (s *ReportService) Resolve(ctx context.Context, reportID uuid.UUID, resolvedBy uuid.UUID, action string) error {
now := time.Now()
status := "resolved"
if action == "dismiss" || action == "dismissed" {
status = "dismissed"
}
updates := map[string]interface{}{
"status": status,
"resolved_by": resolvedBy,
"resolved_at": now,
}
result := s.db.WithContext(ctx).Model(&models.Report{}).Where("id = ?", reportID).Updates(updates)
if result.Error != nil {
return fmt.Errorf("failed to resolve report: %w", result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("report not found")
}
return nil
}