Self-hosted web app to browse and download content from German public TV (ARD, ZDF). Built on .NET 10, Blazor Server, SQLite (swappable), and .NET Aspire.
```bash dotnet workload install aspire brew install ffmpeg ```
```bash dotnet restore cd src/MediathekNext.AppHost dotnet run ```
Aspire dashboard at http://localhost:15000. The app defaults to role=standalone.
The same binary (MediathekNext.Worker) runs in three roles.
Role is resolved in priority order — first match wins:
| Priority | Method | Example |
|---|---|---|
| 1 | CLI argument | dotnet run -- --role worker |
| 2 | Environment variable | MEDIATHEK_ROLE=worker |
| 3 | appsettings.json | "Role": "worker" |
| 4 | Default | standalone |
| Role | Does |
|---|---|
standalone |
Everything: migrations, catalog, API, downloads |
core |
DB owner, catalog refresh, API — no downloads |
worker |
Download execution only — scalable horizontally |
```bash
docker compose --profile standalone up
docker compose --profile distributed up docker compose --profile distributed up --scale worker=3
docker compose --profile distributed --profile observability up ```
Change two config values — no code changes required:
```json { "Database": { "Provider": "postgres", "ConnectionString": "Host=localhost;Database=mediathek;Username=app;Password=secret" } } ```
Supported providers: sqlite (default), postgres, mssql
Uncomment the corresponding package in MediathekNext.Worker.csproj and MediathekNext.Infrastructure.csproj.
```bash cd src/MediathekNext.Worker
dotnet ef migrations add
--project ../MediathekNext.Infrastructure
--startup-project .
dotnet ef database update
--project ../MediathekNext.Infrastructure
--startup-project .
```
``` src/ Domain — Entities, interfaces, enums (zero deps) Application — Use cases, handlers, validators Infrastructure — EF Core, repos, ffmpeg, MediathekView parser, polling service Api — ASP.NET Core 10 Minimal API endpoints Web — Blazor Server frontend Worker — Single binary, role-switchable (standalone/core/worker) Roles/ StandaloneRole.cs CoreRole.cs WorkerRole.cs AppHost — .NET Aspire orchestration ServiceDefaults — OTEL, health checks, service discovery ```
POST /api/downloads→StartDownloadHandlerinserts aDownloadJobrow withStatus=QueuedDownloadPollingService(running inworkerorstandalonerole) polls every 5 seconds- Worker claims a job by calling
job.MarkClaiming(workerId)and callingSaveChanges() - EF Core includes the
RowVersionconcurrency token in theWHEREclause automatically - If two workers race, one gets
DbUpdateConcurrencyExceptionand moves on — no locks, no broker, works on any SQL database
``` GET /api/catalog/search?q= GET /api/catalog/episodes/{id} GET /api/catalog/shows GET /api/catalog/shows/{showId}/episodes GET /api/catalog/channels/{channelId}?contentType= GET /api/catalog/type/{contentType}?channelId=
POST /api/downloads GET /api/downloads GET /api/downloads/{id} DELETE /api/downloads/{id} POST /api/downloads/{id}/retry
GET /api/settings PUT /api/settings ```
OpenAPI: /openapi/v1.json