- 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>
194 lines
7.3 KiB
Markdown
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
|