Project development
Project Overview
Reaparr is a cross-platform Plex media downloader with a .NET 10.0 backend (FastEndpoints) and Nuxt 4 / Vue 3 frontend. It features multi-threaded download management, SignalR real-time updates, and Plex API integration.
Prerequisites
| Tool | Version | Notes |
|---|---|---|
| .NET SDK | 10.0+ | Backend runtime and build toolchain |
| Bun | Latest | Frontend package manager — do not use npm, yarn, or pnpm |
| Docker | Latest | For running the full stack in containers |
| Docker Compose | v2+ | docker compose (not docker-compose) |
Getting Started
1. Clone and build the backend
git clone https://github.com/Reaparr/Reaparr.git
cd Reaparr
dotnet build Reaparr.sln
2. Install frontend dependencies
cd src/AppHost/ClientApp
bun install
3. Run locally
Backend:
dotnet run --project src/AppHost
If you are using JetBrains Rider, a pre-configured run configuration is included at .run/Reaparr Back-End Development.run.xml. It sets DOTNET_ENVIRONMENT=Development, LOG_LEVEL=DEBUG, UNMASKED=true, and points DEVELOPMENT_ROOT_PATH to ../../DATA/ReaparrCache/ relative to the project root. Open the project in Rider and select Reaparr Back-End Development from the run configurations dropdown to start the backend without any manual configuration.
Frontend dev server (from src/AppHost/ClientApp/):
bun run dev
4. Run via Docker
docker compose -f docker/docker-compose.yml up
For debug builds with LOG_LEVEL=DEBUG:
docker compose -f docker/docker-compose.debug.yml up
Docker volume paths (/Config, /Downloads, /Movies, /TvShows) must point to valid writable directories on your host.
Project Structure
src/
├── Domain/ # Core domain types and abstractions
├── Application/ # Application layer, endpoints, use cases
├── Application.Contracts/ # Shared contracts/interfaces for Application
├── Data/ # EF Core DbContext, migrations, repositories
├── Data.Contracts/ # Database interfaces
├── PlexApi/ # Plex API client implementations
├── PlexApi.Contracts/ # Plex API interfaces and DTOs
├── BackgroundJobs/ # Quartz background job handlers
├── BackgroundJobs.Contracts/ # Background job command/query contracts
├── FileSystem/ # File system abstractions
├── Settings/ # Application settings management
├── Identity/ # Authentication and identity
├── Logging/ # Serilog configuration
└── AppHost/
├── Startup/ # ASP.NET Core startup configuration
├── _Shared/ # Shared middleware, filters, DI modules
└── ClientApp/ # Nuxt 4 / Vue 3 frontend
tests/
├── BaseTests/ # Shared test infrastructure, Faker builders
├── UnitTests/
│ ├── Application.UnitTests/
│ ├── BackgroundJobs.UnitTests/
│ └── ...
├── IntegrationTests/
└── E2ETests/
Development Workflow
- Branch from
devusing a descriptive name (e.g.fix/download-null-ref,feat/sonarr-integration). - Make small, focused commits. Prefer a minimal diff.
- Run all relevant tests before opening a PR.
- Target
devfor all pull requests.
Before starting on any task:
- Read existing implementations and tests for precedent before inventing new patterns.
- Follow what already exists; do not introduce new abstractions unless necessary.
- Fix root causes; do not paper over problems with hacks.
Architecture and Key Patterns
FastEndpoints
All API endpoints inherit Endpoint<TRequest, TResponse>. Follow the existing endpoint structure in src/Application/.
CQRS
Commands and queries use FluentResults (Result<T>) for success/failure. Handlers are registered via Autofac modules.
Dependency Injection
Autofac is the DI container. Register services in the corresponding *Module.cs file for the project. IReaparrDbContextFactory is InstancePerDependency — each parallel branch must resolve its own IReaparrDbContext via IReaparrDbContextFactory.Create() to avoid EF Core thread-safety violations.
Real-time Updates
SignalR hubs use MessagePack compression. Progress stores (e.g. LibrarySyncProgressStore) broadcast updates to connected clients.
Background Jobs
Quartz.NET scheduler drives background processing. Job handlers live in src/BackgroundJobs/.
EF Core / SQLite
ExecuteBulkAsync in ReaparrDbContext already wraps bulk operations in their own transactions. Do not add outer transactions around bulk operation chains — SQLite does not support nested transactions.
Resilience
HTTP clients use Polly retry/circuit-breaker policies.
Logging
Structured logging is provided by Serilog.
Backend Development
Adding an Endpoint
- Create the endpoint class in the appropriate folder under
src/Application/, inheritingEndpoint<TRequest, TResponse>. - Define request/response types.
- Register any new services in the relevant Autofac module.
- Add a corresponding unit test in
Application.UnitTestsmirroring the same folder path.
Adding a Background Job Command
- Define the command record in
src/BackgroundJobs.Contracts/. - Implement the handler in
src/BackgroundJobs/. - Write tests in
tests/UnitTests/BackgroundJobs.UnitTests/— not inApplication.UnitTests.
Code Style
- Follow EditorConfig and analyzer settings already in the repository.
- Match existing naming conventions and folder layout.
- Do not break public APIs without coordinating changes across all consumers.
Frontend Development
All frontend commands must be run from src/AppHost/ClientApp/ using Bun.
| Command | Purpose |
|---|---|
bun run dev | Start development server |
bun run build | Production build |
bun run lint | ESLint validation |
bun run lint:fix | Auto-fix lint issues |
bun run typecheck | TypeScript type checking |
bun run generate-ts | Regenerate OpenAPI types from the backend spec, the back-end should be started |
Regenerating API Types
After changing backend endpoints or DTOs, regenerate the TypeScript types:
bun run generate-ts
This writes generated files to src/AppHost/ClientApp/src/types/api/generated/. Do not manually edit files in that directory.
Key Frontend Dependencies
- Nuxt 4 / Vue 3 — framework
- Pinia — state management
- Quasar + PrimeVue — UI component libraries
- Axios — HTTP client
Testing
Backend Unit Tests
- Framework: xUnit with
[Fact]attributes. - Assertions: Shouldly (
ShouldBeTrue,ShouldNotBeNull, etc.). - Mocking: AutoMoq + Moq.
- Test data: Bogus via helpers in
tests/BaseTests/.
Test organization
| Rule | Example |
|---|---|
One *.UnitTests project per production project | Reaparr.Application → Application.UnitTests |
| Same folder structure as SUT (folders only, not namespaces) | Application/Downloads/Foo.cs → Application.UnitTests/Downloads/Foo.UnitTests.cs |
Namespace is always the project root namespace + .UnitTests | namespace Reaparr.Application.UnitTests; |
File named after SUT with .UnitTests.cs appended | FooEndpoint.UnitTests.cs |
| No underscores in test file names | |
Test class name matches file name (minus .cs) | FooEndpoint.UnitTests |
Test method naming
[Fact]
public async Task ShouldReturnNotFound_WhenMediaDoesNotExist()
Endpoint tests
// Arrange
await SetupEndpointUnitTest<MyEndpoint>();
// Act
await HandleAsync(request);
// Assert
Response.ShouldNotBeNull();
var dbContext = IDbContext;
// verify database state using dbContext
Always assert both the endpoint Response and the resulting database state.
Database seeding
await SetupDatabase(seed, config => { /* configure in-memory db */ });
var dbContext = IDbContext; // reuse this single instance throughout the test
Use a single dbContext variable — do not re-access IDbContext multiple times in one test.
Mocking rules
- Every mock must be verified with explicit call counts:
mock.Verify(x => x.Method(), Times.Once()); mock.Verify(x => x.OtherMethod(), Times.Never()); - Never leave a mock unverified.
- Use
Times.Once()orTimes.Never()— avoid vague expectations.
Test data
- Use Faker-based builders from
tests/BaseTests/. - Never instantiate
Bogus.Fakerdirectly inside test files. - Never use random or non-deterministic data in tests.
File system interactions
- Use
SetupFileSystemfromBaseUnitTestwithMockFileSystem. - Do not manually mock
System.IO.Abstractionsinterfaces in test files.
Running Backend Tests
# Unit tests
dotnet test tests/UnitTests/Application.UnitTests/Application.UnitTests.csproj
# Integration tests
dotnet test tests/IntegrationTests/IntegrationTests/IntegrationTests.csproj
Frontend Unit Tests
# From src/AppHost/ClientApp/
bunx vitest
bun run unit-test
Frontend E2E Tests
Uses Cypress.
# From src/AppHost/ClientApp/
bun run cypress:ci
Docker
Production
docker compose -f docker/docker-compose.yml up
Debug (verbose logging)
docker compose -f docker/docker-compose.debug.yml up
Environment Variables
| Variable | Default | Description |
|---|---|---|
PUID | 1000 | User ID for file permissions |
PGID | 1000 | Group ID for file permissions |
TZ | America/New_York | Timezone |
LOG_LEVEL | INFORMATION | Logging verbosity (DEBUG, INFORMATION, WARNING, ERROR) |
LOG_ENV_VARS | false | Dump all env vars to log on startup |
UNMASKED | false | Show sensitive data in logs |
Volume Mounts
| Container Path | Purpose |
|---|---|
/Config | Application configuration and database |
/Downloads | Active downloads staging area |
/Movies | Movie media library |
/TvShows | TV show media library |
All host paths must exist and be writable by the PUID/PGID user.
AI Tools
AI coding assistants (e.g. Claude Code, Cursor, Copilot) are supported and encouraged. To help them work effectively with this codebase, two instruction files are maintained at the repository root:
AGENTS.md— machine-readable project conventions covering architecture, testing rules, mocking patterns, naming conventions, known implementation constraints, and a self-update rule for agents to record new findings after completing tasks.CLAUDE.md— Claude Code entry point; currently delegates toAGENTS.mdvia@AGENTS.md.
When using an AI assistant, point it at these files so it picks up the project's conventions automatically. If you discover a new constraint or gotcha while working, update AGENTS.md with a concise, actionable entry — this benefits both human contributors and future AI-assisted sessions.
Code Conventions
- Small, focused changes. Prefer minimal diffs. Do not clean up surrounding code that was not related to the task.
- No unnecessary abstractions. If something works with the existing pattern, use it.
- Deterministic behavior. Especially in tests — never rely on random data or ordering.
- Fix root causes. Do not use workarounds or hacks that mask underlying problems.
- No over-engineering. Do not add features, error handling, or fallbacks for scenarios that cannot happen.
- No unused code. Delete dead code completely rather than commenting it out or renaming with
_prefixes. - Formatting. Respect EditorConfig and all analyzer/linter settings already configured in the repository.
Commit Messages
Format:
<type>(<scope>): <Imperative message>
- Message is imperative, present tense.
- Capitalize the first word after the colon.
- No trailing punctuation.
Scopes
| Scope | When to use |
|---|---|
WebAPI | Backend (.NET) changes |
Web-UI | Frontend (Nuxt/Vue) changes |
Types
| Type | When to use |
|---|---|
feat | New feature |
fix | Bug fix |
refactor | Code change that neither fixes a bug nor adds a feature |
perf | Performance improvement |
test | Adding or updating tests |
docs | Documentation only |
build | Build system or dependency changes |
chore | Maintenance tasks |
style | Formatting, whitespace, no logic change |
Examples
feat(WebAPI): Add Sonarr integration endpoint
fix(WebAPI): Resolve null reference in download workflow
refactor(WebAPI): Simplify cache handling in library sync
test(WebAPI): Add unit tests for ClearCompletedDownloadTasksEndpoint
feat(Web-UI): Add Sonarr integration settings page
fix(Web-UI): Resolve download progress bar flickering
style(Web-UI): Align table column spacing in downloads view
Branch Strategy
| Branch | Purpose |
|---|---|
dev | Main development branch — target all PRs here |
| Feature branches | Branch from dev, merge back into dev |
Do not target main/master directly unless explicitly instructed.
Pull Requests
- Target the
devbranch. - Ensure all backend tests pass:
dotnet test Reaparr.sln. - Ensure all frontend checks pass:
bun run lint,bun run typecheck,bun run unit-test. - Keep PRs focused — one logical change per PR.
- Write a clear description explaining the what and why, not just the what.
- If your change modifies backend API contracts, regenerate TypeScript types (
bun run generate-ts) and include those changes in the same PR.
Documentation
The Reaparr documentation is helps users understand how to use Reaparr, troubleshoot issues, and contribute to the project. This page outlines how you can contribute to the documentation and make it better for everyone.
Translating
Help make Reaparr accessible to more people by translating the web interface into different languages. This guide will walk you through the process of contributing translations to Reaparr.