Commit Graph

4 Commits

Author SHA1 Message Date
bvandeusen c265b871c3 feat(admin): metadata-profile picker on integrations page
Persists the operator's metadata-profile choice alongside quality
profile + root folder. Defaults to the first profile Lidarr returns
(usually 'Standard' on a vanilla install) so the common case is
zero-click; operators with custom profiles like 'Singles only' can pick
explicitly.

Backend:
- Migration 0013: adds nullable default_metadata_profile_id to
  lidarr_config. Existing rows get NULL and the service falls back to
  fetch-and-pick-first until they save.
- Updated lidarr_config queries + sqlc + lidarrconfig.Config + admin
  view/put body to round-trip the new field.
- handlePutLidarrConfig requires it (along with QP and root folder)
  when enabled=true — matches the existing missing_defaults gate.
- New GET /api/admin/lidarr/metadata-profiles handler + lidarr.Client
  ListMetadataProfiles (GET /api/v1/metadataprofile, same shape as
  the quality-profile endpoint).
- lidarrrequests.Approve prefers cfg.DefaultMetadataProfileID; falls
  back to the fetch-list path only when 0 (back-compat for upgraders).

Frontend:
- LidarrConfig type + LidarrMetadataProfile type + qk.lidarrMetadataProfiles.
- listMetadataProfiles + createMetadataProfilesQuery client helpers.
- Integrations page: third <select> picker, auto-defaults to first
  profile when the saved value is 0, sends the new field on save and
  clears it on disconnect.
- Updated test fixtures + the duplicate 'Standard' option string in
  the dropdown-populates assertion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 00:09:13 -04:00
bvandeusen ab8235dd0b fix(admin): approve flow surfaces real errors; auto-default Lidarr picks
The "I can't approve a request, the toast just says unknown" bug was
four problems compounded:

1. Lidarr config let enabled=true save with default_quality_profile_id=0
   and default_root_folder_path=''. Approve then sent invalid POST bodies
   to Lidarr, which 5xx'd.
2. lidarr.client.post() discarded Lidarr's response body on error, so
   we couldn't tell why Lidarr 5xx'd from server logs.
3. handleApproveRequest's error switch didn't map ErrServerError or
   ErrLookupFailed — both fell through to a generic 500 server_error.
4. apiFetch only parsed {error: {code, message}} envelopes, but admin
   endpoints write {error: 'code_string'}. Every admin error toast
   rendered as 'unknown'.

Fixes:

- internal/lidarr/client.go: capture up to 512 bytes of Lidarr's response
  body when it returns 4xx/5xx; include in the wrapped error so server
  logs show what Lidarr actually said instead of just the status bucket.
- internal/lidarrrequests/service.go: new ErrDefaultsIncomplete fires
  before the Lidarr call when QP=0 or root_folder=''. Stops the bad
  POST entirely.
- internal/api/admin_requests.go: handleApproveRequest now maps
  ErrDefaultsIncomplete -> 'lidarr_defaults_incomplete' (400),
  ErrServerError -> 'lidarr_server_error' (502),
  ErrLookupFailed -> 'lidarr_rejected' (502).
- internal/api/admin_lidarr.go: handlePutLidarrConfig now requires
  QP + root folder to be set whenever enabled=true.
- web/src/lib/api/client.ts: apiFetch handles both error envelope shapes
  so admin error codes propagate to toasts.
- web/src/routes/admin/integrations/+page.svelte: auto-default to the
  first quality profile and first root folder Lidarr returns when the
  operator hasn't picked one yet — saves a click for typical
  one-profile/one-folder home setups.
- web/src/routes/admin/requests/+page.svelte: friendly toast copy for
  the new error codes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:37:17 -04:00
bvandeusen 4b088e6b6f feat(server): slog request log + log dropped Lidarr ping errors
Two debuggability gaps surfaced by the /api/admin route shadowing
investigation:

1. handleTestLidarrConnection swallowed client.Ping's error — only the
   bucket code (lidarr_unreachable) reached the response, the underlying
   *net.DNSError / *net.OpError / TLS error never reached the logs.
   Operators couldn't tell a typo'd hostname from a wrong port from a
   refused TLS handshake. Now logged at Warn (expected-when-misconfigured)
   with the base_url for diagnostic context. The api_key is never logged.

2. The chi router had no access-log middleware — every 4xx/5xx was silent,
   making it impossible to tell whether a request even reached the server.
   chi's middleware.Logger writes to the standard log package not slog,
   so a small slog wrapper handles it instead. /healthz is skipped (the
   compose healthcheck hits it every 5s; would be ~17k log lines/day of
   noise). Severity is keyed off response status: 5xx -> Error,
   4xx -> Warn, else Info — so 4xx/5xx surface even when the operator's
   logger level is set above Info.

Tests cover both the severity routing and the /healthz skip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:10:04 -04:00
bvandeusen 6fcae8dee4 feat(api): add /api/admin/lidarr/* config + profiles + folders + test
Five admin-only handlers under RequireAdmin middleware: GET/PUT config
(api_key masked, empty key on PUT preserves saved), POST test (always
200, maps Lidarr errors to stable codes), GET quality-profiles, GET
root-folders. 10 HTTP integration tests, all green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 17:44:54 -04:00