2026-08-15 09:23:26 +12:00
|
|
|
package credits
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/ponzischeme89/memby/server/internal/store"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// The adapter between this package's vocabulary and the store's.
|
|
|
|
|
//
|
|
|
|
|
// It exists so every rule above it — candidate generation, decay, clustering, confidence,
|
|
|
|
|
// the queue — can be tested against literals with no database, which is most of why this
|
|
|
|
|
// subsystem could be built without a Postgres to hand.
|
|
|
|
|
|
|
|
|
|
// Postgres implements Repository, Database and BehaviourSource over the gateway's store.
|
|
|
|
|
type Postgres struct {
|
|
|
|
|
Store *store.Store
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p Postgres) GetMarker(
|
|
|
|
|
ctx context.Context, itemID, fingerprint string,
|
|
|
|
|
) (Marker, bool, error) {
|
|
|
|
|
row, found, err := p.Store.CreditsMarker(ctx, itemID, fingerprint)
|
|
|
|
|
if err != nil || !found {
|
|
|
|
|
return Marker{}, false, err
|
|
|
|
|
}
|
|
|
|
|
return markerFrom(row), true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p Postgres) SaveMarker(ctx context.Context, marker Marker) error {
|
|
|
|
|
return p.Store.SaveCreditsMarker(ctx, store.CreditsMarkerRow{
|
|
|
|
|
ItemID: marker.ItemID,
|
|
|
|
|
MediaFingerprint: marker.MediaFingerprint,
|
|
|
|
|
CreditsStartMs: marker.CreditsStartMs,
|
|
|
|
|
Confidence: marker.Confidence,
|
|
|
|
|
DetectionMethod: marker.DetectionMethod,
|
|
|
|
|
SeriesID: marker.SeriesID,
|
|
|
|
|
Season: marker.Season,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 12:13:51 +12:00
|
|
|
func (p Postgres) SaveScanAttempt(ctx context.Context, attempt ScanAttempt) error {
|
|
|
|
|
return p.Store.SaveCreditsScanAttempt(ctx, store.CreditsScanHistoryRow{
|
|
|
|
|
ItemID: attempt.ItemID, SeriesID: attempt.SeriesID,
|
|
|
|
|
Season: attempt.Season, Episode: attempt.Episode,
|
|
|
|
|
Reason: attempt.Reason, Priority: attempt.Priority, Outcome: attempt.Outcome,
|
|
|
|
|
MarkerMs: attempt.MarkerMs, Confidence: attempt.Confidence,
|
|
|
|
|
Method: attempt.Method, Frames: attempt.Frames, Error: attempt.Error,
|
|
|
|
|
StartedAt: attempt.StartedAt, FinishedAt: attempt.FinishedAt,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p Postgres) RecentScanTimes(
|
|
|
|
|
ctx context.Context, itemIDs []string, since time.Time,
|
|
|
|
|
) (map[string]time.Time, error) {
|
|
|
|
|
return p.Store.RecentCreditsScanTimes(ctx, itemIDs, since)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-15 09:23:26 +12:00
|
|
|
func (p Postgres) SeasonMarkers(
|
|
|
|
|
ctx context.Context, seriesID string, season, limit int,
|
|
|
|
|
) ([]Marker, error) {
|
|
|
|
|
rows, err := p.Store.CreditsSeasonMarkers(ctx, seriesID, season, limit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out := make([]Marker, 0, len(rows))
|
|
|
|
|
for _, row := range rows {
|
|
|
|
|
out = append(out, markerFrom(row))
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p Postgres) RecentWatches(
|
|
|
|
|
ctx context.Context, since time.Time, limit int,
|
|
|
|
|
) ([]Watch, error) {
|
|
|
|
|
rows, err := p.Store.CreditsRecentWatches(ctx, since, limit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out := make([]Watch, 0, len(rows))
|
|
|
|
|
for _, row := range rows {
|
|
|
|
|
out = append(out, Watch{
|
|
|
|
|
UserKey: row.UserKey,
|
|
|
|
|
SeriesID: row.SeriesID,
|
|
|
|
|
Season: row.Season,
|
|
|
|
|
Episode: row.Episode,
|
|
|
|
|
WatchedAt: row.WatchedAt,
|
|
|
|
|
Completed: row.Completed,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p Postgres) SeriesEpisodes(
|
|
|
|
|
ctx context.Context, seriesIDs []string,
|
|
|
|
|
) ([]SeriesEpisode, error) {
|
|
|
|
|
rows, err := p.Store.CreditsSeriesEpisodes(ctx, seriesIDs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out := make([]SeriesEpisode, 0, len(rows))
|
|
|
|
|
for _, row := range rows {
|
|
|
|
|
out = append(out, SeriesEpisode{
|
|
|
|
|
ItemID: row.ItemID,
|
|
|
|
|
SeriesID: row.SeriesID,
|
|
|
|
|
Season: row.Season,
|
|
|
|
|
Episode: row.Episode,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p Postgres) Stops(ctx context.Context, itemID string) ([]StopEvent, error) {
|
|
|
|
|
rows, err := p.Store.CreditsStops(ctx, itemID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out := make([]StopEvent, 0, len(rows))
|
|
|
|
|
for _, row := range rows {
|
|
|
|
|
out = append(out, StopEvent{
|
|
|
|
|
UserKey: row.UserKey,
|
|
|
|
|
PositionMs: row.PositionMs,
|
|
|
|
|
RuntimeMs: row.RuntimeMs,
|
|
|
|
|
NextEpisode: row.NextEpisode,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func markerFrom(row store.CreditsMarkerRow) Marker {
|
|
|
|
|
return Marker{
|
|
|
|
|
ItemID: row.ItemID,
|
|
|
|
|
MediaFingerprint: row.MediaFingerprint,
|
|
|
|
|
CreditsStartMs: row.CreditsStartMs,
|
|
|
|
|
Confidence: row.Confidence,
|
|
|
|
|
DetectionMethod: row.DetectionMethod,
|
|
|
|
|
SeriesID: row.SeriesID,
|
|
|
|
|
Season: row.Season,
|
|
|
|
|
CreatedAt: row.CreatedAt,
|
|
|
|
|
UpdatedAt: row.UpdatedAt,
|
|
|
|
|
}
|
|
|
|
|
}
|