mirror of
https://github.com/immich-app/yucca.git
synced 2026-09-30 13:33:00 +08:00
92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
package adminapi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
// SettingsEntry mirrors the admin-api SettingsEntryDto (src/dto/settings.dto.ts).
|
|
// Value holds the scoped config overrides (restic_pack_size_mib,
|
|
// connections_math, ...) — the admin-api validates keys and values.
|
|
type SettingsEntry struct {
|
|
Scope string `json:"scope"`
|
|
Value map[string]any `json:"value"`
|
|
UpdatedAt string `json:"updatedAt"`
|
|
}
|
|
|
|
// ListSettings returns every settings scope (global / site:<code> /
|
|
// cluster:<code>) with its overrides.
|
|
func (c *Client) ListSettings(ctx context.Context) ([]SettingsEntry, error) {
|
|
var out struct {
|
|
Settings []SettingsEntry `json:"settings"`
|
|
}
|
|
if err := c.doJSON(ctx, http.MethodGet, "/api/settings", nil, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return out.Settings, nil
|
|
}
|
|
|
|
// SetSettings replaces the overrides stored for scope. The admin-api rejects
|
|
// unknown keys and invalid values with a 400 whose message is surfaced here.
|
|
func (c *Client) SetSettings(ctx context.Context, scope string, value map[string]any) (*SettingsEntry, error) {
|
|
var out struct {
|
|
Entry SettingsEntry `json:"entry"`
|
|
}
|
|
if err := c.doJSON(ctx, http.MethodPut, "/api/settings/"+scope, value, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return &out.Entry, nil
|
|
}
|
|
|
|
// DeleteSettings removes every override stored for scope.
|
|
func (c *Client) DeleteSettings(ctx context.Context, scope string) error {
|
|
return c.deleteReq(ctx, "/api/settings/"+scope)
|
|
}
|
|
|
|
// doJSON is the generic JSON round-trip: like postJSON, but with an explicit
|
|
// method and with the response body included in non-2xx errors so admin-api
|
|
// validation messages reach the operator.
|
|
func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) error {
|
|
u := c.baseURL + path
|
|
var reader io.Reader
|
|
if body != nil {
|
|
b, err := json.Marshal(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reader = bytes.NewReader(b)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, method, u, reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
if body != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
c.setAuth(req)
|
|
|
|
resp, err := c.http.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("%s %s: %w", method, u, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return fmt.Errorf("admin-api rejected the session token (status %d) — run `yuctl login --reauth`", resp.StatusCode)
|
|
}
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
snippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
|
return fmt.Errorf("%s %s: status %d: %s", method, u, resp.StatusCode, bytes.TrimSpace(snippet))
|
|
}
|
|
if out != nil {
|
|
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
|
return fmt.Errorf("parse %s response: %w", u, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|