-- name: GetUser :one SELECT * FROM user WHERE id = ? LIMIT 1; -- name: GetUserByUsername :one SELECT * FROM user WHERE username = ? LIMIT 1; -- name: CreateUser :one INSERT INTO user (id, username, password_hash) VALUES (?, ?, ?) RETURNING *; -- name: CreateSession :one INSERT INTO session (id, user_id, expires_at) VALUES (?, ?, ?) RETURNING *; -- name: GetSession :one SELECT * FROM session WHERE id = ? LIMIT 1; -- name: DeleteSession :exec DELETE FROM session WHERE id = ?; -- name: DeleteUserSessions :exec DELETE FROM session WHERE user_id = ?; -- name: UpsertAnime :one INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (id) DO UPDATE SET title_original = excluded.title_original, title_english = excluded.title_english, title_japanese = excluded.title_japanese, image_url = excluded.image_url, airing = excluded.airing RETURNING *; -- name: GetAnime :one SELECT * FROM anime WHERE id = ? LIMIT 1; -- name: UpsertWatchListEntry :one INSERT INTO watch_list_entry (id, user_id, anime_id, status, current_episode, current_time_seconds, updated_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, anime_id) DO UPDATE SET status = excluded.status, current_episode = excluded.current_episode, current_time_seconds = excluded.current_time_seconds, updated_at = CURRENT_TIMESTAMP RETURNING *; -- name: SaveWatchProgress :exec UPDATE watch_list_entry SET current_episode = ?, current_time_seconds = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND anime_id = ?; -- name: UpsertContinueWatchingEntry :one INSERT INTO continue_watching_entry (id, user_id, anime_id, current_episode, current_time_seconds, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, anime_id) DO UPDATE SET current_episode = excluded.current_episode, current_time_seconds = excluded.current_time_seconds, updated_at = CURRENT_TIMESTAMP RETURNING *; -- name: GetContinueWatchingEntry :one SELECT * FROM continue_watching_entry WHERE user_id = ? AND anime_id = ? LIMIT 1; -- name: GetContinueWatchingEntries :many SELECT c.id, c.user_id, c.anime_id, c.current_episode, c.current_time_seconds, c.created_at, c.updated_at, a.title_original, a.title_english, a.title_japanese, a.image_url FROM continue_watching_entry c JOIN anime a ON c.anime_id = a.id WHERE c.user_id = ? ORDER BY c.updated_at DESC; -- name: DeleteContinueWatchingEntry :exec DELETE FROM continue_watching_entry WHERE user_id = ? AND anime_id = ?; -- name: GetWatchListEntry :one SELECT * FROM watch_list_entry WHERE user_id = ? AND anime_id = ? LIMIT 1; -- name: GetUserWatchList :many SELECT e.*, a.title_original, a.title_english, a.title_japanese, a.image_url, a.airing FROM watch_list_entry e JOIN anime a ON e.anime_id = a.id WHERE e.user_id = ? ORDER BY e.updated_at DESC; -- name: DeleteWatchListEntry :exec DELETE FROM watch_list_entry WHERE user_id = ? AND anime_id = ?; -- name: GetWatchingAnime :many SELECT e.*, a.title_original, a.title_english, a.title_japanese, a.image_url, a.airing FROM watch_list_entry e JOIN anime a ON e.anime_id = a.id WHERE e.user_id = ? AND e.status IN ('watching', 'plan_to_watch') AND a.airing = 1 ORDER BY e.updated_at DESC; -- name: UpsertAnimeRelation :exec INSERT INTO anime_relation (anime_id, related_anime_id, relation_type) VALUES (?, ?, ?) ON CONFLICT (anime_id, related_anime_id) DO UPDATE SET relation_type = excluded.relation_type; -- name: UpdateAnimeStatus :exec UPDATE anime SET status = ? WHERE id = ?; -- name: MarkRelationsSynced :exec UPDATE anime SET relations_synced_at = CURRENT_TIMESTAMP WHERE id = ?; -- name: GetAnimeNeedingRelationSync :many WITH RECURSIVE sequel_chain AS ( SELECT a.id, a.title_original, a.relations_synced_at, w.updated_at as base_updated_at, 0 as depth FROM watch_list_entry w JOIN anime a ON w.anime_id = a.id WHERE w.status IN ('completed', 'watching') UNION SELECT a.id, a.title_original, a.relations_synced_at, sc.base_updated_at, sc.depth + 1 FROM sequel_chain sc JOIN anime_relation r ON sc.id = r.anime_id AND r.relation_type = 'Sequel' JOIN anime a ON r.related_anime_id = a.id WHERE sc.depth < 10 ) SELECT id, title_original FROM sequel_chain WHERE relations_synced_at IS NULL OR relations_synced_at < datetime('now', '-7 days') GROUP BY id, title_original ORDER BY MAX(base_updated_at) DESC, MIN(depth) ASC LIMIT 50; -- name: GetUpcomingSeasons :many WITH RECURSIVE sequel_chain AS ( SELECT w.anime_id as root_id, a.title_original as root_title, r.related_anime_id as current_id, 1 as depth, w.user_id FROM watch_list_entry w JOIN anime a ON w.anime_id = a.id JOIN anime_relation r ON w.anime_id = r.anime_id WHERE w.user_id = ? AND w.status IN ('completed', 'watching') AND r.relation_type = 'Sequel' UNION SELECT sc.root_id, sc.root_title, r.related_anime_id, sc.depth + 1, sc.user_id FROM sequel_chain sc JOIN anime_relation r ON sc.current_id = r.anime_id WHERE r.relation_type = 'Sequel' AND sc.depth < 10 ) SELECT DISTINCT related.*, sc.root_title AS prequel_title FROM sequel_chain sc JOIN anime related ON sc.current_id = related.id WHERE related.status IN ('Not yet aired', 'Currently Airing') AND NOT EXISTS ( SELECT 1 FROM watch_list_entry we WHERE we.user_id = sc.user_id AND we.anime_id = related.id ) ORDER BY related.id DESC; -- name: GetJikanCache :one SELECT data FROM jikan_cache WHERE key = ? AND expires_at > CURRENT_TIMESTAMP LIMIT 1; -- name: GetJikanCacheStale :one SELECT data FROM jikan_cache WHERE key = ? LIMIT 1; -- name: SetJikanCache :exec INSERT INTO jikan_cache (key, data, expires_at) VALUES (?, ?, ?) ON CONFLICT (key) DO UPDATE SET data = excluded.data, expires_at = excluded.expires_at, created_at = CURRENT_TIMESTAMP; -- name: DeleteExpiredJikanCache :exec DELETE FROM jikan_cache WHERE expires_at <= CURRENT_TIMESTAMP; -- name: EnqueueAnimeFetchRetry :exec INSERT INTO anime_fetch_retry (anime_id, attempts, next_retry_at, last_error, updated_at) VALUES (?, 0, CURRENT_TIMESTAMP, ?, CURRENT_TIMESTAMP) ON CONFLICT (anime_id) DO UPDATE SET next_retry_at = CASE WHEN anime_fetch_retry.next_retry_at > CURRENT_TIMESTAMP THEN anime_fetch_retry.next_retry_at ELSE CURRENT_TIMESTAMP END, last_error = excluded.last_error, updated_at = CURRENT_TIMESTAMP; -- name: GetDueAnimeFetchRetries :many SELECT anime_id, attempts, next_retry_at, last_error, created_at, updated_at FROM anime_fetch_retry WHERE next_retry_at <= CURRENT_TIMESTAMP ORDER BY next_retry_at ASC LIMIT ?; -- name: MarkAnimeFetchRetryFailed :exec UPDATE anime_fetch_retry SET attempts = attempts + 1, next_retry_at = datetime(CURRENT_TIMESTAMP, ?), last_error = ?, updated_at = CURRENT_TIMESTAMP WHERE anime_id = ?; -- name: DeleteAnimeFetchRetry :exec DELETE FROM anime_fetch_retry WHERE anime_id = ?; -- name: CountPendingAnimeFetchRetries :one SELECT COUNT(*) FROM anime_fetch_retry WHERE next_retry_at <= CURRENT_TIMESTAMP;