veza/veza-backend-api/cmd/tools/seed/seed_moderation.go

159 lines
5.7 KiB
Go
Raw Normal View History

package main
import (
"database/sql"
"fmt"
"time"
)
// SeedModeration creates reports, moderation_actions, user_strikes, user_suspensions.
func SeedModeration(db *sql.DB, cfg Config, users []SeededUser, tracks []SeededTrack) error {
fmt.Println("\n═══ MODERATION ═══")
mods := GetModerators(users)
admins := GetAdmins(users)
moderators := append(mods, admins...)
if len(moderators) == 0 {
return nil
}
// ── 1. Reports ───────────────────────────────────────────────────────────
p := NewProgress("reports", cfg.Reports)
reportRows := make([][]interface{}, 0, cfg.Reports)
reportReasons := []string{
"spam", "harassment", "copyright", "inappropriate_content",
"misleading", "hate_speech", "violence", "impersonation",
}
reportStatuses := []string{"pending", "pending", "reviewed", "resolved", "dismissed"}
type reportInfo struct {
id string
}
reports := make([]reportInfo, 0, cfg.Reports)
for i := 0; i < cfg.Reports; i++ {
id := newUUID()
reporter := users[rng.Intn(len(users))]
reason := pick(reportReasons)
status := pick(reportStatuses)
createdAt := RandomTimeAfter(reporter.CreatedAt)
// Report target: user or track
var reportedUserID interface{}
var contentType string
var contentID interface{}
if randChance(60) && len(tracks) > 0 {
t := tracks[rng.Intn(len(tracks))]
contentType = "track"
contentID = t.ID
} else {
u := users[rng.Intn(len(users))]
reportedUserID = u.ID
contentType = "user"
}
reports = append(reports, reportInfo{id: id})
reportRows = append(reportRows, []interface{}{
id, reporter.ID, reportedUserID, contentType, contentID,
fmt.Sprintf("Reported for %s", reason),
status, nil, nil, // resolved_by, resolved_at
createdAt,
pick(reportReasons), "normal", "", "", nil, // category, priority, resolution_note, resolution_action, assigned_to
createdAt,
})
}
_, err := BulkInsert(db, "reports",
"id, reporter_id, reported_user_id, content_type, content_id, reason, status, resolved_by, resolved_at, created_at, category, priority, resolution_note, resolution_action, assigned_to, updated_at",
reportRows)
if err != nil {
return fmt.Errorf("insert reports: %w", err)
}
p.Update(cfg.Reports)
p.Done()
// ── 2. Moderation actions ────────────────────────────────────────────────
actionCount := cfg.Reports / 2 // ~50% of reports get an action
p = NewProgress("moderation_actions", actionCount)
actionRows := make([][]interface{}, 0, actionCount)
actionTypes := []string{"warn", "mute", "suspend", "ban", "content_removal", "dismiss"}
for i := 0; i < actionCount && i < len(reports); i++ {
mod := moderators[rng.Intn(len(moderators))]
action := pick(actionTypes)
targetUser := users[rng.Intn(len(users))]
actionRows = append(actionRows, []interface{}{
newUUID(), mod.ID, targetUser.ID,
nil, nil, // target_content_type, target_content_id
action, fmt.Sprintf("Action: %s — reviewed by moderator", action),
"{}", time.Now(),
})
}
_, _ = BulkInsert(db, "moderation_actions",
"id, moderator_id, target_user_id, target_content_type, target_content_id, action, reason, metadata, created_at",
actionRows)
p.Update(len(actionRows))
p.Done()
// ── 3. User strikes ──────────────────────────────────────────────────────
strikeCount := cfg.Reports / 5
p = NewProgress("user_strikes", strikeCount)
strikeRows := make([][]interface{}, 0, strikeCount)
strikeReasons := []string{"spam", "harassment", "copyright_violation", "inappropriate_content"}
for i := 0; i < strikeCount; i++ {
targetUser := users[rng.Intn(len(users))]
mod := moderators[rng.Intn(len(moderators))]
reason := pick(strikeReasons)
severity := pick([]string{"warning", "minor", "major", "critical"})
expiresAt := time.Now().AddDate(0, 3, 0) // 3 months
strikeRows = append(strikeRows, []interface{}{
newUUID(), targetUser.ID, nil, // report_id
reason, severity, mod.ID,
true, false, nil, false, nil, nil, nil, // is_active, appealed, appeal_text, appeal_resolved, appeal_result, appeal_resolved_by, appeal_resolved_at
expiresAt, time.Now(), time.Now(),
})
}
_, _ = BulkInsert(db, "user_strikes",
"id, user_id, report_id, reason, severity, issued_by, is_active, appealed, appeal_text, appeal_resolved, appeal_result, appeal_resolved_by, appeal_resolved_at, expires_at, created_at, updated_at",
strikeRows)
p.Update(len(strikeRows))
p.Done()
// ── 4. User suspensions (very few) ───────────────────────────────────────
suspCount := strikeCount / 3
if suspCount < 2 {
suspCount = 2
}
p = NewProgress("user_suspensions", suspCount)
suspRows := make([][]interface{}, 0, suspCount)
for i := 0; i < suspCount; i++ {
targetUser := users[randInt(len(users)/2, len(users)-1)] // Pick from second half (less important)
mod := moderators[rng.Intn(len(moderators))]
reason := pick(strikeReasons)
suspendedUntil := time.Now().Add(time.Duration(randInt(1, 30)) * 24 * time.Hour)
suspRows = append(suspRows, []interface{}{
newUUID(), targetUser.ID,
fmt.Sprintf("Suspended for %s", reason),
mod.ID, suspendedUntil,
true, nil, nil, // is_active, lifted_by, lifted_at
time.Now(),
})
}
_, _ = BulkInsert(db, "user_suspensions",
"id, user_id, reason, suspended_by, suspended_until, is_active, lifted_by, lifted_at, created_at",
suspRows)
p.Update(len(suspRows))
p.Done()
return nil
}