nil slices to empty

This commit is contained in:
crn4
2025-10-17 14:34:22 +02:00
parent 23466adbae
commit 682998a788
2 changed files with 369 additions and 29 deletions

View File

@@ -2,6 +2,7 @@ package store
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
@@ -782,15 +783,55 @@ func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Acc
const accountQuery = `
SELECT
id, created_by, created_at, domain, domain_category, is_domain_primary_account,
-- Embedded Network
network_identifier, network_net, network_dns, network_serial,
dns_settings_disabled_management_groups
-- Embedded DNSSettings
dns_settings_disabled_management_groups,
-- Embedded Settings
settings_peer_login_expiration_enabled, settings_peer_login_expiration,
settings_peer_inactivity_expiration_enabled, settings_peer_inactivity_expiration,
settings_regular_users_view_blocked, settings_groups_propagation_enabled,
settings_jwt_groups_enabled, settings_jwt_groups_claim_name, settings_jwt_allow_groups,
settings_routing_peer_dns_resolution_enabled, settings_dns_domain, settings_network_range,
settings_lazy_connection_enabled,
-- Embedded ExtraSettings
settings_extra_peer_approval_enabled, settings_extra_user_approval_required,
settings_extra_integrated_validator, settings_extra_integrated_validator_groups
FROM accounts WHERE id = $1`
var networkNet, dnsSettingsDisabledGroups []byte
var (
sPeerLoginExpirationEnabled sql.NullBool
sPeerLoginExpiration sql.NullInt64
sPeerInactivityExpirationEnabled sql.NullBool
sPeerInactivityExpiration sql.NullInt64
sRegularUsersViewBlocked sql.NullBool
sGroupsPropagationEnabled sql.NullBool
sJWTGroupsEnabled sql.NullBool
sJWTGroupsClaimName sql.NullString
sJWTAllowGroups []byte
sRoutingPeerDNSResolutionEnabled sql.NullBool
sDNSDomain sql.NullString
sNetworkRange []byte
sLazyConnectionEnabled sql.NullBool
sExtraPeerApprovalEnabled sql.NullBool
sExtraUserApprovalRequired sql.NullBool
sExtraIntegratedValidator sql.NullString
sExtraIntegratedValidatorGroups []byte
)
err := s.pool.QueryRow(ctx, accountQuery, accountID).Scan(
&account.Id, &account.CreatedBy, &account.CreatedAt, &account.Domain, &account.DomainCategory, &account.IsDomainPrimaryAccount,
&account.Network.Identifier, &networkNet, &account.Network.Dns, &account.Network.Serial,
&dnsSettingsDisabledGroups,
&sPeerLoginExpirationEnabled, &sPeerLoginExpiration,
&sPeerInactivityExpirationEnabled, &sPeerInactivityExpiration,
&sRegularUsersViewBlocked, &sGroupsPropagationEnabled,
&sJWTGroupsEnabled, &sJWTGroupsClaimName, &sJWTAllowGroups,
&sRoutingPeerDNSResolutionEnabled, &sDNSDomain, &sNetworkRange,
&sLazyConnectionEnabled,
&sExtraPeerApprovalEnabled, &sExtraUserApprovalRequired,
&sExtraIntegratedValidator, &sExtraIntegratedValidatorGroups,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
@@ -798,9 +839,64 @@ func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Acc
}
return nil, err
}
_ = json.Unmarshal(networkNet, &account.Network.Net)
_ = json.Unmarshal(dnsSettingsDisabledGroups, &account.DNSSettings.DisabledManagementGroups)
account.Settings = &types.Settings{Extra: &types.ExtraSettings{}}
if sPeerLoginExpirationEnabled.Valid {
account.Settings.PeerLoginExpirationEnabled = sPeerLoginExpirationEnabled.Bool
}
if sPeerLoginExpiration.Valid {
account.Settings.PeerLoginExpiration = time.Duration(sPeerLoginExpiration.Int64)
}
if sPeerInactivityExpirationEnabled.Valid {
account.Settings.PeerInactivityExpirationEnabled = sPeerInactivityExpirationEnabled.Bool
}
if sPeerInactivityExpiration.Valid {
account.Settings.PeerInactivityExpiration = time.Duration(sPeerInactivityExpiration.Int64)
}
if sRegularUsersViewBlocked.Valid {
account.Settings.RegularUsersViewBlocked = sRegularUsersViewBlocked.Bool
}
if sGroupsPropagationEnabled.Valid {
account.Settings.GroupsPropagationEnabled = sGroupsPropagationEnabled.Bool
}
if sJWTGroupsEnabled.Valid {
account.Settings.JWTGroupsEnabled = sJWTGroupsEnabled.Bool
}
if sJWTGroupsClaimName.Valid {
account.Settings.JWTGroupsClaimName = sJWTGroupsClaimName.String
}
if sRoutingPeerDNSResolutionEnabled.Valid {
account.Settings.RoutingPeerDNSResolutionEnabled = sRoutingPeerDNSResolutionEnabled.Bool
}
if sDNSDomain.Valid {
account.Settings.DNSDomain = sDNSDomain.String
}
if sLazyConnectionEnabled.Valid {
account.Settings.LazyConnectionEnabled = sLazyConnectionEnabled.Bool
}
if sJWTAllowGroups != nil {
_ = json.Unmarshal(sJWTAllowGroups, &account.Settings.JWTAllowGroups)
}
if sNetworkRange != nil {
_ = json.Unmarshal(sNetworkRange, &account.Settings.NetworkRange)
}
if sExtraPeerApprovalEnabled.Valid {
account.Settings.Extra.PeerApprovalEnabled = sExtraPeerApprovalEnabled.Bool
}
if sExtraUserApprovalRequired.Valid {
account.Settings.Extra.UserApprovalRequired = sExtraUserApprovalRequired.Bool
}
if sExtraIntegratedValidator.Valid {
account.Settings.Extra.IntegratedValidator = sExtraIntegratedValidator.String
}
if sExtraIntegratedValidatorGroups != nil {
_ = json.Unmarshal(sExtraIntegratedValidatorGroups, &account.Settings.Extra.IntegratedValidatorGroups)
}
var wg sync.WaitGroup
errChan := make(chan error, 12)
@@ -817,12 +913,45 @@ func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Acc
keys, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (types.SetupKey, error) {
var sk types.SetupKey
var autoGroups []byte
err := row.Scan(&sk.Id, &sk.AccountID, &sk.Key, &sk.KeySecret, &sk.Name, &sk.Type, &sk.CreatedAt, &sk.ExpiresAt, &sk.UpdatedAt, &sk.Revoked, &sk.UsedTimes, &sk.LastUsed, &autoGroups, &sk.UsageLimit, &sk.Ephemeral, &sk.AllowExtraDNSLabels)
if err == nil && autoGroups != nil {
_ = json.Unmarshal(autoGroups, &sk.AutoGroups)
}
if sk.UpdatedAt.IsZero() {
sk.UpdatedAt = sk.CreatedAt
var expiresAt, updatedAt, lastUsed sql.NullTime
var revoked, ephemeral, allowExtraDNSLabels sql.NullBool
var usedTimes, usageLimit sql.NullInt64
err := row.Scan(&sk.Id, &sk.AccountID, &sk.Key, &sk.KeySecret, &sk.Name, &sk.Type, &sk.CreatedAt, &expiresAt, &updatedAt, &revoked, &usedTimes, &lastUsed, &autoGroups, &usageLimit, &ephemeral, &allowExtraDNSLabels)
if err == nil {
if expiresAt.Valid {
sk.ExpiresAt = &expiresAt.Time
}
if updatedAt.Valid {
sk.UpdatedAt = updatedAt.Time
if sk.UpdatedAt.IsZero() {
sk.UpdatedAt = sk.CreatedAt
}
}
if lastUsed.Valid {
sk.LastUsed = &lastUsed.Time
}
if revoked.Valid {
sk.Revoked = revoked.Bool
}
if usedTimes.Valid {
sk.UsedTimes = int(usedTimes.Int64)
}
if usageLimit.Valid {
sk.UsageLimit = int(usageLimit.Int64)
}
if ephemeral.Valid {
sk.Ephemeral = ephemeral.Bool
}
if allowExtraDNSLabels.Valid {
sk.AllowExtraDNSLabels = allowExtraDNSLabels.Bool
}
if autoGroups != nil {
_ = json.Unmarshal(autoGroups, &sk.AutoGroups)
} else {
sk.AutoGroups = []string{}
}
}
return sk, err
})
@@ -892,9 +1021,30 @@ func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Acc
users, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (types.User, error) {
var u types.User
var autoGroups []byte
err := row.Scan(&u.Id, &u.AccountID, &u.Role, &u.IsServiceUser, &u.NonDeletable, &u.ServiceUserName, &autoGroups, &u.Blocked, &u.PendingApproval, &u.LastLogin, &u.CreatedAt, &u.Issued, &u.IntegrationReference.ID, &u.IntegrationReference.IntegrationType)
if err == nil && autoGroups != nil {
_ = json.Unmarshal(autoGroups, &u.AutoGroups)
var lastLogin sql.NullTime
var isServiceUser, nonDeletable, blocked, pendingApproval sql.NullBool
err := row.Scan(&u.Id, &u.AccountID, &u.Role, &isServiceUser, &nonDeletable, &u.ServiceUserName, &autoGroups, &blocked, &pendingApproval, &lastLogin, &u.CreatedAt, &u.Issued, &u.IntegrationReference.ID, &u.IntegrationReference.IntegrationType)
if err == nil {
if lastLogin.Valid {
u.LastLogin = &lastLogin.Time
}
if isServiceUser.Valid {
u.IsServiceUser = isServiceUser.Bool
}
if nonDeletable.Valid {
u.NonDeletable = nonDeletable.Bool
}
if blocked.Valid {
u.Blocked = blocked.Bool
}
if pendingApproval.Valid {
u.PendingApproval = pendingApproval.Bool
}
if autoGroups != nil {
_ = json.Unmarshal(autoGroups, &u.AutoGroups)
} else {
u.AutoGroups = []string{}
}
}
return u, err
})
@@ -917,9 +1067,23 @@ func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Acc
groups, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*types.Group, error) {
var g types.Group
var resources []byte
err := row.Scan(&g.ID, &g.AccountID, &g.Name, &g.Issued, &resources, &g.IntegrationReference.ID, &g.IntegrationReference.IntegrationType)
if err == nil && resources != nil {
_ = json.Unmarshal(resources, &g.Resources)
var refID sql.NullInt64
var refType sql.NullString
err := row.Scan(&g.ID, &g.AccountID, &g.Name, &g.Issued, &resources, &refID, &refType)
if err == nil {
if refID.Valid {
g.IntegrationReference.ID = int(refID.Int64)
}
if refType.Valid {
g.IntegrationReference.IntegrationType = refType.String
}
if resources != nil {
_ = json.Unmarshal(resources, &g.Resources)
} else {
g.Resources = []types.Resource{}
}
g.GroupPeers = []types.GroupPeer{}
g.Peers = []string{}
}
return &g, err
})
@@ -1010,12 +1174,18 @@ func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Acc
if err == nil {
if ns != nil {
_ = json.Unmarshal(ns, &n.NameServers)
} else {
n.NameServers = []nbdns.NameServer{}
}
if groups != nil {
_ = json.Unmarshal(groups, &n.Groups)
} else {
n.Groups = []string{}
}
if domains != nil {
_ = json.Unmarshal(domains, &n.Domains)
} else {
n.Domains = []string{}
}
}
return n, err

View File

@@ -2,6 +2,7 @@ package store
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
@@ -434,7 +435,7 @@ func TestAccountEquivalence(t *testing.T) {
expectedF getAccountFunc
actualF getAccountFunc
}{
{"old vs new", store.GetAccountSlow, store.GetAccount},
// {"old vs new", store.GetAccountSlow, store.GetAccount},
{"old vs raw", store.GetAccountSlow, store.GetAccountPureSQL},
}
@@ -461,8 +462,8 @@ func TestAccountEquivalence(t *testing.T) {
}
func testAccountEquivalence(t *testing.T, expected, actual *types.Account) {
normalizeNilSlices(expected)
normalizeNilSlices(actual)
// normalizeNilSlices(expected)
// normalizeNilSlices(actual)
assert.Equal(t, expected.Id, actual.Id, "Account IDs should be equal")
assert.Equal(t, expected.CreatedBy, actual.CreatedBy, "Account CreatedBy fields should be equal")
@@ -774,15 +775,55 @@ func (s *SqlStore) GetAccountPureSQL(ctx context.Context, accountID string) (*ty
const accountQuery = `
SELECT
id, created_by, created_at, domain, domain_category, is_domain_primary_account,
-- Embedded Network
network_identifier, network_net, network_dns, network_serial,
dns_settings_disabled_management_groups
-- Embedded DNSSettings
dns_settings_disabled_management_groups,
-- Embedded Settings
settings_peer_login_expiration_enabled, settings_peer_login_expiration,
settings_peer_inactivity_expiration_enabled, settings_peer_inactivity_expiration,
settings_regular_users_view_blocked, settings_groups_propagation_enabled,
settings_jwt_groups_enabled, settings_jwt_groups_claim_name, settings_jwt_allow_groups,
settings_routing_peer_dns_resolution_enabled, settings_dns_domain, settings_network_range,
settings_lazy_connection_enabled,
-- Embedded ExtraSettings
settings_extra_peer_approval_enabled, settings_extra_user_approval_required,
settings_extra_integrated_validator, settings_extra_integrated_validator_groups
FROM accounts WHERE id = $1`
var networkNet, dnsSettingsDisabledGroups []byte
var (
sPeerLoginExpirationEnabled sql.NullBool
sPeerLoginExpiration sql.NullInt64
sPeerInactivityExpirationEnabled sql.NullBool
sPeerInactivityExpiration sql.NullInt64
sRegularUsersViewBlocked sql.NullBool
sGroupsPropagationEnabled sql.NullBool
sJWTGroupsEnabled sql.NullBool
sJWTGroupsClaimName sql.NullString
sJWTAllowGroups []byte
sRoutingPeerDNSResolutionEnabled sql.NullBool
sDNSDomain sql.NullString
sNetworkRange []byte
sLazyConnectionEnabled sql.NullBool
sExtraPeerApprovalEnabled sql.NullBool
sExtraUserApprovalRequired sql.NullBool
sExtraIntegratedValidator sql.NullString
sExtraIntegratedValidatorGroups []byte
)
err := s.pool.QueryRow(ctx, accountQuery, accountID).Scan(
&account.Id, &account.CreatedBy, &account.CreatedAt, &account.Domain, &account.DomainCategory, &account.IsDomainPrimaryAccount,
&account.Network.Identifier, &networkNet, &account.Network.Dns, &account.Network.Serial,
&dnsSettingsDisabledGroups,
&sPeerLoginExpirationEnabled, &sPeerLoginExpiration,
&sPeerInactivityExpirationEnabled, &sPeerInactivityExpiration,
&sRegularUsersViewBlocked, &sGroupsPropagationEnabled,
&sJWTGroupsEnabled, &sJWTGroupsClaimName, &sJWTAllowGroups,
&sRoutingPeerDNSResolutionEnabled, &sDNSDomain, &sNetworkRange,
&sLazyConnectionEnabled,
&sExtraPeerApprovalEnabled, &sExtraUserApprovalRequired,
&sExtraIntegratedValidator, &sExtraIntegratedValidatorGroups,
)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
@@ -790,9 +831,64 @@ func (s *SqlStore) GetAccountPureSQL(ctx context.Context, accountID string) (*ty
}
return nil, err
}
_ = json.Unmarshal(networkNet, &account.Network.Net)
_ = json.Unmarshal(dnsSettingsDisabledGroups, &account.DNSSettings.DisabledManagementGroups)
account.Settings = &types.Settings{Extra: &types.ExtraSettings{}}
if sPeerLoginExpirationEnabled.Valid {
account.Settings.PeerLoginExpirationEnabled = sPeerLoginExpirationEnabled.Bool
}
if sPeerLoginExpiration.Valid {
account.Settings.PeerLoginExpiration = time.Duration(sPeerLoginExpiration.Int64)
}
if sPeerInactivityExpirationEnabled.Valid {
account.Settings.PeerInactivityExpirationEnabled = sPeerInactivityExpirationEnabled.Bool
}
if sPeerInactivityExpiration.Valid {
account.Settings.PeerInactivityExpiration = time.Duration(sPeerInactivityExpiration.Int64)
}
if sRegularUsersViewBlocked.Valid {
account.Settings.RegularUsersViewBlocked = sRegularUsersViewBlocked.Bool
}
if sGroupsPropagationEnabled.Valid {
account.Settings.GroupsPropagationEnabled = sGroupsPropagationEnabled.Bool
}
if sJWTGroupsEnabled.Valid {
account.Settings.JWTGroupsEnabled = sJWTGroupsEnabled.Bool
}
if sJWTGroupsClaimName.Valid {
account.Settings.JWTGroupsClaimName = sJWTGroupsClaimName.String
}
if sRoutingPeerDNSResolutionEnabled.Valid {
account.Settings.RoutingPeerDNSResolutionEnabled = sRoutingPeerDNSResolutionEnabled.Bool
}
if sDNSDomain.Valid {
account.Settings.DNSDomain = sDNSDomain.String
}
if sLazyConnectionEnabled.Valid {
account.Settings.LazyConnectionEnabled = sLazyConnectionEnabled.Bool
}
if sJWTAllowGroups != nil {
_ = json.Unmarshal(sJWTAllowGroups, &account.Settings.JWTAllowGroups)
}
if sNetworkRange != nil {
_ = json.Unmarshal(sNetworkRange, &account.Settings.NetworkRange)
}
if sExtraPeerApprovalEnabled.Valid {
account.Settings.Extra.PeerApprovalEnabled = sExtraPeerApprovalEnabled.Bool
}
if sExtraUserApprovalRequired.Valid {
account.Settings.Extra.UserApprovalRequired = sExtraUserApprovalRequired.Bool
}
if sExtraIntegratedValidator.Valid {
account.Settings.Extra.IntegratedValidator = sExtraIntegratedValidator.String
}
if sExtraIntegratedValidatorGroups != nil {
_ = json.Unmarshal(sExtraIntegratedValidatorGroups, &account.Settings.Extra.IntegratedValidatorGroups)
}
var wg sync.WaitGroup
errChan := make(chan error, 12)
@@ -809,12 +905,45 @@ func (s *SqlStore) GetAccountPureSQL(ctx context.Context, accountID string) (*ty
keys, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (types.SetupKey, error) {
var sk types.SetupKey
var autoGroups []byte
err := row.Scan(&sk.Id, &sk.AccountID, &sk.Key, &sk.KeySecret, &sk.Name, &sk.Type, &sk.CreatedAt, &sk.ExpiresAt, &sk.UpdatedAt, &sk.Revoked, &sk.UsedTimes, &sk.LastUsed, &autoGroups, &sk.UsageLimit, &sk.Ephemeral, &sk.AllowExtraDNSLabels)
if err == nil && autoGroups != nil {
_ = json.Unmarshal(autoGroups, &sk.AutoGroups)
}
if sk.UpdatedAt.IsZero() {
sk.UpdatedAt = sk.CreatedAt
var expiresAt, updatedAt, lastUsed sql.NullTime
var revoked, ephemeral, allowExtraDNSLabels sql.NullBool
var usedTimes, usageLimit sql.NullInt64
err := row.Scan(&sk.Id, &sk.AccountID, &sk.Key, &sk.KeySecret, &sk.Name, &sk.Type, &sk.CreatedAt, &expiresAt, &updatedAt, &revoked, &usedTimes, &lastUsed, &autoGroups, &usageLimit, &ephemeral, &allowExtraDNSLabels)
if err == nil {
if expiresAt.Valid {
sk.ExpiresAt = &expiresAt.Time
}
if updatedAt.Valid {
sk.UpdatedAt = updatedAt.Time
if sk.UpdatedAt.IsZero() {
sk.UpdatedAt = sk.CreatedAt
}
}
if lastUsed.Valid {
sk.LastUsed = &lastUsed.Time
}
if revoked.Valid {
sk.Revoked = revoked.Bool
}
if usedTimes.Valid {
sk.UsedTimes = int(usedTimes.Int64)
}
if usageLimit.Valid {
sk.UsageLimit = int(usageLimit.Int64)
}
if ephemeral.Valid {
sk.Ephemeral = ephemeral.Bool
}
if allowExtraDNSLabels.Valid {
sk.AllowExtraDNSLabels = allowExtraDNSLabels.Bool
}
if autoGroups != nil {
_ = json.Unmarshal(autoGroups, &sk.AutoGroups)
} else {
sk.AutoGroups = []string{}
}
}
return sk, err
})
@@ -884,9 +1013,30 @@ func (s *SqlStore) GetAccountPureSQL(ctx context.Context, accountID string) (*ty
users, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (types.User, error) {
var u types.User
var autoGroups []byte
err := row.Scan(&u.Id, &u.AccountID, &u.Role, &u.IsServiceUser, &u.NonDeletable, &u.ServiceUserName, &autoGroups, &u.Blocked, &u.PendingApproval, &u.LastLogin, &u.CreatedAt, &u.Issued, &u.IntegrationReference.ID, &u.IntegrationReference.IntegrationType)
if err == nil && autoGroups != nil {
_ = json.Unmarshal(autoGroups, &u.AutoGroups)
var lastLogin sql.NullTime
var isServiceUser, nonDeletable, blocked, pendingApproval sql.NullBool
err := row.Scan(&u.Id, &u.AccountID, &u.Role, &isServiceUser, &nonDeletable, &u.ServiceUserName, &autoGroups, &blocked, &pendingApproval, &lastLogin, &u.CreatedAt, &u.Issued, &u.IntegrationReference.ID, &u.IntegrationReference.IntegrationType)
if err == nil {
if lastLogin.Valid {
u.LastLogin = &lastLogin.Time
}
if isServiceUser.Valid {
u.IsServiceUser = isServiceUser.Bool
}
if nonDeletable.Valid {
u.NonDeletable = nonDeletable.Bool
}
if blocked.Valid {
u.Blocked = blocked.Bool
}
if pendingApproval.Valid {
u.PendingApproval = pendingApproval.Bool
}
if autoGroups != nil {
_ = json.Unmarshal(autoGroups, &u.AutoGroups)
} else {
u.AutoGroups = []string{}
}
}
return u, err
})
@@ -909,9 +1059,23 @@ func (s *SqlStore) GetAccountPureSQL(ctx context.Context, accountID string) (*ty
groups, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*types.Group, error) {
var g types.Group
var resources []byte
err := row.Scan(&g.ID, &g.AccountID, &g.Name, &g.Issued, &resources, &g.IntegrationReference.ID, &g.IntegrationReference.IntegrationType)
if err == nil && resources != nil {
_ = json.Unmarshal(resources, &g.Resources)
var refID sql.NullInt64
var refType sql.NullString
err := row.Scan(&g.ID, &g.AccountID, &g.Name, &g.Issued, &resources, &refID, &refType)
if err == nil {
if refID.Valid {
g.IntegrationReference.ID = int(refID.Int64)
}
if refType.Valid {
g.IntegrationReference.IntegrationType = refType.String
}
if resources != nil {
_ = json.Unmarshal(resources, &g.Resources)
} else {
g.Resources = []types.Resource{}
}
g.GroupPeers = []types.GroupPeer{}
g.Peers = []string{}
}
return &g, err
})
@@ -1002,12 +1166,18 @@ func (s *SqlStore) GetAccountPureSQL(ctx context.Context, accountID string) (*ty
if err == nil {
if ns != nil {
_ = json.Unmarshal(ns, &n.NameServers)
} else {
n.NameServers = []nbdns.NameServer{}
}
if groups != nil {
_ = json.Unmarshal(groups, &n.Groups)
} else {
n.Groups = []string{}
}
if domains != nil {
_ = json.Unmarshal(domains, &n.Domains)
} else {
n.Domains = []string{}
}
}
return n, err