mirror of
https://github.com/azaion/admin.git
synced 2026-04-22 11:56:32 +00:00
[AZ-189] [AZ-190] [AZ-191] [AZ-192] [AZ-193] [AZ-194] [AZ-195] Add e2e blackbox test suite
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
# Data Layer
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Provides database access, ORM mapping, entity definitions, configuration binding, and in-memory caching for the entire application.
|
||||
|
||||
**Architectural Pattern**: Repository/Factory — `DbFactory` creates short-lived `AzaionDb` connections with a read/write separation pattern.
|
||||
|
||||
**Upstream dependencies**: None (leaf component).
|
||||
|
||||
**Downstream consumers**: User Management, Authentication & Security, Resource Management.
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### Interface: IDbFactory
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `Run<T>` | `Func<AzaionDb, Task<T>>` | `T` | Yes | `ArgumentException` (empty conn string) |
|
||||
| `Run` | `Func<AzaionDb, Task>` | void | Yes | `ArgumentException` |
|
||||
| `RunAdmin` | `Func<AzaionDb, Task>` | void | Yes | `ArgumentException` |
|
||||
|
||||
### Interface: ICache
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `GetFromCacheAsync<T>` | `string key, Func<Task<T>>, TimeSpan?` | `T` | Yes | None |
|
||||
| `Invalidate` | `string key` | void | No | None |
|
||||
|
||||
### Entities
|
||||
|
||||
```
|
||||
User:
|
||||
Id: Guid (PK)
|
||||
Email: string (required)
|
||||
PasswordHash: string (required)
|
||||
Hardware: string? (optional)
|
||||
Role: RoleEnum (required)
|
||||
CreatedAt: DateTime (required)
|
||||
LastLogin: DateTime? (optional)
|
||||
UserConfig: UserConfig? (optional, JSON-serialized)
|
||||
IsEnabled: bool (required)
|
||||
|
||||
UserConfig:
|
||||
QueueOffsets: UserQueueOffsets? (optional)
|
||||
|
||||
UserQueueOffsets:
|
||||
AnnotationsOffset: ulong
|
||||
AnnotationsConfirmOffset: ulong
|
||||
AnnotationsCommandsOffset: ulong
|
||||
|
||||
RoleEnum: None=0, Operator=10, Validator=20, CompanionPC=30, Admin=40, ResourceUploader=50, ApiAdmin=1000
|
||||
```
|
||||
|
||||
### Configuration POCOs
|
||||
|
||||
```
|
||||
ConnectionStrings:
|
||||
AzaionDb: string — read-only connection string
|
||||
AzaionDbAdmin: string — admin (read/write) connection string
|
||||
|
||||
JwtConfig:
|
||||
Issuer: string
|
||||
Audience: string
|
||||
Secret: string
|
||||
TokenLifetimeHours: double
|
||||
|
||||
ResourcesConfig:
|
||||
ResourcesFolder: string
|
||||
SuiteInstallerFolder: string
|
||||
SuiteStageInstallerFolder: string
|
||||
```
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
N/A — internal component.
|
||||
|
||||
## 4. Data Access Patterns
|
||||
|
||||
### Queries
|
||||
|
||||
| Query | Frequency | Hot Path | Index Needed |
|
||||
|-------|-----------|----------|--------------|
|
||||
| `SELECT * FROM users WHERE email = ?` | High | Yes | Yes (email) |
|
||||
| `SELECT * FROM users` with optional filters | Medium | No | No |
|
||||
| `UPDATE users SET ... WHERE email = ?` | Medium | No | No |
|
||||
| `INSERT INTO users` | Low | No | No |
|
||||
| `DELETE FROM users WHERE email = ?` | Low | No | No |
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
| Data | Cache Type | TTL | Invalidation |
|
||||
|------|-----------|-----|-------------|
|
||||
| User by email | In-memory (LazyCache) | 4 hours | On hardware update, queue offset update, hardware check |
|
||||
|
||||
### Storage Estimates
|
||||
|
||||
| Table | Est. Row Count (1yr) | Row Size | Total Size | Growth Rate |
|
||||
|-------|---------------------|----------|------------|-------------|
|
||||
| `users` | 100–1000 | ~500 bytes | ~500 KB | Low |
|
||||
|
||||
### Data Management
|
||||
|
||||
**Seed data**: Default admin user (`admin@azaion.com`, `ApiAdmin` role) and uploader user (`uploader@azaion.com`, `ResourceUploader` role) — see `env/db/02_structure.sql`.
|
||||
|
||||
**Rollback**: Standard PostgreSQL transactions; linq2db creates a new connection per `Run`/`RunAdmin` call.
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Stateless factory pattern. `DbFactory` is a singleton holding pre-built `DataOptions`. Cache state is in-memory per process.
|
||||
|
||||
**Key Dependencies**:
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| linq2db | 5.4.1 | ORM for PostgreSQL access |
|
||||
| Npgsql | 10.0.1 | PostgreSQL ADO.NET provider |
|
||||
| LazyCache | 2.4.0 | In-memory cache with async support |
|
||||
| Newtonsoft.Json | 13.0.1 | JSON serialization for UserConfig |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `DbFactory.LoadOptions` throws `ArgumentException` on empty connection strings (fail-fast at startup).
|
||||
- Database exceptions from linq2db/Npgsql propagate unhandled to callers.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
| Helper | Purpose | Used By |
|
||||
|--------|---------|---------|
|
||||
| `StringExtensions.ToSnakeCase` | PascalCase → snake_case column mapping | AzaionDbSchemaHolder |
|
||||
| `EnumExtensions.GetDescriptions` | Enum → description dictionary | BusinessException |
|
||||
| `QueryableExtensions.WhereIf` | Conditional LINQ filters | UserService |
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- No connection pooling configuration exposed; relies on Npgsql defaults.
|
||||
- `AzaionDbSchemaHolder` mapping schema is static — adding new entities requires code changes.
|
||||
- Cache TTL (4 hours) is hardcoded, not configurable.
|
||||
|
||||
**Potential race conditions**:
|
||||
- Cache invalidation after write: there's a small window where stale data could be served between the DB write and cache invalidation.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- `DbFactory` creates a new connection per operation. For high-throughput scenarios, connection reuse or batching would be needed.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: None (leaf component).
|
||||
|
||||
**Can be implemented in parallel with**: Security & Cryptography (no dependency).
|
||||
|
||||
**Blocks**: User Management, Authentication, Resource Management, Admin API.
|
||||
|
||||
## 9. Logging Strategy
|
||||
|
||||
| Log Level | When | Example |
|
||||
|-----------|------|---------|
|
||||
| INFO | SQL trace | `SELECT * FROM users WHERE email = @p1` (via linq2db TraceLevel.Info) |
|
||||
|
||||
**Log format**: Plaintext SQL output to console.
|
||||
|
||||
**Log storage**: Console (via `Console.WriteLine` in `DbFactory.LoadOptions` trace callback).
|
||||
|
||||
## Modules Covered
|
||||
- `Common/Configs/ConnectionStrings`
|
||||
- `Common/Configs/JwtConfig`
|
||||
- `Common/Configs/ResourcesConfig`
|
||||
- `Common/Entities/User`
|
||||
- `Common/Entities/RoleEnum`
|
||||
- `Common/Database/AzaionDb`
|
||||
- `Common/Database/AzaionDbSchemaHolder`
|
||||
- `Common/Database/DbFactory`
|
||||
- `Services/Cache`
|
||||
@@ -0,0 +1,127 @@
|
||||
# User Management
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Full user lifecycle management — registration, credential validation, hardware binding, role changes, account enable/disable, and deletion.
|
||||
|
||||
**Architectural Pattern**: Service layer — stateless business logic operating on the Data Layer through `IDbFactory`.
|
||||
|
||||
**Upstream dependencies**: Data Layer (IDbFactory, ICache, User entity), Security & Cryptography (hashing).
|
||||
|
||||
**Downstream consumers**: Admin API (endpoint handlers), Authentication (GetByEmail).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### Interface: IUserService
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `RegisterUser` | `RegisterUserRequest, CancellationToken` | void | Yes | `BusinessException(EmailExists)` |
|
||||
| `ValidateUser` | `LoginRequest, CancellationToken` | `User` | Yes | `BusinessException(NoEmailFound, WrongPassword)` |
|
||||
| `GetByEmail` | `string? email, CancellationToken` | `User?` | Yes | `ArgumentNullException` |
|
||||
| `UpdateHardware` | `string email, string? hardware, CancellationToken` | void | Yes | None |
|
||||
| `UpdateQueueOffsets` | `string email, UserQueueOffsets, CancellationToken` | void | Yes | None |
|
||||
| `GetUsers` | `string? searchEmail, RoleEnum? searchRole, CancellationToken` | `IEnumerable<User>` | Yes | None |
|
||||
| `CheckHardwareHash` | `User, string hardware, CancellationToken` | `string` (hash) | Yes | `BusinessException(HardwareIdMismatch)` |
|
||||
| `ChangeRole` | `string email, RoleEnum, CancellationToken` | void | Yes | None |
|
||||
| `SetEnableStatus` | `string email, bool, CancellationToken` | void | Yes | None |
|
||||
| `RemoveUser` | `string email, CancellationToken` | void | Yes | None |
|
||||
|
||||
**Input DTOs**:
|
||||
```
|
||||
RegisterUserRequest:
|
||||
Email: string (required) — validated: min 8 chars, valid email format
|
||||
Password: string (required) — validated: min 8 chars
|
||||
Role: RoleEnum (required)
|
||||
|
||||
LoginRequest:
|
||||
Email: string (required)
|
||||
Password: string (required)
|
||||
|
||||
SetHWRequest:
|
||||
Email: string (required, validated: not empty)
|
||||
Hardware: string? (optional — null clears hardware)
|
||||
|
||||
SetUserQueueOffsetsRequest:
|
||||
Email: string (required)
|
||||
Offsets: UserQueueOffsets (required)
|
||||
```
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
N/A — exposed through Admin API component.
|
||||
|
||||
## 4. Data Access Patterns
|
||||
|
||||
### Queries
|
||||
|
||||
| Query | Frequency | Hot Path | Index Needed |
|
||||
|-------|-----------|----------|--------------|
|
||||
| User by email (cached) | High | Yes | Yes |
|
||||
| User list with filters | Medium | No | No |
|
||||
| User insert (registration) | Low | No | No |
|
||||
| User update (hardware, role, config, status) | Medium | No | No |
|
||||
| User delete | Low | No | No |
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
| Data | Cache Type | TTL | Invalidation |
|
||||
|------|-----------|-----|-------------|
|
||||
| User by email | In-memory (via ICache) | 4 hours | After UpdateHardware, UpdateQueueOffsets, CheckHardwareHash (first login) |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Stateless — all state in PostgreSQL + in-memory cache.
|
||||
|
||||
**Key Dependencies**:
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| FluentValidation | 11.10.0 | Request validation (auto-discovered) |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- Domain errors thrown as `BusinessException` with specific `ExceptionEnum` codes.
|
||||
- `GetByEmail` throws `ArgumentNullException` for null/whitespace email.
|
||||
- Database errors propagate from `IDbFactory`.
|
||||
- Write operations use `RunAdmin` (admin connection); reads use `Run` (reader connection).
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
| Helper | Purpose | Used By |
|
||||
|--------|---------|---------|
|
||||
| `Security.ToHash` | Password hashing (SHA-384) | RegisterUser, ValidateUser |
|
||||
| `Security.GetHWHash` | Hardware fingerprint hashing | CheckHardwareHash |
|
||||
| `QueryableExtensions.WhereIf` | Conditional LINQ filters | GetUsers |
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- No pagination on `GetUsers` — returns all matching users.
|
||||
- `CheckHardwareHash` auto-stores hardware on first access (no explicit admin approval step).
|
||||
- `RemoveUser` is a hard delete, not soft delete.
|
||||
|
||||
**Potential race conditions**:
|
||||
- Concurrent `RegisterUser` calls with the same email: both could pass the existence check before insert. Mitigated by database unique constraint on email (if one exists).
|
||||
- `CheckHardwareHash` first-login path: concurrent requests could trigger multiple hardware updates.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- `GetUsers` loads full user objects including `UserConfig` JSON; for large user bases, projection would be more efficient.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: Data Layer, Security & Cryptography.
|
||||
|
||||
**Can be implemented in parallel with**: Resource Management.
|
||||
|
||||
**Blocks**: Authentication (uses `GetByEmail`), Admin API.
|
||||
|
||||
## 9. Logging Strategy
|
||||
|
||||
No explicit logging in UserService.
|
||||
|
||||
## Modules Covered
|
||||
- `Services/UserService`
|
||||
- `Common/Requests/LoginRequest`
|
||||
- `Common/Requests/RegisterUserRequest`
|
||||
- `Common/Requests/SetHWRequest`
|
||||
- `Common/Requests/SetUserQueueOffsetsRequest`
|
||||
@@ -0,0 +1,87 @@
|
||||
# Authentication & Security
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: JWT token creation/validation and cryptographic utilities (password hashing, hardware fingerprint hashing, AES file encryption/decryption).
|
||||
|
||||
**Architectural Pattern**: Service + static utility — `AuthService` is a DI-managed service for JWT operations; `Security` is a static class for cryptographic primitives.
|
||||
|
||||
**Upstream dependencies**: Data Layer (JwtConfig, IUserService for GetByEmail), ASP.NET Core (IHttpContextAccessor).
|
||||
|
||||
**Downstream consumers**: Admin API (token creation on login, current user resolution), User Management (password hashing, hardware hashing), Resource Management (encryption key derivation, stream encryption).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### Interface: IAuthService
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `GetCurrentUser` | (none — reads from HttpContext) | `User?` | Yes | None |
|
||||
| `CreateToken` | `User` | `string` (JWT) | No | None |
|
||||
|
||||
### Static: Security
|
||||
|
||||
| Method | Input | Output | Description |
|
||||
|--------|-------|--------|-------------|
|
||||
| `ToHash` | `string` | `string` (Base64) | SHA-384 hash |
|
||||
| `GetHWHash` | `string hardware` | `string` (Base64) | Salted hardware hash |
|
||||
| `GetApiEncryptionKey` | `string email, string password, string? hwHash` | `string` (Base64) | Derives AES encryption key |
|
||||
| `EncryptTo` | `Stream input, Stream output, string key, CancellationToken` | void | AES-256-CBC encrypt stream |
|
||||
| `DecryptTo` | `Stream encrypted, Stream output, string key, CancellationToken` | void | AES-256-CBC decrypt stream |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
N/A — exposed through Admin API.
|
||||
|
||||
## 4. Data Access Patterns
|
||||
|
||||
No direct database access. `AuthService.GetCurrentUser` delegates to `IUserService.GetByEmail`.
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**Algorithmic Complexity**: Encryption/decryption is O(n) where n is file size, streaming in 512 KB buffers.
|
||||
|
||||
**State Management**: `AuthService` is stateless (reads claims from HTTP context per request). `Security` is purely static.
|
||||
|
||||
**Key Dependencies**:
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| System.IdentityModel.Tokens.Jwt | 7.1.2 | JWT token generation |
|
||||
| Microsoft.AspNetCore.Authentication.JwtBearer | 10.0.3 | JWT middleware integration |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `EncryptTo` throws `ArgumentNullException` for unreadable streams or empty keys.
|
||||
- JWT token creation does not throw (malformed config would cause runtime errors at middleware level).
|
||||
- `GetCurrentUser` returns null if claims are missing or user not found.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
None — `Security` itself is a utility consumed by other components.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- Password hashing uses SHA-384 without per-user salt or key stretching. Not resistant to rainbow table attacks.
|
||||
- Hardware and encryption key salts are hardcoded constants.
|
||||
- `GetCurrentUserEmail` assumes `ClaimTypes.Name` is always present; accessing a missing key would throw `KeyNotFoundException`.
|
||||
- AES encryption prepends IV as first 16 bytes — consumers must know this format.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- Large file encryption loads encrypted output into `MemoryStream` before sending — high memory usage for large files.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: Data Layer (for JwtConfig, IUserService).
|
||||
|
||||
**Can be implemented in parallel with**: User Management (shared dependency on Data Layer).
|
||||
|
||||
**Blocks**: Admin API, Resource Management (uses encryption).
|
||||
|
||||
## 9. Logging Strategy
|
||||
|
||||
No explicit logging in AuthService or Security.
|
||||
|
||||
## Modules Covered
|
||||
- `Services/AuthService`
|
||||
- `Services/Security`
|
||||
@@ -0,0 +1,100 @@
|
||||
# Resource Management
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Server-side file storage management — upload, list, download (with per-user AES encryption), folder clearing, and installer distribution.
|
||||
|
||||
**Architectural Pattern**: Service layer — filesystem operations with encryption applied at the service boundary.
|
||||
|
||||
**Upstream dependencies**: Data Layer (ResourcesConfig), Authentication & Security (encryption via Security.EncryptTo).
|
||||
|
||||
**Downstream consumers**: Admin API (resource endpoints).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### Interface: IResourcesService
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `GetInstaller` | `bool isStage` | `(string?, Stream?)` | No | None (returns nulls if not found) |
|
||||
| `GetEncryptedResource` | `string? dataFolder, string fileName, string key, CancellationToken` | `Stream` | Yes | `FileNotFoundException` |
|
||||
| `SaveResource` | `string? dataFolder, IFormFile data, CancellationToken` | void | Yes | `BusinessException(NoFileProvided)` |
|
||||
| `ListResources` | `string? dataFolder, string? search, CancellationToken` | `IEnumerable<string>` | Yes | `DirectoryNotFoundException` |
|
||||
| `ClearFolder` | `string? dataFolder` | void | No | None |
|
||||
|
||||
**Input DTOs**:
|
||||
```
|
||||
GetResourceRequest:
|
||||
Password: string (required, min 8 chars)
|
||||
Hardware: string (required, not empty)
|
||||
FileName: string (required, not empty)
|
||||
|
||||
CheckResourceRequest:
|
||||
Hardware: string (required)
|
||||
```
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
N/A — exposed through Admin API.
|
||||
|
||||
## 4. Data Access Patterns
|
||||
|
||||
No database access. All operations are filesystem-based.
|
||||
|
||||
### Storage Estimates
|
||||
|
||||
Resources are stored as flat files in configured directories. Size depends on uploaded content (AI models, DLLs, installers — potentially hundreds of MB per file).
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Stateless — reads/writes directly to filesystem.
|
||||
|
||||
**Key Dependencies**: None beyond BCL (System.IO).
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `SaveResource` throws `BusinessException(NoFileProvided)` for null uploads.
|
||||
- Missing files/directories throw standard .NET I/O exceptions.
|
||||
- `ClearFolder` silently returns if directory doesn't exist.
|
||||
- `GetInstaller` returns `(null, null)` tuple if installer file is not found.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
| Helper | Purpose | Used By |
|
||||
|--------|---------|---------|
|
||||
| `Security.EncryptTo` | AES stream encryption | GetEncryptedResource |
|
||||
| `Security.GetApiEncryptionKey` | Key derivation | Admin API (before calling GetEncryptedResource) |
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- No path traversal protection: `dataFolder` parameter is concatenated directly with `ResourcesFolder`. A malicious `dataFolder` like `../../etc` could access arbitrary filesystem paths.
|
||||
- `SaveResource` deletes existing file before writing — no versioning or backup.
|
||||
- `GetEncryptedResource` loads the entire encrypted file into a `MemoryStream` — memory-intensive for large files.
|
||||
- `ListResources` wraps a synchronous `DirectoryInfo.GetFiles` in `Task.FromResult` — not truly async.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- Full file encryption to memory before streaming response: memory usage scales with file size.
|
||||
- `ClearFolder` iterates and deletes files synchronously.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: Data Layer (ResourcesConfig), Authentication & Security (encryption).
|
||||
|
||||
**Can be implemented in parallel with**: User Management.
|
||||
|
||||
**Blocks**: Admin API.
|
||||
|
||||
## 9. Logging Strategy
|
||||
|
||||
| Log Level | When | Example |
|
||||
|-----------|------|---------|
|
||||
| INFO | Successful file save | `Resource {data.FileName} Saved Successfully` |
|
||||
|
||||
**Log format**: String interpolation via Serilog.
|
||||
|
||||
**Log storage**: Console + rolling file (via Serilog configured in Program.cs).
|
||||
|
||||
## Modules Covered
|
||||
- `Services/ResourcesService`
|
||||
- `Common/Requests/GetResourceRequest` (includes CheckResourceRequest)
|
||||
- `Common/Configs/ResourcesConfig`
|
||||
@@ -0,0 +1,124 @@
|
||||
# Admin API
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: HTTP API entry point — configures DI, middleware pipeline, authentication, authorization, CORS, Swagger, and defines all REST endpoints using ASP.NET Core Minimal API.
|
||||
|
||||
**Architectural Pattern**: Composition root + Minimal API endpoints — top-level statements configure the application and map HTTP routes to service methods.
|
||||
|
||||
**Upstream dependencies**: User Management (IUserService), Authentication & Security (IAuthService, Security), Resource Management (IResourcesService), Data Layer (IDbFactory, ICache, configs).
|
||||
|
||||
**Downstream consumers**: None (top-level entry point, consumed by HTTP clients).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### BusinessExceptionHandler
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `TryHandleAsync` | `HttpContext, Exception, CancellationToken` | `bool` | Yes | None |
|
||||
|
||||
Converts `BusinessException` to HTTP 409 JSON response: `{ ErrorCode: int, Message: string }`.
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
### Authentication
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/login` | POST | Anonymous | Validates credentials, returns JWT |
|
||||
|
||||
### User Management
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/users` | POST | ApiAdmin | Creates a new user |
|
||||
| `/users/current` | GET | Authenticated | Returns current user |
|
||||
| `/users` | GET | ApiAdmin | Lists users (optional email/role filters) |
|
||||
| `/users/hardware/set` | PUT | ApiAdmin | Sets user hardware |
|
||||
| `/users/queue-offsets/set` | PUT | Authenticated | Updates queue offsets |
|
||||
| `/users/{email}/set-role/{role}` | PUT | ApiAdmin | Changes user role |
|
||||
| `/users/{email}/enable` | PUT | ApiAdmin | Enables user |
|
||||
| `/users/{email}/disable` | PUT | ApiAdmin | Disables user |
|
||||
| `/users/{email}` | DELETE | ApiAdmin | Removes user |
|
||||
|
||||
### Resource Management
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/resources/{dataFolder?}` | POST | Authenticated | Uploads a file (up to 200 MB) |
|
||||
| `/resources/list/{dataFolder?}` | GET | Authenticated | Lists files |
|
||||
| `/resources/clear/{dataFolder?}` | POST | ApiAdmin | Clears folder |
|
||||
| `/resources/get/{dataFolder?}` | POST | Authenticated | Downloads encrypted resource |
|
||||
| `/resources/get-installer` | GET | Authenticated | Downloads production installer |
|
||||
| `/resources/get-installer/stage` | GET | Authenticated | Downloads staging installer |
|
||||
| `/resources/check` | POST | Authenticated | Validates hardware |
|
||||
|
||||
### Authorization Policies
|
||||
- **apiAdminPolicy**: requires `ApiAdmin` role (used on most admin endpoints)
|
||||
- **apiUploaderPolicy**: requires `ResourceUploader` or `ApiAdmin` role (**defined but never applied to any endpoint — dead code**)
|
||||
|
||||
### CORS
|
||||
- Allowed origins: `https://admin.azaion.com`, `http://admin.azaion.com`
|
||||
- All methods/headers, credentials allowed
|
||||
|
||||
## 4. Data Access Patterns
|
||||
|
||||
No direct data access — delegates to service components.
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Stateless — ASP.NET Core request pipeline.
|
||||
|
||||
**Key Dependencies**:
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| Swashbuckle.AspNetCore | 10.1.4 | Swagger/OpenAPI documentation |
|
||||
| FluentValidation.AspNetCore | 11.3.0 | Request validation pipeline |
|
||||
| Serilog | 4.1.0 | Structured logging |
|
||||
| Serilog.Sinks.Console | 6.0.0 | Console log output |
|
||||
| Serilog.Sinks.File | 6.0.0 | Rolling file log output |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `BusinessException` → `BusinessExceptionHandler` → HTTP 409 with JSON body.
|
||||
- `UnauthorizedAccessException` → thrown in resource endpoints when current user is null.
|
||||
- `FileNotFoundException` → thrown when installer not found.
|
||||
- FluentValidation errors → automatic 400 Bad Request via middleware.
|
||||
- Unhandled exceptions → default ASP.NET Core exception handling.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
None.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
**Known limitations**:
|
||||
- All endpoints are defined in a single `Program.cs` file — no route grouping or controller separation.
|
||||
- Swagger UI only available in Development environment.
|
||||
- CORS origins are hardcoded (not configurable).
|
||||
- Antiforgery disabled for resource upload endpoint.
|
||||
- Root URL (`/`) redirects to `/swagger`.
|
||||
|
||||
**Performance bottlenecks**:
|
||||
- Kestrel max request body: 200 MB — allows large file uploads but could be a memory concern.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: All other components (composition root).
|
||||
|
||||
**Can be implemented in parallel with**: Nothing — depends on all services.
|
||||
|
||||
**Blocks**: Nothing.
|
||||
|
||||
## 9. Logging Strategy
|
||||
|
||||
| Log Level | When | Example |
|
||||
|-----------|------|---------|
|
||||
| WARN | Business exception caught | `BusinessExceptionHandler` logs the exception |
|
||||
| INFO | Serilog minimum level | General application events |
|
||||
|
||||
**Log format**: Serilog structured logging with context enrichment.
|
||||
|
||||
**Log storage**: Console + rolling file (`logs/log.txt`, daily rotation).
|
||||
|
||||
## Modules Covered
|
||||
- `AdminApi/Program`
|
||||
- `AdminApi/BusinessExceptionHandler`
|
||||
Reference in New Issue
Block a user