> ## Documentation Index
> Fetch the complete documentation index at: https://dev-doc.dualmindlab.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Backend Architecture

> Deep dive into the .NET 8 backend — directory structure, controller patterns, service layer, and data access. File paths and code examples for AI agent code generation.

## Technology summary

| Aspect              | Choice                                               |
| ------------------- | ---------------------------------------------------- |
| **Runtime**         | .NET 8                                               |
| **Framework**       | ASP.NET Core Web API                                 |
| **Language**        | C# with nullable reference types                     |
| **Serialization**   | Newtonsoft.Json (camelCase, ignore nulls, UTC dates) |
| **Auth**            | JWT Bearer (Supabase-issued, HS256)                  |
| **API Docs**        | Swashbuckle / Swagger                                |
| **Database Client** | Raw HTTP calls to Supabase PostgREST API             |
| **DI Container**    | Built-in Microsoft.Extensions.DependencyInjection    |

## Controller map

Every controller inherits from `ControllerBase` (no views). Routes use attribute routing.

### Public API controllers

| Controller           | Route prefix             | Auth       | Purpose                    |
| -------------------- | ------------------------ | ---------- | -------------------------- |
| `HealthController`   | `/health`, `/api/health` | Anonymous  | Health checks              |
| `ArenaController`    | `/api/arena`             | Bearer JWT | Chat, dual chat, streaming |
| `ModelsController`   | `/api/models`            | Bearer JWT | List active AI models      |
| `ThreadsController`  | `/api/threads`           | Bearer JWT | Thread CRUD + messages     |
| `UsersController`    | `/api/users`             | Bearer JWT | User sync                  |
| `SettingsController` | `/api/settings`          | Anonymous  | Feature flags              |
| `SpeechController`   | `/api/speech`            | Bearer JWT | Text-to-speech             |

### Admin API controllers

All admin controllers are under `/api/admin/` and use `IAdminSupabaseClient` for data access with service role key.

| Controller                      | Route prefix             | Purpose                       |
| ------------------------------- | ------------------------ | ----------------------------- |
| `AdminDashboardController`      | `/api/admin/dashboard`   | Stats, activity, performance  |
| `AdminAIModelsController`       | `/api/admin/models`      | AI model CRUD                 |
| `AdminUsersController`          | `/api/admin/users`       | User CRUD                     |
| `AdminComparisonsController`    | `/api/admin/comparisons` | Comparison CRUD               |
| `AdminModelVotesController`     | `/api/admin/votes`       | Vote CRUD + stats             |
| `AdminThreadsController`        | `/api/admin/threads`     | Thread CRUD                   |
| `AdminThreadMessagesController` | `/api/admin/messages`    | Message CRUD                  |
| `ProvidersController`           | `/api/admin/providers`   | Provider + API key management |

## ArenaController — the core

The `ArenaController` (`Controllers/Api/ArenaController.cs`) is the heart of DualMind. It handles all chat operations.

### Dependencies injected

```csharp filename="ArenaController.cs" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
private readonly IModelSelector _modelSelector;           // Random model selection from DB
private readonly IChatProviderFactory _chatProviderFactory; // Routes to Groq/Bytez
private readonly IMessageLogger _messageLogger;           // Logs chat messages
private readonly IThreadMessagesService _threadMessagesService; // Thread message persistence
private readonly ILeaderboardModelSelector _leaderboardModelSelector; // "Topper" mode selection
private readonly IComparisonLogger _comparisonLogger;     // Logs dual-chat comparisons
private readonly IUserSyncService _userSyncService;       // Ensures user exists in DB
```

### Single chat flow (`POST /api/arena/chat`)

1. Validate prompt is not empty
2. Select model: if `request.Model` is `"auto"` or null, call `_modelSelector.GetRandomModelAsync()`; otherwise use the specified model
3. Call `ExecuteWithFallbackAsync(model, prompt, system, maxTokens, temperature)`
4. Build `ChatResponse` with output content, model info, usage stats, response time
5. Log message via `_messageLogger.LogMessageAsync()`
6. If `request.ThreadId` is set, persist to thread via `_threadMessagesService.LogSingleAsync()`
7. Return response

### Dual chat flow (`POST /api/arena/dualchat`)

1. Validate prompt
2. Determine selection mode:
   * **Manual**: Both `model1` and `model2` specified by client
   * **Topper**: `_leaderboardModelSelector.GetTopperAndRandomModelAsync()` — top-rated model vs random
   * **Random** (default): `_modelSelector.GetTwoRandomModelsAsync()`
3. Execute both models **in parallel** via `Task.WhenAll(task1, task2)`
4. Build two `ChatResponse` objects
5. Log both messages and the comparison
6. Compute arena metrics (winner by length, winner by tokens, verdict)
7. Return `{ agent1, agent2, comparisonId, arena: { comparison, models } }`

### Fallback logic (`ExecuteWithFallbackAsync`)

```csharp filename="ArenaController.cs" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
private async Task<(GroqResponse Response, string UsedModel)> ExecuteWithFallbackAsync(...)
{
    // 1. Resolve provider from model metadata (default: groq)
    // 2. Try primary provider with 45-second timeout
    // 3. On failure:
    //    - If provider != groq → fallback to Groq with llama-3.3-70b-versatile
    //    - If groq failed → try alternative Groq model (llama-3.3-70b-versatile)
    // 4. If all fail → throw with combined error message
}
```

## Service layer

All business logic is in `Core/Services/`. Services are registered as `Scoped` except `ModelSelector` which is `Singleton`.

| Service                    | Interface                   | Responsibility                                                   |
| -------------------------- | --------------------------- | ---------------------------------------------------------------- |
| `ModelSelector`            | `IModelSelector`            | Query active models from DB, random selection, model info lookup |
| `LeaderboardModelSelector` | `ILeaderboardModelSelector` | Select top-rated model + random opponent                         |
| `ThreadsService`           | `IThreadsService`           | Thread CRUD, visibility management                               |
| `ThreadMessagesService`    | `IThreadMessagesService`    | Log single/dual messages to threads                              |
| `ModelStatsService`        | `IModelStatsService`        | Voting statistics, win rates                                     |
| `ComparisonLogger`         | `IComparisonLogger`         | Persist comparison records                                       |
| `MessageLogger`            | `IMessageLogger`            | Persist individual chat messages                                 |
| `UserSyncService`          | `IUserSyncService`          | Ensure `public.users` row exists before FK operations            |
| `SystemSettingsService`    | `ISystemSettingsService`    | Feature flag queries                                             |
| `ProviderConfigService`    | `IProviderConfigService`    | API key rotation, cooldowns, error tracking                      |

## Data access layer

DualMind does **not** use Entity Framework. It makes direct HTTP calls to the Supabase PostgREST API.

### `ISupabaseService` (user-facing)

Used by public controllers. Configured with service role key in `HttpClient` default headers.

```csharp filename="Infrastructure/Data/ISupabaseService.cs" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
public interface ISupabaseService
{
    Task<List<T>> SelectAsync<T>(string table, string columns, string query);
    // ... other methods
}
```

### `IAdminSupabaseClient` (admin-facing)

Used by admin controllers. Provides generic CRUD operations:

```csharp filename="Infrastructure/Data/IAdminSupabaseClient.cs" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
public interface IAdminSupabaseClient
{
    Task<string> GetAllAsync(string table, string query);
    Task<string> GetByIdAsync(string table, string idColumn, string id);
    Task<HttpResponseMessage> CreateAsync(string table, object data);
    Task<HttpResponseMessage> UpdateAsync(string table, string idColumn, string id, object data);
    Task<HttpResponseMessage> DeleteAsync(string table, string column, string value);
    Task<int> CountFastAsync(string table, string idColumn, string filterQuery = null);
}
```

## Middleware pipeline

Configured in `Program.cs`, executed in order:

1. **Exception handler** — catches unhandled exceptions, returns `ProblemDetails` JSON
2. **Request logging** — logs correlation ID, method, path, duration, status code
3. **CORS** — `AllowAll` policy (any origin, method, header)
4. **HTTPS redirection**
5. **Authentication** — JWT Bearer validation
6. **Authorization** — `[Authorize]` attribute enforcement
7. **Controller routing** — `app.MapControllers()`

## Error response format

All errors follow a consistent shape:

```json filename="Error Response Format" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
  "object": "ai.error",
  "code": "INVALID_REQUEST",
  "message": "Prompt is required and cannot be empty",
  "timestamp": "2024-01-03T10:00:00Z"
}
```

Error codes: `INVALID_REQUEST`, `API_ERROR`, `STREAM_ERROR`, `THREADS_ERROR`, `THREAD_CREATE_ERROR`, `MODELS_ERROR`, `NOT_FOUND`, `UNAUTHORIZED`, `FORBIDDEN`.
