Skip to content

Commit e522daf

Browse files
authored
feat: add tag cleanup worker for low-view tags (#101)
Add a tag cleanup job that removes tags under 500 views along with related history and relations in a single transaction. Wire the worker into the manager and expose an interval via config/env for scheduled runs. Closes #100 ZerGo0 Bot Co-authored-by: ZerGo0 <ZerGo0@users.noreply.github.com>
1 parent 6ca2bb0 commit e522daf

File tree

4 files changed

+94
-1
lines changed

4 files changed

+94
-1
lines changed

backend-go/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ WORKER_DISCOVERY_INTERVAL=60000
2424
# Rank calculation worker interval (milliseconds)
2525
# How often to recalculate tag rankings
2626
RANK_CALCULATION_INTERVAL=60000
27+
WORKER_TAG_CLEANUP_INTERVAL=3600000
2728

2829
# Fansly API Configuration
2930
# Optional: Authentication token for Fansly API (if required)
@@ -35,4 +36,4 @@ FANSLY_GLOBAL_RATE_LIMIT=5
3536

3637
# Time window for global rate limit (seconds)
3738
# Example: 50 requests per 10 seconds
38-
FANSLY_GLOBAL_RATE_LIMIT_WINDOW=10
39+
FANSLY_GLOBAL_RATE_LIMIT_WINDOW=10

backend-go/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Config struct {
2020
WorkerDiscoveryInterval int
2121
RankCalculationInterval int
2222
WorkerStatisticsInterval int
23+
WorkerTagCleanupInterval int
2324
GlobalRateLimit int
2425
GlobalRateLimitWindow int
2526
APIGlobalRateLimit int
@@ -42,6 +43,7 @@ func Load() *Config {
4243
WorkerDiscoveryInterval: getEnvInt("WORKER_DISCOVERY_INTERVAL", 60000*10),
4344
RankCalculationInterval: getEnvInt("RANK_CALCULATION_INTERVAL", 60000*10),
4445
WorkerStatisticsInterval: getEnvInt("WORKER_STATISTICS_INTERVAL", 3600000), // Default to 1 hour
46+
WorkerTagCleanupInterval: getEnvInt("WORKER_TAG_CLEANUP_INTERVAL", 3600000),
4547
GlobalRateLimit: getEnvInt("FANSLY_GLOBAL_RATE_LIMIT", 50),
4648
GlobalRateLimitWindow: getEnvInt("FANSLY_GLOBAL_RATE_LIMIT_WINDOW", 10),
4749
APIGlobalRateLimit: getEnvInt("API_GLOBAL_RATE_LIMIT", 600),

backend-go/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ func main() {
102102
rankCalculator := workers.NewRankCalculatorWorker(db, cfg)
103103
creatorUpdater := workers.NewCreatorUpdaterWorker(db, fanslyClient)
104104
statisticsCalculator := workers.NewStatisticsCalculatorWorker(db, cfg)
105+
tagCleanup := workers.NewTagCleanupWorker(db, cfg)
105106

106107
if err := workerManager.Register(tagUpdater); err != nil {
107108
zap.L().Error("Failed to register tag updater", zap.Error(err))
@@ -118,6 +119,9 @@ func main() {
118119
if err := workerManager.Register(statisticsCalculator); err != nil {
119120
zap.L().Error("Failed to register statistics calculator", zap.Error(err))
120121
}
122+
if err := workerManager.Register(tagCleanup); err != nil {
123+
zap.L().Error("Failed to register tag cleanup", zap.Error(err))
124+
}
121125

122126
// Start workers if enabled
123127
if cfg.WorkerEnabled {
@@ -137,6 +141,9 @@ func main() {
137141
if err := workerManager.Start("statistics-calculator"); err != nil {
138142
zap.L().Error("Failed to start statistics calculator", zap.Error(err))
139143
}
144+
if err := workerManager.Start("tag-cleanup"); err != nil {
145+
zap.L().Error("Failed to start tag cleanup", zap.Error(err))
146+
}
140147
}()
141148
}
142149

backend-go/workers/tag_cleanup.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package workers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"ftoolbox/config"
7+
"ftoolbox/models"
8+
"time"
9+
10+
"go.uber.org/zap"
11+
"gorm.io/gorm"
12+
)
13+
14+
type TagCleanupWorker struct {
15+
BaseWorker
16+
db *gorm.DB
17+
minViews int64
18+
}
19+
20+
func NewTagCleanupWorker(db *gorm.DB, cfg *config.Config) *TagCleanupWorker {
21+
interval := time.Duration(cfg.WorkerTagCleanupInterval) * time.Millisecond
22+
23+
return &TagCleanupWorker{
24+
BaseWorker: NewBaseWorker("tag-cleanup", interval),
25+
db: db,
26+
minViews: 500,
27+
}
28+
}
29+
30+
func (w *TagCleanupWorker) Run(ctx context.Context) error {
31+
select {
32+
case <-ctx.Done():
33+
return ctx.Err()
34+
default:
35+
}
36+
37+
zap.L().Info("Running tag cleanup", zap.Int64("minViews", w.minViews))
38+
39+
var tagIDs []string
40+
if err := w.db.Model(&models.Tag{}).
41+
Where("view_count < ?", w.minViews).
42+
Pluck("id", &tagIDs).Error; err != nil {
43+
return fmt.Errorf("failed to load tags for cleanup: %w", err)
44+
}
45+
46+
if len(tagIDs) == 0 {
47+
zap.L().Debug("No tags to cleanup")
48+
return nil
49+
}
50+
51+
select {
52+
case <-ctx.Done():
53+
return ctx.Err()
54+
default:
55+
}
56+
57+
tx := w.db.Begin()
58+
59+
if err := tx.Where("tag_id IN ?", tagIDs).Delete(&models.TagHistory{}).Error; err != nil {
60+
tx.Rollback()
61+
return fmt.Errorf("failed to delete tag history: %w", err)
62+
}
63+
64+
if err := tx.Where("tag_id IN ? OR related_tag_id IN ?", tagIDs, tagIDs).
65+
Delete(&models.TagRelationDaily{}).Error; err != nil {
66+
tx.Rollback()
67+
return fmt.Errorf("failed to delete tag relations: %w", err)
68+
}
69+
70+
result := tx.Where("id IN ?", tagIDs).Delete(&models.Tag{})
71+
if result.Error != nil {
72+
tx.Rollback()
73+
return fmt.Errorf("failed to delete tags: %w", result.Error)
74+
}
75+
76+
if err := tx.Commit().Error; err != nil {
77+
return fmt.Errorf("failed to commit tag cleanup: %w", err)
78+
}
79+
80+
zap.L().Info("Tag cleanup completed", zap.Int64("deleted", result.RowsAffected))
81+
82+
return nil
83+
}

0 commit comments

Comments
 (0)