# Code Structure ## Build System - **Type**: .NET SDK (MSBuild / `dotnet` CLI) for the backend; pnpm + Vite for the admin SPA. - **Solution**: `SlpModularCms.sln` — 10 projects, organised into three top-level Solution Folders (see `CLAUDE.md` / `AGENTS.md`): - **Application** — `SlpModularCms.Core` plus a nested **Modules** folder (`Modules.Master`, `Modules.Identity`, `Modules.Availability`) - **Tests** — mirrors Application, with its own nested **Modules** folder - **Clients** — the deployable hosts: `SlpModularCms.Api`, `SlpModularCms.Api.Slave` - **Target framework**: `net10.0` for every project. SDK in use: 10.0.301. - **Key build settings**: `Nullable` and `ImplicitUsings` enabled everywhere. `SlpModularCms.Core` uses `` so a class library can depend on ASP.NET Core types. - **Coverage**: `coverlet.runsettings` at the repository root excludes migrations, `obj/`, generated OpenAPI interceptors, and anything marked `[ExcludeFromCodeCoverage]`. - **Frontend build**: `frontend/package.json` — `build` runs `tsc -b && vite build`; `vite.config.ts` sets `base: '/admin/'` for `command === 'build'` only, so the dev server still serves from `/`. - **Publish coupling**: `SlpModularCms.Api.csproj` defines the `BuildAndCopyAdminFrontend` target with `BeforeTargets="Publish"`, which runs `pnpm install --frozen-lockfile` and `pnpm build` in `frontend/` and copies `frontend/dist/**` into `wwwroot/admin/`. **`dotnet publish` therefore requires Node and pnpm on the build machine.** `wwwroot/` is gitignored. ## Project Structure ```mermaid graph TD root["SlpModularCms (repo root)"] sln["SlpModularCms.sln"] src["src/"] fe["frontend/ (admin SPA)"] docs["aidlc-docs/"] api["SlpModularCms.Api
Client / host"] slave["SlpModularCms.Api.Slave
Client / host"] core["SlpModularCms.Core
shared library"] mid["Modules.Identity"] mav["Modules.Availability"] mma["Modules.Master"] tests["4 test projects
Core, Identity, Availability, Master"] root --> sln root --> src root --> fe root --> docs src --> api src --> slave src --> core src --> mid src --> mav src --> mma src --> tests classDef client fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000; classDef corelayer fill:#fbd38d,stroke:#c05621,stroke-width:1px,color:#000; classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000; classDef test fill:#e2e8f0,stroke:#4a5568,stroke-width:1px,color:#000; classDef meta fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000; class api,slave,fe client; class core corelayer; class mid,mav,mma module; class tests test; class root,sln,src,docs meta; ``` Text alternative: The repository root holds the solution file, a `src/` folder with two host projects, Core, three modules and four test projects, plus a separate `frontend/` admin SPA and the `aidlc-docs/` documentation tree. ## Key Classes/Modules ```mermaid classDiagram class IModule { +string Name +string Version +RegisterServices(IServiceCollection) +UseModule(IApplicationBuilder) } class ModuleOrchestrator { +IReadOnlyList~string~ ModuleNames +DiscoverModules() +RegisterModuleServices(IServiceCollection) +UseModules(IApplicationBuilder) } class IdentityModule class AvailabilityModule class MasterModule class IAvailabilityService { +IsAvailableAsync() +UpdateStatusAsync() } class PersistentAvailabilityService class IMasterAvailabilityService { +GetMasterStatus() } class MasterAvailabilityService IModule <|.. IdentityModule IModule <|.. AvailabilityModule IModule <|.. MasterModule ModuleOrchestrator --> IModule IAvailabilityService <|.. PersistentAvailabilityService IMasterAvailabilityService <|.. MasterAvailabilityService ``` Text alternative: `ModuleOrchestrator` works against the `IModule` contract implemented by the three modules; the Availability module supplies the concrete availability and master-gate services behind Core's interfaces. ### Existing Files Inventory #### Clients (deployable hosts) - `src/SlpModularCms.Api/Program.cs` — Host composition: local settings overlay, module discovery, core infrastructure/CORS/rate limiting, `/api/v1` convention, enum-as-string JSON, exception handler, HTTPS redirect, static files, CORS, module middleware, auth, controllers, and the two SPA fallbacks (`/admin/{*path:nonfile}` → `admin/index.html`, `{*path:nonfile}` → `index.html`). - `src/SlpModularCms.Api/Program.Coverage.cs` — Coverage-support partial. - `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — Package/project references plus the `BuildAndCopyAdminFrontend` publish target. - `src/SlpModularCms.Api/appsettings.json` — Production baseline with placeholder secrets; `Logging` default `Warning`; `AllowedHosts: "*"`; empty `Cors:AllowedOrigins`; `JwtSettings`, `Availability`, `MasterModule`, `MasterPolling`, `RateLimiting` sections. - `src/SlpModularCms.Api/appsettings.Development.json` — LocalDB connection, dev JWT secret, `CookieSameSite: None`, CORS for `localhost:5173`, relaxed rate limits, `MasterUrl: https://localhost:7221`. - `src/SlpModularCms.Api/appsettings.local.json` — Gitignored developer overrides. - `src/SlpModularCms.Api/Properties/launchSettings.json` — `http` (5284) and `https` (7221) profiles, both `Development`, launching `/scalar`. - `src/SlpModularCms.Api.Slave/Program.cs`, `…/appsettings*.json`, `…/Properties/launchSettings.json` — Second host on 7222; no Master module reference; expects its own database. #### Core - `src/SlpModularCms.Core/Hosting/ModuleOrchestrator.cs` — Globs `SlpModularCms.Modules.*.dll` from `AppDomain.CurrentDomain.BaseDirectory`, loads assemblies, instantiates every non-abstract `IModule`, and exposes `ModuleNames`. Failures are logged, not thrown. - `src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` — `AddCoreInfrastructure` (DbContext, Identity with password policy, JWT bearer with `ClockSkew.Zero`, the three hierarchical policies, exception handler + ProblemDetails, API versioning, OpenAPI), `AddCmsCors`, `AddCmsRateLimiting` (fixed-window `login`, sliding-window `refresh`). - `src/SlpModularCms.Core/Hosting/ApiPrefixConvention.cs` — Applies the single `api/v1` prefix to every controller. - `src/SlpModularCms.Core/Hosting/SystemController.cs` — `GET /api/v1/System/capabilities`, returns loaded module names. - `src/SlpModularCms.Core/Data/ApplicationDbContext.cs` — Identity + `RefreshToken`, `Invitation`, `ModulePermission`, `GlobalAvailabilityState`. - `src/SlpModularCms.Core/Identity/Entities/*.cs` — `ApplicationUser`, `ApplicationRole`, `RefreshToken`, `Invitation`, `ModulePermission`, `GlobalAvailabilityState`. - `src/SlpModularCms.Core/Identity/Models/*.cs` — `IdentityRequests`, `JwtSettings`, `TokenResponse`. - `src/SlpModularCms.Core/Identity/Services/{AuthService,IAuthService,InvitationService,IInvitationService,SetupService}.cs` — Login/refresh/revoke with token rotation, invitation lifecycle, first-Owner bootstrap. - `src/SlpModularCms.Core/Identity/Authorization/{HierarchicalRoleHandler,HierarchicalRoleRequirement}.cs` — Owner > Administrator > User satisfaction. - `src/SlpModularCms.Core/Availability/{AvailabilityOptions,AvailabilityStatus,AvailabilityStatusDetails,IAvailabilityService,MasterControlledAvailabilityException}.cs` — Availability contract; the exception is what turns a local override attempt into `409 Conflict` while master-controlled. - `src/SlpModularCms.Core/Exceptions/{GlobalExceptionHandler,ValidationException,UnauthorizedException,InvitationOrUserAlreadyExistsException}.cs` — RFC 9457 mapping. - `src/SlpModularCms.Core/Modules/{IModule,ModuleInfo}.cs` — Module contract. - `src/SlpModularCms.Core/Migrations/` — 5 files; `ApplicationDbContext` migrations, applied **manually only**. #### Modules.Identity - `Controllers/AuthController.cs` — `login` (rate-limited `login`), `refresh` (rate-limited `refresh`), `revoke`, `change-password`. - `Controllers/SetupController.cs` — `status`, `owner`. - `Controllers/InvitationController.cs` — `validate`, `complete`; anonymous. - `Controllers/UsersController.cs` — list, `me` update, `invite`, role/active updates, delete; `AdminOnly` by default. - `IdentityModule.cs` — Module registration. #### Modules.Availability - `AvailabilityModule.cs` — Registers services, `AvailabilityDbContext`, Data Protection, polling `HttpClient` and hosted service; on `UseModule` runs `Database.Migrate()` and installs `AvailabilityMiddleware`. - `Middleware/AvailabilityMiddleware.cs` — Bypass prefixes, admin-token bypass, master gate then local status, 503 `ProblemDetails` otherwise. - `Services/PersistentAvailabilityService.cs` — Persisted local status with caching/circuit breaker; throws `MasterControlledAvailabilityException` on local override while master-controlled. - `Services/{MasterAvailabilityService,MasterGateStatus,MasterStatusPollClient,MasterApiKeyProtector,…}.cs` — Master gate state, poll client, encrypted key handling, DI dependency bundle. - `BackgroundServices/MasterStatusPollingBackgroundService.cs` — Periodic pull with fail-open. - `Controllers/{AvailabilityController,MasterController}.cs` — Public status + Owner-only update; inbound master register/status/registered-url. - `Data/AvailabilityDbContext.cs`, `Data/Entities/MasterRegistration.cs`, `Config/MasterPollingOptions.cs`, `Repositories/*`, `Models/MasterModels.cs`; `Migrations/` — 5 files, auto-applied. #### Modules.Master - `MasterModule.cs` — Data Protection, `MasterModuleOptions`, `MasterDbContext`, repositories/services, `SlaveApiClient` with a `slave-resilience` handler (2 retries, exponential backoff with jitter, configurable timeout), `IntegrityCheckBackgroundService`, `HttpContextAccessor`; migrates on `UseModule`. - `Controllers/CmsInstanceController.cs` — Owner-only list/create/update-status. - `Controllers/SlaveStatusController.cs` — Anonymous pull endpoint for slaves. - `Services/{CmsInstanceService,SlaveApiClient,ApiKeyProtector,MasterServiceDependencies,…}.cs` - `BackgroundServices/IntegrityCheckBackgroundService.cs` — Periodic reconciliation and status re-push. - `Data/MasterDbContext.cs`, `Data/Entities/{CmsInstance,CmsInstanceStatus}.cs`, `Models/*`, `Options/MasterModuleOptions.cs`, `Repositories/*`; `Migrations/` — 3 files, auto-applied. #### Tests - `src/SlpModularCms.Core.Tests/` — 7 files (Exceptions, Hosting, Identity). - `src/SlpModularCms.Modules.Identity.Tests/` — 4 files (Controllers). - `src/SlpModularCms.Modules.Availability.Tests/` — 10 files (Controllers, Services, Repositories, BackgroundServices). - `src/SlpModularCms.Modules.Master.Tests/` — 7 files (Controllers, Services, Repositories, BackgroundServices). #### frontend (admin SPA) - `frontend/vite.config.ts` — `base: '/admin/'` on build, `@` alias, dev port 5173, Vitest config with v8 coverage. - `frontend/package.json` — Scripts `dev`, `dev:slave` (mode `slave`, port 5174), `dev:all` (concurrently), `build`, `lint`, `format`, `format:check`, `test`, `test:watch`, `test:coverage`, `preview`. - `frontend/.env.example` — `VITE_API_BASE_URL`, `VITE_APP_TITLE`, plus notes for the slave setup. `.env.local` / `.env.slave.local` are local-only. - `frontend/src/lib/config.ts` — Reads `VITE_API_BASE_URL` and `VITE_APP_TITLE`; Zod-validates `apiBaseUrl` as a URL, warning only in dev. **Makes the production bundle environment-specific.** - `frontend/src/lib/api-client.ts` — `fetch` wrapper: credentials always sent, in-memory access token, 401 refresh-and-retry interceptor, `ProblemDetailsError` and `NetworkError`. - `frontend/src/router.tsx` — TanStack Router with `basepath: import.meta.env.BASE_URL`, so routing follows the `/admin/` base automatically. - `frontend/src/main.tsx` — Sets document title from config, React Query client, optional MSW via `VITE_ENABLE_MSW`, renders only after the initial silent refresh settles. - `frontend/src/api/use*.ts` — Typed hooks per resource (availability, users, profile, invitation, setup, CMS instances, system capabilities), each with a colocated test. - `frontend/src/pages/` — 10 pages, each with a test. - `frontend/src/components/{auth,cms,layout,shared,ui,users}/` — Guards (`ModuleGuard`, `RoleGuard`), CMS instance dialogs/list, layout shell, shadcn-style primitives. - `frontend/src/{contexts,hooks,i18n,mocks,test}/` — Auth provider, hooks, NL/EN translations, MSW handlers per domain, test setup. ## Design Patterns ### Module / plugin pattern - **Location**: `Core/Modules/IModule.cs`, `Core/Hosting/ModuleOrchestrator.cs`, each `*Module.cs`. - **Purpose**: Let one codebase deploy with different capability sets. - **Implementation**: Reflection-based discovery of `SlpModularCms.Modules.*.dll` in the app base directory; each module registers services and middleware itself. Deployment content, not configuration, decides capability. ### Repository pattern - **Location**: `Modules.Master/Repositories/`, `Modules.Availability/Repositories/`. - **Purpose**: Keep EF Core access behind an interface so services stay unit-testable. - **Implementation**: Interface plus EF-backed implementation per aggregate. Core's identity services use `ApplicationDbContext`/Identity managers directly rather than repositories — an intentional inconsistency between old and new code. ### Options pattern - **Location**: `JwtSettings`, `AvailabilityOptions`, `MasterModuleOptions`, `MasterPollingOptions`. - **Purpose**: Bind configuration sections to typed objects. - **Implementation**: `services.Configure` / `AddOptions().BindConfiguration(...)`. ### Dependency-bundle (parameter object) - **Location**: `MasterServiceDependencies`, `MasterAvailabilityServiceDependencies`. - **Purpose**: Keep constructors manageable where a service needs many collaborators. ### Middleware gate - **Location**: `AvailabilityMiddleware`. - **Purpose**: Enforce availability centrally rather than per controller, with explicit bypasses. ### Background reconciliation (push + pull) - **Location**: `IntegrityCheckBackgroundService` (master push), `MasterStatusPollingBackgroundService` (slave pull, fail-open). - **Purpose**: Make distributed status self-healing without a message broker. ### JWT with refresh-token rotation - **Location**: `AuthService`, `AuthController`, `frontend/src/lib/api-client.ts`. - **Purpose**: Short-lived access tokens held in memory; rotating refresh token in an httpOnly cookie scoped to `/api/v1/auth`. ### Global exception handling to RFC 9457 - **Location**: `GlobalExceptionHandler` + typed exceptions. - **Purpose**: One error contract for all clients. ### Resilience pipeline - **Location**: `MasterModule` `slave-resilience` handler. - **Purpose**: Tolerate slow or briefly unreachable slaves without failing the Owner's action outright. ## Critical Dependencies ### Microsoft.EntityFrameworkCore.SqlServer — 10.0.9 - **Usage**: All three `DbContext` types, one shared connection string. - **Purpose**: Persistence. Module contexts self-migrate; the Core context does not. ### Microsoft.AspNetCore.Identity.EntityFrameworkCore — 10.0.9 - **Usage**: `ApplicationUser`/`ApplicationRole` stores, password hashing, policy. - **Purpose**: Account primitives. ### Microsoft.AspNetCore.Authentication.JwtBearer — 10.0.9 - **Usage**: Token validation in `AddCoreInfrastructure` with `ClockSkew.Zero`. - **Purpose**: Stateless authentication. Requires `JwtSettings:Secret` to be present or startup throws. ### ASP.NET Core Data Protection (shared framework) - **Usage**: `ApiKeyProtector`, `MasterApiKeyProtector`. - **Purpose**: Encrypt slave API keys at rest. **Default file-system key ring with no persistent store configured** — a redeploy that loses the key folder makes stored keys unreadable. ### Microsoft.Extensions.Http.Resilience — 9.6.0 - **Usage**: `SlaveApiClient`. - **Purpose**: Retry and timeout for master→slave calls. Note: 9.x package on a `net10.0` target. ### Asp.Versioning.Mvc — 10.0.0 - **Usage**: `AddApiVersioning` with `ReportApiVersions`. - **Purpose**: Version reporting alongside the static `/api/v1` prefix convention. ### Scalar.AspNetCore — 2.16.3 - **Usage**: `MapScalarApiReference()`, Development only. - **Purpose**: API reference UI at `/scalar`. Not exposed in production. ### Vite 8 + React 19 + TanStack Router/Query (frontend) - **Usage**: Admin SPA build and runtime. - **Purpose**: `base: '/admin/'` and `basepath: import.meta.env.BASE_URL` are what make the `/admin` mount work. ### pnpm (build-time, backend publish) - **Usage**: Invoked from `SlpModularCms.Api.csproj` during publish. - **Purpose**: Builds the admin SPA. Makes Node + pnpm a hard prerequisite of `dotnet publish`.