- docs/REFACTOR_WAVE3_REPORT.md and the Wave 3 policy: canonical map, production changes, verification and remaining external steps. - Gap backlog: GAP-BILL-01 resolved; new GAP-BILL-02 (Payple renewal never ran, Payple client key never set), GAP-WEB-01 (tunnel host for /app), GAP-OPS-01 (NAS compose/.env drift), GAP-CI-01, GAP-I18N-02, GAP-TEAM-02. - design.md: hero loop decision (numbers taken from the app capsule), pricing mismatch closed; feature catalog SHELL-11 updated. - docs/map, release guide and mobile release docs no longer describe the deleted wwwroot, binaries, Dockerfile.admin, NAS site copy or .github CI. - docker-compose.nas.yml gains the d3ro-whisper service that only existed in the NAS copy, so the repository file is the complete definition. - refactor-wave skill: Wave 3 index and lessons P10-P12.
106 lines
6.5 KiB
Markdown
106 lines
6.5 KiB
Markdown
# 07 — API Server (.NET) Map
|
|
|
|
> Surface: `apps/api-server` (`D3ROVoice.Api`), tests in `apps/api-server.Tests`
|
|
> Stack: ASP.NET Core 10 + EF Core + SQLite
|
|
> Role: AI proxy (LLM/STT) + admin back-office backend for the .NET identity side
|
|
|
|
---
|
|
|
|
## 1. Composition root (`Program.cs`)
|
|
|
|
- **DI:** `AddControllers`, `AddHttpClient` (proxies), Swagger (`v1`), rate limiter policy `auth` (fixed window 10/min per IP, 429), `AddDbContext<AppDbContext>` (SQLite; `DATA_DIR`/`DB_PATH`), scoped `IAuthService`, `ILlmProxyService`, `ISttProxyService`, `IAdminOperationService`.
|
|
- **Auth:** JWT Bearer HS256; startup hard-fails unless `JWT_SECRET` (≥32 bytes), `JWT_ISSUER`, `JWT_AUDIENCE` are set. Zero clock skew.
|
|
- **Policies:** `ManagerOrAbove` (manager/admin/superadmin), `AdminOrAbove` (admin/superadmin), `SuperAdminOnly` (superadmin). Role normalization strips `_`/`-`, lowercases.
|
|
- **CORS/Hosts:** strict origin validation (`CORS_ALLOWED_ORIGINS`), `ALLOWED_HOSTS` required outside Development.
|
|
- **Startup DB init:** `EnsureCreated()`, raw `CREATE TABLE IF NOT EXISTS` for admin operation/audit tables, legacy SHA-256 password lockdown (`IsActive=false`, `Role="LegacyDisabled"`), idempotent env admin provisioning (`ADMIN_EMAIL`/`ADMIN_PASSWORD`, only when no active user), default LLM/STT endpoints seeded.
|
|
- **Middleware order:** Swagger (dev) → CORS → rate limiter → authentication → authorization. The API no longer serves static files: `wwwroot` (stale site build, download/invite pages, `.well-known` copy, legacy embedded admin SPA, 1.0.0 binaries) was deleted 2026-09-26 (Wave 3, cd9d199), and `UseStaticFiles`/the mobile-asset-block fallbacks were removed from `Program.cs` along with it. The Next.js admin (`apps/admin`) is the only admin UI.
|
|
- **Health:** `GET /health`, `GET /api/health` → `{status, service, version, uptimeSeconds, database, timestamp}`.
|
|
|
|
---
|
|
|
|
## 2. Controllers & routes
|
|
|
|
### `AuthController` (`/api/auth`)
|
|
| Route | Auth | Behavior |
|
|
|---|---|---|
|
|
| `POST /register` | anonymous + `X-D3RO-Bootstrap-Token` | Rate-limited, ≤16KB; requires `ADMIN_BOOTSTRAP_TOKEN`; fixed-time compare; `409` if registered |
|
|
| `POST /login` | anonymous | Rate-limited; returns JWT; `401` invalid |
|
|
| `GET /me` | `[Authorize]` | `UserInfoDto` or `404` |
|
|
|
|
### `LlmController` (`/api/llm`, class `[Authorize]`)
|
|
| Route | Behavior |
|
|
|---|---|
|
|
| `POST /generate` | Requires prompt; `GenerateAsync` |
|
|
| `POST /chat` | `ChatAsync` (last message = prompt) |
|
|
|
|
### `SttController` (`/api/stt`, class `[Authorize]`)
|
|
| Route | Auth | Behavior |
|
|
|---|---|---|
|
|
| `POST /transcribe` | any auth | **Always `410 Gone` `stt_edge_gateway_required`** — user transcription is Edge-only |
|
|
| `POST /internal/transcribe` | anonymous + `X-D3RO-STT-Gateway-Token` | multipart, ≤26MB; requires `D3RO_API_TOKEN`; fixed-time compare; provider orchestration; error map `503/400/502` |
|
|
| `GET /providers` | ManagerOrAbove | List STT endpoints |
|
|
| `POST /test?endpointId=` | ManagerOrAbove | Test endpoint |
|
|
|
|
### `AdminController` (`/api/admin`, class `ManagerOrAbove`)
|
|
All mutations flow through `ExecuteAdminMutationAsync` (idempotent + audited).
|
|
|
|
| Route | Effective policy |
|
|
|---|---|
|
|
| `GET /stats` | Manager+ |
|
|
| `GET /users` | Manager+ |
|
|
| `GET/POST/PUT/DELETE /endpoints[/{id}]` | Manager+ read; Admin+ write |
|
|
| `GET /stt-endpoints`, `/stt-endpoints/{id}` | Manager+ |
|
|
| `POST/PUT/DELETE /stt-endpoints[/{id}]`, `POST .../set-default` | Admin+ |
|
|
| `POST /stt-endpoints/{id}/test` | Manager+ |
|
|
| `POST /stt-endpoints/test-direct` | Admin+ |
|
|
| `GET /stt-usage`, `GET /usage` | Manager+ |
|
|
| `POST /license-audit` | **SuperAdminOnly** |
|
|
|
|
---
|
|
|
|
## 3. Services
|
|
|
|
| Service | Purpose |
|
|
|---|---|
|
|
| `AuthService` | Register (one-time SuperAdmin bootstrap only, `PasswordHasher<User>`, serialized), login (timing-safe dummy verify, inactive rejection, rehash), JWT gen (8h, claims id/email/role) |
|
|
| `LlmProxyService` | Resolve endpoint by model → fallback → Mock echo; OpenAI-style POST; parse content+usage; token cost → `ApiUsageLog`; errors → `ServerErrorLog` + fallback text |
|
|
| `SttProxyService` (~1182 lines) | Provider adapters (groq/openai/custom, deepgram, google, assemblyai, azure, local-sidecar), candidate resolution + fallback, content-type/duration detection, `SttUsageLog` cost, synthetic-tone endpoint test, CRUD with exclusive default, usage report |
|
|
| `AdminOperationService` | Validates actor/idempotency key/memo, SHA-256 request hash, `Serializable` transaction, idempotent replay, before/after audit entry |
|
|
|
|
---
|
|
|
|
## 4. Data (`Data/AppDbContext.cs`)
|
|
|
|
| DbSet / table | Key fields |
|
|
|---|---|
|
|
| `Users` | Id, unique Email, PasswordHash, Role (default "User"), IsActive, CreatedAt, LastLoginAt |
|
|
| `ModelEndpoints` | Id, unique ModelId, ModelName, Provider, EndpointUrl, ApiKey, per-1k costs, IsActive |
|
|
| `UsageLogs` | UserId/Email, model, tokens, cost, duration, status |
|
|
| `ErrorLogs` | ErrorType, Message, StackTrace, Endpoint |
|
|
| `SttProviderEndpoints` | Name, ProviderType, URL, ApiKey, ModelId, Method, Language, Prompt, Temperature, per-minute/second cost, IsDefault, FallbackPriority, ExtraHeadersJson |
|
|
| `SttUsageLogs` | User, endpoint, provider, duration, cost, latency, status, transcript preview |
|
|
| `AdminOperationRequests` | ActorEmail + IdempotencyKey (unique), Operation, RequestHash, ResponseJson |
|
|
| `AdminAuditEntries` | ActorEmail, Action, TargetType/Id, BeforeJson, AfterJson, Memo, IdempotencyKey |
|
|
|
|
DTOs (`Dtos/Dtos.cs`): auth, license audit, LLM, admin/model endpoints, STT (transcribe, endpoints, test, usage reports).
|
|
|
|
---
|
|
|
|
## 5. Tests (`apps/api-server.Tests/`, xUnit)
|
|
|
|
`AdminAuthorizationE2ETests`, `AdminOperationServiceTests`, `AuthBootstrapControllerTests`, `AuthSecurityTests`, `SttControllerSecurityTests`, `SttFailClosedTests`, `SttGatewayAuthorizationE2ETests` (26/26 per SSOT).
|
|
|
|
---
|
|
|
|
## 6. API server status summary
|
|
|
|
- Auth, login, JWT, role policies, rate limiting, CORS, host restrictions: **implemented**.
|
|
- LLM proxy: **implemented** (with local Mock echo fallback for standalone testing).
|
|
- STT proxy: **implemented** for internal gateway; public `/transcribe` intentionally `410` (Edge-only by design).
|
|
- Admin API: users, endpoints (LLM + STT), usage, license audit: **implemented** with idempotency + audit.
|
|
- Known intentional states (not bugs):
|
|
- Legacy SHA-256 users force-disabled.
|
|
- No hardcoded/seeded admin credentials.
|
|
- No static file serving — `wwwroot` and its mobile/legacy release asset block were deleted 2026-09-26 (Wave 3, cd9d199); downloads/releases are served only from `site/`.
|
|
- `LlmProxyService` Mock fallback.
|
|
- This backend holds a **separate identity** from Supabase; see `11-gap-backlog.md` `ID-01`.
|