130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"wucher/internal/config"
|
|
filemanager "wucher/internal/domain/file_manager"
|
|
"wucher/internal/repository/mysql"
|
|
s3repo "wucher/internal/repository/s3"
|
|
)
|
|
|
|
type backfillReport struct {
|
|
Total int `json:"total"`
|
|
ActiveFiles int `json:"active_files"`
|
|
ProbableOrphans int `json:"probable_orphan_files"`
|
|
MissingS3Key int `json:"missing_s3_key"`
|
|
InvalidReference int `json:"invalid_references"`
|
|
Skipped int `json:"skipped"`
|
|
DryRun bool `json:"dry_run"`
|
|
}
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
cfg, err := config.LoadFromFile(configFilePath())
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "load config: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
db, err := mysql.Connect(cfg.MySQL)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "connect db: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fileRepo := mysql.NewFileManagerFileRepository(db)
|
|
lifecycleRepo, ok := any(fileRepo).(filemanager.FileLifecycleRepository)
|
|
if !ok {
|
|
fmt.Fprintln(os.Stderr, "file repository does not support lifecycle operations")
|
|
os.Exit(1)
|
|
}
|
|
|
|
dryRun := true
|
|
if raw := os.Getenv("BACKFILL_DRY_RUN"); raw != "" && (raw == "false" || raw == "0") {
|
|
dryRun = false
|
|
}
|
|
|
|
var storage filemanager.ObjectStorage
|
|
if cfg.FileStorage.Bucket != "" && cfg.FileStorage.Region != "" {
|
|
objStorage, s3Err := s3repo.NewFileObjectStorage(ctx, cfg.FileStorage)
|
|
if s3Err == nil {
|
|
storage = objStorage
|
|
}
|
|
}
|
|
|
|
rows := make([]filemanager.File, 0)
|
|
if err := db.WithContext(ctx).Model(&filemanager.File{}).Find(&rows).Error; err != nil {
|
|
fmt.Fprintf(os.Stderr, "query files: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
report := backfillReport{DryRun: dryRun}
|
|
now := time.Now().UTC()
|
|
for i := range rows {
|
|
row := rows[i]
|
|
report.Total++
|
|
if row.DeletedAt != nil {
|
|
report.Skipped++
|
|
continue
|
|
}
|
|
|
|
referenced, refErr := lifecycleRepo.IsFileReferenced(ctx, row.ID)
|
|
if refErr != nil {
|
|
report.InvalidReference++
|
|
report.Skipped++
|
|
continue
|
|
}
|
|
|
|
if storage != nil {
|
|
if _, headErr := storage.HeadObject(ctx, row.ObjectKey); headErr != nil {
|
|
report.MissingS3Key++
|
|
}
|
|
}
|
|
|
|
if referenced {
|
|
report.ActiveFiles++
|
|
if !dryRun {
|
|
_ = lifecycleRepo.UpdateLifecycle(ctx, filemanager.FileLifecycleTransitionParams{
|
|
ID: row.ID,
|
|
Status: filemanager.FileLifecycleStatusActive,
|
|
AttachedAt: &now,
|
|
ClearOrphanedAt: true,
|
|
})
|
|
}
|
|
continue
|
|
}
|
|
|
|
report.ProbableOrphans++
|
|
if !dryRun {
|
|
_ = lifecycleRepo.UpdateLifecycle(ctx, filemanager.FileLifecycleTransitionParams{
|
|
ID: row.ID,
|
|
Status: filemanager.FileLifecycleStatusOrphanPending,
|
|
OrphanedAt: &now,
|
|
})
|
|
}
|
|
}
|
|
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetIndent("", " ")
|
|
_ = enc.Encode(report)
|
|
}
|
|
|
|
func configFilePath() string {
|
|
if v := os.Getenv("CONFIG_FILE"); v != "" {
|
|
return v
|
|
}
|
|
basePath := os.Getenv("CONFIG_BASE_PATH")
|
|
if basePath == "" {
|
|
basePath = "/tmp"
|
|
}
|
|
filename := os.Getenv("CONFIG_FILENAME")
|
|
if filename == "" {
|
|
filename = "config.generated.json"
|
|
}
|
|
return basePath + "/" + filename
|
|
}
|