# Architecture A mental model of the DorkNet codebase for new contributors. Read this before you touch code; it explains layout, request flow, the design patterns that look weird in isolation, and where to start. For deployment, see [`deploy.md`](deploy.md). For wire-protocol reverse-engineering deliverables, see the `recroom-2020-client-*` files in this folder. --- ## Projects in the solution ``` DorkNet.sln ├── DorkNet.Server ASP.NET Core 9 backend — full public API today ├── DorkNet.Models Shared DTO + serialization types ├── DorkNet.Contracts Shared service contracts for the service split ├── DorkNet.ServiceDefaults Shared service-host setup ├── DorkNet.Gateway Gateway/edge host scaffold ├── DorkNet.Services.Identity Identity service scaffold ├── DorkNet.Services.Rooms Rooms service scaffold ├── DorkNet.Services.Notify Notify service scaffold └── DorkNet.ClientMod MelonLoader 6 IL2CPP mod — the client patcher ``` | Project | Responsibility | Target | |---|---|---| | `DorkNet.Server` | REST API, SignalR hub, admin SPA host, static-site host, image CDN, matchmaking, persistence | .NET 9 | | `DorkNet.Models` | DTOs the wire protocol speaks. No project dependencies. | .NET 9 | | `DorkNet.Contracts` | Service names, service-map options, health responses, probe responses | .NET 9 | | `DorkNet.ServiceDefaults` | Common microservice health checks, JSON defaults, HTTP clients, service identity | .NET 9 | | `DorkNet.Gateway` | Edge/gateway scaffold; exposes internal service map and service health probes | .NET 9 | | `DorkNet.Services.Identity` | Future home for accounts, auth, platform login, JWT issuance | .NET 9 | | `DorkNet.Services.Rooms` | Future home for rooms, room keys, matchmaking, discovery | .NET 9 | | `DorkNet.Services.Notify` | Future home for SignalR edge, notification fan-out, presence, request logs | .NET 9 | | `DorkNet.ClientMod` | MelonLoader 0.6.x IL2CPP mod — the client patcher, JSON config | .NET 6 | `ClientMod` applies the client-side patches needed to point the 2020 watch at a DorkNet server: URI rewrite (`.rec.net` → configured apex), Photon AppId override, and TLS trust bypass. It loads under MelonLoader 0.6.x and reads its settings from a JSON config under `MelonLoader/UserData/`. --- ## Server folder layout ``` DorkNet.Server/ ├── Auth/ JWT config, IP-ban + player-ban middleware, AdminOnly attribute ├── Compat/ Version detection middleware, client-version registry, HttpContext extensions ├── Controllers/ REST endpoints (see below) ├── Data/ DbContext, Entities/, EF migrations, LegacyUpgrades, seed JSON ├── Hubs/ SignalR NotifyHub (real-time presence on notify.{apex}/hub/v1) ├── Protos/ Protobuf schemas — trimmed `dorknet_room_data.proto` + full decompiled `recroom_2020.proto` ├── Services/ ~27 business-logic services (PlayerService, RoomService, etc.) ├── Versions/ Per-version plugins; Late2020VersionPlugin marks the 2020.12.18 build ├── admin-ui/ Vite + React SPA source — builds to wwwroot/admin/ ├── site/ Public website Vite SPA source — builds to wwwroot/site/ ├── wwwroot/ Static file root, with per-host subdirs (admin/, site/, feed/) ├── Startup/ Host composition, DI registration, database bootstrap, middleware pipeline └── Program.cs Short composition root ``` ## Runtime topology `DorkNet.Server` is still the only production-complete runtime. It owns the full public Rec Room-compatible HTTP surface, SignalR hub, static hosts, database bootstrap, Redis-backed ephemeral state, and S3-backed blob storage. The microservice projects are the migration scaffold. They are wired into the solution and Docker Compose, but they only expose internal health and capability endpoints today: - `DorkNet.Gateway` exposes `/internal/services` and `/internal/services/health`. - `DorkNet.Services.Identity` exposes `/internal/identity/capabilities`. - `DorkNet.Services.Rooms` exposes `/internal/rooms/capabilities`. - `DorkNet.Services.Notify` exposes `/internal/notify/capabilities`. `docker-compose.microservices.yml` starts gateway, identity, rooms, and notify alongside Compose-managed Postgres and Redis for local testing. `docker-compose.microservices.dokploy.yml` is the Dokploy shape: it adds a `cloudflared` sidecar that joins the same Compose network and forwards Cloudflare Tunnel traffic to `http://gateway:8080`. Object storage is external S3-compatible storage configured with `DORKNET_S3_*` environment variables; the compose files do not run MinIO or Garage. The next migration step is to move one public route group at a time behind the gateway while preserving existing URLs and response shapes. ### Controllers/ groupings 20 top-level groups, each responsible for one product surface the watch (game client) talks to. The grouping mirrors the watch's URL layout, which is why some folders are large — splitting them would obscure the mirror relationship. | Folder | Surface | |---|---| | `API/` | Main REST API split by subsystem (Avatar, Store, Rooms, Messages, Inventions, Leaderboard, BugReporting, Moderation, …) | | `Accounts/` | Account creation, login, profile updates | | `Admin/` | Admin SPA backend, gated by `AdminOnlyAttribute` | | `Auth/` | Photon custom-auth (`/photon/customauth`), version-check `/api/versioncheck/v4` | | `Cdn/` | Image transforms, room blob serving | | `Clubs/` | `clubs.{apex}/*` — clubs surface | | `Commerce/` | `commerce.{apex}/*` — monetization catalog | | `Discovery/` | `discovery.{apex}/*` — room discovery + search | | `Econ/` | `econ.{apex}/*` — currency + inventory | | `Geo/` | `geo.{apex}/*` — region lookup | | `Health/` | `/healthz` probe | | `Match/` | Matchmaking + `/goto/*` flow | | `Notify/` | REST-side companion to the SignalR hub | | `Ns/` | `ns.{apex}/*` — service-URL registry the watch queries on startup | | `PlayerSettings/`, `Rooms/`, `Site/`, `Storage/`, `Strings/` | Smaller surfaces with self-explanatory scope | Destructive admin account removal lives in `AdminController` under `DELETE api/admin/v1/players/{id}` and is surfaced from the Players detail modal. It requires two exact confirmations: the current username and `DELETE {id}`. The endpoint refuses system, self, and still-admin accounts; personal rows are deleted while durable authored content is reassigned to the system account. --- ## Request lifecycle Middleware ordering matters in ASP.NET Core; the pipeline order in `Program.cs` is below. Numbers are the order, not line numbers. 1. **Logging** — Serilog enriches every request with structured fields. 2. **HTTPS redirect** — Only when Kestrel has an HTTPS endpoint (direct mode); a no-op behind nginx/cloudflared. 3. **WebSocket config** — 30-second keep-alive pings to keep cloudflared from killing idle tunnels. 4. **Per-host static branches** (`MountStaticHost`) — `admin.{apex}`, apex, `feed.{apex}` each serve their own SPA from `wwwroot/