feat: recursive sequel graph syncing with real-time UI polling

This commit is contained in:
2026-04-08 14:05:30 +02:00
parent a861729476
commit 8b46edc15a
7 changed files with 295 additions and 177 deletions

View File

@@ -89,25 +89,60 @@ ON CONFLICT (anime_id, related_anime_id) DO UPDATE SET
UPDATE anime SET status = ?, relations_synced_at = CURRENT_TIMESTAMP WHERE id = ?;
-- name: GetAnimeNeedingRelationSync :many
SELECT a.id, a.title_original
FROM watch_list_entry w
JOIN anime a ON w.anime_id = a.id
WHERE w.status IN ('completed', 'watching')
AND (a.relations_synced_at IS NULL OR a.relations_synced_at < datetime('now', '-7 days'))
GROUP BY a.id
ORDER BY MAX(w.updated_at) DESC
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
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 = sqlc.arg('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
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.*,
a.title_original AS prequel_title
FROM watch_list_entry w
JOIN anime_relation r ON w.anime_id = r.anime_id
JOIN anime a ON w.anime_id = a.id
JOIN anime related ON r.related_anime_id = related.id
WHERE w.user_id = ?
AND w.status IN ('completed', 'watching')
AND r.relation_type = 'Sequel'
AND related.status IN ('Not yet aired', 'Currently Airing')
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 = sqlc.arg('user_id') AND we.anime_id = related.id
)
ORDER BY related.id DESC;

View File

@@ -114,13 +114,25 @@ func (q *Queries) GetAnime(ctx context.Context, id int64) (Anime, error) {
}
const getAnimeNeedingRelationSync = `-- name: GetAnimeNeedingRelationSync :many
SELECT a.id, a.title_original
FROM watch_list_entry w
JOIN anime a ON w.anime_id = a.id
WHERE w.status IN ('completed', 'watching')
AND (a.relations_synced_at IS NULL OR a.relations_synced_at < datetime('now', '-7 days'))
GROUP BY a.id
ORDER BY MAX(w.updated_at) DESC
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
`
@@ -169,17 +181,40 @@ func (q *Queries) GetSession(ctx context.Context, id string) (Session, error) {
}
const getUpcomingSeasons = `-- 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
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 = ?1
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
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.id, related.title_original, related.image_url, related.created_at, related.title_english, related.title_japanese, related.airing, related.status, related.relations_synced_at,
a.title_original AS prequel_title
FROM watch_list_entry w
JOIN anime_relation r ON w.anime_id = r.anime_id
JOIN anime a ON w.anime_id = a.id
JOIN anime related ON r.related_anime_id = related.id
WHERE w.user_id = ?
AND w.status IN ('completed', 'watching')
AND r.relation_type = 'Sequel'
AND related.status IN ('Not yet aired', 'Currently Airing')
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 = sqlc.arg('user_id') AND we.anime_id = related.id
)
ORDER BY related.id DESC
`