- pin the four new sensitive setting keys (SMTP password, Discord secret/bot token, VAPID keypair) in the encryption audit test so a future drop from SensitiveSettingKeys fails CI - bound account-channel digest drains strictly before the stamped digest time so consecutive digest windows partition rows exactly, instead of recapping rows created at or after the previous stamp - keep the events websocket open when an event-frame snapshot fails, matching the writeSnapshotFrame degrade-gracefully contract - rename the seed task to Seed Content Availability to match its episode+movie seeding behavior - carry poster_source_path into realtime dispatch rows per the DeliveryRow contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
144 lines
5.8 KiB
Go
144 lines
5.8 KiB
Go
package tasks
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/notifications"
|
|
"github.com/Silo-Server/silo-server/internal/taskmanager"
|
|
)
|
|
|
|
// 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 {
|
|
system *notifications.System
|
|
}
|
|
|
|
// NewSeedContentAvailabilityTask creates the seeding task.
|
|
func NewSeedContentAvailabilityTask(system *notifications.System) *SeedContentAvailabilityTask {
|
|
return &SeedContentAvailabilityTask{system: system}
|
|
}
|
|
|
|
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."
|
|
}
|
|
func (t *SeedContentAvailabilityTask) Category() taskmanager.TaskCategory {
|
|
return taskmanager.TaskCategorySystem
|
|
}
|
|
func (t *SeedContentAvailabilityTask) IsHidden() bool { return true }
|
|
|
|
func (t *SeedContentAvailabilityTask) DefaultTriggers() []taskmanager.TriggerConfig {
|
|
return []taskmanager.TriggerConfig{{Type: taskmanager.TriggerTypeStartup}}
|
|
}
|
|
|
|
func (t *SeedContentAvailabilityTask) 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, "Seeding content availability")
|
|
if err := t.system.SeedAvailability(ctx, func(percent int, message string) {
|
|
progress.Report(float64(percent), message)
|
|
}); err != nil {
|
|
return fmt.Errorf("seeding content availability: %w", err)
|
|
}
|
|
progress.Report(100, "Content availability seeded")
|
|
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
|
|
}
|