Commit Graph
90 Commits
Author SHA1 Message Date
Quick 87986d88d0 fix(catalog): use season posters for episode cards 2026-05-27 14:54:58 -04:00
Quick d27b177817 fix(collections): cap smart collection results 2026-05-27 14:27:14 -04:00
Quick 0b01c2c413 fix(catalog): support relative date filters 2026-05-27 13:59:29 -04:00
QuickandGitHub bfe73a4dc9 Merge pull request #15 from Silo-Server/fix/tv-scan-settle-window-cancel
fix(libraryingest): TV full scans cancelled at settle-window boundary
2026-05-27 13:44:57 -04:00
Quick 01b0926748 Fix settle-window drainer cancellation in library ingest 2026-05-27 13:37:33 -04:00
Quick b4d4ad218a fix(catalog): address episode catalog review feedback 2026-05-27 12:48:59 -04:00
Quick 3a23798094 perf(catalog): add episode browse index fast path 2026-05-27 11:57:49 -04:00
CoffeeKnyte a3397be489 fix(libraryingest): treat drainer shutdown cancel as clean stop
TV/series full scans (libraries with new or updated items) were recorded as
"cancelled" with an empty error message and never completed matching.

When the file-walk finishes, the ingest executor waits out a settle window and
then calls stopDrainers() to shut down the concurrent match goroutines. That
cancels the drainer context while a ProcessBatchByFolderAndPathPrefix call may
still be in flight. The drainer treated the resulting context.Canceled as a
fatal error: it pushed the error to drainerErrCh and called cancel() on the
whole scan context, so scanqueue.process() mapped it to cancelRun().

Large/slow libraries (many series, slow provider lookups) keep a batch in
flight continuously, so stopDrainers() almost always landed mid-call and the
scan was cancelled; small/fast libraries were usually idle at that instant and
completed normally.

Treat a cancelled drainer context as a deliberate shutdown: return cleanly
without escalating. Genuine external cancellation still reaches the run via the
main goroutine's scanCtx checks, so real cancels are not swallowed.

Adds a regression test (settle window made injectable) that fails against the
old handler with 'concurrent match scope ...: context canceled' and passes
with the fix.
2026-05-27 15:46:24 +00:00
Quick a720467d3b Optimize episode added_at sorting 2026-05-27 10:11:58 -04:00
Quick cba9e7a65a fix(metadata): accept exact cross-provider match ties 2026-05-27 09:13:30 -04:00
Quick 578100f5ef fix(jellycompat): stabilize signed image tags across restarts
{"subject":"fix(jellycompat): stabilize signed image tags across restarts","body":"- Sign library poster and episode parent series image tags from canonical paths/thumbhashes instead of presigned URLs so tags survive restarts\n- Accept signed canonical tags in the image handler without a session and fall back to legacy URL-derived cache tags\n- Always fetch series detail for episodes to build stable parent image tags"}
2026-05-26 22:29:49 -04:00
Quick deb8305a1d fix(jellycompat): harden signed image tags 2026-05-26 21:50:10 -04:00
Quick 0788e7c211 feat(jellycompat): sign image tags and accept them without session
- HMAC-sign image tags using the configured JWT secret
- Serve item/season/episode images via signed tag without requiring a session or cache hit
2026-05-26 21:34:44 -04:00
Quick ee54942b01 fix(metadata): break duplicate provider candidate ties
- Score candidate metadata completeness and auto-match the richer duplicate when title/year/type tie
- Enrich near-duplicate candidates via the provider chain before initial match selection
- Seed both movie and series match queues for mixed-type libraries and wait for TV queue settle
- Add taskmanager worker test coverage and a plan doc for the tie-breaker work
2026-05-26 20:07:43 -04:00
Quick c152298ad3 fix(auth): gate media file paths on metadata curation permission
- Allow curators (not just admins) to view media file paths and locations
- Apply library access filter to file-level access checks
2026-05-26 20:04:54 -04:00
Quick 9f48ae2d8b test(auth): expand session revocation coverage 2026-05-26 19:41:55 -04:00
Quick 2e550c3d35 fix(auth): tighten curator job response review fixes 2026-05-26 19:34:16 -04:00
Quick 60959f5812 fix(auth): address metadata curation review issues 2026-05-26 19:27:56 -04:00
Quick 99f10fb2d4 feat(api): route metadata curation by permission 2026-05-26 18:18:19 -04:00
Quick 5ba7ecdca7 feat(api): authorize item metadata curation 2026-05-26 18:15:05 -04:00
Quick c95c40c3fe feat(auth): expose user permissions 2026-05-26 18:13:32 -04:00
Quick 24d48c5e6e feat(auth): add assignable user permissions 2026-05-26 18:12:20 -04:00
Quick 2728d2f50a feat(subtitles): restore upload management 2026-05-26 17:57:26 -04:00
Silo Server Migration 86afa748de Merge branch 'main' of https://github.com/Silo-Server/silo-server 2026-05-26 08:35:16 -04:00
Silo Server Migration ef1c6accb6 fix(webhooksync): skip events for unmapped external users
- Require explicit profile mapping instead of falling back to the default profile
- Update settings UI copy to reflect that unmapped users are ignored
2026-05-26 08:34:17 -04:00
QuickandGitHub 291e7a382b Merge pull request #8 from Silo-Server/t3code/jellyfin-autoscan-scan-compat
feat(jellycompat): support Autoscan via Jellyfin media-update endpoint
2026-05-26 08:33:48 -04:00
zZebrahz fd39824c0e fix(jellycompat): tolerate autoscan sidecar updates 2026-05-25 19:37:25 -07:00
Silo Server Migration a639cf60de fix(catalog): gate search overview-only matches behind title FTS
- Always apply stats CTE + CROSS JOIN so single-word queries no longer flood results with description-only hits
- Require overview_rank >= 0.15 for overview-only fallback rows
- Switch title gate from contiguous LIKE to title_rank > 0 so reordered-token title matches aren't demoted
2026-05-25 12:49:38 -04:00
Silo Server Migration b2fae0aa70 refactor(scantrigger): drop redundant Target.LibraryID field
- Read library ID from Target.Folder.ID everywhere
- Guard scan queue enqueue against nil Folder
- Simplify admin API key auth error plumbing
2026-05-25 12:11:56 -04:00
Silo Server Migration c3cf485cd9 chore: add planning docs and requests updates
- Add plans for date-named episodes and Jellyfin autoscan compat
- Update requests handlers, service, and UI hooks
- Remove Makefile.local.example
2026-05-25 12:07:50 -04:00
Silo Server Migration 4a5ca0b9eb fix(jellycompat): harden autoscan auth and batch scan enqueue
- Reject nil API keys and bound last-used update with a 5s timeout
- Stop leaking internal queue errors in autoscan responses
- Batch scan enqueues via new CreateBatch and reuse folder list across path resolves
2026-05-25 11:39:35 -04:00
Silo Server Migration f73febbd55 feat(jellycompat): add autoscan media update route 2026-05-25 11:18:02 -04:00
Silo Server Migration 6ceb75e717 feat(jellycompat): accept admin api keys for autoscan 2026-05-25 11:18:02 -04:00
Silo Server Migration bf0aefa050 refactor(api): share scan target resolution 2026-05-25 11:18:02 -04:00
Silo Server Migration 4f46580210 refactor(scan): extract scan trigger resolver 2026-05-25 11:18:02 -04:00
QuickandGitHub a75d232ceb Merge pull request #7 from Silo-Server/t3code/discover-studios-networks-genres-clean
feat(requests): add media request system
2026-05-25 10:56:57 -04:00
Silo Server Migration 7a9d7bcc94 fix(requests): clear prior failed rows on re-request
{"subject":"fix(requests): clear prior failed rows on re-request","body":""}
2026-05-25 10:02:06 -04:00
Silo Server Migration b5d9243942 fix(requests/radarr): decode tmdb lookup as single object
- Radarr's /api/v3/movie/lookup/tmdb returns a single MovieResource, not an array
- Update test fixtures to match the actual response shape
2026-05-25 02:22:14 -04:00
Silo Server Migration cc09e14154 feat(tmdb): cache discover and external ID responses
- Add TTL caches with singleflight for DiscoverSection, DiscoverPage, and GetExternalIDs to reduce upstream TMDB calls
- Reuse CastCarousel with a fullBleed variant on RequestDetail and drop the bespoke RequestCastRow
2026-05-25 02:02:33 -04:00
Silo Server Migration 9cf5062bf0 chore: restore tmdb project api key fallback and move requests nav
- tmdb client falls back to bundled project API key when none configured
- move Requests link next to Recommendations in the sidebar
2026-05-25 01:12:47 -04:00
Silo Server MigrationandClaude Opus 4.7 8a86e0cf08 refactor: tmdb and requests polish
- GetExternalIDs now uses the dedicated /movie/{id}/external_ids and
  /tv/{id}/external_ids endpoints instead of fetching the full detail
  with append_to_response=external_ids. The dedicated payload is
  one or two orders of magnitude smaller for the same fields.
- Document PosterPath/BackdropPath on MediaResult as raw TMDB path
  fragments that callers must prefix with the image base URL.
- normalizeCast switches from inline insertion sort to sort.SliceStable.
  The output is identical; the new form is one line and O(n log n).
- normalizeIntegration no longer reuses integration.Tags' backing
  array via Tags[:0]; the slice is callable code, so reusing the
  array would silently corrupt the caller's slice if it kept a
  reference. Allocate a fresh slice instead.
- HandleGet now requires a profile, matching the rest of the
  /requests user-group handlers. Router middleware enforces this
  already, but the inline check is defense-in-depth for any future
  remount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:27:39 -04:00
Silo Server MigrationandClaude Opus 4.7 9250661aaa fix(tmdb): require an api key instead of falling back to a default
The client previously fell back to a hardcoded project-level API key
when the caller passed an empty string. Once committed the key cannot
be rotated without a rebuild, and the silent fallback meant that
misconfiguration produced unattributed traffic against a shared key.
Remove the default. Clients constructed with an empty key now return
ErrMissingAPIKey from every API call so operators see the
configuration problem immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:21:56 -04:00
Silo Server MigrationandClaude Opus 4.7 34f92fdc1d refactor(requests): consolidate shared Arr client helpers
The radarr and sonarr clients carried byte-identical copies of
rootFolderResource, qualityProfileResource, tagResource (and the
corresponding list helpers) plus acceptedWithoutResponse and
statusFromQueueEvaluation. Move the shared wire types and helpers
into the arrclient package and update the callers to use the
exported helpers. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:21:50 -04:00
Silo Server MigrationandClaude Opus 4.7 b382136e6d perf(requests): parallelize DiscoverAll across discovery sections
Each homepage discovery section fired a serial TMDB round trip with
its own presence lookup, so the response time grew linearly with the
number of sections (~1.2 s at 6 sections * 200 ms). Fan the calls out
across a bounded errgroup using the same concurrency cap as
external-id hydration. The first section to error cancels the rest
via the group context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:21:44 -04:00
Silo Server MigrationandClaude Opus 4.7 ab520d2743 fix(metadata): skip refresh-failure mask when provider id is missing
syncRefreshDebtFailure unconditionally set RefreshDebtReasonRefreshFailure
on matched items, even when the refresh failed because the item is
missing a provider id. The priority logic already preferred
ProviderIDIncomplete (priority 240) over RefreshFailure (priority
190), so scheduling was correct — but the persisted reason mask
combined both bits and the reason-count metric reported these items
as "refresh failures" instead of "provider id incomplete". Guard the
OR so the failure bit only lands on items that actually have a
provider id and failed for a different reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:16:48 -04:00
Silo Server MigrationandClaude Opus 4.7 38868b2128 feat(requests): add cancel endpoint and tighten state guards
Owners can now POST /requests/{id}/cancel to withdraw a pending
request; admins can cancel any active request that has not entered
the fulfillment pipeline. The route is mounted on both the user
group (with profile required) and the admin group. The cancelled
outcome was already reserved in the migration's CHECK constraint
but was unreachable from any handler.

Decline now also rejects approved requests — between Approve setting
StatusApproved and the reconciler picking the request up, an admin
could declare the request declined while submission was about to
fire. The reconciler's outcome filter would skip the request, but
the narrow window meant external state could diverge from Silo's
view. Refuse decline once a request is approved; callers should
wait for completion or use the failed/retry path.

Reconcile now emits a slog.WarnContext at the per-request failure
site with request id, media type, tmdb id, status, and integration
kind. Aggregated counters in ReconcileResult are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:16:41 -04:00
Silo Server MigrationandClaude Opus 4.7 860978cf2f fix(requests): recover external id after empty arr add response
Radarr and Sonarr can return HTTP 201 with no body when a movie or
series is added. The previous code returned an "accepted_without_response"
result with an empty ExternalID, which trapped the reconciler: every
subsequent CheckStatus call short-circuited on the empty ID and the
request never advanced past queued.

When the add POST decodes empty, look the freshly-added record up by
TMDB or TVDB ID via the standard list endpoints and use the resulting
Arr ID. Fall back to the previous accepted-without-response result
only when the lookup also returns no match, preserving the original
behavior as a safety net.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:12:53 -04:00
Silo Server MigrationandClaude Opus 4.7 fca23d77e9 fix(requests): serialize quota check and clamp list limits
CreateRequest previously read the user's request count outside the
insert transaction, so two concurrent submissions at MaxRequests-1
could both pass the quota gate and end up at MaxRequests+1. Move the
count inside the same transaction as the insert and acquire a per-user
advisory lock so concurrent inserts serialize. The store reports
ErrQuotaExceeded when the racing path catches the user at the limit
and the service maps it back to QuotaError.

normalizeListFilter previously reset limit to 50 when callers asked
for more than 100, which is surprising. Clamp to the cap instead so a
request for 150 returns 100 and a request for 1_000_000 still cannot
hit the database with an unbounded scan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:12:45 -04:00
Silo Server Migration 4e99df18eb fix(metadata): preserve refresh debt reason masks 2026-05-24 23:11:44 -04:00
Silo Server Migration dbb33c630d fix(metadata): refresh items missing tmdb ids 2026-05-24 23:08:05 -04:00