Files
shopdb-flask/CLAUDE.md
cproudlock 593dd46525 Show the kiosk label prefix, and let a plugin declare the settings it owns
Three defects, all found on printedparts_label_prefix, all one root cause:
nothing in the framework knew that setting existed.

The parts kiosk runs logged out. An unauthenticated read of a setting is
limited to an allowlist, the key was not on it, so the kiosk got a 404 and
fell back to no prefix. An admin previewing the same page while logged in saw
the prefix, which is why it looked like it worked.

The same setting also looked like it would not save. The row did not exist on
a site that installed the plugin before the setting was added, so the first
save created it - under the placeholder category the settings API uses for
keys it does not recognise, where the plugin's settings page, which lists by
category, could no longer see it. The value was in the database the whole
time.

And the row was missing in the first place because seeding ran from
on_install / on_enable, which fire only on a state transition. Neither runs
again on an upgrade, so a setting added in a later plugin version never
reached a site that installed an earlier one. The comment claiming enable ran
every upgrade cycle was simply wrong.

A plugin now declares the settings it owns in get_settings_defaults(): key,
default, type, category, description, and whether a logged-out page may read
it. The framework seeds declared keys at install, at enable, and on every
flask plugin upgrade-all; files a first-time write under the declared
category; re-homes any row left in the placeholder category, value untouched;
and answers an anonymous read for keys marked public. Core carries no list of
any plugin's keys.

Contract 0.16.0 (additive optional hook). printedparts and printers move to
the hook and floor their core_version at 0.16.0. The dev database had two rows
in the misfiled state (printedparts_alert_email, employee_db_host); the first
repairs itself on the next upgrade pass.
2026-08-06 18:17:49 -04:00

9.0 KiB

ShopDB Flask Project

Modern rewrite of the classic-ASP shopdb. Built as a framework so sister GE Aerospace sites can adopt it. Plugin system is the product.

Database

  • Active database: shopdb_flask (MySQL, asset-based schema)
  • Legacy database: shopdb (Classic ASP schema, used only for one-time data import via scripts/import_from_mysql.py)
  • Connection: .env file. See .env.example.

Architecture decisions live in docs/adr/. Read those before making schema or contract changes.

  • ADR-001: Asset model is the platform contract (Machine retires) - ACCEPTED
  • ADR-002: Plugin contract versioning (semver) - ACCEPTED
  • ADR-003: Plugin distribution model (in-tree bundled + filesystem-based external) - ACCEPTED
  • ADR-004: Deployment topology (per-site instances, not multi-tenant) - ACCEPTED
  • ADR-005: Equipment vs measuringtools plugin scope - ACCEPTED
  • ADR-006: Plugin collector contract pattern - ACCEPTED
  • ADR-007: Product versioning and releases - ACCEPTED
  • ADR-008: Plugin migration ownership (per-plugin chains) - ACCEPTED
  • ADR-009: Frontend plugin route gating - ACCEPTED
  • ADR-010: Frontend plugin hook contract - ACCEPTED
  • ADR-011: Machines rename + modeltypes retyping - ACCEPTED
  • ADR-012: GE-Enforce manifest ownership in shopdb - ACCEPTED
  • ADR-013: Plugin catalog, curated shelf, and lean per-site builds - PROPOSED
  • ADR-014: Schema-lean per-site builds (retire cross-plugin FKs, prune not-installed plugin tables) - ACCEPTED

Coding convention

CONTRIBUTING.md defines naming rules (DB tables, columns, Python, JS, Vue, API). Pre-commit hook at scripts/check-naming-and-style.sh enforces them. Read CONTRIBUTING.md before naming any new identifier.

Current state (as of 2026-07-13)

Refactor phases 0-5 landed; phase 6 (multi-site distribution readiness) largely complete; the last big milestone is the legacy-data import + a production pilot.

Phases done

  • Phase 0: 6 ADRs accepted, naming convention v1, pre-commit style hook
  • Phase 1: 8 smoke tests + 7 production-config tests, Flask-SQLAlchemy 3 fixtures, uv lockfile, hardened ProductionConfig
  • Phase 2: contract surface defined (__contract_version__, BasePlugin hooks, docs/PLUGIN-HOOKS.md), 51 contract tests
  • Phase 3: manifest-first loader, fail-loud/isolate policy, contract-version range checking, auto-register core blueprints, shopdb.api namespace, BasePlugin.get_setting/set_setting helpers
  • Phase 4: flask plugin new <name> CLI, scaffold templates, 14 canary tests, docs/PLUGIN-QUICKSTART.md
  • Phase 5: ADRs moved to docs/adr/, Alembic baseline migration, per-site deploy artifacts (Dockerfile, docker-compose.yml, docs/DEPLOY.md)

Active state

  • 1159 tests, naming/style check green, Gitea Actions CI (backend + naming + frontend build + a lean-build job + a migrations-mysql job that runs the real fresh upgrade on utf8mb4 MySQL 8)
  • GE-Enforce HTTPS cutover: the displays/kiosks cohort now fetches manifest + inline payloads entirely over HTTPS (share-less); the gea-shopfloor-display scope is authored in code (plugins/geenforce/seed_display_scope.py) and published via seed_display_scope(publish=True). Other fleet PC types still enforce from the SMB share and only report. See docs/geenforce-api-cutover.md.
  • __contract_version__ at 0.16.0 (0.12.0 mailer, 0.13.0 User/Role, 0.14.0 send_webhook, 0.15.0 authorized_service_token, 0.16.0 get_settings_defaults) (product __version__ 0.7.0, tags v0.5.0/v0.6.0/v0.7.0 - distinct series, ADR-007)
  • 13 bundled plugins all satisfy contract: computers, employees, geenforce, knowledgebase, machines, measuringtools, network, notifications, printedparts, printers, slides, usb, warranty
  • Core Alembic chain: baseline 68b3947ae14f -> head 7d26_settings_description_text (33 core migrations). Each plugin owns its own chain (ADR-008); deploy runs flask db upgrade then flask plugin upgrade-all. Reproducible + idempotent from empty (env.py relaxes session sql_mode so the chain runs on strict MySQL 8).
  • Lean per-site builds (ADR-013 + ADR-014): scripts/build-site.sh (backend) + SITE_PLUGINS via scripts/stage-frontend.mjs (frontend) ship only chosen plugins; flask plugin prune-schema drops non-installed plugins' tables at provisioning. Sidebar nav / settings / Displays all gate on staged routes. Manifest-less plugins/<name>/frontend/ dirs (e.g. applications) are core and always ship.
  • Windows sites install from a single air-gapped installer .exe built per site from its plugin profile (deploy/windows/installer/, built by build-installer.sh or build-installer.ps1). Operator docs: docs/INSTALL-WINDOWS.md + docs/OPERATE-WINDOWS.md - these are canonical for a NEW site. docs/INSTALL-WINDOWS-IIS.md and docs/DEPLOY-WINDOWS-IIS.md are the MANUAL procedure, kept for hand-built servers only. The installer verifies its third-party payload against bundle-lock.json and installs wheels with pip --require-hashes; every build stages a CycloneDX SBOM (sbom.cdx.json) onto the server.
  • Legacy import: docs/IMPORT-API.md is the schema-agnostic import contract; docs/IMPORT-ADOPTION.md + docs/PILOT-DEPLOY.md cover adopting a site; scripts/site_imports/wjf/ is the West Jefferson reference loader (all 16 stages, validated end-to-end including on a Windows + MySQL 8 VM).
  • API is migration-complete: an admin PAT + docs/IMPORT-API.md let a script import the whole legacy DB (X-Import-Mode preserves timestamps).
  • Pre-1.0 framework; sister sites should pin tight core_version ranges until contract reaches 1.0

Deferred

  • Equipment data migration (one-shot script for legacy ASP shopdb -> assets). Per ADR-001, only category='Equipment' AND machinenumber IS NOT NULL migrates. Skill migrating-asset-schema documents the pattern; the actual one-shot script lives in scripts/migration/ when run.
  • Printers retirement: legacy PrinterData model + frontend changes. Coordinated with the equipment data migration.
  • (DONE 2026-07-11) measuringtools plugin (ADR-005) is built and bundled; docs/PLUGIN-GUIDE.md narrates its construction as the plugin tutorial.
  • (DONE) Frontend plugin hook contract (ADR-010): get_settings_cards / get_asset_panels / get_map_overlays / get_asset_presentation shipped; generic renderers for panels/overlays land incrementally.
  • (DONE) Per-plugin Alembic chains (ADR-008): every bundled plugin carries its own chain; no plugin uses db.create_all().
  • Legacy ASP data import against the renamed schema (unblocked; run via docs/IMPORT-API.md) + a production pilot deployment.

Quick start

# Start dev environment
~/start-dev-env.sh

# Activate venv and install deps
cd /home/camp/projects/shopdb-flask
source venv/bin/activate
pip install -r requirements.txt

# Configure environment
cp .env.example .env
# Edit .env with DB credentials, JWT secrets

# Create / update database tables via the Alembic chain
flask db upgrade

# Seed RBAC, default settings, and reference data (all idempotent)
flask seed permissions
flask seed settings
flask seed reference-data

# Restart services
pm2 restart shopdb-flask-api shopdb-flask-ui

Service URLs

Plugin structure

plugins/
  <plugin_name>/
    __init__.py
    plugin.py          # BasePlugin implementation
    manifest.json      # Plugin metadata (name, version, dependencies, api_prefix)
    models/
      __init__.py      # Export all models
      <model>.py       # SQLAlchemy models
    api/
      __init__.py
      routes.py        # Flask Blueprint
    services/          # Optional, business logic
    schemas/           # Optional, marshmallow schemas
    migrations/        # Optional, plugin-specific Alembic migrations

Each plugin must have:

  • models/__init__.py exports all models
  • plugin.py extends BasePlugin
  • manifest.json with metadata (single source of truth per ADR-002)
  • No direct imports from core code (use the contract surface defined in ADR-001)

Key files

  • shopdb/__init__.py - app factory, blueprint registration
  • shopdb/plugins/base.py - BasePlugin ABC, PluginMeta dataclass
  • shopdb/plugins/loader.py - filesystem discovery, dependency-aware loading
  • shopdb/core/api/assets.py - example of optional plugin imports
  • frontend/src/router/index.js - frontend routing
  • frontend/src/components/AppSidebar.vue - navigation menu
  • docs/adr/ - architecture decision records

Migration notes

  • migrations/DATA_MIGRATION_GUIDE.md - one-time import from legacy ASP shopdb
  • migrations/MIGRATE_USB_DEVICES_FROM_EQUIPMENT.md - USB device migration from equipment table
  • migrations/FIX_LOCATIONONLY_EQUIPMENT_TYPES.md - LocationOnly equipment type fix
  • migrations/PRODUCTION_MIGRATION_GUIDE.md - production import methods
  • migrations/rename_underscore_columns.sql - one-time rename of snake_case columns to lowercase concatenated (per CONTRIBUTING.md)
  • migrations/versions/ - the core Alembic chain (baseline 68b3947ae14f -> head 7d26_settings_description_text). Run flask db upgrade to apply.