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

7.3 KiB

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)

{
  _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-pvcnfs-general, ReadWriteOnce, 5Gi
  3. StatefulSet parts-dbmongo:7, mounts PVC at /data/db
  4. Service (headless) parts-db — port 27017, clusterIP: None
  5. Deployment parts-apiparts-api:latest, imagePullPolicy: Never, port 3001
  6. Service parts-api — ClusterIP, 3001
  7. Deployment parts-uiparts-ui:latest, imagePullPolicy: Never, port 8080
  8. Service parts-ui — ClusterIP, 80→8080
  9. Ingress parts-ui-ingressparts.vandachevici.ro, letsencrypt-prod TLS

Secret (create manually before applying)

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-searchfuse.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-scriptbuild-and-load.sh
  9. k8s-manifestsdeployment/infrastructure/parts-inventory.yaml
  10. readme — usage docs, dev setup, deploy steps