feat(playlists): gocron-based per-user scheduler
Builds the per-user daily-at-03:00-local scheduler for #392 Half B. Uses github.com/go-co-op/gocron/v2 with WithLocation(userTZ) for each user's job. Hourly reconciliation pass keeps the in-memory job set in sync with the active-users query. Start sequence: clear stale in_flight rows; register a daily job for every active user at their stored timezone; fire a one-shot runOnce to catch up missed schedules during downtime; start gocron. Stop drains and shuts down the gocron loop. Refresh(ctx, userID) removes the user's existing job (if any) and registers a fresh one at their current timezone — wired into the PUT /api/me/timezone handler and new-user registration in the next commit. Server struct gains PlaylistScheduler; main.go constructs it after the eventbus and threads it through to api.Mount. The existing StartSystemPlaylistCron call stays for one more commit so we don't have a window where no scheduler is running; Task 3 deletes it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,12 @@ type Server struct {
|
||||
// lidarr reconciler, scan scheduler) constructed in cmd/minstrel/main.go.
|
||||
// When nil, Router() constructs a local fallback (test contexts).
|
||||
Bus *eventbus.Bus
|
||||
// PlaylistScheduler fires per-user daily system-playlist builds at
|
||||
// 03:00 in each user's stored timezone (#392 Half B). Constructed
|
||||
// in cmd/minstrel/main.go and threaded into the API handlers so
|
||||
// PUT /api/me/timezone and POST /api/auth/register can call
|
||||
// Refresh synchronously.
|
||||
PlaylistScheduler *playlists.Scheduler
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, pool *pgxpool.Pool, scanner ScanTrigger, subCfg subsonic.Config, eventsCfg config.EventsConfig, recCfg config.RecommendationConfig, dataDir string, brandingCfg config.BrandingConfig, coverEnricher *coverart.Enricher, coverArtBackfillCap int, coverSettings *coverart.SettingsService, libraryScanner *library.Scanner, scanCfg library.RunScanConfig, scheduler *library.Scheduler) *Server {
|
||||
@@ -133,7 +139,7 @@ func (s *Server) Router() http.Handler {
|
||||
if bus == nil {
|
||||
bus = eventbus.New()
|
||||
}
|
||||
api.Mount(r, s.Pool, s.Logger, writer, s.RecommendationCfg, lidarrCfg, lidarrReqs, lidarrQuar, tracksSvc, playlistsSvc, s.CoverEnricher, s.CoverArtBackfillCap, s.CoverSettings, s.LibraryScanner, s.ScanCfg, s.Scheduler, s.DataDir, smtpSender, bus)
|
||||
api.Mount(r, s.Pool, s.Logger, writer, s.RecommendationCfg, lidarrCfg, lidarrReqs, lidarrQuar, tracksSvc, playlistsSvc, s.CoverEnricher, s.CoverArtBackfillCap, s.CoverSettings, s.LibraryScanner, s.ScanCfg, s.Scheduler, s.DataDir, smtpSender, bus, s.PlaylistScheduler)
|
||||
// /api/admin/scan is the only admin route owned by the server package
|
||||
// (it needs the Scanner). Register it as a single inline-middleware
|
||||
// route — using r.Route("/api/admin", ...) here would create a second
|
||||
|
||||
Reference in New Issue
Block a user