- Plugin contract gains get_config_schema(); the plugins list API returns it.
Employees plugin declares its directory-DB fields (host/name/user + password).
- employee_connection reads host/name/user settings-first (env fallback); the
password stays env-only.
- Setup wizard Features step renders each enabled plugin's config: non-secret
fields save to settings; secrets are never stored - the wizard emits .env
lines to paste. Fixed the plugins-list data path (data.plugins).
- Settings PUT now upserts (creates the row on first write) so plugin-config
keys can be saved without pre-seeding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verification audit (re-run of the 6 skill lenses) confirmed the prior fixes hold
and surfaced a few misses:
Security (HIGH):
- search.py _check_smart_redirect still opened a raw pymysql connection with
root/rootpassword (reachable on any 9-digit SSO query). Now uses the shared
env-backed employee_connection helper.
- Deleted dead shopdb/core/services/employee_service.py (zero importers; carried
another root/rootpassword literal). No hardcoded credentials remain in app
logic; config.py dev defaults stay gated by ProductionConfig.validate.
Dead hook:
- get_services was implemented by the printers plugin but had no consumer (docs
claimed otherwise). Added PluginManager.get_service(name) that resolves a
service from enabled plugins; updated PLUGIN-HOOKS.md.
Tests:
- search disabled-plugin exclusion (the high-value gap): enabled plugin's
hostname appears, disabled plugin's hostname drops out (searched by a hostname
distinct from assetnumber so only the gated domain can match).
- get_service consumer test (unknown name -> None).
Simplify:
- Extract the triplicated GE_LOGO_SVG + loadLogo + drawLogoOverlay into shared
frontend/src/views/print/qrLogo.js (renderQrDataUrl); both QR views use it.
- applications.py: lift the misplaced pagination import to the top; drop unused
Computer unpacking in the 3 endpoints that only touch ComputerInstalledApp.
154 tests pass, naming/style green, app boots, QR render verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses findings from a 6-lens review against the project skills
(defining-asset-contract, enforcing-plugin-contract, hardening-flask-config,
integrating-plugin-hooks, pinning-flask-behavior, simplifying-python).
Security (hardening-flask-config):
- Load per-plugin COLLECTOR_API_KEY_<PLUGIN> from env in create_app. from_object
only copies class attributes, so per-plugin keys (ADR-006) were dead in real
deploys and silently fell back to the shared key.
- EMPLOYEE_DB_USER/PASSWORD no longer default to root/rootpassword (no safe
default for a secret; unset fails loud). Documented in .env.example + DEPLOY.md.
- COLLECTOR_API_KEY + per-plugin + EMPLOYEE_DB_* added to .env.example/DEPLOY.md.
Hook isolation (integrating-plugin-hooks):
- collector _collector_plugins and dashboard get_navigation now re-raise in
dev/test and log+isolate in prod, instead of silently swallowing a broken
plugin hook.
Plugin loader (enforcing-plugin-contract):
- enable_plugin/install_plugin read dependencies+version from the manifest
instead of instantiating the plugin class.
- _register_plugin_components rejects a second plugin claiming an already-used
api_prefix (reset per app in init_app).
Tests (pinning-flask-behavior):
- test_identifiers.py: gauge/maintenance round-trip on computer/printer/network
create+update; per-type seed yields the 12 identifier keys.
- contract tests for apply_collector_payload presence + schema-declarers-implement.
- security tests for per-plugin key env loading + no employee-db password default.
Docs/contract sync (defining-asset-contract):
- PLUGIN-HOOKS.md documents apply_collector_payload; stale 0.2.0 -> 0.3.0.
- ADR-006 documents apply_collector_payload + single-dispatch rationale.
- ADR-001 enumerates the expanded shopdb.api import surface.
Simplify (simplifying-python):
- De-duplicate the 21-entry settings defaults: shared build_default_settings()
used by both the /settings/seed route and the CLI (were drifting copies).
- Remove dead AssetStatus import + redundant AssetType local import in computers
plugin; comment the statusid=1 collector default.
153 tests pass (was 145), naming/style green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
enable_plugin called register_blueprint at runtime, which Flask forbids after
the first request (AssertionError -> 500). Enabling now flips the registry flag
and fires on_enable best-effort; routes register on the next restart, symmetric
with disable. Nav reflects the re-enable immediately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each of the six bundled plugins (computers, equipment, network,
notifications, printers, usb) now has its own Alembic chain with a
baseline migration. Sister sites adopting one of these plugins can
manage its schema via `flask plugin migrate <name>` instead of relying
on db.create_all to bootstrap everything.
Existing single-site deploys that bootstrap via db.create_all continue
to work unchanged. The chains coexist; the bootstrap path stays the
operator's choice.
Framework
- shopdb/plugins/alembic_template.py: shared env.py logic + helpers.
PLUGIN_TABLE_OWNERS pins which tables belong to which plugin (explicit
registry, not import-side-effect). _get_plugin_metadata filters
db.metadata to only the named plugin's tables. create_plugin_tables /
drop_plugin_tables emit DDL via SQLAlchemy CreateTable so the table
definitions stay sourced from the models, not duplicated.
- shopdb/plugins/__init__.py: PluginManager.upgrade_all_plugins() runs
pending migrations across every discovered plugin and returns a status
dict. Idempotent (Alembic skips applied revisions).
CLI
- `flask plugin upgrade-all` runs pending migrations for every plugin.
Used on a fresh deploy after the core schema is in place.
Per-plugin scaffolding
- plugins/{computers,equipment,network,notifications,printers,usb}/
migrations/{alembic.ini, env.py, script.py.mako, versions/0001_baseline.py}
- Each env.py is a 5-line shim that sets PLUGIN_NAME and delegates to
the shared template. Each 0001_baseline calls create_plugin_tables(name)
/ drop_plugin_tables(name); no duplication of column definitions.
Tests
- tests/test_plugin_migrations.py (18 cases): every bundled plugin has
an entry in PLUGIN_TABLE_OWNERS, has the on-disk Alembic scaffolding,
and the filtered MetaData contains every owned table (catches drift
between the template's table list and what the models declare).
- 129 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix equipment badge barcode not rendering (loading race condition)
- Fix printer QR code not rendering on initial load (same race condition)
- Add model image to equipment badge via imageurl from Model table
- Fix white-on-white machine number text on badge, tighten barcode spacing
- Add PaginationBar component used across all list pages
- Split monolithic router into per-plugin route modules
- Fix 25 GET API endpoints returning 401 (jwt_required -> optional=True)
- Align list page columns across Equipment, PCs, and Network pages
- Add print views: EquipmentBadge, PrinterQRSingle, PrinterQRBatch, USBLabelBatch
- Add PC Relationships report, migration docs, and CLAUDE.md project guide
- Various plugin model, API, and frontend refinements
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Flask backend with Vue 3 frontend for shop floor machine management.
Includes database schema export for MySQL shopdb_flask database.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>