Added functionality to track the last refreshed timestamp for CDN sources. Updated the database schema and relevant methods to include the last refreshed timestamp during creation and updates. Implemented logic to skip fetching CDN sources based on their refresh interval, improving efficiency in the module prefix collection process. Enhanced the data retrieval methods to support the new timestamp field, ensuring accurate state management for CDN sources.
609 lines
17 KiB
Go
609 lines
17 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"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(prefix_path,''), COALESCE(etag,''), refresh_interval_sec, community_id::text, last_refreshed_at
|
|
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
|
|
var last *time.Time
|
|
if err := rows.Scan(&s.ID, &s.SourceKind, &s.URL, &s.PrefixPath, &s.Etag, &ri, &comm, &last); err != nil {
|
|
continue
|
|
}
|
|
if ri != nil {
|
|
v := int(*ri)
|
|
s.RefreshIntervalSec = &v
|
|
}
|
|
s.CommunityID = strOrNil(comm)
|
|
if last != nil {
|
|
t := last.UTC()
|
|
s.LastRefreshedAt = &t
|
|
}
|
|
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, prefix_path, etag, refresh_interval_sec, community_id, last_refreshed_at)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7, NULLIF($8::uuid, '00000000-0000-0000-0000-000000000000'::uuid), $9)`,
|
|
id, moduleID, in.SourceKind, strings.TrimSpace(in.URL), strings.TrimSpace(in.PrefixPath), in.Etag, nullInt32Ptr(in.RefreshIntervalSec), uuidOrNilPtr(in.CommunityID), nullTimePtr(in.LastRefreshedAt))
|
|
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
|
|
var last *time.Time
|
|
err := p.pool.QueryRow(ctx, `
|
|
SELECT id::text, source_kind, url, COALESCE(prefix_path,''), COALESCE(etag,''), refresh_interval_sec, community_id::text, last_refreshed_at
|
|
FROM module_cdn_source WHERE id=$1 AND module_id=$2`, id, moduleID).Scan(&s.ID, &s.SourceKind, &s.URL, &s.PrefixPath, &s.Etag, &ri, &comm, &last)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ri != nil {
|
|
v := int(*ri)
|
|
s.RefreshIntervalSec = &v
|
|
}
|
|
s.CommunityID = strOrNil(comm)
|
|
if last != nil {
|
|
t := last.UTC()
|
|
s.LastRefreshedAt = &t
|
|
}
|
|
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.PrefixPath != nil {
|
|
cur.PrefixPath = strings.TrimSpace(*patch.PrefixPath)
|
|
}
|
|
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
|
|
}
|
|
}
|
|
if patch.LastRefreshedAt != nil {
|
|
t := patch.LastRefreshedAt.UTC()
|
|
cur.LastRefreshedAt = &t
|
|
}
|
|
ctx := context.Background()
|
|
_, err = p.pool.Exec(ctx, `
|
|
UPDATE module_cdn_source SET source_kind=$3, url=$4, prefix_path=$5, etag=$6, refresh_interval_sec=$7,
|
|
community_id=NULLIF($8::uuid, '00000000-0000-0000-0000-000000000000'::uuid), last_refreshed_at=$9, updated_at=now()
|
|
WHERE id=$1 AND module_id=$2`,
|
|
sourceID, moduleID, cur.SourceKind, cur.URL, cur.PrefixPath, cur.Etag, nullInt32Ptr(cur.RefreshIntervalSec), uuidOrNilPtr(cur.CommunityID), nullTimePtr(cur.LastRefreshedAt))
|
|
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, community_id::text, asn_name, prefix_count, asn_resolved_at
|
|
FROM module_as_entry WHERE module_id=$1 ORDER BY asn`, 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 comm, asnName *string
|
|
var pc *int64
|
|
var at *time.Time
|
|
if err := rows.Scan(&e.ID, &e.ASN, &comm, &asnName, &pc, &at); err != nil {
|
|
continue
|
|
}
|
|
e.CommunityID = strOrNil(comm)
|
|
if asnName != nil {
|
|
e.ASNName = *asnName
|
|
}
|
|
e.PrefixCount = pc
|
|
e.ASNResolvedAt = at
|
|
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 || !store.ValidASN(in.ASN) {
|
|
return nil, store.ErrInvalidInput
|
|
}
|
|
ctx := context.Background()
|
|
id := uuid.NewString()
|
|
_, err = p.pool.Exec(ctx, `
|
|
INSERT INTO module_as_entry (id, module_id, asn, community_id)
|
|
VALUES ($1,$2,$3,NULLIF($4::uuid, '00000000-0000-0000-0000-000000000000'::uuid))`,
|
|
id, moduleID, in.ASN, 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 comm, asnName *string
|
|
var pc *int64
|
|
var at *time.Time
|
|
err := p.pool.QueryRow(ctx, `
|
|
SELECT id::text, asn, community_id::text, asn_name, prefix_count, asn_resolved_at
|
|
FROM module_as_entry WHERE id=$1 AND module_id=$2`, id, moduleID).Scan(
|
|
&e.ID, &e.ASN, &comm, &asnName, &pc, &at)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
e.CommunityID = strOrNil(comm)
|
|
if asnName != nil {
|
|
e.ASNName = *asnName
|
|
}
|
|
e.PrefixCount = pc
|
|
e.ASNResolvedAt = at
|
|
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
|
|
}
|
|
prevASN := cur.ASN
|
|
if patch.ASN != nil {
|
|
cur.ASN = *patch.ASN
|
|
}
|
|
if patch.CommunityID != nil {
|
|
v := strings.TrimSpace(*patch.CommunityID)
|
|
if v == "" {
|
|
cur.CommunityID = nil
|
|
} else {
|
|
cur.CommunityID = &v
|
|
}
|
|
}
|
|
if !store.ValidASN(cur.ASN) {
|
|
return nil, store.ErrInvalidInput
|
|
}
|
|
clearResolve := patch.ASN != nil && cur.ASN != prevASN
|
|
ctx := context.Background()
|
|
if clearResolve {
|
|
_, err = p.pool.Exec(ctx, `
|
|
UPDATE module_as_entry SET asn=$3, community_id=NULLIF($4::uuid, '00000000-0000-0000-0000-000000000000'::uuid),
|
|
asn_name=NULL, prefix_count=NULL, asn_resolved_at=NULL, updated_at=now()
|
|
WHERE id=$1 AND module_id=$2`,
|
|
entryID, moduleID, cur.ASN, uuidOrNilPtr(cur.CommunityID))
|
|
} else {
|
|
_, err = p.pool.Exec(ctx, `
|
|
UPDATE module_as_entry SET asn=$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.ASN, uuidOrNilPtr(cur.CommunityID))
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.getASEntry(ctx, moduleID, entryID)
|
|
}
|
|
|
|
func (p *Postgres) UpdateASEntryResolveMeta(tenantID, moduleID, entryID string, asnName string, prefixCount int64, resolvedAt time.Time) error {
|
|
if _, err := p.GetModule(tenantID, moduleID); err != nil {
|
|
return err
|
|
}
|
|
ctx := context.Background()
|
|
var nameArg any
|
|
sn := strings.TrimSpace(asnName)
|
|
if sn == "" {
|
|
nameArg = nil
|
|
} else {
|
|
nameArg = sn
|
|
}
|
|
tag, err := p.pool.Exec(ctx, `
|
|
UPDATE module_as_entry SET asn_name=$3, prefix_count=$4, asn_resolved_at=$5, updated_at=now()
|
|
WHERE id=$1 AND module_id=$2`,
|
|
entryID, moduleID, nameArg, prefixCount, resolvedAt.UTC())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return store.ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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)
|