chore: commit pending gateway changes
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type GatewayTenantInput struct {
|
||||
TenantKey string `json:"tenantKey"`
|
||||
Source string `json:"source"`
|
||||
ExternalTenantID string `json:"externalTenantId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DefaultUserGroupID string `json:"defaultUserGroupId"`
|
||||
PlanKey string `json:"planKey"`
|
||||
BillingProfile map[string]any `json:"billingProfile"`
|
||||
RateLimitPolicy map[string]any `json:"rateLimitPolicy"`
|
||||
AuthPolicy map[string]any `json:"authPolicy"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type GatewayUserInput struct {
|
||||
UserKey string `json:"userKey"`
|
||||
Source string `json:"source"`
|
||||
ExternalUserID string `json:"externalUserId"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
AvatarURL string `json:"avatarUrl"`
|
||||
Password string `json:"password"`
|
||||
GatewayTenantID string `json:"gatewayTenantId"`
|
||||
TenantID string `json:"tenantId"`
|
||||
TenantKey string `json:"tenantKey"`
|
||||
DefaultUserGroupID string `json:"defaultUserGroupId"`
|
||||
Roles []string `json:"roles"`
|
||||
AuthProfile map[string]any `json:"authProfile"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type UserGroupInput struct {
|
||||
GroupKey string `json:"groupKey"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Source string `json:"source"`
|
||||
Priority int `json:"priority"`
|
||||
RechargeDiscountPolicy map[string]any `json:"rechargeDiscountPolicy"`
|
||||
BillingDiscountPolicy map[string]any `json:"billingDiscountPolicy"`
|
||||
RateLimitPolicy map[string]any `json:"rateLimitPolicy"`
|
||||
QuotaPolicy map[string]any `json:"quotaPolicy"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (s *Store) CreateTenant(ctx context.Context, input GatewayTenantInput) (GatewayTenant, error) {
|
||||
input = normalizeTenantInput(input)
|
||||
billingProfile, _ := json.Marshal(emptyObjectIfNil(input.BillingProfile))
|
||||
rateLimitPolicy, _ := json.Marshal(emptyObjectIfNil(input.RateLimitPolicy))
|
||||
authPolicy, _ := json.Marshal(emptyObjectIfNil(input.AuthPolicy))
|
||||
metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
|
||||
return scanTenant(s.pool.QueryRow(ctx, `
|
||||
INSERT INTO gateway_tenants (
|
||||
tenant_key, source, external_tenant_id, name, description, default_user_group_id, plan_key,
|
||||
billing_profile, rate_limit_policy, auth_policy, metadata, status
|
||||
)
|
||||
VALUES ($1, $2, NULLIF($3, ''), $4, NULLIF($5, ''), NULLIF($6, '')::uuid, NULLIF($7, ''), $8, $9, $10, $11, $12)
|
||||
RETURNING `+tenantColumns,
|
||||
input.TenantKey, input.Source, input.ExternalTenantID, input.Name, input.Description, input.DefaultUserGroupID, input.PlanKey,
|
||||
billingProfile, rateLimitPolicy, authPolicy, metadata, input.Status,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *Store) UpdateTenant(ctx context.Context, id string, input GatewayTenantInput) (GatewayTenant, error) {
|
||||
input = normalizeTenantInput(input)
|
||||
billingProfile, _ := json.Marshal(emptyObjectIfNil(input.BillingProfile))
|
||||
rateLimitPolicy, _ := json.Marshal(emptyObjectIfNil(input.RateLimitPolicy))
|
||||
authPolicy, _ := json.Marshal(emptyObjectIfNil(input.AuthPolicy))
|
||||
metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
|
||||
return scanTenant(s.pool.QueryRow(ctx, `
|
||||
UPDATE gateway_tenants
|
||||
SET tenant_key = $2,
|
||||
source = $3,
|
||||
external_tenant_id = NULLIF($4, ''),
|
||||
name = $5,
|
||||
description = NULLIF($6, ''),
|
||||
default_user_group_id = NULLIF($7, '')::uuid,
|
||||
plan_key = NULLIF($8, ''),
|
||||
billing_profile = $9,
|
||||
rate_limit_policy = $10,
|
||||
auth_policy = $11,
|
||||
metadata = $12,
|
||||
status = $13,
|
||||
updated_at = now()
|
||||
WHERE id = $1::uuid AND deleted_at IS NULL
|
||||
RETURNING `+tenantColumns,
|
||||
id, input.TenantKey, input.Source, input.ExternalTenantID, input.Name, input.Description, input.DefaultUserGroupID,
|
||||
input.PlanKey, billingProfile, rateLimitPolicy, authPolicy, metadata, input.Status,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *Store) DeleteTenant(ctx context.Context, id string) error {
|
||||
result, err := s.pool.Exec(ctx, `
|
||||
UPDATE gateway_tenants
|
||||
SET deleted_at = now(),
|
||||
status = 'deleted',
|
||||
tenant_key = tenant_key || ':deleted:' || left(id::text, 8),
|
||||
external_tenant_id = NULL,
|
||||
updated_at = now()
|
||||
WHERE id = $1::uuid AND deleted_at IS NULL`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateGatewayUser(ctx context.Context, input GatewayUserInput) (GatewayUser, error) {
|
||||
input = normalizeUserInput(input)
|
||||
passwordHash := ""
|
||||
if strings.TrimSpace(input.Password) != "" {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return GatewayUser{}, err
|
||||
}
|
||||
passwordHash = string(hash)
|
||||
}
|
||||
roles, _ := json.Marshal(input.Roles)
|
||||
authProfile, _ := json.Marshal(emptyObjectIfNil(input.AuthProfile))
|
||||
metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
|
||||
return scanUser(s.pool.QueryRow(ctx, `
|
||||
INSERT INTO gateway_users (
|
||||
user_key, source, external_user_id, username, display_name, email, phone, avatar_url, password_hash,
|
||||
gateway_tenant_id, tenant_id, tenant_key, default_user_group_id, roles, auth_profile, metadata, status
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, NULLIF($3, ''), $4, NULLIF($5, ''), NULLIF($6, ''), NULLIF($7, ''), NULLIF($8, ''), NULLIF($9, ''),
|
||||
NULLIF($10, '')::uuid, NULLIF($11, ''), NULLIF($12, ''), NULLIF($13, '')::uuid, $14, $15, $16, $17
|
||||
)
|
||||
RETURNING `+userColumns,
|
||||
input.UserKey, input.Source, input.ExternalUserID, input.Username, input.DisplayName, input.Email, input.Phone, input.AvatarURL, passwordHash,
|
||||
input.GatewayTenantID, input.TenantID, input.TenantKey, input.DefaultUserGroupID, roles, authProfile, metadata, input.Status,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *Store) UpdateGatewayUser(ctx context.Context, id string, input GatewayUserInput) (GatewayUser, error) {
|
||||
input = normalizeUserInput(input)
|
||||
passwordHash := ""
|
||||
if strings.TrimSpace(input.Password) != "" {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return GatewayUser{}, err
|
||||
}
|
||||
passwordHash = string(hash)
|
||||
}
|
||||
roles, _ := json.Marshal(input.Roles)
|
||||
authProfile, _ := json.Marshal(emptyObjectIfNil(input.AuthProfile))
|
||||
metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
|
||||
return scanUser(s.pool.QueryRow(ctx, `
|
||||
UPDATE gateway_users
|
||||
SET user_key = $2,
|
||||
source = $3,
|
||||
external_user_id = NULLIF($4, ''),
|
||||
username = $5,
|
||||
display_name = NULLIF($6, ''),
|
||||
email = NULLIF($7, ''),
|
||||
phone = NULLIF($8, ''),
|
||||
avatar_url = NULLIF($9, ''),
|
||||
password_hash = COALESCE(NULLIF($10, ''), password_hash),
|
||||
gateway_tenant_id = NULLIF($11, '')::uuid,
|
||||
tenant_id = NULLIF($12, ''),
|
||||
tenant_key = NULLIF($13, ''),
|
||||
default_user_group_id = NULLIF($14, '')::uuid,
|
||||
roles = $15,
|
||||
auth_profile = $16,
|
||||
metadata = $17,
|
||||
status = $18,
|
||||
updated_at = now()
|
||||
WHERE id = $1::uuid AND deleted_at IS NULL
|
||||
RETURNING `+userColumns,
|
||||
id, input.UserKey, input.Source, input.ExternalUserID, input.Username, input.DisplayName, input.Email, input.Phone,
|
||||
input.AvatarURL, passwordHash, input.GatewayTenantID, input.TenantID, input.TenantKey, input.DefaultUserGroupID,
|
||||
roles, authProfile, metadata, input.Status,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *Store) DeleteGatewayUser(ctx context.Context, id string) error {
|
||||
result, err := s.pool.Exec(ctx, `
|
||||
UPDATE gateway_users
|
||||
SET deleted_at = now(),
|
||||
status = 'deleted',
|
||||
user_key = user_key || ':deleted:' || left(id::text, 8),
|
||||
external_user_id = NULL,
|
||||
email = NULL,
|
||||
updated_at = now()
|
||||
WHERE id = $1::uuid AND deleted_at IS NULL`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateUserGroup(ctx context.Context, input UserGroupInput) (UserGroup, error) {
|
||||
input = normalizeUserGroupInput(input)
|
||||
rechargeDiscountPolicy, _ := json.Marshal(emptyObjectIfNil(input.RechargeDiscountPolicy))
|
||||
billingDiscountPolicy, _ := json.Marshal(emptyObjectIfNil(input.BillingDiscountPolicy))
|
||||
rateLimitPolicy, _ := json.Marshal(emptyObjectIfNil(input.RateLimitPolicy))
|
||||
quotaPolicy, _ := json.Marshal(emptyObjectIfNil(input.QuotaPolicy))
|
||||
metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
|
||||
return scanUserGroup(s.pool.QueryRow(ctx, `
|
||||
INSERT INTO gateway_user_groups (
|
||||
group_key, name, description, source, priority, recharge_discount_policy, billing_discount_policy,
|
||||
rate_limit_policy, quota_policy, metadata, status
|
||||
)
|
||||
VALUES ($1, $2, NULLIF($3, ''), $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING `+userGroupColumns,
|
||||
input.GroupKey, input.Name, input.Description, input.Source, input.Priority, rechargeDiscountPolicy, billingDiscountPolicy,
|
||||
rateLimitPolicy, quotaPolicy, metadata, input.Status,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUserGroup(ctx context.Context, id string, input UserGroupInput) (UserGroup, error) {
|
||||
input = normalizeUserGroupInput(input)
|
||||
rechargeDiscountPolicy, _ := json.Marshal(emptyObjectIfNil(input.RechargeDiscountPolicy))
|
||||
billingDiscountPolicy, _ := json.Marshal(emptyObjectIfNil(input.BillingDiscountPolicy))
|
||||
rateLimitPolicy, _ := json.Marshal(emptyObjectIfNil(input.RateLimitPolicy))
|
||||
quotaPolicy, _ := json.Marshal(emptyObjectIfNil(input.QuotaPolicy))
|
||||
metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
|
||||
return scanUserGroup(s.pool.QueryRow(ctx, `
|
||||
UPDATE gateway_user_groups
|
||||
SET group_key = $2,
|
||||
name = $3,
|
||||
description = NULLIF($4, ''),
|
||||
source = $5,
|
||||
priority = $6,
|
||||
recharge_discount_policy = $7,
|
||||
billing_discount_policy = $8,
|
||||
rate_limit_policy = $9,
|
||||
quota_policy = $10,
|
||||
metadata = $11,
|
||||
status = $12,
|
||||
updated_at = now()
|
||||
WHERE id = $1::uuid
|
||||
RETURNING `+userGroupColumns,
|
||||
id, input.GroupKey, input.Name, input.Description, input.Source, input.Priority,
|
||||
rechargeDiscountPolicy, billingDiscountPolicy, rateLimitPolicy, quotaPolicy, metadata, input.Status,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *Store) DeleteUserGroup(ctx context.Context, id string) error {
|
||||
result, err := s.pool.Exec(ctx, `DELETE FROM gateway_user_groups WHERE id = $1::uuid`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const tenantColumns = `
|
||||
id::text, tenant_key, source, COALESCE(external_tenant_id, ''), name, COALESCE(description, ''),
|
||||
COALESCE(default_user_group_id::text, ''), COALESCE(plan_key, ''), billing_profile, rate_limit_policy,
|
||||
auth_policy, metadata, status, COALESCE(synced_at::text, ''), COALESCE(source_updated_at::text, ''),
|
||||
created_at, updated_at`
|
||||
|
||||
const userColumns = `
|
||||
id::text, user_key, source, COALESCE(external_user_id, ''), username,
|
||||
COALESCE(display_name, ''), COALESCE(email, ''), COALESCE(phone, ''), COALESCE(avatar_url, ''),
|
||||
COALESCE(gateway_tenant_id::text, ''), COALESCE(tenant_id, ''), COALESCE(tenant_key, ''),
|
||||
COALESCE(default_user_group_id::text, ''), roles, auth_profile, metadata,
|
||||
status, COALESCE(last_login_at::text, ''), COALESCE(synced_at::text, ''), COALESCE(source_updated_at::text, ''),
|
||||
created_at, updated_at`
|
||||
|
||||
const userGroupColumns = `
|
||||
id::text, group_key, name, COALESCE(description, ''), source, priority,
|
||||
recharge_discount_policy, billing_discount_policy, rate_limit_policy, quota_policy, metadata,
|
||||
status, created_at, updated_at`
|
||||
|
||||
type scanner interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func scanTenant(row scanner) (GatewayTenant, error) {
|
||||
var item GatewayTenant
|
||||
var billingProfile []byte
|
||||
var rateLimitPolicy []byte
|
||||
var authPolicy []byte
|
||||
var metadata []byte
|
||||
if err := row.Scan(
|
||||
&item.ID, &item.TenantKey, &item.Source, &item.ExternalTenantID, &item.Name, &item.Description,
|
||||
&item.DefaultUserGroupID, &item.PlanKey, &billingProfile, &rateLimitPolicy, &authPolicy, &metadata,
|
||||
&item.Status, &item.SyncedAt, &item.SourceUpdatedAt, &item.CreatedAt, &item.UpdatedAt,
|
||||
); err != nil {
|
||||
return GatewayTenant{}, err
|
||||
}
|
||||
item.BillingProfile = decodeObject(billingProfile)
|
||||
item.RateLimitPolicy = decodeObject(rateLimitPolicy)
|
||||
item.AuthPolicy = decodeObject(authPolicy)
|
||||
item.Metadata = decodeObject(metadata)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func scanUser(row scanner) (GatewayUser, error) {
|
||||
var item GatewayUser
|
||||
var roles []byte
|
||||
var authProfile []byte
|
||||
var metadata []byte
|
||||
if err := row.Scan(
|
||||
&item.ID, &item.UserKey, &item.Source, &item.ExternalUserID, &item.Username, &item.DisplayName,
|
||||
&item.Email, &item.Phone, &item.AvatarURL, &item.GatewayTenantID, &item.TenantID, &item.TenantKey,
|
||||
&item.DefaultUserGroupID, &roles, &authProfile, &metadata, &item.Status, &item.LastLoginAt,
|
||||
&item.SyncedAt, &item.SourceUpdatedAt, &item.CreatedAt, &item.UpdatedAt,
|
||||
); err != nil {
|
||||
return GatewayUser{}, err
|
||||
}
|
||||
item.Roles = decodeStringArray(roles)
|
||||
item.AuthProfile = decodeObject(authProfile)
|
||||
item.Metadata = decodeObject(metadata)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func scanUserGroup(row scanner) (UserGroup, error) {
|
||||
var item UserGroup
|
||||
var rechargeDiscountPolicy []byte
|
||||
var billingDiscountPolicy []byte
|
||||
var rateLimitPolicy []byte
|
||||
var quotaPolicy []byte
|
||||
var metadata []byte
|
||||
if err := row.Scan(
|
||||
&item.ID, &item.GroupKey, &item.Name, &item.Description, &item.Source, &item.Priority,
|
||||
&rechargeDiscountPolicy, &billingDiscountPolicy, &rateLimitPolicy, "aPolicy, &metadata,
|
||||
&item.Status, &item.CreatedAt, &item.UpdatedAt,
|
||||
); err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
item.RechargeDiscountPolicy = decodeObject(rechargeDiscountPolicy)
|
||||
item.BillingDiscountPolicy = decodeObject(billingDiscountPolicy)
|
||||
item.RateLimitPolicy = decodeObject(rateLimitPolicy)
|
||||
item.QuotaPolicy = decodeObject(quotaPolicy)
|
||||
item.Metadata = decodeObject(metadata)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func normalizeTenantInput(input GatewayTenantInput) GatewayTenantInput {
|
||||
input.TenantKey = strings.TrimSpace(input.TenantKey)
|
||||
input.Source = firstNonEmpty(strings.TrimSpace(input.Source), "gateway")
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
input.Status = firstNonEmpty(strings.TrimSpace(input.Status), "active")
|
||||
return input
|
||||
}
|
||||
|
||||
func normalizeUserInput(input GatewayUserInput) GatewayUserInput {
|
||||
input.Username = strings.TrimSpace(input.Username)
|
||||
input.Source = firstNonEmpty(strings.TrimSpace(input.Source), "gateway")
|
||||
input.UserKey = firstNonEmpty(strings.TrimSpace(input.UserKey), input.Source+":"+input.Username)
|
||||
input.Status = firstNonEmpty(strings.TrimSpace(input.Status), "active")
|
||||
input.Roles = normalizeGatewayRoles(input.Roles)
|
||||
return input
|
||||
}
|
||||
|
||||
func normalizeGatewayRoles(roles []string) []string {
|
||||
for _, role := range roles {
|
||||
switch strings.TrimSpace(role) {
|
||||
case "manager":
|
||||
return []string{"manager"}
|
||||
case "admin":
|
||||
return []string{"admin"}
|
||||
case "operator":
|
||||
return []string{"operator"}
|
||||
case "creator":
|
||||
return []string{"creator"}
|
||||
case "user":
|
||||
return []string{"user"}
|
||||
}
|
||||
}
|
||||
return []string{"user"}
|
||||
}
|
||||
|
||||
func normalizeUserGroupInput(input UserGroupInput) UserGroupInput {
|
||||
input.GroupKey = strings.TrimSpace(input.GroupKey)
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
input.Source = firstNonEmpty(strings.TrimSpace(input.Source), "gateway")
|
||||
input.Status = firstNonEmpty(strings.TrimSpace(input.Status), "active")
|
||||
if input.Priority == 0 {
|
||||
input.Priority = 100
|
||||
}
|
||||
return input
|
||||
}
|
||||
Reference in New Issue
Block a user