updated docs for new feature

This commit is contained in:
2026-06-10 01:10:23 +02:00
parent fabe5319ae
commit ae41b96fa4
3 changed files with 575 additions and 260 deletions

View File

@@ -1,97 +1,194 @@
# Database Documentation
MiauInv utilizes an embedded SQLite database instance for persistent data storage. Foreign key constraints are strictly enforced at the database level.
MiauInv uses SQLite for persistent storage. The schema is initialized in `storage.InitDB` and foreign-key enforcement is explicitly enabled with:
## Configuration
To ensure data integrity, every database connection initialization explicitly executes the following command before handling queries:
```sql
PRAGMA foreign_keys = ON;
```
---
The database stores users, refresh tokens, 2FA recovery codes, inventory items, locations, projects, stock mappings, and project allocations.
## Schema Architecture
## Entity Overview
### Entity-Relationship Summary
```text
[users] 1 ──── N [refresh_tokens]
[users] 1 ──── N [two_factor_recovery_codes]
The database consists of primary entity tables (`users`, `items`, `locations`, `projects`) and relational junction tables (`stock`, `project_items`, `refresh_tokens`) designed to track stock distribution and access sessions.
```
[users] <--- (1:N) ---> [refresh_tokens]
[items] <--- (1:N) ---> [stock] <--- (N:1) ---> [locations]
[items] <--- (1:N) ---> [project_items] <--- (N:1) ---> [projects]
[items] 1 ──── N [stock] N ──── 1 [locations]
[items] 1 ──── N [project_items] N ──── 1 [projects]
```
---
## Tables
## Table Definitions
### `users`
### 1. users
Stores account credentials, roles, and 2FA state.
Stores user credentials and operational roles within the system.
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `TEXT` | Primary key | User UUID. |
| `username` | `TEXT` | Not null, unique | Lowercased account name. |
| `password` | `TEXT` | Not null | bcrypt password hash. |
| `role` | `TEXT` | Not null | User role, for example `user` or `admin`. |
| `two_factor_enabled` | `INTEGER` | Not null, default `0` | Boolean flag for TOTP 2FA state. |
| `two_factor_secret` | `TEXT` | Not null, default `''` | TOTP secret used to validate authenticator codes. Empty when 2FA is disabled. |
* **id (TEXT, PK):** Unique UUID
* **username (TEXT, Unique):** Unique account identifier.
* **password (TEXT):** Hashed user password.
* **role (TEXT):** Access control flag (e.g., admin, user).
Migration note: existing databases are migrated with `ALTER TABLE` statements for `two_factor_enabled` and `two_factor_secret` if those columns do not exist yet.
### 2. refresh_tokens
### `refresh_tokens`
Tracks valid extended sessions linked to specific user accounts.
Stores refresh-token sessions. Tokens are stored as hashes, not plaintext.
* **id (TEXT, PK):** Unique identifier.
* **user_id (TEXT, FK):** References `users(id)`.
* **token_hash (TEXT):** Cryptographic hash of the active refresh token.
* **expires_at (INTEGER):** Unix timestamp indicating token expiration.
* **created_at (INTEGER):** Unix timestamp indicating session creation.
* **revoked (INTEGER):** Boolean flag (0 or 1) indicating if the session was manually invalidated.
* **device_info (TEXT, Optional):** Client metadata for auditing.
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `TEXT` | Primary key | Refresh-token row UUID. |
| `user_id` | `TEXT` | Not null, foreign key to `users(id)` | Owning user. |
| `token_hash` | `TEXT` | Not null | Hash of the refresh token. |
| `expires_at` | `INTEGER` | Not null | Unix timestamp for expiry. |
| `created_at` | `INTEGER` | Not null | Unix timestamp for creation. |
| `revoked` | `INTEGER` | Not null, default `0` | Boolean revocation flag. |
| `device_info` | `TEXT` | Optional | User-Agent string recorded when the session is created. |
### 3. items
Refresh-token rotation revokes the used refresh token and inserts a new row for the next token.
Represents individual tracked assets.
### `two_factor_recovery_codes`
* **id (INTEGER, PK, Autoincrement):** Primary key.
* **name (TEXT):** Asset designation.
* **category (TEXT, Optional):** Grouping classification.
* **description (TEXT, Optional):** Detailed asset context.
* **total_quantity (INTEGER):** Absolute global stock baseline counter.
Stores recovery-code hashes for 2FA fallback login.
### 4. locations
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `TEXT` | Primary key | Recovery-code row UUID. |
| `user_id` | `TEXT` | Not null, foreign key to `users(id)` with `ON DELETE CASCADE` | Owning user. |
| `code_hash` | `TEXT` | Not null, unique with `user_id` | Hash of the normalized recovery code. |
| `created_at` | `INTEGER` | Not null | Unix timestamp for creation. |
| `used_at` | `INTEGER` | Nullable | Unix timestamp when the code was consumed. `NULL` means unused. |
Defines logical or physical facilities.
Recovery codes are deleted and replaced when the user regenerates them. A recovery code is consumed with an atomic update that only matches unused codes.
* **id (INTEGER, PK, Autoincrement):** Primary key.
* **name (TEXT, Unique):** Unique facility naming constraint.
### `items`
### 5. projects
Stores tracked inventory items.
Defines distinct tasks or allocation targets.
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `INTEGER` | Primary key, autoincrement | Item ID. |
| `name` | `TEXT` | Not null | Item name. |
| `category` | `TEXT` | Optional | Category label. |
| `description` | `TEXT` | Optional | Item description. |
| `total_quantity` | `INTEGER` | Not null, default `0` | Global quantity baseline. |
* **id (INTEGER, PK, Autoincrement):** Primary key.
* **name (TEXT, Unique):** Unique operational tracking name.
* **description (TEXT, Optional):** Scope description.
### `locations`
### 6. stock
Stores physical or logical storage locations.
Junction table mapping physical asset distributions across facilities.
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `INTEGER` | Primary key, autoincrement | Location ID. |
| `name` | `TEXT` | Not null, unique | Location name. |
* **id (INTEGER, PK, Autoincrement):** Primary key.
* **item_id (INTEGER, FK):** References `items(id)`.
* **location_id (INTEGER, FK):** References `locations(id)`.
* **quantity (INTEGER):** Specific quantity present at this location node.
### `projects`
### 7. project_items
Stores project contexts for item allocation.
Junction table tracking asset assignments dedicated to specific ongoing project environments.
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `INTEGER` | Primary key, autoincrement | Project ID. |
| `name` | `TEXT` | Not null, unique | Project name. |
| `description` | `TEXT` | Optional | Project description. |
* **id (INTEGER, PK, Autoincrement):** Primary key.
* **item_id (INTEGER, FK):** References `items(id)`.
* **project_id (INTEGER, FK):** References `projects(id)`.
* **quantity (INTEGER):** Quantity allocated to this project context.
---
### `stock`
## Data Integrity Constraints
Maps item quantities to locations.
* **Foreign Keys:** Because standard `ON DELETE` cascades are not defined explicitly in the schema rules, SQLite blocks parent deletion actions if dependent rows exist in `stock` or `project_items`. You must clear out stock allocations and project associations manually before deleting an item, location, or project.
* **Uniqueness:** String uniqueness constraints protect against duplicate namespace registration on `users(username)`, `locations(name)`, and `projects(name)`.
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `INTEGER` | Primary key, autoincrement | Stock row ID. |
| `item_id` | `INTEGER` | Not null, foreign key to `items(id)` | Item reference. |
| `location_id` | `INTEGER` | Not null, foreign key to `locations(id)` | Location reference. |
| `quantity` | `INTEGER` | Not null | Quantity at this location. |
### `project_items`
Maps item quantities to projects.
| Column | Type | Constraints | Description |
| --- | --- | --- | --- |
| `id` | `INTEGER` | Primary key, autoincrement | Association row ID. |
| `item_id` | `INTEGER` | Not null, foreign key to `items(id)` | Item reference. |
| `project_id` | `INTEGER` | Not null, foreign key to `projects(id)` | Project reference. |
| `quantity` | `INTEGER` | Not null | Quantity allocated to the project. |
## Data Integrity
### Foreign keys
Foreign keys are enabled per connection. Because most inventory foreign keys do not define explicit cascade behavior, SQLite blocks deletion of referenced items, locations, or projects while dependent rows exist.
`two_factor_recovery_codes.user_id` uses `ON DELETE CASCADE`, so deleting a user also deletes their recovery-code rows.
### Uniqueness
The following values are unique:
- `users.username`
- `locations.name`
- `projects.name`
- `two_factor_recovery_codes(user_id, code_hash)`
### Current schema limitations
The current schema does not yet enforce all business rules at the database level. In particular:
- `items.total_quantity` has no explicit `CHECK(total_quantity >= 0)` constraint.
- `stock.quantity` has no explicit `CHECK(quantity >= 0)` constraint.
- `project_items.quantity` has no explicit `CHECK(quantity >= 0)` constraint.
- Duplicate stock rows for the same `(item_id, location_id)` pair are not prevented by a unique constraint.
- Duplicate project allocations for the same `(item_id, project_id)` pair are not prevented by a unique constraint.
These constraints should be added in a future migration once the desired application behavior is finalized.
## Important Queries and Behaviors
### User lookup
Users can be loaded by lowercased username or ID. Username updates store the new username lowercased.
### Password update
When the password is updated:
1. The new password is bcrypt-hashed.
2. The `users.password` field is updated.
3. Existing refresh tokens for that user are revoked.
4. A new session is issued.
### 2FA enable
When 2FA is enabled:
1. The TOTP secret must already exist from `/api/2fa/setup`.
2. The supplied TOTP code is validated.
3. Existing recovery codes are deleted.
4. New recovery-code hashes are inserted.
5. `users.two_factor_enabled` is set to `1`.
### 2FA disable
When 2FA is disabled:
1. `two_factor_enabled` is set to `0`.
2. `two_factor_secret` is cleared.
3. Recovery-code rows for the user are deleted.
4. Refresh tokens for the user are revoked.
### Recovery-code use
A recovery code is consumed with an update equivalent to:
```sql
UPDATE two_factor_recovery_codes
SET used_at = ?
WHERE user_id = ? AND code_hash = ? AND used_at IS NULL;
```
The login succeeds only if exactly one row is updated.