feat: add episode availability sql queries and generated code

This commit is contained in:
2026-05-17 21:16:14 +02:00
parent cb143be35c
commit 66333b335b
2 changed files with 276 additions and 0 deletions

View File

@@ -243,3 +243,77 @@ WHERE anime_id = ?;
SELECT COUNT(*)
FROM anime_fetch_retry
WHERE next_retry_at <= CURRENT_TIMESTAMP;
-- name: GetEpisodeAvailabilityCache :one
SELECT anime_id, data, next_refresh_at, retry_until_at, last_attempt_at, last_success_at, failure_count, last_error, updated_at
FROM episode_availability_cache
WHERE anime_id = ? LIMIT 1;
-- name: UpsertEpisodeAvailabilityCache :exec
INSERT INTO episode_availability_cache (
anime_id,
data,
next_refresh_at,
retry_until_at,
last_attempt_at,
last_success_at,
failure_count,
last_error,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id) DO UPDATE SET
data = excluded.data,
next_refresh_at = excluded.next_refresh_at,
retry_until_at = excluded.retry_until_at,
last_attempt_at = excluded.last_attempt_at,
last_success_at = excluded.last_success_at,
failure_count = excluded.failure_count,
last_error = excluded.last_error,
updated_at = CURRENT_TIMESTAMP;
-- name: MarkEpisodeAvailabilityRefreshFailed :exec
UPDATE episode_availability_cache
SET last_attempt_at = ?,
failure_count = failure_count + 1,
last_error = ?,
next_refresh_at = ?,
retry_until_at = ?,
updated_at = CURRENT_TIMESTAMP
WHERE anime_id = ?;
-- name: UpsertEpisodeProviderMapping :exec
INSERT INTO episode_provider_mapping (anime_id, provider, provider_show_id, failed_until, last_error, updated_at)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id, provider) DO UPDATE SET
provider_show_id = excluded.provider_show_id,
failed_until = excluded.failed_until,
last_error = excluded.last_error,
updated_at = CURRENT_TIMESTAMP;
-- name: GetEpisodeProviderMapping :one
SELECT anime_id, provider, provider_show_id, failed_until, last_error, updated_at
FROM episode_provider_mapping
WHERE anime_id = ? AND provider = ? LIMIT 1;
-- name: GetTrackedAiringAnimeIDsDueForEpisodeRefresh :many
WITH tracked AS (
SELECT DISTINCT w.anime_id
FROM watch_list_entry w
JOIN anime a ON a.id = w.anime_id
WHERE a.airing = 1
AND w.status IN ('watching', 'plan_to_watch')
UNION
SELECT DISTINCT c.anime_id
FROM continue_watching_entry c
JOIN anime a ON a.id = c.anime_id
WHERE a.airing = 1
)
SELECT tracked.anime_id
FROM tracked
LEFT JOIN episode_availability_cache e ON e.anime_id = tracked.anime_id
WHERE e.anime_id IS NULL OR e.next_refresh_at IS NULL OR e.next_refresh_at <= CURRENT_TIMESTAMP
ORDER BY tracked.anime_id
LIMIT ?;

View File

@@ -339,6 +339,54 @@ func (q *Queries) GetDueAnimeFetchRetries(ctx context.Context, limit int64) ([]A
return items, nil
}
const getEpisodeAvailabilityCache = `-- name: GetEpisodeAvailabilityCache :one
SELECT anime_id, data, next_refresh_at, retry_until_at, last_attempt_at, last_success_at, failure_count, last_error, updated_at
FROM episode_availability_cache
WHERE anime_id = ? LIMIT 1
`
func (q *Queries) GetEpisodeAvailabilityCache(ctx context.Context, animeID int64) (EpisodeAvailabilityCache, error) {
row := q.db.QueryRowContext(ctx, getEpisodeAvailabilityCache, animeID)
var i EpisodeAvailabilityCache
err := row.Scan(
&i.AnimeID,
&i.Data,
&i.NextRefreshAt,
&i.RetryUntilAt,
&i.LastAttemptAt,
&i.LastSuccessAt,
&i.FailureCount,
&i.LastError,
&i.UpdatedAt,
)
return i, err
}
const getEpisodeProviderMapping = `-- name: GetEpisodeProviderMapping :one
SELECT anime_id, provider, provider_show_id, failed_until, last_error, updated_at
FROM episode_provider_mapping
WHERE anime_id = ? AND provider = ? LIMIT 1
`
type GetEpisodeProviderMappingParams struct {
AnimeID int64 `json:"anime_id"`
Provider string `json:"provider"`
}
func (q *Queries) GetEpisodeProviderMapping(ctx context.Context, arg GetEpisodeProviderMappingParams) (EpisodeProviderMapping, error) {
row := q.db.QueryRowContext(ctx, getEpisodeProviderMapping, arg.AnimeID, arg.Provider)
var i EpisodeProviderMapping
err := row.Scan(
&i.AnimeID,
&i.Provider,
&i.ProviderShowID,
&i.FailedUntil,
&i.LastError,
&i.UpdatedAt,
)
return i, err
}
const getJikanCache = `-- name: GetJikanCache :one
SELECT data FROM jikan_cache
WHERE key = ? AND expires_at > CURRENT_TIMESTAMP LIMIT 1
@@ -379,6 +427,52 @@ func (q *Queries) GetSession(ctx context.Context, id string) (Session, error) {
return i, err
}
const getTrackedAiringAnimeIDsDueForEpisodeRefresh = `-- name: GetTrackedAiringAnimeIDsDueForEpisodeRefresh :many
WITH tracked AS (
SELECT DISTINCT w.anime_id
FROM watch_list_entry w
JOIN anime a ON a.id = w.anime_id
WHERE a.airing = 1
AND w.status IN ('watching', 'plan_to_watch')
UNION
SELECT DISTINCT c.anime_id
FROM continue_watching_entry c
JOIN anime a ON a.id = c.anime_id
WHERE a.airing = 1
)
SELECT tracked.anime_id
FROM tracked
LEFT JOIN episode_availability_cache e ON e.anime_id = tracked.anime_id
WHERE e.anime_id IS NULL OR e.next_refresh_at IS NULL OR e.next_refresh_at <= CURRENT_TIMESTAMP
ORDER BY tracked.anime_id
LIMIT ?
`
func (q *Queries) GetTrackedAiringAnimeIDsDueForEpisodeRefresh(ctx context.Context, limit int64) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getTrackedAiringAnimeIDsDueForEpisodeRefresh, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []int64
for rows.Next() {
var anime_id int64
if err := rows.Scan(&anime_id); err != nil {
return nil, err
}
items = append(items, anime_id)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getUpcomingSeasons = `-- name: GetUpcomingSeasons :many
WITH RECURSIVE sequel_chain AS (
SELECT
@@ -687,6 +781,36 @@ func (q *Queries) MarkAnimeFetchRetryFailed(ctx context.Context, arg MarkAnimeFe
return err
}
const markEpisodeAvailabilityRefreshFailed = `-- name: MarkEpisodeAvailabilityRefreshFailed :exec
UPDATE episode_availability_cache
SET last_attempt_at = ?,
failure_count = failure_count + 1,
last_error = ?,
next_refresh_at = ?,
retry_until_at = ?,
updated_at = CURRENT_TIMESTAMP
WHERE anime_id = ?
`
type MarkEpisodeAvailabilityRefreshFailedParams struct {
LastAttemptAt sql.NullTime `json:"last_attempt_at"`
LastError string `json:"last_error"`
NextRefreshAt sql.NullTime `json:"next_refresh_at"`
RetryUntilAt sql.NullTime `json:"retry_until_at"`
AnimeID int64 `json:"anime_id"`
}
func (q *Queries) MarkEpisodeAvailabilityRefreshFailed(ctx context.Context, arg MarkEpisodeAvailabilityRefreshFailedParams) error {
_, err := q.db.ExecContext(ctx, markEpisodeAvailabilityRefreshFailed,
arg.LastAttemptAt,
arg.LastError,
arg.NextRefreshAt,
arg.RetryUntilAt,
arg.AnimeID,
)
return err
}
const markRelationsSynced = `-- name: MarkRelationsSynced :exec
UPDATE anime SET relations_synced_at = CURRENT_TIMESTAMP WHERE id = ?
`
@@ -864,6 +988,84 @@ func (q *Queries) UpsertContinueWatchingEntry(ctx context.Context, arg UpsertCon
return i, err
}
const upsertEpisodeAvailabilityCache = `-- name: UpsertEpisodeAvailabilityCache :exec
INSERT INTO episode_availability_cache (
anime_id,
data,
next_refresh_at,
retry_until_at,
last_attempt_at,
last_success_at,
failure_count,
last_error,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id) DO UPDATE SET
data = excluded.data,
next_refresh_at = excluded.next_refresh_at,
retry_until_at = excluded.retry_until_at,
last_attempt_at = excluded.last_attempt_at,
last_success_at = excluded.last_success_at,
failure_count = excluded.failure_count,
last_error = excluded.last_error,
updated_at = CURRENT_TIMESTAMP
`
type UpsertEpisodeAvailabilityCacheParams struct {
AnimeID int64 `json:"anime_id"`
Data string `json:"data"`
NextRefreshAt sql.NullTime `json:"next_refresh_at"`
RetryUntilAt sql.NullTime `json:"retry_until_at"`
LastAttemptAt sql.NullTime `json:"last_attempt_at"`
LastSuccessAt sql.NullTime `json:"last_success_at"`
FailureCount int64 `json:"failure_count"`
LastError string `json:"last_error"`
}
func (q *Queries) UpsertEpisodeAvailabilityCache(ctx context.Context, arg UpsertEpisodeAvailabilityCacheParams) error {
_, err := q.db.ExecContext(ctx, upsertEpisodeAvailabilityCache,
arg.AnimeID,
arg.Data,
arg.NextRefreshAt,
arg.RetryUntilAt,
arg.LastAttemptAt,
arg.LastSuccessAt,
arg.FailureCount,
arg.LastError,
)
return err
}
const upsertEpisodeProviderMapping = `-- name: UpsertEpisodeProviderMapping :exec
INSERT INTO episode_provider_mapping (anime_id, provider, provider_show_id, failed_until, last_error, updated_at)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id, provider) DO UPDATE SET
provider_show_id = excluded.provider_show_id,
failed_until = excluded.failed_until,
last_error = excluded.last_error,
updated_at = CURRENT_TIMESTAMP
`
type UpsertEpisodeProviderMappingParams struct {
AnimeID int64 `json:"anime_id"`
Provider string `json:"provider"`
ProviderShowID string `json:"provider_show_id"`
FailedUntil sql.NullTime `json:"failed_until"`
LastError string `json:"last_error"`
}
func (q *Queries) UpsertEpisodeProviderMapping(ctx context.Context, arg UpsertEpisodeProviderMappingParams) error {
_, err := q.db.ExecContext(ctx, upsertEpisodeProviderMapping,
arg.AnimeID,
arg.Provider,
arg.ProviderShowID,
arg.FailedUntil,
arg.LastError,
)
return err
}
const upsertWatchListEntry = `-- name: UpsertWatchListEntry :one
INSERT INTO watch_list_entry (id, user_id, anime_id, status, current_episode, current_time_seconds, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)