# Azaion Admin API — Data Model ## Entity-Relationship Diagram ```mermaid erDiagram USERS { uuid id PK varchar email "unique, not null" varchar password_hash "not null" text hardware "nullable" varchar hardware_hash "nullable" varchar role "not null (text enum)" varchar user_config "nullable (JSON)" timestamp created_at "not null, default now()" timestamp last_login "nullable" bool is_enabled "not null, default true" } ``` The system has a single table (`users`). There are no foreign key relationships. ## Table: `users` ### Columns | Column | Type | Nullable | Default | Description | |--------|------|----------|---------|-------------| | `id` | `uuid` | No | (application-generated) | Primary key, `Guid.NewGuid()` | | `email` | `varchar(160)` | No | — | Unique user identifier | | `password_hash` | `varchar(255)` | No | — | SHA-384 hash, Base64-encoded | | `hardware` | `text` | Yes | null | Raw hardware fingerprint string | | `hardware_hash` | `varchar(120)` | Yes | null | Defined in DDL but not used by application code | | `role` | `varchar(20)` | No | — | Text representation of `RoleEnum` | | `user_config` | `varchar(512)` | Yes | null | JSON-serialized `UserConfig` object | | `created_at` | `timestamp` | No | `now()` | Account creation time | | `last_login` | `timestamp` | Yes | null | Last hardware check / resource access time | | `is_enabled` | `bool` | No | `true` | Account active flag | ### ORM Mapping (linq2db) Column names are auto-converted from PascalCase to snake_case via `AzaionDbSchemaHolder`: - `User.PasswordHash` → `password_hash` - `User.CreatedAt` → `created_at` Special mappings: - `Role`: stored as text, converted to/from `RoleEnum` via `Enum.Parse` - `UserConfig`: stored as nullable JSON string, serialized/deserialized via `Newtonsoft.Json` ### Permissions | Role | Privileges | |------|-----------| | `azaion_reader` | SELECT on `users` | | `azaion_admin` | SELECT, INSERT, UPDATE, DELETE on `users` | | `azaion_superadmin` | Superuser (DB owner) | ### Seed Data Two default users (from `env/db/02_structure.sql`): | Email | Role | |-------|------| | `admin@azaion.com` | `ApiAdmin` | | `uploader@azaion.com` | `ResourceUploader` | ## Schema Migration History Schema is managed via SQL scripts in `env/db/`: 1. `00_install.sh` — PostgreSQL installation and configuration 2. `01_permissions.sql` — Role creation (superadmin, admin, reader) 3. `02_structure.sql` — Table creation + seed data 4. `03_add_timestamp_columns.sql` — Adds `created_at`, `last_login`, `is_enabled` columns No ORM migration framework is used. Schema changes are applied manually via SQL scripts. ## UserConfig JSON Schema ```json { "QueueOffsets": { "AnnotationsOffset": 0, "AnnotationsConfirmOffset": 0, "AnnotationsCommandsOffset": 0 } } ``` Stored in the `user_config` column. Deserialized to `UserConfig` → `UserQueueOffsets` on read. Default empty `UserConfig` is created when the field is null or empty. ## Observations - The `hardware_hash` column exists in the DDL but is not referenced in application code. The application stores the raw hardware string in `hardware` and computes hashes at runtime. - No unique constraint on `email` column in the DDL — uniqueness is enforced at the application level (`UserService.RegisterUser` checks for duplicates before insert). - `user_config` is limited to `varchar(512)`, which could be insufficient if queue offsets grow or additional config fields are added.