d3ro-voice/docs/map/07-api-server.md
Yun Chan c3ddd36c6f
Some checks failed
deploy-site / deploy (push) Failing after 40s
docs: record the 1.1.0 release and add the infrastructure map
Release notes for 1.1.0 were split between an Unreleased section and the
version section, so the published notes would have omitted the update-feed
and desktop changes. Everything shipping in this version now sits under one
`## [1.1.0]` heading.

`docs/map/` becomes the entry point for what infrastructure exists per
platform and how far each feature is developed, with a documented update
protocol so feature work and this map do not drift apart again. The release
guide now states that installer binaries live in the update feed rather than
the repository.
2026-09-16 23:27:52 +09:00

107 lines
6.4 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 → invite-page hardening (CSP/no-store) → default files → mobile/legacy asset block (404 for `.apk`/`.aab`/signed zips + legacy bundles) → static files → rate limiter → authentication → authorization.
- **Health:** `GET /health`, `GET /api/health``{status, service, version, uptimeSeconds, database, timestamp}`.
- **Fallbacks:** `/accept-invite``accept-invite.html`; `/admin/{*path}` → legacy embedded admin SPA in `wwwroot/admin`.
---
## 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.
- Mobile/legacy release assets blocked with 404.
- `LlmProxyService` Mock fallback.
- This backend holds a **separate identity** from Supabase; see `11-gap-backlog.md` `ID-01`.