Skip to content

Add Torznab indexer, bulk job/wishlist ops, and OpenAPI spec#3

Merged
JeremiahM37 merged 1 commit into
mainfrom
feat/torznab-bulk-openapi
May 13, 2026
Merged

Add Torznab indexer, bulk job/wishlist ops, and OpenAPI spec#3
JeremiahM37 merged 1 commit into
mainfrom
feat/torznab-bulk-openapi

Conversation

@JeremiahM37
Copy link
Copy Markdown
Owner

Summary

Three additions to bring gamarr's API to feature-parity with librarr for non-book-specific functionality:

  1. Torznab/Newznab indexer — gamarr can now be added as a Torznab indexer in Prowlarr / Sonarr / other *arr apps
  2. Bulk job + wishlist operations — useful for AI/UI clients managing many items at once
  3. OpenAPI 3.1 spec endpoint — AI agents and tooling can introspect to discover the API

What changed

Torznab indexer (/torznab/api + /api alias)

New internal/torznab package implementing the Newznab/Torznab protocol:

  • ?t=caps returns the capabilities document with game-specific categories (Newznab 1000-series Console + 4000-series PC, with subcategories for NDS / PSP / Wii / Xbox / Xbox 360 / PS3 / Other / PC/Games)
  • ?t=search&q=<title> runs the same 3-source fan-out as /api/search and returns RSS results, with per-platform category mapping in torznab:attr
  • ?t=search with no query returns a Prowlarr-validator-compliant placeholder (guid prefixed gamarr-placeholder- so downstream *arr apps don't grab it)
  • /api alias is mounted exactly (not /api/*) so it doesn't shadow the JSON tree — it's the path Prowlarr probes during indexer discovery
  • TORZNAB_API_KEY env var gates access via ?apikey= when set; empty key disables the check
  • Search results carry magnet/download/info-hash with sensible fallback to detail-page GUID

Bulk operations

  • POST /api/admin/bulk/retry — empty body retries ALL failed jobs; otherwise retries the listed job_ids
  • POST /api/admin/bulk/cancel — same shape, cancels active jobs
  • POST /api/wishlist/bulk-delete{"ids": [...]} body required

All three return per-id outcomes ({success, message}) so clients can show partial successes. Hard cap of 500 ids per request. Content-Type gating + size limits applied.

OpenAPI 3.1 spec

  • GET /api/openapi.json returns an embedded (//go:embed) OpenAPI 3.1 document describing 23 endpoints + 5 schemas
  • Documents both apiKey (X-Api-Key header / ?apikey=) and session (cookie) security schemes
  • AI agents can fetch this once and call gamarr without prior knowledge of the codebase

Test plan

  • go build ./..., go vet ./..., go test -race -count=1 ./... all clean.
  • 457 tests pass, 0 fail, 0 skip, 0 slog noise in go test -v output.
  • 14 new Torznab subtest assertions covering caps shape, API-key gating (missing/wrong/correct), Prowlarr-validator-compliant probe response, search invocation flow, category mapping per platform slug, link-selection priority (magnet > download URL > info-hash-synthesized magnet > GUID), unknown-function error, Host-header propagation, alias path.
  • Deployed to isolated LXC 104 container with TORZNAB_API_KEY=test-torznab-key:
    • Torznab caps document well-formed, all subcategories present
    • API-key gating works (401 / 401 / 200 for missing / wrong / correct)
    • Probe response includes every Prowlarr-validator-required field (pubDate, size, enclosure length+type, category+seeders attrs)
    • /api alias works for Prowlarr's t=caps discovery probe
    • OpenAPI spec serves and validates (openapi: 3.1.0, 23 paths, 5 schemas)
    • Bulk retry returns per-id outcomes for fake job_ids
    • Bulk wishlist delete rejects empty arrays + >500 ids; per-id success on negative/zero ids
  • Fuzz pass on new endpoints: 15 edge cases (Torznab unknown function / no t param / 1000-char query / XSS query / POST method, bulk retry wrong content-type / malformed JSON / 501 ids / exactly 500 / wrong JSON shape / GET method, bulk wishlist no body / text-plain / negative ids / 501 ids, OpenAPI POST) — all return appropriate HTTP statuses.

Backwards compatibility

Strictly additive. One new env var (TORZNAB_API_KEY), four new routes. No existing route, response shape, or env var changed.

Three additions to bring gamarr's API to feature-parity with librarr for
non-book-specific functionality:

* Torznab/Newznab indexer (new internal/torznab package): gamarr can
  now be added as a Torznab indexer in Prowlarr / Sonarr / other *arr
  apps. /torznab/api?t=caps returns Newznab-style game categories
  (1000-series Console, 4000-series PC); ?t=search runs the same 3-
  source fan-out as /api/search and returns RSS results. /api alias is
  the path Prowlarr probes during indexer discovery. TORZNAB_API_KEY
  env var gates access when set; empty key disables the check.

* Bulk operations (new internal/api/bulk.go):
  - POST /api/admin/bulk/retry — retry many failed jobs in one call;
    empty body retries ALL failed jobs.
  - POST /api/admin/bulk/cancel — cancel many active jobs; empty body
    cancels ALL active jobs.
  - POST /api/wishlist/bulk-delete — delete many wishlist items.
  All three return per-id outcomes so AI/UI clients can show partial
  successes. Hard cap of 500 ids per request.

* OpenAPI 3.1 spec (new internal/api/openapi.go embedded from
  openapi.json): AI agents and tooling can introspect /api/openapi.json
  to discover endpoints, request bodies, and response shapes without
  prior knowledge of the codebase. 23 endpoints + 5 schemas documented.

The Torznab handler has 14 subtest assertions covering caps document
shape, API-key gating, probe-response shape (matches Prowlarr's
validator requirements), search invocation flow, category mapping per
platform slug, link-selection priority (magnet > download URL >
info-hash-synthesized magnet > detail-page GUID), unknown-function
error handling, and Host-header propagation in probes.

457 tests pass; go build / vet / test -race all clean.
@JeremiahM37 JeremiahM37 merged commit a3d8f75 into main May 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant