2026-06-11 14:24:52 -04:00
package tasks
import (
"context"
"encoding/json"
"fmt"
"github.com/Silo-Server/silo-server/internal/notifications"
"github.com/Silo-Server/silo-server/internal/taskmanager"
)
2026-06-12 10:32:21 -04:00
// SeedContentAvailabilityTask inserts episode_availability and
// movie_availability rows for every currently playable episode and movie
// without creating release events, then writes the per-library, per-kind seed
// markers. Running it is what allows release events to flow for libraries
// that predate the notifications feature; rerunning is a cheap idempotent
// repair pass.
type SeedContentAvailabilityTask struct {
2026-06-11 14:24:52 -04:00
system * notifications . System
}
2026-06-12 10:32:21 -04:00
// NewSeedContentAvailabilityTask creates the seeding task.
func NewSeedContentAvailabilityTask ( system * notifications . System ) * SeedContentAvailabilityTask {
return & SeedContentAvailabilityTask { system : system }
2026-06-11 14:24:52 -04:00
}
2026-06-12 10:32:21 -04:00
func ( t * SeedContentAvailabilityTask ) Key () string { return "seed_content_availability" }
func ( t * SeedContentAvailabilityTask ) Name () string { return "Seed Content Availability" }
func ( t * SeedContentAvailabilityTask ) Description () string {
return "Records the existing episode and movie back-catalog as already-released so new-content notifications only fire for items that arrive afterwards."
2026-06-11 14:24:52 -04:00
}
2026-06-12 10:32:21 -04:00
func ( t * SeedContentAvailabilityTask ) Category () taskmanager . TaskCategory {
2026-06-11 14:24:52 -04:00
return taskmanager . TaskCategorySystem
}
2026-06-12 10:32:21 -04:00
func ( t * SeedContentAvailabilityTask ) IsHidden () bool { return true }
2026-06-11 14:24:52 -04:00
2026-06-12 10:32:21 -04:00
func ( t * SeedContentAvailabilityTask ) DefaultTriggers () [] taskmanager . TriggerConfig {
2026-06-11 14:24:52 -04:00
return [] taskmanager . TriggerConfig {{ Type : taskmanager . TriggerTypeStartup }}
}
2026-06-12 10:32:21 -04:00
func ( t * SeedContentAvailabilityTask ) Execute ( ctx context . Context , progress taskmanager . ProgressReporter ) error {
2026-06-11 14:24:52 -04:00
if t == nil || t . system == nil {
progress . Report ( 100 , "Notifications are not configured" )
return nil
}
2026-06-12 10:32:21 -04:00
progress . Report ( 0 , "Seeding content availability" )
2026-06-11 14:24:52 -04:00
if err := t . system . SeedAvailability ( ctx , func ( percent int , message string ) {
progress . Report ( float64 ( percent ), message )
}); err != nil {
2026-06-12 10:32:21 -04:00
return fmt . Errorf ( "seeding content availability: %w" , err )
2026-06-11 14:24:52 -04:00
}
2026-06-12 10:32:21 -04:00
progress . Report ( 100 , "Content availability seeded" )
2026-06-11 14:24:52 -04:00
return nil
}
// RebuildReleaseInterestTask rebuilds profile_series_interest from favorites,
// watchlist, and watch progress. It is the rollout backfill and the periodic
// drift-repair pass; recomputes share the same code as live updates.
type RebuildReleaseInterestTask struct {
system * notifications . System
}
// NewRebuildReleaseInterestTask creates the interest rebuild task.
func NewRebuildReleaseInterestTask ( system * notifications . System ) * RebuildReleaseInterestTask {
return & RebuildReleaseInterestTask { system : system }
}
func ( t * RebuildReleaseInterestTask ) Key () string { return "rebuild_release_interest" }
func ( t * RebuildReleaseInterestTask ) Name () string { return "Rebuild Notification Interest" }
func ( t * RebuildReleaseInterestTask ) Description () string {
return "Recomputes which profiles care about which series (favorites, watchlist, watch progress) for new-episode notifications."
}
func ( t * RebuildReleaseInterestTask ) Category () taskmanager . TaskCategory {
return taskmanager . TaskCategorySystem
}
func ( t * RebuildReleaseInterestTask ) IsHidden () bool { return true }
func ( t * RebuildReleaseInterestTask ) DefaultTriggers () [] taskmanager . TriggerConfig {
return [] taskmanager . TriggerConfig {
{ Type : taskmanager . TriggerTypeStartup },
{ Type : taskmanager . TriggerTypeDaily , TimeOfDay : "04:30" },
}
}
func ( t * RebuildReleaseInterestTask ) Execute ( ctx context . Context , progress taskmanager . ProgressReporter ) error {
if t == nil || t . system == nil {
progress . Report ( 100 , "Notifications are not configured" )
return nil
}
progress . Report ( 0 , "Rebuilding profile series interest" )
if err := t . system . RebuildInterest ( ctx , func ( percent int , message string ) {
progress . Report ( float64 ( percent ), message )
}); err != nil {
return fmt . Errorf ( "rebuilding notification interest: %w" , err )
}
progress . Report ( 100 , "Notification interest rebuilt" )
return nil
}
// NotificationsRetentionTask applies the notification retention policy: read
// inbox rows past the read window, unread rows past the unread window,
// processed release events past the debug window, and inert interest rows.
type NotificationsRetentionTask struct {
system * notifications . System
}
// NewNotificationsRetentionTask creates the retention task.
func NewNotificationsRetentionTask ( system * notifications . System ) * NotificationsRetentionTask {
return & NotificationsRetentionTask { system : system }
}
func ( t * NotificationsRetentionTask ) Key () string { return "notifications_retention" }
func ( t * NotificationsRetentionTask ) Name () string { return "Clean Up Notifications" }
func ( t * NotificationsRetentionTask ) Description () string {
return "Prunes old notifications and processed release events according to the retention settings."
}
func ( t * NotificationsRetentionTask ) Category () taskmanager . TaskCategory {
return taskmanager . TaskCategorySystem
}
func ( t * NotificationsRetentionTask ) IsHidden () bool { return false }
func ( t * NotificationsRetentionTask ) DefaultTriggers () [] taskmanager . TriggerConfig {
return [] taskmanager . TriggerConfig {{ Type : taskmanager . TriggerTypeDaily , TimeOfDay : "05:00" }}
}
func ( t * NotificationsRetentionTask ) Execute ( ctx context . Context , progress taskmanager . ProgressReporter ) error {
if t == nil || t . system == nil {
progress . Report ( 100 , "Notifications are not configured" )
return nil
}
progress . Report ( 0 , "Applying notification retention policy" )
stats , err := t . system . RunRetention ( ctx )
if err != nil {
return fmt . Errorf ( "notification retention: %w" , err )
}
if data , err := json . Marshal ( stats ); err == nil {
progress . SetResultData ( data )
}
progress . Report ( 100 , fmt . Sprintf (
"Removed %d notifications, %d release events, %d inert interest rows" ,
stats . DeliveriesDeleted , stats . EventsDeleted , stats . InterestPruned ))
return nil
}