# Parts Inventory A catalog of what's actually on the shelf — components, filament, fasteners, tooling — so "do I have one of those?" is a five-second search instead of an hour of opening drawers. Runs as a FastAPI + SQLite container on `mainserver` (`192.168.50.8`) behind Caddy at **https://parts.tjm77.com**, alongside the other `~/srv` services. ## The data model One `parts` table holds what every kind of stock has in common — name, quantity, unit, location, cost, min-stock threshold. Everything type-specific lives in `part_specs` as key/value rows, so a 0603 resistor, a spool of PLA and a box of M3 screws share a table without a schema fork. Categories and locations are both **nestable trees**. `Workshop / Bin A3` is a real path, and filtering by `Workshop` returns everything in every bin beneath it. Sidebar counts roll up the same way, so they always match what clicking the filter actually returns. Each category carries a **spec template** — the properties worth recording for that kind of thing. Pick "Filament" on the add form and it pre-fills Material, Colour, Diameter, Brand, Print Temp, and switches the unit to grams. That's what keeps manual entry from being a blank page. Templates are suggestions only: delete any row, add your own, ignore them entirely. Every quantity change is written to `stock_log`, so a part's history answers "where did those 40 headers go" instead of just showing a smaller number than you remembered. Search is SQLite FTS5 over name, description, manufacturer, MPN, spec values, tags, category and location, with prefix matching so results narrow as you type. Typing `1.75mm`, `prusament`, `0603` or `Bin A3` all find the right things. ## Running it locally ```sh python3 -m venv .venv && .venv/bin/pip install -r requirements.txt PARTS_AUTH=off PARTS_DB=$PWD/data/parts.db .venv/bin/uvicorn app.main:app --port 8123 ``` Then open http://127.0.0.1:8123. ## Tests ```sh .venv/bin/python -m tests.test_api ``` Exercises the API end to end against a throwaway database — the auth gate, nested categories and locations, search across every indexed field, filter rollups, stock adjustment and history, patch semantics, and cascade behaviour when a category or location is deleted. ## Configuration | Variable | Meaning | |---|---| | `PARTS_PASSWORD` | The single shared password. Required when auth is on. | | `PARTS_SECRET` | Signing key for the session cookie. `openssl rand -hex 32`. | | `PARTS_SESSION_DAYS` | Session lifetime, default 30. | | `PARTS_AUTH` | `off` disables the login gate (LAN-only use). | | `PARTS_DB` | SQLite path. `/data/parts.db` in the container. | ## Deploying Lives at `/home/jay/srv/parts/` on `.8` and follows the same conventions as the other services there — `build: .`, external `caddy_web` network, named volume for `/data`. ```sh ssh jay@192.168.50.8 cd ~/srv/parts && sudo docker compose up -d --build ``` The database is in the `parts_parts_data` docker volume, which survives rebuilds. To back it up: ```sh sudo docker run --rm -v parts_parts_data:/d -v "$PWD":/out alpine \ sh -c 'cp /d/parts.db /out/parts-backup.db' ``` ## API Everything under `/api` is JSON and cookie-authenticated. `GET /healthz` is open. ``` GET /api/parts?q=&category_id=&location_id=&tag=&low_stock=&sort=&limit=&offset= POST /api/parts GET /api/parts/{id} PATCH /api/parts/{id} DELETE /api/parts/{id} POST /api/parts/{id}/adjust {delta, reason} GET /api/parts/{id}/history GET /api/categories POST /api/categories PATCH|DELETE /api/categories/{id} GET /api/locations POST /api/locations PATCH|DELETE /api/locations/{id} GET /api/tags GET /api/stats ``` The "what could I build with what I'm holding?" idea is meant to arrive as another consumer of these endpoints — the arbiter on `.8` already has the lane routing and typed-action machinery for it — rather than as a fork of them.