- Deleted the deploy.cmd script as it was no longer needed. - Updated Dockerfile to include curl for health checks and added a non-root user for improved security. - Modified health check command to use curl for better reliability. - Adjusted docker-compose.test.yml to reflect changes in health check configuration. - Cleaned up appsettings.json and removed unused configuration properties. - Removed Resource entity and related requests from the codebase as part of the architectural shift. - Updated documentation to reflect the removal of hardware binding and related endpoints. Co-authored-by: Cursor <cursoragent@cursor.com>
7.4 KiB
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
Cycle 1 (2026-05-13) note —
DetectionClass(AZ-513) entity was added.Resource(AZ-183) was added then removed in the same cycle (post-cycle-1 revert; security audit F-1 + the OTA delivery model itself was deemed obsolete). TheUser.Hardwarecolumn is left in place as a tombstone (nullable, unused) per AZ-197. A UNIQUE INDEXusers_email_uidxwas added onusers.email(security audit F-3,env/db/06_users_email_unique.sql).
User:
Id: Guid (PK)
Email: string (required)
PasswordHash: string (required)
Hardware: string? (optional — TOMBSTONED by AZ-197; nullable, unused; no application code reads or writes)
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
DetectionClass (AZ-513):
Id: int (PK, DB-assigned identity)
Name, ShortName, Color: string
MaxSizeM: double
PhotoMode: string?
CreatedAt: DateTime
// Resource entity — REMOVED post-cycle-1 (AZ-183 reverted). The `resources`
// table no longer exists; see env/db/ for the current migration set.
RoleEnum: None=0, Operator=10, Validator=20, CompanionPC=30, Admin=40, ResourceUploader=50, ApiAdmin=1000
// ResourceUploader is now data-only — no endpoint policy references it
// after AZ-183 was reverted.
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
# EncryptionMasterKey was added by AZ-183 and removed in the post-cycle-1 revert.
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 — UNIQUE INDEX users_email_uidx on email (security audit F-3, env/db/06_users_email_unique.sql) |
SELECT * FROM users with optional filters |
Medium | No | No |
UPDATE users SET ... WHERE email = ? |
Medium | No | No |
INSERT INTO users |
Low | No | No (UNIQUE INDEX above also enforces single-row-per-email atomically) |
DELETE FROM users WHERE email = ? |
Low | No | No |
Caching Strategy
| Data | Cache Type | TTL | Invalidation |
|---|---|---|---|
| User by email | In-memory (LazyCache) | 4 hours | On UpdateQueueOffsets (post-AZ-197 — hardware paths gone) |
The
Resources.Latest.{arch}.{stage}cache key (added by AZ-183) was removed in the post-cycle-1 revert.
Storage Estimates
| Table | Est. Row Count (1yr) | Row Size | Total Size | Growth Rate |
|---|---|---|---|---|
users |
100–1000 web users + 2000–10000 CompanionPC device users (AZ-196 grows this) | ~500 bytes | ~5 MB | Medium (device fleet) |
detection_classes (AZ-513) |
10–200 | ~250 bytes | ~50 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.4 | JSON serialization for UserConfig (bumped from 13.0.1 by security audit D-1, GHSA-5crp-9r3c-p9vr) |
Error Handling Strategy:
DbFactory.LoadOptionsthrowsArgumentExceptionon 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.
AzaionDbSchemaHoldermapping 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:
DbFactorycreates 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/ConnectionStringsCommon/Configs/JwtConfigCommon/Configs/ResourcesConfigCommon/Entities/UserCommon/Entities/RoleEnumCommon/Entities/DetectionClass(added cycle 1, AZ-513)Common/Database/AzaionDb(now also holds theDetectionClassestable; theResourcesITable added by AZ-183 was removed in the post-cycle-1 revert)Common/Database/AzaionDbSchemaHolderCommon/Database/DbFactoryServices/Cache