Files
MiauInv/docs/DATABASE.md

7.2 KiB

Database Documentation

MiauInv uses SQLite for persistent storage. The schema is initialized in storage.InitDB and foreign-key enforcement is explicitly enabled with:

PRAGMA foreign_keys = ON;

The database stores users, refresh tokens, 2FA recovery codes, inventory items, locations, projects, stock mappings, and project allocations.

Entity Overview

[users] 1 ──── N [refresh_tokens]
[users] 1 ──── N [two_factor_recovery_codes]

[items] 1 ──── N [stock] N ──── 1 [locations]
[items] 1 ──── N [project_items] N ──── 1 [projects]

Tables

users

Stores account credentials, roles, and 2FA state.

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. During setup the secret is held in a short-lived signed setup token and is only stored after the first valid TOTP code.

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.

refresh_tokens

Stores refresh-token sessions. Tokens are stored as hashes, not plaintext.

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.

Refresh-token rotation revokes the used refresh token and inserts a new row for the next token.

two_factor_recovery_codes

Stores recovery-code hashes for 2FA fallback login.

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.

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.

items

Stores tracked inventory items.

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.

locations

Stores physical or logical storage locations.

Column Type Constraints Description
id INTEGER Primary key, autoincrement Location ID.
name TEXT Not null, unique Location name.

projects

Stores project contexts for item allocation.

Column Type Constraints Description
id INTEGER Primary key, autoincrement Project ID.
name TEXT Not null, unique Project name.
description TEXT Optional Project description.

stock

Maps item quantities to locations.

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 temporary setup token is validated.
  2. The supplied TOTP code is validated against the setup secret.
  3. Existing recovery codes are deleted.
  4. New recovery-code hashes are inserted.
  5. users.two_factor_enabled is set to 1 and users.two_factor_secret is set to the confirmed secret.
  6. Existing refresh tokens are revoked and a new session is issued for the current browser.

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:

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.