feat: enhance EvoBGP with new command-line options for the evobgp-agent, including a watch command for periodic configuration updates. Update go.mod with additional dependencies and improve Docker Compose setup for new services, including NATS and various worker components.
This commit is contained in:
@@ -0,0 +1,560 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"evobgp/internal/store"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
func (p *Postgres) ListCDNSources(tenantID, moduleID string) ([]*store.CDNSource, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "CDN_CIDRS" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
rows, err := p.pool.Query(ctx, `
|
||||
SELECT id::text, source_kind, url, COALESCE(etag,''), refresh_interval_sec, community_id::text
|
||||
FROM module_cdn_source WHERE module_id=$1 ORDER BY url`, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []*store.CDNSource
|
||||
for rows.Next() {
|
||||
var s store.CDNSource
|
||||
s.ModuleID = moduleID
|
||||
var ri *int32
|
||||
var comm *string
|
||||
if err := rows.Scan(&s.ID, &s.SourceKind, &s.URL, &s.Etag, &ri, &comm); err != nil {
|
||||
continue
|
||||
}
|
||||
if ri != nil {
|
||||
v := int(*ri)
|
||||
s.RefreshIntervalSec = &v
|
||||
}
|
||||
s.CommunityID = strOrNil(comm)
|
||||
out = append(out, &s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) CreateCDNSource(tenantID, moduleID string, in *store.CDNSource) (*store.CDNSource, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "CDN_CIDRS" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
if in == nil || strings.TrimSpace(in.URL) == "" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
id := uuid.NewString()
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
INSERT INTO module_cdn_source (id, module_id, source_kind, url, etag, refresh_interval_sec, community_id)
|
||||
VALUES ($1,$2,$3,$4,$5,$6, NULLIF($7::uuid, '00000000-0000-0000-0000-000000000000'::uuid))`,
|
||||
id, moduleID, in.SourceKind, strings.TrimSpace(in.URL), in.Etag, nullInt32Ptr(in.RefreshIntervalSec), uuidOrNilPtr(in.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getCDNSource(ctx, moduleID, id)
|
||||
}
|
||||
|
||||
func (p *Postgres) getCDNSource(ctx context.Context, moduleID, id string) (*store.CDNSource, error) {
|
||||
var s store.CDNSource
|
||||
s.ModuleID = moduleID
|
||||
var ri *int32
|
||||
var comm *string
|
||||
err := p.pool.QueryRow(ctx, `
|
||||
SELECT id::text, source_kind, url, COALESCE(etag,''), refresh_interval_sec, community_id::text
|
||||
FROM module_cdn_source WHERE id=$1 AND module_id=$2`, id, moduleID).Scan(&s.ID, &s.SourceKind, &s.URL, &s.Etag, &ri, &comm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ri != nil {
|
||||
v := int(*ri)
|
||||
s.RefreshIntervalSec = &v
|
||||
}
|
||||
s.CommunityID = strOrNil(comm)
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func uuidOrNilPtr(s *string) any {
|
||||
if s == nil || strings.TrimSpace(*s) == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.TrimSpace(*s)
|
||||
}
|
||||
|
||||
func (p *Postgres) UpdateCDNSource(tenantID, moduleID, sourceID string, patch *store.CDNSourcePatch) (*store.CDNSource, error) {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cur, err := p.getCDNSource(context.Background(), moduleID, sourceID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if patch.SourceKind != nil {
|
||||
cur.SourceKind = *patch.SourceKind
|
||||
}
|
||||
if patch.URL != nil {
|
||||
cur.URL = strings.TrimSpace(*patch.URL)
|
||||
}
|
||||
if patch.Etag != nil {
|
||||
cur.Etag = *patch.Etag
|
||||
}
|
||||
if patch.RefreshIntervalSec != nil {
|
||||
cur.RefreshIntervalSec = patch.RefreshIntervalSec
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
cur.CommunityID = nil
|
||||
} else {
|
||||
cur.CommunityID = &v
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
UPDATE module_cdn_source SET source_kind=$3, url=$4, etag=$5, refresh_interval_sec=$6,
|
||||
community_id=NULLIF($7::uuid, '00000000-0000-0000-0000-000000000000'::uuid), updated_at=now()
|
||||
WHERE id=$1 AND module_id=$2`,
|
||||
sourceID, moduleID, cur.SourceKind, cur.URL, cur.Etag, nullInt32Ptr(cur.RefreshIntervalSec), uuidOrNilPtr(cur.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getCDNSource(ctx, moduleID, sourceID)
|
||||
}
|
||||
|
||||
func (p *Postgres) DeleteCDNSource(tenantID, moduleID, sourceID string) error {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
tag, err := p.pool.Exec(ctx, `DELETE FROM module_cdn_source WHERE id=$1 AND module_id=$2`, sourceID, moduleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Postgres) ListASEntries(tenantID, moduleID string) ([]*store.ASEntry, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "AS_PREFIXES" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
rows, err := p.pool.Query(ctx, `
|
||||
SELECT id::text, asn, prefix::text, community_id::text FROM module_as_entry WHERE module_id=$1`, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []*store.ASEntry
|
||||
for rows.Next() {
|
||||
var e store.ASEntry
|
||||
e.ModuleID = moduleID
|
||||
var asn *int64
|
||||
var pref, comm *string
|
||||
if err := rows.Scan(&e.ID, &asn, &pref, &comm); err != nil {
|
||||
continue
|
||||
}
|
||||
e.ASN = asn
|
||||
e.Prefix = pref
|
||||
e.CommunityID = strOrNil(comm)
|
||||
out = append(out, &e)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) CreateASEntry(tenantID, moduleID string, in *store.ASEntry) (*store.ASEntry, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "AS_PREFIXES" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
if in == nil || (in.ASN == nil && (in.Prefix == nil || strings.TrimSpace(*in.Prefix) == "")) {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
id := uuid.NewString()
|
||||
var pref any
|
||||
if in.Prefix != nil && strings.TrimSpace(*in.Prefix) != "" {
|
||||
pref = strings.TrimSpace(*in.Prefix)
|
||||
}
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
INSERT INTO module_as_entry (id, module_id, asn, prefix, community_id)
|
||||
VALUES ($1,$2,$3,$4::cidr, NULLIF($5::uuid, '00000000-0000-0000-0000-000000000000'::uuid))`,
|
||||
id, moduleID, in.ASN, pref, uuidOrNilPtr(in.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getASEntry(ctx, moduleID, id)
|
||||
}
|
||||
|
||||
func (p *Postgres) getASEntry(ctx context.Context, moduleID, id string) (*store.ASEntry, error) {
|
||||
var e store.ASEntry
|
||||
e.ModuleID = moduleID
|
||||
var asn *int64
|
||||
var pref, comm *string
|
||||
err := p.pool.QueryRow(ctx, `
|
||||
SELECT id::text, asn, prefix::text, community_id::text FROM module_as_entry WHERE id=$1 AND module_id=$2`, id, moduleID).Scan(
|
||||
&e.ID, &asn, &pref, &comm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.ASN = asn
|
||||
e.Prefix = pref
|
||||
e.CommunityID = strOrNil(comm)
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) UpdateASEntry(tenantID, moduleID, entryID string, patch *store.ASEntryPatch) (*store.ASEntry, error) {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cur, err := p.getASEntry(context.Background(), moduleID, entryID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if patch.ASN != nil {
|
||||
cur.ASN = patch.ASN
|
||||
}
|
||||
if patch.Prefix != nil {
|
||||
p := strings.TrimSpace(*patch.Prefix)
|
||||
if p == "" {
|
||||
cur.Prefix = nil
|
||||
} else {
|
||||
cur.Prefix = &p
|
||||
}
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
cur.CommunityID = nil
|
||||
} else {
|
||||
cur.CommunityID = &v
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
var pref any
|
||||
if cur.Prefix != nil {
|
||||
pref = *cur.Prefix
|
||||
}
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
UPDATE module_as_entry SET asn=$3, prefix=$4::cidr, community_id=NULLIF($5::uuid, '00000000-0000-0000-0000-000000000000'::uuid), updated_at=now()
|
||||
WHERE id=$1 AND module_id=$2`,
|
||||
entryID, moduleID, cur.ASN, pref, uuidOrNilPtr(cur.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getASEntry(ctx, moduleID, entryID)
|
||||
}
|
||||
|
||||
func (p *Postgres) DeleteASEntry(tenantID, moduleID, entryID string) error {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
tag, err := p.pool.Exec(ctx, `DELETE FROM module_as_entry WHERE id=$1 AND module_id=$2`, entryID, moduleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Postgres) ListDomainEntries(tenantID, moduleID string) ([]*store.DomainEntry, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "DOMAINS" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
rows, err := p.pool.Query(ctx, `SELECT id::text, fqdn, community_id::text FROM module_domain_entry WHERE module_id=$1`, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []*store.DomainEntry
|
||||
for rows.Next() {
|
||||
var e store.DomainEntry
|
||||
e.ModuleID = moduleID
|
||||
var comm *string
|
||||
if err := rows.Scan(&e.ID, &e.FQDN, &comm); err != nil {
|
||||
continue
|
||||
}
|
||||
e.CommunityID = strOrNil(comm)
|
||||
out = append(out, &e)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) CreateDomainEntry(tenantID, moduleID string, in *store.DomainEntry) (*store.DomainEntry, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "DOMAINS" || in == nil || strings.TrimSpace(in.FQDN) == "" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
id := uuid.NewString()
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
INSERT INTO module_domain_entry (id, module_id, fqdn, community_id)
|
||||
VALUES ($1,$2,$3, NULLIF($4::uuid, '00000000-0000-0000-0000-000000000000'::uuid))`,
|
||||
id, moduleID, strings.TrimSpace(in.FQDN), uuidOrNilPtr(in.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getDomainEntry(ctx, moduleID, id)
|
||||
}
|
||||
|
||||
func (p *Postgres) getDomainEntry(ctx context.Context, moduleID, id string) (*store.DomainEntry, error) {
|
||||
var e store.DomainEntry
|
||||
e.ModuleID = moduleID
|
||||
var comm *string
|
||||
err := p.pool.QueryRow(ctx, `SELECT id::text, fqdn, community_id::text FROM module_domain_entry WHERE id=$1 AND module_id=$2`, id, moduleID).Scan(&e.ID, &e.FQDN, &comm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.CommunityID = strOrNil(comm)
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) UpdateDomainEntry(tenantID, moduleID, entryID string, patch *store.DomainEntryPatch) (*store.DomainEntry, error) {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cur, err := p.getDomainEntry(context.Background(), moduleID, entryID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if patch.FQDN != nil {
|
||||
cur.FQDN = strings.TrimSpace(*patch.FQDN)
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
cur.CommunityID = nil
|
||||
} else {
|
||||
cur.CommunityID = &v
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
UPDATE module_domain_entry SET fqdn=$3, community_id=NULLIF($4::uuid, '00000000-0000-0000-0000-000000000000'::uuid), updated_at=now()
|
||||
WHERE id=$1 AND module_id=$2`,
|
||||
entryID, moduleID, cur.FQDN, uuidOrNilPtr(cur.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getDomainEntry(ctx, moduleID, entryID)
|
||||
}
|
||||
|
||||
func (p *Postgres) DeleteDomainEntry(tenantID, moduleID, entryID string) error {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
tag, err := p.pool.Exec(ctx, `DELETE FROM module_domain_entry WHERE id=$1 AND module_id=$2`, entryID, moduleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Postgres) ListIPRangeEntries(tenantID, moduleID string) ([]*store.IPRangeEntry, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "IP_RANGES" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
rows, err := p.pool.Query(ctx, `SELECT id::text, prefix::text, community_id::text FROM module_ip_range_entry WHERE module_id=$1`, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []*store.IPRangeEntry
|
||||
for rows.Next() {
|
||||
var e store.IPRangeEntry
|
||||
e.ModuleID = moduleID
|
||||
var comm *string
|
||||
if err := rows.Scan(&e.ID, &e.Prefix, &comm); err != nil {
|
||||
continue
|
||||
}
|
||||
e.CommunityID = strOrNil(comm)
|
||||
out = append(out, &e)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) CreateIPRangeEntry(tenantID, moduleID string, in *store.IPRangeEntry) (*store.IPRangeEntry, error) {
|
||||
mod, err := p.GetModule(tenantID, moduleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Type != "IP_RANGES" || in == nil || strings.TrimSpace(in.Prefix) == "" {
|
||||
return nil, store.ErrInvalidInput
|
||||
}
|
||||
ctx := context.Background()
|
||||
id := uuid.NewString()
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
INSERT INTO module_ip_range_entry (id, module_id, prefix, community_id)
|
||||
VALUES ($1,$2,$3::cidr, NULLIF($4::uuid, '00000000-0000-0000-0000-000000000000'::uuid))`,
|
||||
id, moduleID, strings.TrimSpace(in.Prefix), uuidOrNilPtr(in.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getIPRangeEntry(ctx, moduleID, id)
|
||||
}
|
||||
|
||||
func (p *Postgres) getIPRangeEntry(ctx context.Context, moduleID, id string) (*store.IPRangeEntry, error) {
|
||||
var e store.IPRangeEntry
|
||||
e.ModuleID = moduleID
|
||||
var comm *string
|
||||
err := p.pool.QueryRow(ctx, `SELECT id::text, prefix::text, community_id::text FROM module_ip_range_entry WHERE id=$1 AND module_id=$2`, id, moduleID).Scan(&e.ID, &e.Prefix, &comm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.CommunityID = strOrNil(comm)
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) UpdateIPRangeEntry(tenantID, moduleID, entryID string, patch *store.IPRangePatch) (*store.IPRangeEntry, error) {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cur, err := p.getIPRangeEntry(context.Background(), moduleID, entryID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, store.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if patch.Prefix != nil {
|
||||
cur.Prefix = strings.TrimSpace(*patch.Prefix)
|
||||
}
|
||||
if patch.CommunityID != nil {
|
||||
v := strings.TrimSpace(*patch.CommunityID)
|
||||
if v == "" {
|
||||
cur.CommunityID = nil
|
||||
} else {
|
||||
cur.CommunityID = &v
|
||||
}
|
||||
}
|
||||
ctx := context.Background()
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
UPDATE module_ip_range_entry SET prefix=$3::cidr, community_id=NULLIF($4::uuid, '00000000-0000-0000-0000-000000000000'::uuid), updated_at=now()
|
||||
WHERE id=$1 AND module_id=$2`,
|
||||
entryID, moduleID, cur.Prefix, uuidOrNilPtr(cur.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.getIPRangeEntry(ctx, moduleID, entryID)
|
||||
}
|
||||
|
||||
func (p *Postgres) DeleteIPRangeEntry(tenantID, moduleID, entryID string) error {
|
||||
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
tag, err := p.pool.Exec(ctx, `DELETE FROM module_ip_range_entry WHERE id=$1 AND module_id=$2`, entryID, moduleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Postgres) ListGlobalSettings(tenantID string) (map[string]any, error) {
|
||||
ctx := context.Background()
|
||||
rows, err := p.pool.Query(ctx, `SELECT key, value_json FROM global_settings WHERE tenant_id=$1`, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make(map[string]any)
|
||||
for rows.Next() {
|
||||
var k string
|
||||
var vj []byte
|
||||
if err := rows.Scan(&k, &vj); err != nil {
|
||||
continue
|
||||
}
|
||||
var v any
|
||||
_ = json.Unmarshal(vj, &v)
|
||||
out[k] = v
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) PatchGlobalSettings(tenantID string, patch map[string]any) error {
|
||||
if patch == nil {
|
||||
return nil
|
||||
}
|
||||
ctx := context.Background()
|
||||
for k, v := range patch {
|
||||
if strings.TrimSpace(k) == "" {
|
||||
continue
|
||||
}
|
||||
if v == nil {
|
||||
_, _ = p.pool.Exec(ctx, `DELETE FROM global_settings WHERE tenant_id=$1 AND key=$2`, tenantID, k)
|
||||
continue
|
||||
}
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
INSERT INTO global_settings (tenant_id, key, value_json) VALUES ($1,$2,$3::jsonb)
|
||||
ON CONFLICT (tenant_id, key) DO UPDATE SET value_json = EXCLUDED.value_json, updated_at = now()`,
|
||||
tenantID, k, string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ store.Backend = (*Postgres)(nil)
|
||||
|
||||
var _ store.Backend = (*Postgres)(nil)
|
||||
Reference in New Issue
Block a user