feat: implement configurable background jobs and toggle flags

Introduce environment variables to globally and individually control background jobs:
- `WARPBOX_JOBS_ENABLED` to toggle all background workers.
- `WARPBOX_CLEANUP_ENABLED` to toggle the expired box cleanup job.
- `WARPBOX_THUMBNAIL_ENABLED` to toggle the thumbnail generation job.

Refactor background tasks into a dedicated `backend/libs/jobs` package, allowing jobs to be registered, scheduled, and conditionally run based on the new configuration flags. Additionally, update the default maximum upload size in `.env.example` to 16GB and document the new settings in the README.
This commit is contained in:
2026-05-29 22:25:59 +03:00
parent 720b45a9a6
commit 74ede000b4
11 changed files with 431 additions and 275 deletions

75
backend/libs/jobs/jobs.go Normal file
View File

@@ -0,0 +1,75 @@
package jobs
import (
"log/slog"
"sync"
"time"
"warpbox.dev/backend/libs/config"
"warpbox.dev/backend/libs/services"
)
type job struct {
name string
enabled bool
interval time.Duration
run func()
}
func StartAll(cfg config.Config, logger *slog.Logger, uploadService *services.UploadService) func() {
if !cfg.JobsEnabled {
logger.Info("background jobs disabled", "source", "jobs", "severity", "dev")
return func() {}
}
stops := []func(){
start(newCleanupJob(cfg, logger, uploadService), logger),
start(newThumbnailsJob(cfg, logger, uploadService), logger),
}
var once sync.Once
return func() {
once.Do(func() {
for _, stop := range stops {
stop()
}
})
}
}
func start(j job, logger *slog.Logger) func() {
if !j.enabled {
logger.Info("background job disabled", "source", "jobs", "severity", "dev", "job", j.name)
return func() {}
}
if j.interval <= 0 {
logger.Info("background job disabled by interval", "source", "jobs", "severity", "dev", "job", j.name, "interval", j.interval.String())
return func() {}
}
stop := make(chan struct{})
done := make(chan struct{})
go func() {
defer close(done)
j.run()
ticker := time.NewTicker(j.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
j.run()
case <-stop:
return
}
}
}()
var once sync.Once
return func() {
once.Do(func() {
close(stop)
<-done
})
}
}