feat(server/m7-352): cron loop + main.go wiring for system playlist build

This commit is contained in:
2026-05-04 09:16:07 -04:00
parent 797a2c8a45
commit 391f5f16a1
2 changed files with 63 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
package playlists
import (
"context"
"log/slog"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
// StartSystemPlaylistCron launches the in-process daily build loop in a
// goroutine. Caller passes a context that's cancelled on graceful shutdown.
//
// Lifecycle:
// 1. Clear any stale in_flight=true rows from a previously crashed process
// (safe at startup: by definition, nothing is running yet).
// 2. Run once at startup so users catch up after a server restart without
// waiting up to 24h.
// 3. Tick every 24h; runOnce on each tick.
// 4. Exit on context cancellation.
//
// Per-user errors are logged but don't halt the loop. Per-cycle errors (e.g.,
// "list active users" failed) are logged but the next tick still fires.
func StartSystemPlaylistCron(ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger) {
go func() {
q := dbq.New(pool)
if err := q.ClearStaleSystemPlaylistInFlight(ctx); err != nil {
logger.Warn("system playlist cron: startup recovery failed", "err", err)
}
runOnce(ctx, pool, logger)
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
runOnce(ctx, pool, logger)
}
}
}()
}
func runOnce(ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger) {
q := dbq.New(pool)
users, err := q.ListActiveUsersForSystemPlaylists(ctx)
if err != nil {
logger.Error("system playlist cron: list active users failed", "err", err)
return
}
for _, userID := range users {
if err := BuildSystemPlaylists(ctx, pool, logger, userID, time.Now()); err != nil {
logger.Warn("system playlist build failed in cron",
"user_id", uuidStringPL(userID), "err", err)
}
}
}