Commit graph

13 commits

Author SHA1 Message Date
9db7707a64 feat(07-02): SearchHandler — NL query to NetBox filter with Tier 1 LLM
- internal/api/handlers/search.go: SearchHandler, NewSearchHandler, SearchDevices
- Sanitizes query (non-printable stripped, 200 char max) per T-07-05
- LLM extracts catalog_status/name_contains/tag; falls back to substring on parse failure
- internal/api/handlers/search_test.go: 4 tests covering 400, fallback, status filter, combined
- internal/api/router.go: wires GET /api/search with nil guard (503)
- cmd/hwlab/main.go: constructs searchHandler and passes to NewRouter
2026-04-10 07:55:07 +00:00
0072aa41bd feat(07-01): ResearchAgent worker, trigger endpoint, main.go wiring
- internal/research/agent.go: Agent with RunOnce+Start, sanitizeQuery, interface adapters
- internal/research/agent_test.go: stub-based unit tests (sanitize, enrich, skip, empty)
- internal/ai/client.go: TierClient.TextComplete for text-only LLM calls
- internal/api/handlers/research.go: POST /api/research/trigger handler (202 Accepted)
- internal/api/router.go: researchHandler param + /api/research/trigger route
- cmd/hwlab/main.go: research agent goroutine started with 10min interval
2026-04-10 07:51:13 +00:00
0190e8583c feat(06-lab-advisor-02): AdvisorHandler SSE streaming + router wiring
- internal/advisor/handler.go: StreamChat (SSE, token-by-token),
  GetConversations, GetConversation; body limited to 64KB, message
  truncated to 8000 chars (T-06-02-03); API key never echoed (T-06-02-02)
- internal/api/router.go: /api/advisor/{chat,conversations,conversations/{id}}
  with nil-guard returning 503 when DB not configured
- internal/config/config.go: Tier3 defaults + HWLAB_AI_TIER3_* env bindings
- cmd/hwlab/main.go: store init from HWLAB_DATABASE_URL, RunMigrations,
  InventoryContextBuilder, AdvisorHandler wired into NewRouter
2026-04-10 07:36:16 +00:00
1764c7b06a feat(05-02): add TestHandler with 3 cable-test endpoints + router wiring
- TestHandler: POST /api/test/cable, GET /api/test/events, GET /api/test/recent
- POST /api/test/cable: DisallowUnknownFields (T-05-03), creates NetBox cable,
  auto-prints with 1s rate limit (T-05-05), prepends to 20-item ring buffer
- GET /api/test/events: SSE, 30s keepalive, exits on context cancel (T-05-04)
- GET /api/test/recent: thread-safe ring buffer, returns [] when empty
- AttachStream() wires StreamingTesterDriver channel to SSE broadcaster
- Router: three /api/test/* routes added to NewRouter signature
- main.go: constructs TestHandler, wires USB Manager event loop stub
- All handler tests pass race-clean (7 test cases)
2026-04-10 07:15:30 +00:00
f9b1b3ff29 feat(04-04): integrate auto-print into intake handler
- Add IntakePrinter interface to intake.go (optional, nil-safe)
- Add printer field to IntakeHandler, update NewIntakeHandler signature
- Add PrintSkipped bool to IntakeResponse (json: print_skipped)
- Auto-print label after NetBox record creation using labels.RenderStandard + printer.ImageToRawBitmap
- Printer errors are non-fatal: logged and surfaced via print_skipped=true
- Update main.go to pass mockDriver as IntakePrinter
- Add 4 new tests covering success, ErrNoDevice, nil printer, and non-fatal error
- All 10 intake tests pass (6 existing + 4 new)
2026-04-10 06:57:27 +00:00
9f57cbdf6c feat(04-03): LabelHandler, USBEventsHandler, router wiring, main.go USB+printer init
- LabelHandler: POST /api/labels/:deviceID/print with 1/s rate limit (T-04-09)
- USBEventsHandler: GET /api/usb/events SSE stream, exits on context cancel (T-04-11)
- router.go: two new parameters + routes wired
- main.go: USB Manager started with ctx, MockDriver connected, handlers passed to router
2026-04-10 06:52:52 +00:00
dd381eefa3 test(04-03): add failing handler tests for label print and USB SSE (TDD RED) 2026-04-10 06:50:51 +00:00
743611f488 feat(03-02): wire GET /api/inventory and GET /api/inventory/{id} routes
- NewRouter signature extended to accept *handlers.InventoryHandler
- GET /inventory and GET /inventory/{id} registered in /api route group
- main.go constructs handlers.NewInventoryHandler(nbClient) and passes to NewRouter
2026-04-10 06:15:12 +00:00
b0b6153b24 feat(03-02): InventoryHandler with list+detail endpoints and 7 unit tests
- InventoryNetBoxClient interface (ListDevices + GetDevice) for testability
- ListInventory returns 200 JSON array (limit=200, 502 on NetBox error)
- GetInventoryItem returns 200/404/400/502 based on ID validity and NetBox response
- deviceToResponse maps netbox.Device to InventoryItemResponse (nil PhotoURLs → [])
- 7 TDD tests: empty list, 2-item list, NetBox error, found, not found, invalid ID, server error
2026-04-10 06:14:43 +00:00
59aa89b199 feat(02-03): wire POST /api/intake route, real WAQ handler, and NetBox defaults in config
- router.go: NewRouter accepts intakeHandler http.Handler, registers POST /api/intake
- config.go: adds NetBoxDefaultDeviceTypeID/RoleID/SiteID fields with defaults and env bindings
- main.go: creates netbox.Client, ai.Orchestrator, inventory.CatalogUpdater, handlers.IntakeHandler
- main.go: replaces NoOpHandler with NewNetBoxOpHandler(nbClient) for WAQ worker
- main.go: uses typed interface variable for WAQ to avoid nil-interface-wrapping bug
2026-04-10 05:55:41 +00:00
4fc9362519 feat(02-03): POST /api/intake handler with orchestrator and NetBox wiring
- IntakeHandler with IntakeOrchestrator/IntakeNetBoxClient/IntakeCatalogUpdater/IntakeWAQ interfaces
- Validates 1-3 photos, base64-encodes, calls Analyze, allocates HW-ID
- Quick-add mode: confidence >= threshold skips review, creates NetBox record immediately
- WAQ enqueue on NetBox failure returns 202 with queued=true
- nil WAQ + NetBox down returns 503
- Six unit tests: reject-0, reject-4, high-confidence, low-confidence, quick-add, netbox-down
- [Rule 1 - Bug] PatchCustomFields signature changed int -> int64 to match NetBoxOpsClient interface
- [Rule 1 - Bug] UpdateCatalogStatus signature changed int -> int64 for consistency with CreateDevice return type
2026-04-10 05:54:33 +00:00
6595e345a2 feat(01-foundation-01): viper config loader and SPA fallback fix
- Add internal/config/config.go with viper-backed Config struct
- Explicit BindEnv calls for reliable env var -> config mapping (mapstructure v2 compat)
- Config loads from config.json + .env, env vars take precedence
- Add config.json with non-secret defaults (port, timeouts, URLs)
- Fix SPA fallback: spaHandler serves index.html for unknown paths (client-side routing)
- All 5 tests pass: TestHealth, TestLoadDefaults, TestLoadEnvOverride, TestLoadNetBoxURL
- Add Makefile with build/dev/test/clean targets
2026-04-10 01:27:54 +00:00
77e5a78d5a feat(01-foundation-01): Go module init, chi server, go:embed SPA scaffold
- Initialize module git.georgsen.dk/hwlab with Go 1.23
- Install chi v5.2.5, go-redis v9.18.0, viper v1.21.0, godotenv v1.5.1, uuid v1.6.0, go-netbox v4.3.0
- Create health handler GET /api/health returning {status:ok, version:0.1.0}
- Create chi router with Logger/Recoverer/RealIP middleware and SPA fallback
- Embed web/dist via assets.go at module root (go:embed cannot use .. paths)
- Create stub web/dist/index.html with ClickHouse dark theme
- TestHealth passes
2026-04-10 01:17:03 +00:00