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>
This commit is contained in:
2026-05-02 00:09:13 -04:00
parent 7d33166f23
commit c265b871c3
15 changed files with 236 additions and 85 deletions
+16 -10
View File
@@ -15,11 +15,12 @@ import (
// Config is the typed projection of lidarr_config (no NULL fields exposed
// to callers — empty strings / zero ints carry the "unset" meaning).
type Config struct {
Enabled bool
BaseURL string
APIKey string
DefaultQualityProfileID int
DefaultRootFolderPath string
Enabled bool
BaseURL string
APIKey string
DefaultQualityProfileID int
DefaultMetadataProfileID int
DefaultRootFolderPath string
}
// Service reads and writes the singleton.
@@ -44,6 +45,9 @@ func (s *Service) Get(ctx context.Context) (Config, error) {
if row.DefaultQualityProfileID != nil {
cfg.DefaultQualityProfileID = int(*row.DefaultQualityProfileID)
}
if row.DefaultMetadataProfileID != nil {
cfg.DefaultMetadataProfileID = int(*row.DefaultMetadataProfileID)
}
if row.DefaultRootFolderPath != nil {
cfg.DefaultRootFolderPath = *row.DefaultRootFolderPath
}
@@ -57,14 +61,16 @@ func (s *Service) Save(ctx context.Context, cfg Config) error {
baseURL = strPtr(cfg.BaseURL)
apiKey = strPtr(cfg.APIKey)
qpID = int32Ptr(cfg.DefaultQualityProfileID)
mpID = int32Ptr(cfg.DefaultMetadataProfileID)
rootPath = strPtr(cfg.DefaultRootFolderPath)
)
_, err := dbq.New(s.pool).UpdateLidarrConfig(ctx, dbq.UpdateLidarrConfigParams{
Enabled: cfg.Enabled,
BaseUrl: baseURL,
ApiKey: apiKey,
DefaultQualityProfileID: qpID,
DefaultRootFolderPath: rootPath,
Enabled: cfg.Enabled,
BaseUrl: baseURL,
ApiKey: apiKey,
DefaultQualityProfileID: qpID,
DefaultMetadataProfileID: mpID,
DefaultRootFolderPath: rootPath,
})
if err != nil {
return fmt.Errorf("lidarrconfig: %w", err)