Tool inventory, drawer/toolbox organization, and 3D-printable magnetic
label generation for home and pro workshops. Ground-zero rebuild of
Workshop-Designer, new schema/API/UI, with the parametric label/magnet-pocket
geometry engine ported over as working, validated code.
Milestone 1 scope: auth, organizations/shops/toolboxes/drawers/tools CRUD, the
5 profile/preset types (printer, magnet, material, drawer, label style), plan
limit enforcement, and the geometry package ported and importable (not yet
wired to a route). See ARCHITECTURE.md for the full system design and
/home/terry/.claude/plans/sleepy-wandering-nest.md for the original build
plan this repo implements.
- Frontend: React + TypeScript + Vite, Tailwind + shadcn/ui, dark-mode-default
- Backend: Python 3.12, FastAPI, SQLAlchemy 2 (async), Alembic, asyncpg, Pydantic v2
- Jobs: Redis + Dramatiq (geometry generation runs off the request thread — see ARCHITECTURE.md)
- Geometry:
workshop_geometry— trimesh + shapely + matplotlib + manifold3d, ported verbatim fromWorkshop-Designer/geometry - DB: PostgreSQL 16
- Deploy: Docker Compose, Caddy + Cloudflared reverse proxy (applied separately, not part of this repo)
- Docker + Docker Compose
- (Optional, for running backend/geometry tests outside containers) Python 3.12
cp .env.example .env
# edit .env: set real POSTGRES_PASSWORD and JWT_SECRET before anything but local testing
docker compose up --buildThis brings up 6 containers: workshopos-postgres, workshopos-redis,
workshopos-migrate (runs alembic upgrade head then exits 0),
workshopos-api, workshopos-worker, workshopos-frontend.
The app is served at http://localhost:${APP_PORT:-3027}. The frontend's
nginx proxies /api/ and /health to the API container; the API itself
listens on :8000 internally.
docker compose downPostgres/Redis data persist in named volumes (workshopos-postgres-data,
workshopos-redis-data) across restarts; docker compose down -v wipes them.
Standard install.sh / start.sh / stop.sh / restart.sh / update.sh
scripts are generated by this homelab's scriptupdate.sh and follow the same
conventions as sibling apps (e.g. cardforge-v2).
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt -e ../geometry
# Point at a real Postgres instance (docker run a throwaway one, or use the
# compose postgres service published locally).
export DATABASE_URL=postgresql+asyncpg://workshopos:workshopos@localhost:5432/workshopos
export JWT_SECRET=dev-secret
alembic upgrade head
uvicorn app.main:app --reloadcd backend
pytest # needs a live Postgres reachable at $DATABASE_URL (see conftest.py)
pytest ../geometry # pure-Python, no DB requiredPOST /auth/register creates an Organization (with a unique slug and the
free Plan), a User, and an owner Membership in one transaction, and returns a
JWT. Every other route that touches organization-scoped data is nested under
/organizations/{organization_id}/... and depends on
get_current_membership, which requires a real Membership row tying the
authenticated user to that organization_id — the API never trusts a
client-supplied org id on its own. See ARCHITECTURE.md for why this matters
(it fixes a real vulnerability in the prior codebase).
Two plans are seeded by the initial migration:
| plan | max_shops | max_toolboxes | max_drawers | max_users |
|---|---|---|---|---|
| free | 1 | 1 | 5 | 1 |
| pro | unlimited | unlimited | unlimited | unlimited |
Creating past a limit returns 402 Payment Required. Billing/license-key
infrastructure beyond the Plan table itself is out of scope for this
milestone.