cproudlock 98458672eb
Some checks failed
CI / backend (push) Failing after 7m16s
CI / naming (push) Failing after 7m5s
CI / frontend (push) Failing after 7m18s
CI / migrations-mysql (push) Failing after 7m10s
Hand somebody a password without leaving it in a chat window
The habit this replaces is pasting a credential into Teams, where it stays until
someone deletes the conversation. Tabled on 2026-08-21 pending a decision about
scope; the decision was text only, so this is the half that survives it.

BACKEND ONLY. There is no page yet, so nothing here is reachable by a person.

TEXT, NOT DOCUMENTS, and that shapes everything. Dropping files removed the
export-control question entirely - a password is not technical data - along with
blob storage, size and MIME handling, and the need to authenticate the recipient
so a document fetch could be pinned to a name.

TWO DOORS. Creating needs a login AND securesend.create: anonymous creation
would be an untraceable way to move data out of a system that otherwise records
who did what, and the creator is the only person these rows can ever name.
Reading needs neither, because the recipient usually has no account here and the
link is handed over on a call - the unguessable token IS the authorisation,
which is why it is 128 bits of randomness and why the row is spent on first read.

REDEEMING IS A POST, and that is the detail that would have broken this in
practice. Teams, Outlook and Safe Links prefetch GET urls to build previews and
scan for malware, so a GET that redeemed would let a preview window consume a
secret before its recipient ever saw it. The GET route that remains reports only
whether a link is still live, so the read page can say "opening this will
destroy it" without spending the only view to find out.

THE SIZE LIMIT IS THE WHOLE ENFORCEMENT. The key lives in the URL fragment,
which browsers never transmit, so the server holds ciphertext it cannot read and
cannot tell a password from a page of a drawing. MAX_CIPHERTEXT is therefore not
a tuning knob: raising it quietly turns this into the document transfer it was
scoped not to be. The refusal says what the limit is FOR, because somebody
hitting it is usually trying to send a file and the answer is a different tool.
Line counts, file signatures and refusing a pasted file belong in the browser,
guide an honest person, and are bypassable by anyone willing to POST directly.

NOT ENUMERABLE. Missing, expired and spent all answer identically - telling them
apart would confirm to anyone holding a guessed token whether it ever named
anything. A spent or expired row is deleted on the way past rather than waiting
for a sweeper, because it has no reason to keep its ciphertext.

Its own plugin rather than a Tech Tool: that plugin is browser-only by design,
and this needs storage, expiry and a view counter. Not an asset extension
either, so no assetid and no asset type - the scaffold assumes one and it was
removed.

FOUND BY ITS OWN TESTS: expiryhours=0 returned 201. `int(x or DEFAULT)` treats
an explicit zero as absent, so "expire immediately" silently became "expire in a
day". A caller who sends a number is now told when it is not allowed rather than
quietly given a different one.

BEFORE ANYONE RELIES ON THIS, test that your link rewriter preserves URL
fragments: create a secret, send the link to yourself in Teams, click it from the
message, and check the key is still after the #. If Safe Links strips it the
secret is unrecoverable and the server cannot help, which is the point of the
design. The fallback is passphrase mode, worth building only if that test says so.

Three product gates caught what a new plugin has to declare, and each was right
to: the route-parity gate wanted the endpoints described in api-inventory.json,
the lean-build guard wanted the plugin listed in the universal profile the
released installer builds from, and the authz sweep wanted the public redeem
route named in EXEMPT_ENDPOINTS with a reason. That last one matters most - a
mutating route that answers an unauthenticated caller is a decision somebody has
to write down, not a default, and reading a secret is a mutation because it
spends it.
2026-08-21 13:44:13 -04:00
2026-02-17 12:47:08 -05:00

ShopDB Flask

A modern rewrite of the classic ASP/VBScript ShopDB application using Flask (Python) and Vue 3. This application manages shop floor machines, PCs, printers, applications, and related infrastructure for manufacturing environments.

Overview

ShopDB tracks and manages:

  • Machines - CNC equipment, CMMs, inspection systems, etc.
  • PCs - Shopfloor computers, engineering workstations
  • Printers - Network printers with Zabbix supply integration
  • Network devices - Switches, routers, and the subnet browser
  • Measuring tools - Gage-lab instruments with calibration tracking
  • Applications - Software deployed across the shop floor, with per-PC install tracking
  • Employees - Directory, recognition and training notifications
  • Warranties - Coverage records with Dell warranty lookups
  • USB devices - CMMC check-in/out tracking
  • Knowledge Base - Documentation and troubleshooting guides
  • GE-Enforce manifests - Imaging/software manifest editing and fleet compliance

Tech Stack

Backend:

  • Python 3.14 with Flask
  • SQLAlchemy ORM
  • MySQL 5.7+ database (5.6 works with extra utf8mb4 config; see docs/DEPLOY.md)
  • JWT authentication
  • Plugin architecture for extensibility

Frontend:

  • Vue 3 with Composition API
  • Vue Router for navigation
  • Pinia for state management
  • Vite build system

Project Structure

shopdb-flask/
+-- shopdb/                 # Flask application
|   +-- core/
|   |   +-- api/            # REST API endpoints
|   |   +-- models/         # SQLAlchemy models
|   |   +-- schemas/        # Validation schemas
|   |   `-- services/       # Business logic
|   +-- plugins/            # Plugin system
|   `-- utils/              # Shared utilities
+-- frontend/               # Vue 3 application
|   +-- src/
|   |   +-- api/            # API client
|   |   +-- components/     # Reusable components
|   |   +-- views/          # Page components
|   |   +-- router/         # Route definitions
|   |   `-- stores/         # Pinia stores
|   `-- public/             # Static assets
+-- plugins/                # Bundled and external plugins
+-- migrations/             # Alembic migration chain (flask db upgrade)
+-- scripts/                # Import and utility scripts
`-- tests/                  # Test suite

Naming Conventions

To maintain consistency with the legacy ShopDB database and codebase, the following naming standards apply:

Database

  • Table names: Lowercase, single word, no underscores or dashes
    • Examples: assets, computers, printers, businessunits
  • Column names: Lowercase, single word, no underscores or dashes
    • Examples: assetid, assetnumber, hostname, isactive, createddate
  • Foreign keys: Referenced table name + id
    • Examples: locationid, vendorid, modelnumberid, computertypeid
  • Boolean columns: Prefixed with is or has
    • Examples: isactive, isshopfloor, iscolor, isdhcp, islicenced

Code

  • Python variables: Follow database naming where applicable (lowercase, no underscores for model fields)
  • JavaScript variables: camelCase for local variables, but match API field names from backend
  • Vue components: PascalCase for component names
  • CSS classes: Lowercase with dashes for multi-word classes

API

  • Endpoints: Lowercase, plural nouns
    • Examples: /api/machines, /api/computers, /api/locations
  • Query parameters: Lowercase, single word
    • Examples: ?locationid=5, ?isactive=true, ?assettype=computer

Style Guidelines

  • No emojis in code, comments, documentation, or UI
  • Keep UI functional and professional
  • Dark theme is the default
  • Consistent table layouts across all list views

Setup

Prerequisites

  • Python 3.14
  • Node.js 18+
  • MySQL 5.7+ (5.6 works with extra utf8mb4 config; see docs/DEPLOY.md)

ShopDB Flask uses MySQL as the canonical database. SQLite is used only for the test suite (TestingConfig in shopdb/config.py points at an in-memory SQLite). Do not run dev or production against SQLite.

Distribution

Source lives here. The Windows installer ships as a release asset rather than a file in the repository - it is around 240 MB, well past what a repository takes and well inside what a release asset does. There is no package or image registry; a site installs from that .exe or builds from source.

Fast path (Docker)

The Docker image builds the Vue frontend and serves it from the API container, so a container deploy needs no separate Node build step.

cp .env.example .env
# Edit .env: set SECRET_KEY, JWT_SECRET_KEY, DATABASE_URL, CORS_ORIGINS,
# and the MYSQL_* passwords (see docs/CONFIG.md for every variable).

docker compose up -d --build

# Create the schema and seed the platform data (idempotent, safe to re-run):
docker compose exec api flask db upgrade
docker compose exec api flask seed permissions
docker compose exec api flask seed settings
docker compose exec api flask seed reference-data

Then browse to the site and complete the first-run setup wizard at /setup (it creates the first admin account and captures site identity). To create the admin headlessly instead of using the wizard:

docker compose exec api flask seed admin --username admin --email admin@facility.example.com

Manual path (venv + Node)

# Backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Edit .env with your database credentials and secrets.

export FLASK_APP=shopdb
flask db upgrade
flask plugin upgrade-all      # per-plugin schema (ADR-008)
flask seed permissions
flask seed settings
flask seed reference-data
flask run --port 5001         # MUST be 5001 - the frontend dev server proxies here

# Frontend (separate terminal)
cd frontend
npm install
npm run dev          # dev server on :5173
npm run build        # production build into frontend/dist (served by Flask)

Complete first-run setup at /setup, or run flask seed admin for a headless admin account. The repo ships VS Code config in .vscode/ (F5 debugs the backend; a "Dev site" task runs both servers). A fuller day-one walkthrough, including VS Code and troubleshooting, is the DEVELOPMENT-SETUP page in the project wiki.

To import a site's legacy data, use the HTTP import surface: an admin API token plus docs/IMPORT-API.md drive the whole migration through documented endpoints (X-Import-Mode preserves original timestamps). scripts/site_imports/wjf/ is the reference site's loader, kept as a worked example of the whole sequence.

Which deployment route

Target Use
Windows Server + IIS (how sister sites run) docs/INSTALL-WINDOWS.md - one installer .exe, offline, no manual IIS work. Day 2: docs/OPERATE-WINDOWS.md
Linux / Docker, air-gapped docs/DEPLOY-AIRGAP.md
Linux / Docker, connected docs/DEPLOY.md

For every environment variable and Setting key see docs/CONFIG.md.

What the Windows installer requires

The installer checks all of this before it changes anything and refuses rather than half-installing.

Minimum Notes
Windows Server 2019 (build 10.0.17763) Ships IIS 10.0, which is therefore the earliest IIS supported. Server 2016 is also IIS 10.0 but falls below the build floor and is refused.
Windows client (test boxes) 10 22H2 (build 10.0.19045) or 11 Pro or higher - Home has no IIS at all.
Architecture 64-bit The payload is cp314 win_amd64.
IIS Web Server role installed beforehand Install-WindowsFeature -Name Web-Server -IncludeManagementTools. Needs no internet.
Free disk 5 GB Refused below this. 40 GB is comfortable once uploads and config backups accumulate.
Database bundled MySQL 8.4 LTS, or your own Bring credentials if you use an existing server.

The binding constraint is the operating system rather than IIS: the payload is Python 3.14 plus HttpPlatformHandler and URL Rewrite, and the handler itself runs on older IIS. An older host is untested rather than known-broken.

If you must deploy onto something older, docs/INSTALL-WINDOWS-IIS.md is the manual procedure and has no OS gate - it even documents MySQL 5.6, which is that era of machine. The trade is stated there: it produces a server the installer will not subsequently upgrade.

Configuration

Environment variables (.env):

Variable Description
DATABASE_URL MySQL connection string
SECRET_KEY Flask secret key
JWT_SECRET_KEY JWT signing key
JWT_ACCESS_TOKEN_EXPIRES Access token TTL (seconds)
LOG_LEVEL Logging verbosity

API Documentation

The REST API follows standard conventions:

Method Endpoint Description
GET /api/machines List machines (filterable by type)
GET /api/machines/:id Get machine details
POST /api/machines Create machine
PUT /api/machines/:id Update machine
DELETE /api/machines/:id Soft delete machine

Each asset plugin exposes the same CRUD pattern on its own prefix (/api/computers, /api/printers, /api/network, /api/measuringtools), and cross-cutting asset endpoints live under /api/assets.

Query parameters for list endpoints:

  • page - Page number (default: 1)
  • perpage - Items per page
  • sort - Sort field
  • dir - Sort direction (asc/desc)
  • search - Search term
  • assettype - Filter by asset type (computer, printer, machine, networkdevice, measuringtool)

Where to start

docs/START-HERE.md routes by what you are here to do - standing up a site, deploying the shop-floor tools, writing a plugin, integrating with the API, or diagnosing something. On the published mirror the same page is the wiki's front door.

Plugin System

ShopDB supports plugins for extending functionality. See CONTRIBUTING.md for plugin development guidelines.

The image bundles these plugins; only the ones a site installs are loaded. The current count and each plugin's version and migration head are in docs/PROJECT-MAP.md, which is generated:

  • backups - Machine configuration backups (NTLARS/DNC, CMM, part marker, UDC) with revision history and diffs
  • computers - Shopfloor PCs and workstations, collector fleet ingest
  • employees - Employee directory
  • geenforce - GE-Enforce imaging/software manifests and fleet compliance
  • machines - CNC, CMM, and other shop-floor machines
  • measuringtools - Gage-lab instruments with calibration tracking
  • knowledgebase - Documentation and troubleshooting guides
  • network - Network devices and subnets
  • notifications - Shopfloor notifications and recognition feed
  • printedparts - 3D-printed part catalogue, kiosk issue tracking and stock alerts
  • printers - Extended printer management with Zabbix integration
  • slides - TV/kiosk slideshows
  • tools - Tech Tools: barcode and QR generation laid out for real label stock
  • usb - CMMC USB check-in/out tracking
  • warranty - Warranty records with Dell lookups

Legacy Migration

This project replicates functionality from the classic ASP/VBScript ShopDB site. Key mappings:

Legacy Modern
ASP/VBScript Flask/Python
Classic ADO SQLAlchemy
Server-side HTML Vue 3 SPA
Session auth JWT tokens

License

Internal use only.

Description
Shop Database Flask Application with Vue 3 Frontend
Readme 98 MiB
Languages
Python 52.3%
Vue 19.7%
JavaScript 17.5%
PowerShell 5.5%
C# 2.8%
Other 2%