This commit is contained in:
ponzischeme89
2026-08-11 23:41:10 +12:00
parent 0e183cf560
commit 5fed3360c2
42 changed files with 1340 additions and 130 deletions
+96
View File
@@ -3,6 +3,7 @@ package store
import (
"context"
"fmt"
"strings"
"time"
)
@@ -31,6 +32,24 @@ type UserNotification struct {
ReadAt *time.Time `json:"readAt,omitempty"`
}
// SonarrSeriesStatus is one daily observation. SeriesKey prefers Sonarr's stable TVDB id;
// the local Sonarr id is retained for diagnosis and as a fallback when TVDB has no answer.
type SonarrSeriesStatus struct {
SeriesKey string
SonarrSeriesID int
TVDBID int
Title string
Year int
Status string
ObservedAt time.Time
}
type SonarrSeriesStatusChange struct {
HistoryID int64
PreviousStatus string
Current SonarrSeriesStatus
}
func (s *Store) SaveUserShow(ctx context.Context, userID string, show UserShow) error {
_, err := s.pool.Exec(ctx, `
INSERT INTO user_shows (emby_user_id, item_id, title, year, image_tag)
@@ -89,6 +108,83 @@ func (s *Store) UserShows(ctx context.Context, userID string) ([]UserShow, error
return shows, rows.Err()
}
// RecordSonarrSeriesStatuses appends only first sightings and changes. A first sighting is
// the baseline and is deliberately absent from the returned changes, so enabling the
// scanner cannot announce every series that was already cancelled before it existed.
func (s *Store) RecordSonarrSeriesStatuses(
ctx context.Context, observations []SonarrSeriesStatus,
) ([]SonarrSeriesStatusChange, error) {
tx, err := s.pool.Begin(ctx)
if err != nil {
return nil, fmt.Errorf("store: begin Sonarr status history: %w", err)
}
defer tx.Rollback(ctx)
rows, err := tx.Query(ctx, `
SELECT DISTINCT ON (series_key) series_key, status
FROM sonarr_series_status_history
ORDER BY series_key, observed_at DESC, id DESC`)
if err != nil {
return nil, fmt.Errorf("store: read Sonarr status history: %w", err)
}
previous := map[string]string{}
for rows.Next() {
var key, status string
if err := rows.Scan(&key, &status); err != nil {
rows.Close()
return nil, fmt.Errorf("store: scan Sonarr status history: %w", err)
}
previous[key] = status
}
if err := rows.Err(); err != nil {
rows.Close()
return nil, fmt.Errorf("store: iterate Sonarr status history: %w", err)
}
rows.Close()
changes := []SonarrSeriesStatusChange{}
seen := map[string]bool{}
for _, observation := range observations {
observation.SeriesKey = strings.TrimSpace(observation.SeriesKey)
observation.Title = strings.TrimSpace(observation.Title)
observation.Status = strings.ToLower(strings.TrimSpace(observation.Status))
if observation.SeriesKey == "" || observation.Title == "" || observation.Status == "" ||
seen[observation.SeriesKey] {
continue
}
seen[observation.SeriesKey] = true
prior, known := previous[observation.SeriesKey]
if known && strings.EqualFold(prior, observation.Status) {
continue
}
if observation.ObservedAt.IsZero() {
observation.ObservedAt = time.Now()
}
var historyID int64
err := tx.QueryRow(ctx, `
INSERT INTO sonarr_series_status_history
(series_key, sonarr_series_id, tvdb_id, title, year, status, observed_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id`,
observation.SeriesKey, observation.SonarrSeriesID, observation.TVDBID,
observation.Title, observation.Year, observation.Status, observation.ObservedAt,
).Scan(&historyID)
if err != nil {
return nil, fmt.Errorf("store: append Sonarr status history: %w", err)
}
if known {
changes = append(changes, SonarrSeriesStatusChange{
HistoryID: historyID, PreviousStatus: prior, Current: observation,
})
}
previous[observation.SeriesKey] = observation.Status
}
if err := tx.Commit(ctx); err != nil {
return nil, fmt.Errorf("store: commit Sonarr status history: %w", err)
}
return changes, nil
}
func (s *Store) NotificationPreferences(ctx context.Context, userID string) (NotificationPreferences, error) {
prefs := NotificationPreferences{Enabled: true, ShowReturnAlerts: true, LeadDays: 7}
err := s.pool.QueryRow(ctx, `