homelab/services/parts-inventory/PLAN.md
Dan V deb6c38d7b chore: commit homelab setup — deployment, services, orchestration, skill
- Add .gitignore: exclude compiled binaries, build artifacts, and Helm
  values files containing real secrets (authentik, prometheus)
- Add all Kubernetes deployment manifests (deployment/)
- Add services source code: ha-sync, device-inventory, games-console,
  paperclip, parts-inventory
- Add Ansible orchestration: playbooks, roles, inventory, cloud-init
- Add hardware specs, execution plans, scripts, HOMELAB.md
- Add skills/homelab/SKILL.md + skills/install.sh to preserve Copilot skill
- Remove previously-tracked inventory-cli binary from git index

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-09 08:10:32 +02:00

194 lines
7.3 KiB
Markdown

# 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 <id>
│ │ ├── add.go # parts add (interactive prompts)
│ │ ├── update.go # parts update <id> (interactive)
│ │ └── delete.go # parts delete <id>
│ ├── 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<string, string | number | boolean> // 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 <id> # Show full detail
parts add # Interactive prompts to create a part
parts update <id> # Interactive update
parts delete <id> # 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