# Parts Inventory — Implementation Plan A full-stack web app + CLI to organize hardware (tools, screws, relays, electronics, etc.) with a flexible schema, fuzzy search, MongoDB backend, and Kubernetes deployment. --- ## Architecture ``` Browser ──► nginx (parts-ui) ──► /api/* ──► parts-api ──► parts-db (MongoDB) ▲ parts-cli (Go) ``` | Component | Language | Description | |-----------|----------|-------------| | `parts-api` | TypeScript / Node.js + Express | REST API, fuzzy search | | `parts-ui` | TypeScript / React + Vite | Web UI, served by nginx | | `parts-cli` | Go + cobra | CLI tool connecting to the API over HTTP | | `parts-db` | MongoDB 7 (StatefulSet) | Database | --- ## Project Structure ``` services/parts-inventory/ ├── api/ │ ├── src/ │ │ ├── models/ │ │ │ └── part.ts # Mongoose schema + model │ │ ├── routes/ │ │ │ ├── parts.ts # CRUD + search router │ │ │ └── health.ts # GET /health │ │ └── index.ts # Express app entry │ ├── Dockerfile │ ├── package.json │ └── tsconfig.json │ ├── ui/ │ ├── src/ │ │ ├── api/ │ │ │ └── client.ts # Typed fetch wrapper │ │ ├── components/ │ │ │ ├── SearchBar.tsx # Debounced input │ │ │ ├── FilterPanel.tsx # Type + category dropdowns │ │ │ ├── PartCard.tsx # Card in grid view │ │ │ └── PartForm.tsx # Create/edit form + dynamic properties │ │ ├── pages/ │ │ │ ├── Home.tsx # Search + card grid │ │ │ └── PartDetail.tsx # Full detail + inline edit/delete │ │ └── App.tsx │ ├── nginx.conf # Serves static + proxies /api to parts-api │ ├── Dockerfile │ ├── package.json │ └── vite.config.ts │ ├── cli/ │ ├── cmd/ │ │ ├── root.go # Root command, config loading │ │ ├── list.go # parts list [--query] [--type] [--category] │ │ ├── get.go # parts get │ │ ├── add.go # parts add (interactive prompts) │ │ ├── update.go # parts update (interactive) │ │ └── delete.go # parts delete │ ├── internal/ │ │ └── client/ │ │ └── client.go # HTTP client for parts-api │ ├── Dockerfile │ ├── go.mod │ └── go.sum │ ├── build-and-load.sh └── PLAN.md # This file ``` --- ## MongoDB Schema (`Part`) ```typescript { _id: ObjectId title: string // required; text-indexed type: string // e.g. "tool" | "screw" | "relay" | "sensor" | "cable" category: string // e.g. "power tools" | "fasteners" | "automation" manufacturer?: string // text-indexed dimensions?: { width?: number length?: number height?: number unit?: string // "mm" | "cm" | "in" } quantity: number // default 1 location?: string // e.g. "shelf A3, bin 2" notes?: string // text-indexed tags: string[] // text-indexed; free-form labels properties: Record // type-specific extras createdAt: Date updatedAt: Date } ``` **Text index:** `title`, `manufacturer`, `category`, `notes`, `tags` Examples of `properties` in use: - Screw: `{ thread: "M3", pitch_mm: 0.5, head: "hex", material: "stainless" }` - Relay: `{ coil_voltage: 12, contact_rating_A: 10, form: "SPDT" }` - Resistor: `{ value_ohm: 10000, tolerance: "1%", package: "0603" }` --- ## REST API Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/health` | Health check | | `GET` | `/api/parts` | List / search (`?q=`, `?type=`, `?category=`, `?limit=`, `?skip=`) | | `POST` | `/api/parts` | Create a part | | `GET` | `/api/parts/meta/types` | Distinct `type` values | | `GET` | `/api/parts/meta/categories` | Distinct `category` values | | `GET` | `/api/parts/:id` | Get part by ID | | `PUT` | `/api/parts/:id` | Update part | | `DELETE` | `/api/parts/:id` | Delete part | ### Fuzzy Search Strategy 1. If `?q=` present → MongoDB `$text` search to get candidates 2. Pass candidates through `fuse.js` (keys: `title`, `manufacturer`, `tags`, `notes`) for fuzzy re-ranking 3. If very short query or no text match → `fuse.js` over a capped recent-fetch fallback 4. Filters (`type`, `category`) applied before fuzzy pass --- ## CLI Commands Config: `PARTS_API_URL` env var (default: `http://parts-api.infrastructure.svc.cluster.local:3001`) or `~/.parts-inventory.yaml` with `api_url:` key. ``` parts list # List all parts (table view) parts list --query "m3 screw" # Fuzzy search parts list --type screw --category fasteners parts get # Show full detail parts add # Interactive prompts to create a part parts update # Interactive update parts delete # Delete with confirmation ``` --- ## Kubernetes Manifests (`deployment/infrastructure/parts-inventory.yaml`) Resources in order: 1. **Comment block** — manual secret creation instructions 2. **PVC** `parts-db-pvc` — `nfs-general`, `ReadWriteOnce`, 5Gi 3. **StatefulSet** `parts-db` — `mongo:7`, mounts PVC at `/data/db` 4. **Service (headless)** `parts-db` — port 27017, `clusterIP: None` 5. **Deployment** `parts-api` — `parts-api:latest`, `imagePullPolicy: Never`, port 3001 6. **Service** `parts-api` — ClusterIP, 3001 7. **Deployment** `parts-ui` — `parts-ui:latest`, `imagePullPolicy: Never`, port 8080 8. **Service** `parts-ui` — ClusterIP, 80→8080 9. **Ingress** `parts-ui-ingress` — `parts.vandachevici.ro`, letsencrypt-prod TLS ### Secret (create manually before applying) ```bash kubectl create secret generic parts-inventory-secret \ --from-literal=MONGO_URI="mongodb://parts-db.infrastructure.svc.cluster.local:27017/parts" \ -n infrastructure ``` ### Resource Estimates | Component | CPU req / limit | Memory req / limit | |-----------|----------------|-------------------| | parts-db | 100m / 500m | 256Mi / 512Mi | | parts-api | 50m / 200m | 64Mi / 128Mi | | parts-ui (nginx) | 10m / 100m | 16Mi / 64Mi | --- ## Implementation Order 1. **project-scaffold** — dir layout, package.json, tsconfig, go.mod skeletons 2. **mongo-model** — Mongoose `Part` schema with text indexes 3. **api-crud** — Express routes, all CRUD endpoints 4. **api-fuzzy-search** — `fuse.js` integration on `?q=` queries 5. **go-cli** — cobra CLI connecting to API over HTTP 6. **react-ui** — full React UI (search, list, form, detail) 7. **dockerfiles** — multi-stage Dockerfiles for api, ui, cli 8. **build-script** — `build-and-load.sh` 9. **k8s-manifests** — `deployment/infrastructure/parts-inventory.yaml` 10. **readme** — usage docs, dev setup, deploy steps