feat: ensure anime row exists before saving progress

This commit is contained in:
2026-06-14 21:53:41 +02:00
parent 4a74fdcf31
commit 3e100c1a97
4 changed files with 61 additions and 7 deletions

View File

@@ -89,6 +89,8 @@ type EpisodeData struct {
type PlaybackRepository interface { type PlaybackRepository interface {
InTx(ctx context.Context, fn func(ctx context.Context, repo PlaybackRepository) error) error InTx(ctx context.Context, fn func(ctx context.Context, repo PlaybackRepository) error) error
UpsertAnime(ctx context.Context, params db.UpsertAnimeParams) (db.Anime, error)
GetAnime(ctx context.Context, id int64) (db.Anime, error)
GetWatchListEntry(ctx context.Context, params db.GetWatchListEntryParams) (db.WatchListEntry, error) GetWatchListEntry(ctx context.Context, params db.GetWatchListEntryParams) (db.WatchListEntry, error)
GetContinueWatchingEntry(ctx context.Context, params db.GetContinueWatchingEntryParams) (db.ContinueWatchingEntry, error) GetContinueWatchingEntry(ctx context.Context, params db.GetContinueWatchingEntryParams) (db.ContinueWatchingEntry, error)
SaveWatchProgress(ctx context.Context, params db.SaveWatchProgressParams) error SaveWatchProgress(ctx context.Context, params db.SaveWatchProgressParams) error

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"strconv" "strconv"
"github.com/google/uuid" "github.com/google/uuid"
@@ -108,7 +109,14 @@ func (s *playbackService) CompleteAnime(ctx context.Context, userID string, anim
} }
func (s *playbackService) SaveProgress(ctx context.Context, userID string, animeID int64, episode int, timeSeconds float64) error { func (s *playbackService) SaveProgress(ctx context.Context, userID string, animeID int64, episode int, timeSeconds float64) error {
_, err := s.repo.UpsertContinueWatchingEntry(ctx, db.UpsertContinueWatchingEntryParams{ err := s.repo.InTx(ctx, func(txCtx context.Context, repo domain.PlaybackRepository) error {
if _, err := repo.GetAnime(txCtx, animeID); err != nil {
if _, err := repo.UpsertAnime(txCtx, minimalAnimeParams(animeID)); err != nil {
return err
}
}
_, err := repo.UpsertContinueWatchingEntry(txCtx, db.UpsertContinueWatchingEntryParams{
ID: uuid.New().String(), ID: uuid.New().String(),
UserID: userID, UserID: userID,
AnimeID: animeID, AnimeID: animeID,
@@ -116,6 +124,8 @@ func (s *playbackService) SaveProgress(ctx context.Context, userID string, anime
CurrentTimeSeconds: timeSeconds, CurrentTimeSeconds: timeSeconds,
DurationSeconds: sql.NullFloat64{Valid: false}, DurationSeconds: sql.NullFloat64{Valid: false},
}) })
return err
})
if err != nil { if err != nil {
return err return err
} }
@@ -148,3 +158,36 @@ func (s *playbackService) SaveProgress(ctx context.Context, userID string, anime
}) })
return nil return nil
} }
func (s *playbackService) ensureAnimeRow(ctx context.Context, anime domain.Anime) {
if _, err := s.repo.GetAnime(ctx, int64(anime.MalID)); err == nil {
return
}
_, _ = s.repo.UpsertAnime(ctx, animeParams(anime))
}
func animeParams(anime domain.Anime) db.UpsertAnimeParams {
durationSeconds := anime.DurationSeconds()
duration := sql.NullFloat64{Valid: durationSeconds > 0}
if duration.Valid {
duration.Float64 = durationSeconds
}
return db.UpsertAnimeParams{
ID: int64(anime.MalID),
TitleOriginal: anime.Title,
TitleEnglish: sql.NullString{String: anime.TitleEnglish, Valid: anime.TitleEnglish != ""},
TitleJapanese: sql.NullString{String: anime.TitleJapanese, Valid: anime.TitleJapanese != ""},
ImageUrl: anime.ImageURL(),
Airing: sql.NullBool{Bool: anime.Airing, Valid: true},
DurationSeconds: duration,
}
}
func minimalAnimeParams(animeID int64) db.UpsertAnimeParams {
return db.UpsertAnimeParams{
ID: animeID,
TitleOriginal: fmt.Sprintf("Anime %d", animeID),
Airing: sql.NullBool{Valid: false},
}
}

View File

@@ -23,6 +23,14 @@ func (r *playbackRepository) InTx(ctx context.Context, fn func(ctx context.Conte
}, fn) }, fn)
} }
func (r *playbackRepository) UpsertAnime(ctx context.Context, params db.UpsertAnimeParams) (db.Anime, error) {
return r.queries.UpsertAnime(ctx, params)
}
func (r *playbackRepository) GetAnime(ctx context.Context, id int64) (db.Anime, error) {
return r.queries.GetAnime(ctx, id)
}
func (r *playbackRepository) GetWatchListEntry(ctx context.Context, params db.GetWatchListEntryParams) (db.WatchListEntry, error) { func (r *playbackRepository) GetWatchListEntry(ctx context.Context, params db.GetWatchListEntryParams) (db.WatchListEntry, error) {
return r.queries.GetWatchListEntry(ctx, params) return r.queries.GetWatchListEntry(ctx, params)
} }

View File

@@ -18,6 +18,7 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
} }
animeData := domain.Anime{Anime: anime} animeData := domain.Anime{Anime: anime}
s.ensureAnimeRow(ctx, animeData)
searchTitles := buildSearchTitles(animeData, titleCandidates) searchTitles := buildSearchTitles(animeData, titleCandidates)
canonicalEpisodes, err := s.episodes.GetCanonicalEpisodes(ctx, animeData, false) canonicalEpisodes, err := s.episodes.GetCanonicalEpisodes(ctx, animeData, false)
if err != nil { if err != nil {