ultisuite-backend/cmd/resanitize-bodies/main.go
R3D347HR4Y cd0a80f5e8 huhu
2026-05-25 13:52:27 +02:00

126 lines
3.1 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/ultisuite/ulti-backend/internal/api/mail"
"github.com/ultisuite/ulti-backend/internal/dbmigrate"
"github.com/ultisuite/ulti-backend/internal/envexpand"
"github.com/ultisuite/ulti-backend/internal/mail/sanitize"
)
func main() {
accountID := flag.String("account", "", "mail account UUID (optional; all accounts if empty)")
dryRun := flag.Bool("dry-run", false, "scan only, do not write updates")
flag.Parse()
for _, path := range []string{".env", "../.env"} {
_ = envexpand.ApplyFile(path)
}
dbURL := os.Getenv("DATABASE_URL")
if dbURL == "" {
slog.Error("DATABASE_URL is required")
os.Exit(1)
}
ctx := context.Background()
if err := dbmigrate.Up(dbURL); err != nil {
slog.Error("migration failed", "error", err)
os.Exit(1)
}
pool, err := pgxpool.New(ctx, dbURL)
if err != nil {
slog.Error("db connect failed", "error", err)
os.Exit(1)
}
defer pool.Close()
if *dryRun {
scanned, changed, err := scanBodies(ctx, pool, *accountID)
if err != nil {
slog.Error("scan failed", "error", err)
os.Exit(1)
}
fmt.Printf("dry-run: scanned=%d would_update=%d\n", scanned, changed)
return
}
if *accountID != "" {
svc := mail.NewService(pool, nil, nil, nil, "")
result, err := svc.ResanitizeAccountBodiesByID(ctx, *accountID)
if err != nil {
slog.Error("resanitize failed", "account_id", *accountID, "error", err)
os.Exit(1)
}
fmt.Printf("account=%s scanned=%d updated=%d\n", *accountID, result.Scanned, result.Updated)
return
}
rows, err := pool.Query(ctx, `SELECT id FROM mail_accounts WHERE is_active = true ORDER BY created_at`)
if err != nil {
slog.Error("list accounts failed", "error", err)
os.Exit(1)
}
defer rows.Close()
svc := mail.NewService(pool, nil, nil, nil, "")
var totalScanned, totalUpdated int
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
slog.Error("scan account id failed", "error", err)
os.Exit(1)
}
result, err := svc.ResanitizeAccountBodiesByID(ctx, id)
if err != nil {
slog.Error("resanitize failed", "account_id", id, "error", err)
os.Exit(1)
}
fmt.Printf("account=%s scanned=%d updated=%d\n", id, result.Scanned, result.Updated)
totalScanned += result.Scanned
totalUpdated += result.Updated
}
if err := rows.Err(); err != nil {
slog.Error("list accounts failed", "error", err)
os.Exit(1)
}
fmt.Printf("done: accounts scanned_messages=%d updated=%d\n", totalScanned, totalUpdated)
}
func scanBodies(ctx context.Context, pool *pgxpool.Pool, accountID string) (scanned, changed int, err error) {
query := `
SELECT id, body_html FROM messages
WHERE body_html <> ''`
args := []any{}
if accountID != "" {
query += ` AND account_id = $1`
args = append(args, accountID)
}
rows, err := pool.Query(ctx, query, args...)
if err != nil {
return 0, 0, err
}
defer rows.Close()
for rows.Next() {
var id, body string
if err := rows.Scan(&id, &body); err != nil {
return scanned, changed, err
}
scanned++
if sanitize.SanitizeHTML(body) != body {
changed++
}
}
return scanned, changed, rows.Err()
}