0.2.78
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// IntegrationPolicyKey is the operator's global on/off switch for external services that
|
||||
// have no configuration document of their own.
|
||||
//
|
||||
// It is deliberately *not* a second copy of every integration's switch. Sonarr and Radarr
|
||||
// already store theirs in the arr integration policy and MDBList stores its own in the
|
||||
// ratings settings; moving those here would mean a migration and, worse, a window in which
|
||||
// two documents disagreed about whether a service was on. The rule is one stored truth per
|
||||
// integration, kept where that integration's other configuration already lives — and this
|
||||
// document is that home for the ones that have nowhere else, which today is Tracearr.
|
||||
//
|
||||
// The API's integrationEnabled/setIntegrationEnabled pair is the single reader and writer,
|
||||
// so the console never has to know which document answers for which service.
|
||||
const IntegrationPolicyKey = "integration_policy"
|
||||
|
||||
// IntegrationPolicy is a map of integration id to whether it is switched on.
|
||||
//
|
||||
// Absence means on. A household that upgrades into this feature has every service it had
|
||||
// configured still working, which is the only safe reading: the alternative silently turns
|
||||
// off recommendation imports on the day the console gained a switch for them.
|
||||
type IntegrationPolicy struct {
|
||||
Disabled map[string]bool `json:"disabled"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// Enabled reports whether one integration is switched on. A service nobody has ever
|
||||
// touched has no entry, and no entry is on.
|
||||
func (p IntegrationPolicy) Enabled(id string) bool {
|
||||
return !p.Disabled[strings.TrimSpace(id)]
|
||||
}
|
||||
|
||||
func (s *Store) IntegrationPolicy(ctx context.Context) (IntegrationPolicy, error) {
|
||||
empty := IntegrationPolicy{Disabled: map[string]bool{}}
|
||||
var raw []byte
|
||||
err := s.pool.QueryRow(ctx,
|
||||
`SELECT value FROM app_settings WHERE key = $1`, IntegrationPolicyKey).Scan(&raw)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return empty, nil
|
||||
}
|
||||
if err != nil {
|
||||
return empty, fmt.Errorf("store: read integration policy: %w", err)
|
||||
}
|
||||
var policy IntegrationPolicy
|
||||
if err := json.Unmarshal(raw, &policy); err != nil {
|
||||
return empty, fmt.Errorf("store: decode integration policy: %w", err)
|
||||
}
|
||||
if policy.Disabled == nil {
|
||||
policy.Disabled = map[string]bool{}
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// SetIntegrationEnabled records one service's switch, leaving every other entry alone.
|
||||
//
|
||||
// Read-modify-write rather than a whole-document put, because the console sends one
|
||||
// switch at a time and a put would let a page rendered before another integration existed
|
||||
// silently re-enable it.
|
||||
func (s *Store) SetIntegrationEnabled(ctx context.Context, id string, enabled bool) error {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
return fmt.Errorf("store: integration policy needs an id")
|
||||
}
|
||||
policy, err := s.IntegrationPolicy(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if enabled {
|
||||
delete(policy.Disabled, id)
|
||||
} else {
|
||||
policy.Disabled[id] = true
|
||||
}
|
||||
policy.UpdatedAt = time.Now().UTC()
|
||||
raw, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.pool.Exec(ctx, `
|
||||
INSERT INTO app_settings (key, value, updated_at)
|
||||
VALUES ($1, $2::jsonb, now())
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`,
|
||||
IntegrationPolicyKey, string(raw))
|
||||
if err != nil {
|
||||
return fmt.Errorf("store: write integration policy: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user