Commit Graph

76 Commits

Author SHA1 Message Date
Miguel Palhas 88a353dc68 Merge #176: refresh metadata on add
Closes #176
2026-08-24 18:41:48 +01:00
Miguel Palhas d8797e1996 feat(daemon): refresh metadata on add
The metadata lane runs daily, so a series added a moment ago showed no
seasons for up to 24 hours and a movie had no digital release date —
the field §6.2 gates targeted search on.

AppState now carries a MetadataCommand channel alongside the movie,
episode and season ones. Both create handlers send on it after the row
is committed, and a new daemon lane drains it. Its own task rather than
an arm of manual::run: a refresh against TMDB can take a while and must
not sit in front of an operator's manual search.

The add never waits on TMDB and never fails because of it. A refresh
that fails leaves metadata_refreshed_at NULL, which is what the daily
sweep already treats as due, so the title is retried rather than lost.
A command naming a title deleted in between finds no row and does
nothing. METADATA_INTERVAL is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 18:12:10 +01:00
Miguel Palhas ef4722e1dd fix(db): bump sqlx to 0.9, honour no-transaction
sqlx-sqlite 0.8 ignored Migration.no_tx and wrapped every
migration in a transaction, where PRAGMA foreign_keys = OFF
is a no-op — so any table rebuild cascade-deleted children.
0.9 honours `-- no-transaction`, so 0014 drops its
movie_releases_backup / episode_releases_backup workaround
and runs the plain rebuild recipe with foreign keys off.
A migration test rebuilds both parents from a pre-0014
database and asserts the child link rows survive.

Closes #155

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 18:10:53 +01:00
Miguel Palhas 3dda666273 Merge #174: delete files and untrack a season or episode
Closes #174
2026-08-24 16:45:21 +01:00
Miguel Palhas 64e28e5930 feat: remove files for one season or episode
DELETE /api/series/{id}/seasons/{n}/files and
DELETE /api/episodes/{id}/files unlink what the scope covers, drop the
matching media_files rows and clear wanted, in one action. 204 on
success, 404 for an unknown season or episode, and a scope with nothing
on disk still clears intent.

Season and episode rows stay: TMDB owns that metadata and the next
refresh would recreate them.

The three scopes share one unlink path. A whole series still resolves to
its title folder (§7.4, atomic); a season or episode resolves to the
recorded file and nothing else, so a narrow call cannot reach a sibling.
The intent clear goes through arr_core::tracking::apply_tracked(false),
the same §4.1 rule #171 landed.

An episode whose file just went is set back to 'missing' when it was
'available', matching what a failed import already does. Closes #174.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:43:53 +01:00
Miguel Palhas f3336d066e Merge #172: search returns titles only
Closes #172
2026-08-24 16:40:44 +01:00
Miguel Palhas 4b89245f00 fix(api): drop episode rows from unified search
Search returns titles only (§9.2, amended): the episode branch of the
library query, its json_each token machinery, and the SPA's episode
row rendering are removed. Deep links to episode releases stay.

Fixes #172
2026-08-24 16:39:48 +01:00
Miguel Palhas 9e003823c6 feat: untracking a season clears its wanted 2026-08-24 16:31:09 +01:00
Miguel Palhas 4d5cc120d1 fix(api): newest-first season and episode order
DESIGN.md §9.6 now puts the current season at the top: seasons
descend by number within a series (season 0 lands last under plain
numeric descending) and episodes descend within each season. Changed
the two queries in load_seasons; the SPA renders this order as given.

arr-compat checked and untouched: it reads seasons with its own
ORDER BY number query straight from the database, never through
arr-api, and SeasonResource carries season_number, which Jellyseerr
matches on rather than position. Sonarr's real API also returns
seasons ascending, so the shim keeps the contract it emulates.

The existing vanished-flag test indexed seasons positionally; it now
looks them up by number so it tests the flag, not the order. New test
asserts [2, 1, 0] for seasons and descending episodes within each.
.sqlx regenerated via just db-prepare. just ci green locally.
2026-08-24 12:09:52 +01:00
Miguel Palhas 2c293e7f60 fix(api): episode rows need a token beyond series title
Per amended DESIGN.md 9.2, an episode row now also requires at
least one query token to match the episode title on its own, so
a series-title-only query lists the series and no episodes.
2026-08-24 11:59:18 +01:00
Miguel Palhas 8f7373d73d fix(core): auto-track skips the seeding refresh
A series' first metadata refresh reveals its whole back catalogue, and
apply_auto_track flagged every season as new — adding Rick and Morty
tracked S01-S09 and wanted 91 episodes (#160).

Per DESIGN.md 4.1 the rule applies from the second refresh onward.
The caller passes whether metadata_refreshed_at is set; arr-core stays
IO-free and decides. Wired through the daemon's daily refresh and the
API's create-season endpoint.
2026-08-24 11:40:44 +01:00
Miguel Palhas 07741c80fe feat(web): rich search rows (#148)
Every search row — library and TMDB alike — gains a w92 poster
thumbnail with a same-size blank fallback, a TMDB rating chip
(hidden when there are no votes), and a trailer chip that resolves
GET /api/trailer on click only, opening the tab inside the handler.
Episode hits carry the series' TMDB id so their chip resolves too.
2026-08-24 01:55:00 +01:00
Miguel Palhas 5e2bb5dff7 feat(api): carry poster and rating through search and lists
Widen the unified search so both §9.2 halves can render a thumbnail
and a rating: TMDB results gain vote_average/vote_count from the
search bodies, library results read the #145 artwork columns, and
episodes take their series' art. GET /api/movies and /api/series gain
the same two fields for the #151 grid. Mirrors in web updated.
2026-08-23 22:45:08 +01:00
Miguel Palhas 22f5d6b463 Merge #146: title metadata endpoints
Closes #146
2026-08-23 22:19:03 +01:00
Miguel Palhas 60fcc9f3d5 feat(api): metadata endpoints for library titles 2026-08-23 22:17:51 +01:00
Miguel Palhas 8478c0f8a9 feat(db): store poster, backdrop and rating on titles
§9.6 keeps rich detail out of the database except for the three fields
pure-SQL views need. Adds poster_path, backdrop_path and vote_average to
movies and series, written by the daily metadata refresh in both lanes
and filled at add time from the TMDB response the create flows already
fetch.
2026-08-23 22:12:05 +01:00
Miguel Palhas 468f31f0b0 Merge #141: expose seasons.vanished on the API
Closes #141
2026-08-23 20:31:13 +01:00
Miguel Palhas 571ec42e92 feat(api): expose seasons.vanished (#141)
Season objects from the series endpoints now carry the conflict flag
#137 writes, the way episodes already do.
2026-08-23 20:27:34 +01:00
Miguel Palhas d5013d4ca8 Merge #129: series detail view
Closes #129
2026-08-23 20:21:15 +01:00
Miguel Palhas 7f7f26dbc7 feat(api): name attention episodes and seasons 2026-08-23 19:45:33 +01:00
Miguel Palhas 409967eef6 feat(daemon): reconcile vanished seasons (#137) 2026-08-23 19:34:55 +01:00
Miguel Palhas a378b22306 Merge #134: satisfied titles leave the attention queue
Closes #134
2026-08-23 19:09:15 +01:00
Miguel Palhas 04b2688330 Merge #124: match RSS results against wanted episodes
Closes #124
2026-08-23 19:09:15 +01:00
Miguel Palhas 91414a043f chore: regenerate sqlx query data 2026-08-23 19:07:29 +01:00
Miguel Palhas 9df120b92a feat(daemon): match RSS results to wanted episodes
One feed pass now serves films and episodes alike. A
single-episode release grabs its open episode directly; a season
pack only grabs when season_grab_mode allows packs for that
season (§14, #117), and when it wins, the singles stand down.
Blocked series keep matching RSS (§6.3) and nothing here touches
the targeted-search backoff (§6.2).
2026-08-23 19:07:00 +01:00
Miguel Palhas 03e7774869 Merge #136: resolve IMDb ids to series as well
Closes #136
2026-08-23 19:01:42 +01:00
Miguel Palhas fea1a04c75 feat(api): surface series for IMDb id searches 2026-08-23 18:59:35 +01:00
Miguel Palhas d8316ae0c8 feat(api): series files keyed by episode id 2026-08-23 18:41:36 +01:00
Miguel Palhas 127338fd4d Merge #132: drain manual episode and season commands
Closes #132
2026-08-23 18:27:43 +01:00
Miguel Palhas 695f1882be Merge #128: series delete unlinks its files
Closes #128
2026-08-23 18:25:56 +01:00
Miguel Palhas 39e6bce2a0 feat(daemon): manual episode/season triggers 2026-08-23 18:24:50 +01:00
Miguel Palhas b0f56f59aa fix(api): delete series files and media_files rows 2026-08-23 18:23:41 +01:00
Miguel Palhas 50b68e24b6 api: derive status from episode season numbers
with_status drops its seasons slice for the same reason as
derive_series_status; tv_by_series no longer loads seasons at all.
Offline query data regenerated.
2026-08-23 18:18:26 +01:00
Miguel Palhas 34ccbeeb5a feat(api): expose vanished flag on episodes 2026-08-23 18:01:41 +01:00
Miguel Palhas 8b9a6d5a16 feat(daemon): reconcile episodes that vanish upstream 2026-08-23 17:59:45 +01:00
Miguel Palhas 384e260d72 Merge #121: daily series metadata refresh
Closes #121
2026-08-23 17:47:46 +01:00
Miguel Palhas 808145d73b feat(db): stamp series metadata refresh time 2026-08-23 17:45:09 +01:00
Miguel Palhas 360a4bf43c Merge #127: unified search covers series and episodes
Closes #127
2026-08-23 17:41:22 +01:00
Miguel Palhas 9f8d58fd58 feat(api): unified search covers series and episodes 2026-08-23 17:35:40 +01:00
Miguel Palhas 8b8e1dcec9 Merge #126: TV titles in the needs-a-decision queue
Closes #126
2026-08-23 16:58:08 +01:00
Miguel Palhas 24f5295e87 chore(db): refresh offline query data 2026-08-23 16:55:58 +01:00
Miguel Palhas 28e7dd9a20 Merge #119: seasons.tracked governs episode intent
Closes #119
2026-08-23 16:54:47 +01:00
Miguel Palhas e3d41c24e1 refactor(daemon): extract tv queue roll-up 2026-08-23 16:53:52 +01:00
Miguel Palhas fa3b5226e7 feat(api): season tracked toggle marks revealed episodes 2026-08-23 16:50:45 +01:00
Miguel Palhas 42a0895975 feat(db): season_releases table and season policy loader 2026-08-23 16:49:42 +01:00
Miguel Palhas 24f6dab109 feat(api): tv series in the attention queues endpoint 2026-08-23 16:44:16 +01:00
Miguel Palhas a1971ed82c feat(daemon): tv series in the needs-a-decision queue 2026-08-23 16:39:06 +01:00
Miguel Palhas fc93d27f7e Merge #120: series.tvdb_id and tvsearch by ID
Closes #120
2026-08-23 16:35:34 +01:00
Miguel Palhas 1c9caa7478 chore(db): refresh offline query data 2026-08-23 16:32:28 +01:00
Miguel Palhas 3bd613ec63 feat(core): derived status ignores season 0 2026-08-23 16:32:13 +01:00