Files
memby/server/internal/credits/postgres.go
2026-08-16 12:13:51 +12:00

144 lines
4.0 KiB
Go

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,
})
}
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)
}
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,
}
}