Imported from zxibizz/releasarr (
AGENTS.md). Install upstream withnpx skills add zxibizz/releasarr. Copyright stays with the author.
AGENTS.md
Orientation for coding agents working in this repository. Read this first, then the deeper guide for whichever half you are touching.
What this project is
Releasarr orchestrates media requests across Sonarr, Radarr, Prowlarr, and qBittorrent: it
tracks what the *arrs report as missing, searches indexers, downloads torrents, lets a human
map files to episodes, and imports the result back through Sonarr and Radarr's manual import.
See README.md for the user-facing picture.
Two halves, one contract:
| Path | What it is |
|---|---|
openapi.yaml |
The contract. Single source of truth for the HTTP API. |
services/backend/ |
Python 3.12 / FastAPI, layered architecture, uv-managed. |
services/frontend/ |
React 19 / Vite / TypeScript / Mantine. |
services/frontend/mock-server/ |
Express mock of the contract, for UI work without a backend. |
services/bot/ |
Untracked scratch work. Ignore it. |
cicd/containers/all-in-one/root/ |
Overlay baked into the production image: nginx site, s6 services. |
Deeper guides
Read the relevant one before making non-trivial changes. They contain the concrete patterns, with file paths and snippets.
| Doc | When to read it |
|---|---|
docs/architecture.md |
Layers, process model, how a request flows end to end |
docs/backend.md |
Any backend change: routes, use cases, adapters, DI, settings, migrations |
docs/frontend.md |
Any frontend change: feature slices, queries, i18n, styling, mock server |
docs/data-model.md |
Schema, enums, constraints, and the invariants they encode |
docs/testing.md |
Writing or fixing tests on either side |
Narrower docs live next to the code they describe:
| Doc | When to read it |
|---|---|
services/backend/README.md |
Running the backend; what each directory is for |
services/backend/docs/tasks.md |
Background tasks, the scheduler, job queueing, log filtering |
services/backend/docs/integrations.md |
Touching any Sonarr / Radarr / Prowlarr / qBittorrent / TVDB / TMDB adapter |
services/backend/docs/file-mapping.md |
Release-name parsing, the file matcher, auto-mapping, import |
services/frontend/README.md |
Running the frontend; routing, i18n, mobile |
services/frontend/docs/file-mapping.md |
The mapping editor's state model and bulk actions |
services/frontend/docs/pwa.md |
The service worker, the manifest, offline behaviour, regenerating icons |
services/frontend/docs/mock-server.md |
Adding or changing a mock endpoint |
services/frontend/docs/screenshots.md |
Regenerating or adding a README screenshot |
Hard rules
Violating these produces changes that look fine and break something elsewhere.
openapi.yamlchanges first. It is consumed by three things: the backend's contract test, the frontend's type codegen, and the mock server. An API change means updating all four — spec, backend schemas,npm run codegen, andmock-server/.- Never hand-edit
services/frontend/src/lib/api/generated/types.ts. It is committed but generated. Runnpm run codegenfromservices/frontend/. - Dependencies point inward in the backend.
application/must not import FastAPI, SQLAlchemy models, or anything frominfrastructure/. Use cases depend onProtocols inapplication/interfaces/;infrastructure/implements them. - Route handlers stay thin. Map schema to command,
await use_case.execute(...), map DTO to schema. Do not catch domain exceptions in routes — register them inDOMAIN_ERROR_MAPinservices/backend/src/api/errors.pyand let the global handler map them. - Never start background work inside the FastAPI app. The scheduler is a separate
process. The API only writes rows to
sync_jobs; the worker claims them. - All frontend HTTP goes through
apiRequestinservices/frontend/src/lib/api/client.ts. No barefetch, no second client. - Add UI strings to both
enandruinservices/frontend/src/locales/resources.ts. Russian plurals need_one/_few/_many/_other, English only_one/_other. - Status colors live in exactly one place:
services/frontend/src/utils/status.ts, rendered byStatusBadge. Do not hardcode a status color anywhere else. - Review autogenerated migrations before applying them. Postgres enum changes need
hand-written SQL;
batch_alter_tablesilently no-ops there. Seedocs/data-model.md. - Every new backend route declares a guard.
Depends(require_user),require_admin, orrequire_permission(Permission.X)fromsrc/api/dependencies/auth.py— never add a router with no dependency and assume it inherits one. A use case that returns a singlemedia_requestsrow must be checked against the caller'sRequestScopebefore the row is returned; an out-of-scope row is a 404, not a 403. Seedocs/architecture.md. - Never cache
/apiin the service worker. There is noruntimeCachingblock today, and adding one looks harmless and is not: every response is authenticated and the refresh cookie at/api/authrotates exactly once per use, so a replayed response reaches the backend as a rotated token — which is what token theft looks like.navigateFallbackDenylistkeeps/apiout of the SPA fallback for the same reason. Seeservices/frontend/docs/pwa.md. - Route loaders prefetch through
prefetchWhenOnline. React Query pauses a query instead of failing it while the browser is offline, so a loader awaiting one never settles and the router then never renders the app. Any newensureQueryDatainservices/frontend/src/router.tsxgoes inside that wrapper.
Commands
Backend, from services/backend/:
uv sync # install
uv run fastapi dev src/api/app.py # API on :8001
uv run python -m src.tasks.scheduler_service # the worker, separately
uv run pytest # tests
uv run ruff check ./src # lint (what CI runs)
uv run ruff format ./src # format
uv run mypy src # type check
uv run alembic upgrade head # apply migrations
uv run alembic revision --autogenerate -m "describe change"
Frontend, from services/frontend/:
npm install
npm run dev:mock # mock API on :8001 + Vite on :3000
npm test # vitest
npm run lint
npm run build # tsc --noEmit && vite build
npm run codegen # regenerate types from ../../openapi.yaml
The production image is Dockerfile.all-in-one, built from the repository root
(docker build -f Dockerfile.all-in-one .) and served on :8050. For a containerised stack
that reloads on edit instead, docker compose -f docker-compose.dev.yaml up --build — UI on
:3000, API on :8000, scheduler in its own container.
Code style
Beyond the linters, this codebase has one strong convention worth matching.
Comments explain why, not what. The existing comments are prose that records a constraint, a tradeoff, or a decision someone would otherwise undo. They do not narrate the code. Match this; do not add comments that restate the next line, and do not leave behind comments that describe your edit.
# Sonarr and Radarr refuse an add without a quality profile, which releasarr
# itself never grabs by. Left unset, the first profile they report is used.
sonarr_quality_profile_id: int | None = Field(default=None)
Other conventions, in brief:
- Python:
from __future__ import annotationsat the top;@dataclass(slots=True)for commands, DTOs, and interface records;asyncthroughout;__all__at module end. - Logging:
get_logger(LogComponent.…)and structured kwargs (logger.info("Added series requests", tvdb_id=…, seasons=…)), never f-strings for identifiers. - Partial updates: the
UNSETsentinel fromsrc/application/utility/sentinels.pydistinguishes "not provided" from "explicitly null".Nonemeans null. - TypeScript:
@/alias forsrc/; strict mode withnoUnusedLocals; features own theirapi.tsandqueries.ts; tests colocated as*.test.tsx, mobile variants as*.mobile.test.tsx.
Before you finish
- Ran the relevant tests and linters for the side you touched.
- If the API changed:
openapi.yaml, backend schemas,npm run codegen, and the mock server are all in agreement, anduv run pytest tests/api/test_openapi_contract.pypasses. - If the schema changed: a migration exists and has been reviewed, not just autogenerated.
- If UI strings changed: both locales have the key.
- No new comments narrating what the code does.