feat: enrich task record details
This commit is contained in:
@@ -25,6 +25,7 @@ var (
|
||||
ErrInvalidCredentials = errors.New("invalid account or password")
|
||||
ErrInvalidInvitation = errors.New("invalid or expired invitation code")
|
||||
ErrLocalUserRequired = errors.New("local gateway user is required")
|
||||
ErrProtectedDefault = errors.New("protected default resource cannot be deleted")
|
||||
ErrUserAlreadyExists = errors.New("user already exists")
|
||||
ErrWeakPassword = errors.New("password must be at least 8 characters")
|
||||
)
|
||||
@@ -54,6 +55,7 @@ type Platform struct {
|
||||
Provider string `json:"provider"`
|
||||
PlatformKey string `json:"platformKey"`
|
||||
Name string `json:"name"`
|
||||
InternalName string `json:"internalName,omitempty"`
|
||||
BaseURL string `json:"baseUrl,omitempty"`
|
||||
AuthType string `json:"authType"`
|
||||
Status string `json:"status"`
|
||||
@@ -62,6 +64,9 @@ type Platform struct {
|
||||
DefaultDiscountFactor float64 `json:"defaultDiscountFactor"`
|
||||
PricingRuleSetID string `json:"pricingRuleSetId,omitempty"`
|
||||
Config map[string]any `json:"config,omitempty"`
|
||||
CredentialsPreview map[string]any `json:"credentialsPreview,omitempty"`
|
||||
RetryPolicy map[string]any `json:"retryPolicy,omitempty"`
|
||||
RateLimitPolicy map[string]any `json:"rateLimitPolicy,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -70,10 +75,13 @@ type CreatePlatformInput struct {
|
||||
Provider string `json:"provider"`
|
||||
PlatformKey string `json:"platformKey"`
|
||||
Name string `json:"name"`
|
||||
InternalName string `json:"internalName"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
AuthType string `json:"authType"`
|
||||
Credentials map[string]any `json:"credentials"`
|
||||
Config map[string]any `json:"config"`
|
||||
RetryPolicy map[string]any `json:"retryPolicy"`
|
||||
RateLimitPolicy map[string]any `json:"rateLimitPolicy"`
|
||||
DefaultPricingMode string `json:"defaultPricingMode"`
|
||||
DefaultDiscountFactor float64 `json:"defaultDiscountFactor"`
|
||||
PricingRuleSetID string `json:"pricingRuleSetId"`
|
||||
@@ -106,6 +114,11 @@ type APIKey struct {
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type PlayableAPIKey struct {
|
||||
APIKey
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
type CreatedAPIKey struct {
|
||||
APIKey APIKey `json:"apiKey"`
|
||||
Secret string `json:"secret"`
|
||||
@@ -128,11 +141,32 @@ type PlatformModel struct {
|
||||
PricingRuleSetID string `json:"pricingRuleSetId,omitempty"`
|
||||
BillingConfigOverride map[string]any `json:"billingConfigOverride,omitempty"`
|
||||
BillingConfig map[string]any `json:"billingConfig,omitempty"`
|
||||
PermissionConfig map[string]any `json:"permissionConfig,omitempty"`
|
||||
RetryPolicy map[string]any `json:"retryPolicy,omitempty"`
|
||||
RateLimitPolicy map[string]any `json:"rateLimitPolicy,omitempty"`
|
||||
RuntimePolicySetID string `json:"runtimePolicySetId,omitempty"`
|
||||
RuntimePolicyOverride map[string]any `json:"runtimePolicyOverride,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AccessRule struct {
|
||||
ID string `json:"id"`
|
||||
SubjectType string `json:"subjectType"`
|
||||
SubjectID string `json:"subjectId"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
ResourceID string `json:"resourceId"`
|
||||
Effect string `json:"effect"`
|
||||
Priority int `json:"priority"`
|
||||
MinPermissionLevel int `json:"minPermissionLevel"`
|
||||
Conditions map[string]any `json:"conditions,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CatalogProvider struct {
|
||||
ID string `json:"id"`
|
||||
ProviderKey string `json:"providerKey"`
|
||||
@@ -140,6 +174,8 @@ type CatalogProvider struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
ProviderType string `json:"providerType"`
|
||||
IconPath string `json:"iconPath,omitempty"`
|
||||
DefaultBaseURL string `json:"defaultBaseUrl,omitempty"`
|
||||
DefaultAuthType string `json:"defaultAuthType,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
CapabilitySchema map[string]any `json:"capabilitySchema,omitempty"`
|
||||
DefaultRateLimitPolicy map[string]any `json:"defaultRateLimitPolicy,omitempty"`
|
||||
@@ -154,18 +190,40 @@ type BaseModel struct {
|
||||
ProviderKey string `json:"providerKey"`
|
||||
CanonicalModelKey string `json:"canonicalModelKey"`
|
||||
ProviderModelName string `json:"providerModelName"`
|
||||
ModelType string `json:"modelType"`
|
||||
DisplayName string `json:"displayName"`
|
||||
ModelType StringList `json:"modelType"`
|
||||
ModelAlias string `json:"modelAlias"`
|
||||
DisplayName string `json:"-"`
|
||||
Capabilities map[string]any `json:"capabilities,omitempty"`
|
||||
BaseBillingConfig map[string]any `json:"baseBillingConfig,omitempty"`
|
||||
DefaultRateLimitPolicy map[string]any `json:"defaultRateLimitPolicy,omitempty"`
|
||||
PricingRuleSetID string `json:"pricingRuleSetId,omitempty"`
|
||||
RuntimePolicySetID string `json:"runtimePolicySetId,omitempty"`
|
||||
RuntimePolicyOverride map[string]any `json:"runtimePolicyOverride,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
CatalogType string `json:"catalogType"`
|
||||
DefaultSnapshot map[string]any `json:"defaultSnapshot,omitempty"`
|
||||
CustomizedAt string `json:"customizedAt,omitempty"`
|
||||
PricingVersion int `json:"pricingVersion"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type RuntimePolicySet struct {
|
||||
ID string `json:"id"`
|
||||
PolicyKey string `json:"policyKey"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
RateLimitPolicy map[string]any `json:"rateLimitPolicy,omitempty"`
|
||||
RetryPolicy map[string]any `json:"retryPolicy,omitempty"`
|
||||
AutoDisablePolicy map[string]any `json:"autoDisablePolicy,omitempty"`
|
||||
DegradePolicy map[string]any `json:"degradePolicy,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type PricingRule struct {
|
||||
ID string `json:"id"`
|
||||
RuleSetID string `json:"ruleSetId,omitempty"`
|
||||
@@ -298,27 +356,54 @@ type CreateTaskInput struct {
|
||||
}
|
||||
|
||||
type GatewayTask struct {
|
||||
ID string `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
RunMode string `json:"runMode"`
|
||||
UserID string `json:"userId"`
|
||||
GatewayUserID string `json:"gatewayUserId,omitempty"`
|
||||
UserSource string `json:"userSource,omitempty"`
|
||||
GatewayTenantID string `json:"gatewayTenantId,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
TenantKey string `json:"tenantKey,omitempty"`
|
||||
UserGroupID string `json:"userGroupId,omitempty"`
|
||||
UserGroupKey string `json:"userGroupKey,omitempty"`
|
||||
Model string `json:"model"`
|
||||
Request map[string]any `json:"request,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Result map[string]any `json:"result,omitempty"`
|
||||
Billings []any `json:"billings,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID string `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
RunMode string `json:"runMode"`
|
||||
UserID string `json:"userId"`
|
||||
GatewayUserID string `json:"gatewayUserId,omitempty"`
|
||||
UserSource string `json:"userSource,omitempty"`
|
||||
GatewayTenantID string `json:"gatewayTenantId,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
TenantKey string `json:"tenantKey,omitempty"`
|
||||
APIKeyID string `json:"apiKeyId,omitempty"`
|
||||
APIKeyName string `json:"apiKeyName,omitempty"`
|
||||
APIKeyPrefix string `json:"apiKeyPrefix,omitempty"`
|
||||
UserGroupID string `json:"userGroupId,omitempty"`
|
||||
UserGroupKey string `json:"userGroupKey,omitempty"`
|
||||
Model string `json:"model"`
|
||||
ModelType string `json:"modelType,omitempty"`
|
||||
RequestedModel string `json:"requestedModel,omitempty"`
|
||||
ResolvedModel string `json:"resolvedModel,omitempty"`
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
Request map[string]any `json:"request,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Result map[string]any `json:"result,omitempty"`
|
||||
Billings []any `json:"billings,omitempty"`
|
||||
Usage map[string]any `json:"usage"`
|
||||
Metrics map[string]any `json:"metrics"`
|
||||
BillingSummary map[string]any `json:"billingSummary"`
|
||||
FinalChargeAmount float64 `json:"finalChargeAmount"`
|
||||
ResponseStartedAt string `json:"responseStartedAt,omitempty"`
|
||||
ResponseFinishedAt string `json:"responseFinishedAt,omitempty"`
|
||||
ResponseDurationMS int64 `json:"responseDurationMs"`
|
||||
FinishedAt string `json:"finishedAt,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
const gatewayTaskColumns = `
|
||||
id::text, kind, run_mode, user_id, COALESCE(gateway_user_id::text, ''), user_source,
|
||||
COALESCE(gateway_tenant_id::text, ''), COALESCE(tenant_id, ''), COALESCE(tenant_key, ''),
|
||||
COALESCE(api_key_id, ''), COALESCE(api_key_name, ''), COALESCE(api_key_prefix, ''),
|
||||
COALESCE(user_group_id::text, ''), COALESCE(user_group_key, ''), model,
|
||||
COALESCE(model_type, ''), COALESCE(requested_model, ''), COALESCE(resolved_model, ''), COALESCE(request_id, ''),
|
||||
request, status, COALESCE(result, '{}'::jsonb), COALESCE(billings, '[]'::jsonb),
|
||||
COALESCE(usage, '{}'::jsonb), COALESCE(metrics, '{}'::jsonb), COALESCE(billing_summary, '{}'::jsonb),
|
||||
COALESCE(final_charge_amount, 0)::float8, COALESCE(response_started_at::text, ''),
|
||||
COALESCE(response_finished_at::text, ''), COALESCE(response_duration_ms, 0), COALESCE(error, ''),
|
||||
created_at, updated_at, COALESCE(finished_at::text, '')`
|
||||
|
||||
type TaskEvent struct {
|
||||
ID string `json:"id"`
|
||||
TaskID string `json:"taskId"`
|
||||
@@ -335,9 +420,9 @@ type TaskEvent struct {
|
||||
|
||||
func (s *Store) ListPlatforms(ctx context.Context) ([]Platform, error) {
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT id::text, provider, platform_key, name, COALESCE(base_url, ''), auth_type, status, priority,
|
||||
SELECT id::text, provider, platform_key, name, COALESCE(internal_name, ''), COALESCE(base_url, ''), auth_type, status, priority,
|
||||
default_pricing_mode, default_discount_factor::float8, COALESCE(pricing_rule_set_id::text, ''),
|
||||
config, created_at, updated_at
|
||||
config, credentials, retry_policy, rate_limit_policy, created_at, updated_at
|
||||
FROM integration_platforms
|
||||
ORDER BY priority ASC, created_at DESC`)
|
||||
if err != nil {
|
||||
@@ -349,11 +434,15 @@ ORDER BY priority ASC, created_at DESC`)
|
||||
for rows.Next() {
|
||||
var platform Platform
|
||||
var configBytes []byte
|
||||
var credentialsBytes []byte
|
||||
var retryPolicyBytes []byte
|
||||
var rateLimitPolicyBytes []byte
|
||||
if err := rows.Scan(
|
||||
&platform.ID,
|
||||
&platform.Provider,
|
||||
&platform.PlatformKey,
|
||||
&platform.Name,
|
||||
&platform.InternalName,
|
||||
&platform.BaseURL,
|
||||
&platform.AuthType,
|
||||
&platform.Status,
|
||||
@@ -362,20 +451,28 @@ ORDER BY priority ASC, created_at DESC`)
|
||||
&platform.DefaultDiscountFactor,
|
||||
&platform.PricingRuleSetID,
|
||||
&configBytes,
|
||||
&credentialsBytes,
|
||||
&retryPolicyBytes,
|
||||
&rateLimitPolicyBytes,
|
||||
&platform.CreatedAt,
|
||||
&platform.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
platform.Config = decodeObject(configBytes)
|
||||
platform.CredentialsPreview = maskCredentialsPreview(credentialsBytes)
|
||||
platform.RetryPolicy = decodeObject(retryPolicyBytes)
|
||||
platform.RateLimitPolicy = decodeObject(rateLimitPolicyBytes)
|
||||
platforms = append(platforms, platform)
|
||||
}
|
||||
return platforms, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) CreatePlatform(ctx context.Context, input CreatePlatformInput) (Platform, error) {
|
||||
credentials, _ := json.Marshal(input.Credentials)
|
||||
config, _ := json.Marshal(input.Config)
|
||||
credentials, _ := json.Marshal(emptyObjectIfNil(input.Credentials))
|
||||
config, _ := json.Marshal(emptyObjectIfNil(input.Config))
|
||||
retryPolicy, _ := json.Marshal(emptyObjectIfNil(input.RetryPolicy))
|
||||
rateLimitPolicy, _ := json.Marshal(emptyObjectIfNil(input.RateLimitPolicy))
|
||||
if input.DefaultPricingMode == "" {
|
||||
input.DefaultPricingMode = "inherit_discount"
|
||||
}
|
||||
@@ -387,17 +484,31 @@ func (s *Store) CreatePlatform(ctx context.Context, input CreatePlatformInput) (
|
||||
}
|
||||
var platform Platform
|
||||
var configBytes []byte
|
||||
var credentialsResultBytes []byte
|
||||
var retryPolicyBytes []byte
|
||||
var rateLimitPolicyBytes []byte
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
INSERT INTO integration_platforms (provider, platform_key, name, base_url, auth_type, credentials, config, default_pricing_mode, default_discount_factor, pricing_rule_set_id, priority)
|
||||
VALUES ($1, COALESCE(NULLIF($2, ''), 'platform_' || replace(gen_random_uuid()::text, '-', '')), $3, $4, $5, $6, $7, $8, $9, NULLIF($10, '')::uuid, $11)
|
||||
RETURNING id::text, provider, platform_key, name, COALESCE(base_url, ''), auth_type, status, priority,
|
||||
default_pricing_mode, default_discount_factor::float8, COALESCE(pricing_rule_set_id::text, ''), config, created_at, updated_at`,
|
||||
input.Provider, input.PlatformKey, input.Name, input.BaseURL, input.AuthType, credentials, config, input.DefaultPricingMode, input.DefaultDiscountFactor, input.PricingRuleSetID, input.Priority,
|
||||
INSERT INTO integration_platforms (
|
||||
provider, platform_key, name, internal_name, base_url, auth_type, credentials, config,
|
||||
default_pricing_mode, default_discount_factor, pricing_rule_set_id,
|
||||
priority, retry_policy, rate_limit_policy
|
||||
)
|
||||
VALUES (
|
||||
$1, COALESCE(NULLIF($2, ''), 'platform_' || replace(gen_random_uuid()::text, '-', '')), $3, NULLIF($4, ''), $5, $6, $7, $8,
|
||||
$9, $10, NULLIF($11, '')::uuid, $12, $13, $14
|
||||
)
|
||||
RETURNING id::text, provider, platform_key, name, COALESCE(internal_name, ''), COALESCE(base_url, ''), auth_type, status, priority,
|
||||
default_pricing_mode, default_discount_factor::float8, COALESCE(pricing_rule_set_id::text, ''),
|
||||
config, credentials, retry_policy, rate_limit_policy, created_at, updated_at`,
|
||||
input.Provider, input.PlatformKey, input.Name, strings.TrimSpace(input.InternalName), input.BaseURL, input.AuthType, credentials, config,
|
||||
input.DefaultPricingMode, input.DefaultDiscountFactor, input.PricingRuleSetID, input.Priority,
|
||||
string(retryPolicy), string(rateLimitPolicy),
|
||||
).Scan(
|
||||
&platform.ID,
|
||||
&platform.Provider,
|
||||
&platform.PlatformKey,
|
||||
&platform.Name,
|
||||
&platform.InternalName,
|
||||
&platform.BaseURL,
|
||||
&platform.AuthType,
|
||||
&platform.Status,
|
||||
@@ -406,6 +517,9 @@ RETURNING id::text, provider, platform_key, name, COALESCE(base_url, ''), auth_t
|
||||
&platform.DefaultDiscountFactor,
|
||||
&platform.PricingRuleSetID,
|
||||
&configBytes,
|
||||
&credentialsResultBytes,
|
||||
&retryPolicyBytes,
|
||||
&rateLimitPolicyBytes,
|
||||
&platform.CreatedAt,
|
||||
&platform.UpdatedAt,
|
||||
)
|
||||
@@ -413,19 +527,143 @@ RETURNING id::text, provider, platform_key, name, COALESCE(base_url, ''), auth_t
|
||||
return Platform{}, err
|
||||
}
|
||||
platform.Config = decodeObject(configBytes)
|
||||
platform.CredentialsPreview = maskCredentialsPreview(credentialsResultBytes)
|
||||
platform.RetryPolicy = decodeObject(retryPolicyBytes)
|
||||
platform.RateLimitPolicy = decodeObject(rateLimitPolicyBytes)
|
||||
return platform, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdatePlatform(ctx context.Context, id string, input CreatePlatformInput) (Platform, error) {
|
||||
var credentials any
|
||||
if input.Credentials != nil {
|
||||
credentialsBytes, _ := json.Marshal(emptyObjectIfNil(input.Credentials))
|
||||
credentials = string(credentialsBytes)
|
||||
}
|
||||
config, _ := json.Marshal(emptyObjectIfNil(input.Config))
|
||||
retryPolicy, _ := json.Marshal(emptyObjectIfNil(input.RetryPolicy))
|
||||
rateLimitPolicy, _ := json.Marshal(emptyObjectIfNil(input.RateLimitPolicy))
|
||||
if input.DefaultPricingMode == "" {
|
||||
input.DefaultPricingMode = "inherit_discount"
|
||||
}
|
||||
if input.DefaultDiscountFactor == 0 {
|
||||
input.DefaultDiscountFactor = 1
|
||||
}
|
||||
if input.Priority == 0 {
|
||||
input.Priority = 100
|
||||
}
|
||||
var platform Platform
|
||||
var configBytes []byte
|
||||
var credentialsResultBytes []byte
|
||||
var retryPolicyBytes []byte
|
||||
var rateLimitPolicyBytes []byte
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
UPDATE integration_platforms
|
||||
SET provider = $2,
|
||||
platform_key = COALESCE(NULLIF($3, ''), platform_key),
|
||||
name = $4,
|
||||
internal_name = NULLIF($5, ''),
|
||||
base_url = $6,
|
||||
auth_type = $7,
|
||||
credentials = CASE
|
||||
WHEN $8::jsonb IS NULL THEN credentials
|
||||
WHEN $8::jsonb = '{}'::jsonb THEN '{}'::jsonb
|
||||
ELSE credentials || $8::jsonb
|
||||
END,
|
||||
config = $9,
|
||||
default_pricing_mode = $10,
|
||||
default_discount_factor = $11,
|
||||
pricing_rule_set_id = NULLIF($12, '')::uuid,
|
||||
priority = $13,
|
||||
retry_policy = $14,
|
||||
rate_limit_policy = $15,
|
||||
updated_at = now()
|
||||
WHERE id = $1::uuid
|
||||
RETURNING id::text, provider, platform_key, name, COALESCE(internal_name, ''), COALESCE(base_url, ''), auth_type, status, priority,
|
||||
default_pricing_mode, default_discount_factor::float8, COALESCE(pricing_rule_set_id::text, ''),
|
||||
config, credentials, retry_policy, rate_limit_policy, created_at, updated_at`,
|
||||
id,
|
||||
input.Provider,
|
||||
input.PlatformKey,
|
||||
input.Name,
|
||||
strings.TrimSpace(input.InternalName),
|
||||
input.BaseURL,
|
||||
input.AuthType,
|
||||
credentials,
|
||||
string(config),
|
||||
input.DefaultPricingMode,
|
||||
input.DefaultDiscountFactor,
|
||||
input.PricingRuleSetID,
|
||||
input.Priority,
|
||||
string(retryPolicy),
|
||||
string(rateLimitPolicy),
|
||||
).Scan(
|
||||
&platform.ID,
|
||||
&platform.Provider,
|
||||
&platform.PlatformKey,
|
||||
&platform.Name,
|
||||
&platform.InternalName,
|
||||
&platform.BaseURL,
|
||||
&platform.AuthType,
|
||||
&platform.Status,
|
||||
&platform.Priority,
|
||||
&platform.DefaultPricingMode,
|
||||
&platform.DefaultDiscountFactor,
|
||||
&platform.PricingRuleSetID,
|
||||
&configBytes,
|
||||
&credentialsResultBytes,
|
||||
&retryPolicyBytes,
|
||||
&rateLimitPolicyBytes,
|
||||
&platform.CreatedAt,
|
||||
&platform.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return Platform{}, err
|
||||
}
|
||||
platform.Config = decodeObject(configBytes)
|
||||
platform.CredentialsPreview = maskCredentialsPreview(credentialsResultBytes)
|
||||
platform.RetryPolicy = decodeObject(retryPolicyBytes)
|
||||
platform.RateLimitPolicy = decodeObject(rateLimitPolicyBytes)
|
||||
return platform, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeletePlatform(ctx context.Context, id string) error {
|
||||
result, err := s.pool.Exec(ctx, `DELETE FROM integration_platforms WHERE id = $1::uuid`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return pgx.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) ListModels(ctx context.Context) ([]PlatformModel, error) {
|
||||
return s.listModels(ctx, "")
|
||||
}
|
||||
|
||||
func (s *Store) ListPlatformModels(ctx context.Context, platformID string) ([]PlatformModel, error) {
|
||||
return s.listModels(ctx, strings.TrimSpace(platformID))
|
||||
}
|
||||
|
||||
func (s *Store) listModels(ctx context.Context, platformID string) ([]PlatformModel, error) {
|
||||
args := []any{}
|
||||
where := ""
|
||||
if platformID != "" {
|
||||
where = "WHERE m.platform_id = $1::uuid"
|
||||
args = append(args, platformID)
|
||||
}
|
||||
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT m.id::text, m.platform_id::text, COALESCE(m.base_model_id::text, ''), p.provider, p.name,
|
||||
m.model_name, COALESCE(m.model_alias, ''), m.model_type, m.display_name,
|
||||
m.capability_override, m.capabilities, m.pricing_mode, COALESCE(m.discount_factor, 0)::float8,
|
||||
COALESCE(m.pricing_rule_set_id::text, ''), m.billing_config_override, m.billing_config,
|
||||
m.permission_config, m.retry_policy, m.rate_limit_policy, COALESCE(m.runtime_policy_set_id::text, ''), m.runtime_policy_override,
|
||||
m.enabled, m.created_at, m.updated_at
|
||||
FROM platform_models m
|
||||
JOIN integration_platforms p ON p.id = m.platform_id
|
||||
ORDER BY m.model_type ASC, m.model_name ASC`)
|
||||
`+where+`
|
||||
ORDER BY m.model_type ASC, m.model_name ASC`, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -438,6 +676,10 @@ ORDER BY m.model_type ASC, m.model_name ASC`)
|
||||
var capabilities []byte
|
||||
var billingConfigOverride []byte
|
||||
var billingConfig []byte
|
||||
var permissionConfig []byte
|
||||
var retryPolicy []byte
|
||||
var rateLimitPolicy []byte
|
||||
var runtimePolicyOverride []byte
|
||||
if err := rows.Scan(
|
||||
&model.ID,
|
||||
&model.PlatformID,
|
||||
@@ -455,6 +697,11 @@ ORDER BY m.model_type ASC, m.model_name ASC`)
|
||||
&model.PricingRuleSetID,
|
||||
&billingConfigOverride,
|
||||
&billingConfig,
|
||||
&permissionConfig,
|
||||
&retryPolicy,
|
||||
&rateLimitPolicy,
|
||||
&model.RuntimePolicySetID,
|
||||
&runtimePolicyOverride,
|
||||
&model.Enabled,
|
||||
&model.CreatedAt,
|
||||
&model.UpdatedAt,
|
||||
@@ -465,6 +712,10 @@ ORDER BY m.model_type ASC, m.model_name ASC`)
|
||||
model.Capabilities = decodeObject(capabilities)
|
||||
model.BillingConfigOverride = decodeObject(billingConfigOverride)
|
||||
model.BillingConfig = decodeObject(billingConfig)
|
||||
model.PermissionConfig = decodeObject(permissionConfig)
|
||||
model.RetryPolicy = decodeObject(retryPolicy)
|
||||
model.RateLimitPolicy = decodeObject(rateLimitPolicy)
|
||||
model.RuntimePolicyOverride = decodeObject(runtimePolicyOverride)
|
||||
models = append(models, model)
|
||||
}
|
||||
return models, rows.Err()
|
||||
@@ -731,6 +982,60 @@ ORDER BY created_at DESC`, gatewayUserID)
|
||||
return items, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) ListPlayableAPIKeys(ctx context.Context, user *auth.User) ([]PlayableAPIKey, error) {
|
||||
items, err := s.listRecoverableAPIKeys(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) > 0 {
|
||||
return items, nil
|
||||
}
|
||||
created, err := s.CreateAPIKey(ctx, CreateAPIKeyInput{
|
||||
Name: "Playground API Key",
|
||||
Scopes: []string{"chat", "image", "video"},
|
||||
}, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []PlayableAPIKey{{APIKey: created.APIKey, Secret: created.Secret}}, nil
|
||||
}
|
||||
|
||||
func (s *Store) listRecoverableAPIKeys(ctx context.Context, user *auth.User) ([]PlayableAPIKey, error) {
|
||||
gatewayUserID := localGatewayUserID(user)
|
||||
if gatewayUserID == "" {
|
||||
return nil, ErrLocalUserRequired
|
||||
}
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT id::text, COALESCE(gateway_tenant_id::text, ''), gateway_user_id::text,
|
||||
COALESCE(tenant_id, ''), COALESCE(tenant_key, ''), COALESCE(user_id, ''),
|
||||
key_prefix, name, scopes, COALESCE(user_group_id::text, ''),
|
||||
rate_limit_policy, quota_policy, status, COALESCE(expires_at::text, ''),
|
||||
COALESCE(last_used_at::text, ''), created_at, updated_at, COALESCE(key_secret, '')
|
||||
FROM gateway_api_keys
|
||||
WHERE gateway_user_id = $1::uuid
|
||||
AND status = 'active'
|
||||
AND deleted_at IS NULL
|
||||
AND COALESCE(key_secret, '') <> ''
|
||||
AND (expires_at IS NULL OR expires_at > now())
|
||||
ORDER BY created_at DESC`, gatewayUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]PlayableAPIKey, 0)
|
||||
for rows.Next() {
|
||||
var item PlayableAPIKey
|
||||
apiKey, err := scanAPIKeyWithSecret(rows, &item.Secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.APIKey = apiKey
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) CreateAPIKey(ctx context.Context, input CreateAPIKeyInput, user *auth.User) (CreatedAPIKey, error) {
|
||||
gatewayUserID := localGatewayUserID(user)
|
||||
if gatewayUserID == "" {
|
||||
@@ -764,17 +1069,17 @@ func (s *Store) CreateAPIKey(ctx context.Context, input CreateAPIKeyInput, user
|
||||
err = s.pool.QueryRow(ctx, `
|
||||
INSERT INTO gateway_api_keys (
|
||||
gateway_tenant_id, gateway_user_id, tenant_id, tenant_key, user_id,
|
||||
key_prefix, key_hash, name, scopes, expires_at
|
||||
key_prefix, key_secret, key_hash, name, scopes, expires_at
|
||||
)
|
||||
VALUES (NULLIF($1, '')::uuid, $2::uuid, NULLIF($3, ''), NULLIF($4, ''), NULLIF($5, ''),
|
||||
$6, $7, $8, $9::jsonb, NULLIF($10, '')::timestamptz)
|
||||
$6, $7, $8, $9, $10::jsonb, NULLIF($11, '')::timestamptz)
|
||||
RETURNING id::text, COALESCE(gateway_tenant_id::text, ''), gateway_user_id::text,
|
||||
COALESCE(tenant_id, ''), COALESCE(tenant_key, ''), COALESCE(user_id, ''),
|
||||
key_prefix, name, scopes, COALESCE(user_group_id::text, ''),
|
||||
rate_limit_policy, quota_policy, status, COALESCE(expires_at::text, ''),
|
||||
COALESCE(last_used_at::text, ''), created_at, updated_at`,
|
||||
user.GatewayTenantID, gatewayUserID, user.TenantID, user.TenantKey, user.ID,
|
||||
apiKeyPrefix(secret), string(keyHash), name, string(scopesJSON), strings.TrimSpace(input.ExpiresAt),
|
||||
apiKeyPrefix(secret), secret, string(keyHash), name, string(scopesJSON), strings.TrimSpace(input.ExpiresAt),
|
||||
).Scan(
|
||||
&item.ID,
|
||||
&item.GatewayTenantID,
|
||||
@@ -856,7 +1161,7 @@ func (s *Store) VerifyLocalAPIKey(ctx context.Context, secret string) (*auth.Use
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT k.id::text, k.key_hash, COALESCE(k.user_group_id::text, ''),
|
||||
SELECT k.id::text, k.key_hash, k.key_prefix, k.name, COALESCE(k.user_group_id::text, ''),
|
||||
u.id::text, u.username, u.roles, COALESCE(u.gateway_tenant_id::text, ''),
|
||||
COALESCE(u.tenant_id, ''), COALESCE(u.tenant_key, '')
|
||||
FROM gateway_api_keys k
|
||||
@@ -875,6 +1180,8 @@ WHERE k.key_prefix = $1
|
||||
for rows.Next() {
|
||||
var apiKeyID string
|
||||
var hash string
|
||||
var keyPrefix string
|
||||
var keyName string
|
||||
var userGroupID string
|
||||
var gatewayUserID string
|
||||
var username string
|
||||
@@ -882,7 +1189,7 @@ WHERE k.key_prefix = $1
|
||||
var gatewayTenantID string
|
||||
var tenantID string
|
||||
var tenantKey string
|
||||
if err := rows.Scan(&apiKeyID, &hash, &userGroupID, &gatewayUserID, &username, &rolesBytes, &gatewayTenantID, &tenantID, &tenantKey); err != nil {
|
||||
if err := rows.Scan(&apiKeyID, &hash, &keyPrefix, &keyName, &userGroupID, &gatewayUserID, &username, &rolesBytes, &gatewayTenantID, &tenantID, &tenantKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(hash), []byte(secret)) != nil {
|
||||
@@ -902,7 +1209,8 @@ WHERE k.key_prefix = $1
|
||||
GatewayUserID: gatewayUserID,
|
||||
UserGroupID: userGroupID,
|
||||
APIKeyID: apiKeyID,
|
||||
APIKeyName: prefix,
|
||||
APIKeyName: keyName,
|
||||
APIKeyPrefix: keyPrefix,
|
||||
}, nil
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
@@ -953,7 +1261,7 @@ SELECT NOT EXISTS (
|
||||
return GatewayUser{}, err
|
||||
}
|
||||
if isBootstrapUser {
|
||||
role = "admin"
|
||||
role = "manager"
|
||||
}
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO gateway_tenants (tenant_key, source, external_tenant_id, name)
|
||||
@@ -1197,21 +1505,16 @@ func (s *Store) CreateTask(ctx context.Context, input CreateTaskInput, user *aut
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
var task GatewayTask
|
||||
var requestBytes []byte
|
||||
var resultBytes []byte
|
||||
var billingsBytes []byte
|
||||
err = tx.QueryRow(ctx, `
|
||||
task, err := scanGatewayTask(tx.QueryRow(ctx, `
|
||||
INSERT INTO gateway_tasks (
|
||||
kind, run_mode, user_id, gateway_user_id, user_source, gateway_tenant_id, tenant_id, tenant_key,
|
||||
api_key_id, user_group_id, user_group_key, model, request, status, result, billings, finished_at
|
||||
api_key_id, api_key_name, api_key_prefix, user_group_id, user_group_key,
|
||||
model, requested_model, request, status, result, billings, finished_at
|
||||
)
|
||||
VALUES ($1, $2, $3, NULLIF($4, '')::uuid, COALESCE(NULLIF($5, ''), 'gateway'), NULLIF($6, '')::uuid, NULLIF($7, ''), NULLIF($8, ''), NULLIF($9, ''), NULLIF($10, '')::uuid, NULLIF($11, ''), $12, $13, $14, $15::jsonb, $16::jsonb, CASE WHEN $17 THEN now() ELSE NULL END)
|
||||
RETURNING id::text, kind, run_mode, user_id, COALESCE(gateway_user_id::text, ''), user_source,
|
||||
COALESCE(gateway_tenant_id::text, ''), COALESCE(tenant_id, ''), COALESCE(tenant_key, ''),
|
||||
COALESCE(user_group_id::text, ''), COALESCE(user_group_key, ''), model, request, status, result, billings, COALESCE(error, ''), created_at, updated_at`,
|
||||
input.Kind, runMode, user.ID, user.GatewayUserID, user.Source, user.GatewayTenantID, user.TenantID, user.TenantKey, user.APIKeyID, user.UserGroupID, user.UserGroupKey, input.Model, requestBody, status, resultBody, billingsBody, false,
|
||||
).Scan(&task.ID, &task.Kind, &task.RunMode, &task.UserID, &task.GatewayUserID, &task.UserSource, &task.GatewayTenantID, &task.TenantID, &task.TenantKey, &task.UserGroupID, &task.UserGroupKey, &task.Model, &requestBytes, &task.Status, &resultBytes, &billingsBytes, &task.Error, &task.CreatedAt, &task.UpdatedAt)
|
||||
VALUES ($1, $2, $3, NULLIF($4, '')::uuid, COALESCE(NULLIF($5, ''), 'gateway'), NULLIF($6, '')::uuid, NULLIF($7, ''), NULLIF($8, ''), NULLIF($9, ''), NULLIF($10, ''), NULLIF($11, ''), NULLIF($12, '')::uuid, NULLIF($13, ''), $14, $14, $15, $16, $17::jsonb, $18::jsonb, CASE WHEN $19 THEN now() ELSE NULL END)
|
||||
RETURNING `+gatewayTaskColumns,
|
||||
input.Kind, runMode, user.ID, user.GatewayUserID, user.Source, user.GatewayTenantID, user.TenantID, user.TenantKey, user.APIKeyID, user.APIKeyName, user.APIKeyPrefix, user.UserGroupID, user.UserGroupKey, input.Model, requestBody, status, resultBody, billingsBody, false,
|
||||
))
|
||||
if err != nil {
|
||||
return GatewayTask{}, err
|
||||
}
|
||||
@@ -1229,30 +1532,77 @@ VALUES ($1::uuid, $2, $3, NULLIF($4, ''), NULLIF($5, ''), $6, NULLIF($7, ''), $8
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return GatewayTask{}, err
|
||||
}
|
||||
task.Request = decodeObject(requestBytes)
|
||||
task.Result = decodeObject(resultBytes)
|
||||
task.Billings = decodeArray(billingsBytes)
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetTask(ctx context.Context, taskID string) (GatewayTask, error) {
|
||||
task, err := scanGatewayTask(s.pool.QueryRow(ctx, `
|
||||
SELECT `+gatewayTaskColumns+`
|
||||
FROM gateway_tasks
|
||||
WHERE id=$1`, taskID,
|
||||
))
|
||||
if err != nil {
|
||||
return GatewayTask{}, err
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
type taskScanner interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func scanGatewayTask(scanner taskScanner) (GatewayTask, error) {
|
||||
var task GatewayTask
|
||||
var requestBytes []byte
|
||||
var resultBytes []byte
|
||||
var billingsBytes []byte
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT id::text, kind, run_mode, user_id, COALESCE(gateway_user_id::text, ''), user_source,
|
||||
COALESCE(gateway_tenant_id::text, ''), COALESCE(tenant_id, ''), COALESCE(tenant_key, ''),
|
||||
COALESCE(user_group_id::text, ''), COALESCE(user_group_key, ''), model, request, status, result, billings, COALESCE(error, ''), created_at, updated_at
|
||||
FROM gateway_tasks
|
||||
WHERE id=$1`, taskID,
|
||||
).Scan(&task.ID, &task.Kind, &task.RunMode, &task.UserID, &task.GatewayUserID, &task.UserSource, &task.GatewayTenantID, &task.TenantID, &task.TenantKey, &task.UserGroupID, &task.UserGroupKey, &task.Model, &requestBytes, &task.Status, &resultBytes, &billingsBytes, &task.Error, &task.CreatedAt, &task.UpdatedAt)
|
||||
if err != nil {
|
||||
var usageBytes []byte
|
||||
var metricsBytes []byte
|
||||
var billingSummaryBytes []byte
|
||||
if err := scanner.Scan(
|
||||
&task.ID,
|
||||
&task.Kind,
|
||||
&task.RunMode,
|
||||
&task.UserID,
|
||||
&task.GatewayUserID,
|
||||
&task.UserSource,
|
||||
&task.GatewayTenantID,
|
||||
&task.TenantID,
|
||||
&task.TenantKey,
|
||||
&task.APIKeyID,
|
||||
&task.APIKeyName,
|
||||
&task.APIKeyPrefix,
|
||||
&task.UserGroupID,
|
||||
&task.UserGroupKey,
|
||||
&task.Model,
|
||||
&task.ModelType,
|
||||
&task.RequestedModel,
|
||||
&task.ResolvedModel,
|
||||
&task.RequestID,
|
||||
&requestBytes,
|
||||
&task.Status,
|
||||
&resultBytes,
|
||||
&billingsBytes,
|
||||
&usageBytes,
|
||||
&metricsBytes,
|
||||
&billingSummaryBytes,
|
||||
&task.FinalChargeAmount,
|
||||
&task.ResponseStartedAt,
|
||||
&task.ResponseFinishedAt,
|
||||
&task.ResponseDurationMS,
|
||||
&task.Error,
|
||||
&task.CreatedAt,
|
||||
&task.UpdatedAt,
|
||||
&task.FinishedAt,
|
||||
); err != nil {
|
||||
return GatewayTask{}, err
|
||||
}
|
||||
task.Request = decodeObject(requestBytes)
|
||||
task.Result = decodeObject(resultBytes)
|
||||
task.Billings = decodeArray(billingsBytes)
|
||||
task.Usage = decodeObject(usageBytes)
|
||||
task.Metrics = decodeObject(metricsBytes)
|
||||
task.BillingSummary = decodeObject(billingSummaryBytes)
|
||||
return task, nil
|
||||
}
|
||||
|
||||
@@ -1342,6 +1692,39 @@ func scanAPIKey(scanner apiKeyScanner) (APIKey, error) {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func scanAPIKeyWithSecret(scanner apiKeyScanner, secret *string) (APIKey, error) {
|
||||
var item APIKey
|
||||
var scopesBytes []byte
|
||||
var rateLimitPolicy []byte
|
||||
var quotaPolicy []byte
|
||||
if err := scanner.Scan(
|
||||
&item.ID,
|
||||
&item.GatewayTenantID,
|
||||
&item.GatewayUserID,
|
||||
&item.TenantID,
|
||||
&item.TenantKey,
|
||||
&item.UserID,
|
||||
&item.KeyPrefix,
|
||||
&item.Name,
|
||||
&scopesBytes,
|
||||
&item.UserGroupID,
|
||||
&rateLimitPolicy,
|
||||
"aPolicy,
|
||||
&item.Status,
|
||||
&item.ExpiresAt,
|
||||
&item.LastUsedAt,
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
secret,
|
||||
); err != nil {
|
||||
return APIKey{}, err
|
||||
}
|
||||
item.Scopes = decodeStringArray(scopesBytes)
|
||||
item.RateLimitPolicy = decodeObject(rateLimitPolicy)
|
||||
item.QuotaPolicy = decodeObject(quotaPolicy)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func localGatewayUserID(user *auth.User) string {
|
||||
if user == nil {
|
||||
return ""
|
||||
@@ -1477,6 +1860,50 @@ func decodeObject(bytes []byte) map[string]any {
|
||||
return out
|
||||
}
|
||||
|
||||
func maskCredentialsPreview(bytes []byte) map[string]any {
|
||||
credentials := decodeObject(bytes)
|
||||
if len(credentials) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]any, len(credentials))
|
||||
for key, value := range credentials {
|
||||
out[key] = maskCredentialValue(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func maskCredentialValue(value any) any {
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return maskSecret(typed)
|
||||
case map[string]any:
|
||||
out := make(map[string]any, len(typed))
|
||||
for key, nested := range typed {
|
||||
out[key] = maskCredentialValue(nested)
|
||||
}
|
||||
return out
|
||||
case []any:
|
||||
out := make([]any, 0, len(typed))
|
||||
for _, nested := range typed {
|
||||
out = append(out, maskCredentialValue(nested))
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func maskSecret(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
if len(value) <= 6 {
|
||||
return strings.Repeat("*", len(value))
|
||||
}
|
||||
return value[:3] + strings.Repeat("*", len(value)-6) + value[len(value)-3:]
|
||||
}
|
||||
|
||||
func decodeArray(bytes []byte) []any {
|
||||
if len(bytes) == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user