Document external-DB schema contracts for employees + USB plugins

Every site's HR directory and USB check-in/out databases may use a different
schema, so document exactly what each plugin queries and how to adapt.

- plugins/employees/README.md: required employees table columns (SSO,
  First_Name, Last_Name, Team, Role, Picture), the queries run, photo handling,
  and a CREATE VIEW recipe to map a different site schema without code changes.
- plugins/usb/README.md: cmmc_usb devices / checkinoutlog / users columns,
  read-write ops, the employee-directory dependency, and a view recipe.
- USB plugin gains get_config_schema() (cmmc_usb_db_host/name/user + password);
  cmmc_usb_connection reads host/name/user settings-first (env fallback), the
  password stays env-only - matching the employees plugin.
- Config-field help points at the READMEs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-10 08:19:26 -04:00
parent 087ece0f8c
commit 65b0197b2b
5 changed files with 233 additions and 5 deletions

98
plugins/usb/README.md Normal file
View File

@@ -0,0 +1,98 @@
# USB plugin - CMMC USB check-in/out database contract
The USB plugin tracks removable-media check-in/out for CMMC compliance. Device
and log state live in a **separate, read-write** MySQL database (`cmmc_usb`),
reached with parameterized pymysql via `cmmc_usb_connection()`. Names of people
are resolved from the HR employee directory (see the employees plugin).
The plugin's own reference tables (`usbdevicetypes`, `usbdevices`,
`usbcheckouts`) live in the main app database; only the live check-in/out data
is in `cmmc_usb`.
## Connection
Credentials resolve **settings-first, then environment**, except the password
which is env-only (never stored in the app database).
| Field | Setting key (editable in the setup wizard) | Env var (fallback) |
| -------- | ------------------------------------------ | ----------------------- |
| Host | `cmmc_usb_db_host` | `CMMC_USB_DB_HOST` |
| Database | `cmmc_usb_db_name` | `CMMC_USB_DB_NAME` |
| User | `cmmc_usb_db_user` | `CMMC_USB_DB_USER` |
| Password | (not stored) | `CMMC_USB_DB_PASSWORD` |
Set the password in `.env`; the setup wizard's Features step shows the exact
line to paste. This database is **read-write** - the app account needs
`SELECT, INSERT, UPDATE`. Engine is MySQL/MariaDB (pymysql).
## Required schema
The plugin runs raw SQL against these tables (or **views** - see below).
### `devices`
| Column | Type | Notes |
| ----------------- | ------- | -------------------------------------- |
| `device_id` | VARCHAR | Primary key; the device serial/tag |
| `device_desc` | VARCHAR | Description |
| `device_owner` | VARCHAR | Owner |
| `status` | VARCHAR | Check-in/out state |
| `locker_location` | VARCHAR | Where the device is stored |
Operations: `SELECT` (list + by id), `INSERT` (register device), `UPDATE`
(edit fields, change status).
### `checkinoutlog`
| Column | Type | Notes |
| ----------------- | --------- | ---------------------------------- |
| `log_id` | INT (PK) | Auto id |
| `badge_number` | VARCHAR | Person's badge |
| `device_id` | VARCHAR | FK to `devices.device_id` |
| `action` | VARCHAR | check-in / check-out |
| `timestamp` | DATETIME | When it happened |
| `scanned_viruses` | (text/int)| Scan result |
| `locker_location` | VARCHAR | Locker at time of event |
| `sanitized` | (bool/int)| Sanitization flag |
Operations: `SELECT` (history per device), `INSERT` (log an event).
### `users`
| Column | Type | Notes |
| -------------- | ------- | ------------------------------------- |
| `badge_number` | VARCHAR | Primary key; the scanned badge |
| `first_name` | VARCHAR | Given name |
| `last_name` | VARCHAR | Surname |
Operations: `SELECT` by badge, `INSERT` (auto-add a badge on first scan).
## Employee directory dependency
To turn a scanned badge into a name, the plugin also reads the HR `employees`
directory (via the employees plugin's connection). A badge shaped `0<digits>BZ`
carries a PayNo (the digits); lookups try `employees.SSO` and `employees.PayNo`.
See `plugins/employees/README.md` for that schema and connection.
## Adapting a different site schema (recommended: views)
Sites whose USB-tracking database uses different table/column names should
create read-only/updatable **views** named `devices`, `checkinoutlog`, and
`users` that map local columns to the names above. Example:
```sql
CREATE VIEW devices AS
SELECT
asset_tag AS device_id,
description AS device_desc,
owner AS device_owner,
state AS status,
storage_bay AS locker_location
FROM usb_assets;
```
Because the plugin writes to `devices`/`checkinoutlog`/`users`, either make the
views updatable (single-table views usually are) or expose real tables with
these column names. Grant the app account `SELECT, INSERT, UPDATE`.
Notes:
- `device_id` and `badge_number` are the natural keys the plugin matches on.
- If `cmmc_usb` is unreachable, USB endpoints return an error and the rest of
the app keeps working (the feature degrades, it does not crash the app).

View File

@@ -55,7 +55,24 @@ class USBPlugin(BasePlugin):
def get_models(self) -> List[Type]:
"""Return list of SQLAlchemy model classes."""
return [USBDeviceType, USBDevice, USBCheckout]
def get_config_schema(self) -> List[Dict]:
"""CMMC USB check-in/out database connection. Host/name/user are settings
the wizard can edit; the password stays in .env (emitted, not stored)."""
return [
{'key': 'cmmc_usb_db_host', 'label': 'USB DB host', 'type': 'text',
'secret': False, 'default': 'localhost',
'help': 'This DB must expose devices / checkinoutlog / users tables '
'(or views). See plugins/usb/README.md.'},
{'key': 'cmmc_usb_db_name', 'label': 'USB DB name', 'type': 'text',
'secret': False, 'default': 'cmmc_usb'},
{'key': 'cmmc_usb_db_user', 'label': 'USB DB user', 'type': 'text',
'secret': False},
{'key': 'cmmc_usb_db_password', 'label': 'USB DB password', 'type': 'password',
'secret': True, 'envvar': 'CMMC_USB_DB_PASSWORD',
'help': 'Stored in .env, not the database. The wizard shows the line to paste.'},
]
def init_app(self, app: Flask, db_instance) -> None:
"""Initialize plugin with Flask app."""
logger.info(f"USB plugin initialized (v{self.meta.version})")