fix gateway loopback validation chains

This commit is contained in:
2026-05-11 08:48:02 +08:00
parent ff666b1ece
commit ca7e76e815
42 changed files with 1641 additions and 129 deletions
+6 -3
View File
@@ -376,7 +376,7 @@ CREATE TABLE IF NOT EXISTS platform_models (
base_model_id uuid REFERENCES base_model_catalog(id) ON DELETE SET NULL,
model_name text NOT NULL,
model_alias text,
model_type text NOT NULL,
model_type jsonb NOT NULL DEFAULT '[]'::jsonb,
display_name text NOT NULL DEFAULT '',
capability_override jsonb NOT NULL DEFAULT '{}'::jsonb,
capabilities jsonb NOT NULL DEFAULT '{}'::jsonb,
@@ -391,14 +391,17 @@ CREATE TABLE IF NOT EXISTS platform_models (
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(platform_id, model_name, model_type)
UNIQUE(platform_id, model_name)
);
CREATE INDEX IF NOT EXISTS idx_platform_models_base
ON platform_models(base_model_id);
CREATE INDEX IF NOT EXISTS idx_platform_models_lookup
ON platform_models(model_type, model_name, enabled);
ON platform_models(model_name, enabled);
CREATE INDEX IF NOT EXISTS idx_platform_models_model_type
ON platform_models USING gin(model_type);
CREATE INDEX IF NOT EXISTS idx_platform_models_alias
ON platform_models(model_alias);
@@ -323,7 +323,7 @@ CREATE TABLE IF NOT EXISTS platform_models (
base_model_id uuid REFERENCES base_model_catalog(id) ON DELETE SET NULL,
model_name text NOT NULL,
model_alias text,
model_type text NOT NULL,
model_type jsonb NOT NULL DEFAULT '[]'::jsonb,
display_name text NOT NULL DEFAULT '',
capability_override jsonb NOT NULL DEFAULT '{}'::jsonb,
capabilities jsonb NOT NULL DEFAULT '{}'::jsonb,
@@ -338,7 +338,7 @@ CREATE TABLE IF NOT EXISTS platform_models (
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(platform_id, model_name, model_type)
UNIQUE(platform_id, model_name)
);
ALTER TABLE IF EXISTS platform_models
@@ -636,7 +636,10 @@ CREATE INDEX IF NOT EXISTS idx_gateway_recharge_orders_user
CREATE INDEX IF NOT EXISTS idx_platform_models_base
ON platform_models(base_model_id);
CREATE INDEX IF NOT EXISTS idx_platform_models_lookup
ON platform_models(model_type, model_name, enabled);
ON platform_models(model_name, enabled);
CREATE INDEX IF NOT EXISTS idx_platform_models_model_type
ON platform_models USING gin(model_type);
CREATE INDEX IF NOT EXISTS idx_platform_models_alias
ON platform_models(model_alias);
CREATE INDEX IF NOT EXISTS idx_platform_models_capabilities
@@ -199,7 +199,19 @@ INSERT INTO platform_models (
platform_id, base_model_id, model_name, model_alias, model_type, display_name,
capabilities, pricing_mode, billing_config, retry_policy, rate_limit_policy, enabled
)
SELECT p.id, b.id, b.provider_model_name, b.canonical_model_key, b.model_type, b.display_name,
SELECT p.id, b.id, b.provider_model_name, b.canonical_model_key,
CASE b.model_type
WHEN 'chat' THEN '["text_generate"]'::jsonb
WHEN 'text' THEN '["text_generate"]'::jsonb
WHEN 'responses' THEN '["text_generate"]'::jsonb
WHEN 'image' THEN '["image_generate","image_edit"]'::jsonb
WHEN 'images.generations' THEN '["image_generate"]'::jsonb
WHEN 'images.edits' THEN '["image_edit"]'::jsonb
WHEN 'video' THEN '["video_generate"]'::jsonb
WHEN 'videos.generations' THEN '["video_generate"]'::jsonb
ELSE jsonb_build_array(b.model_type)
END,
b.display_name,
b.capabilities, 'inherit_discount', b.base_billing_config,
'{"enabled":true,"maxAttempts":2}'::jsonb,
b.default_rate_limit_policy,
@@ -207,7 +219,7 @@ SELECT p.id, b.id, b.provider_model_name, b.canonical_model_key, b.model_type, b
FROM integration_platforms p
JOIN base_model_catalog b ON b.provider_key = p.provider
WHERE p.platform_key IN ('openai-simulation', 'gemini-simulation')
ON CONFLICT (platform_id, model_name, model_type) DO UPDATE
ON CONFLICT (platform_id, model_name) DO UPDATE
SET base_model_id = EXCLUDED.base_model_id,
model_alias = EXCLUDED.model_alias,
display_name = EXCLUDED.display_name,
@@ -0,0 +1,138 @@
DO $$
DECLARE
column_type text;
BEGIN
SELECT data_type
INTO column_type
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'platform_models'
AND column_name = 'model_type';
IF column_type IS DISTINCT FROM 'jsonb' THEN
DROP INDEX IF EXISTS idx_platform_models_lookup;
ALTER TABLE platform_models
DROP CONSTRAINT IF EXISTS platform_models_platform_id_model_name_model_type_key;
ALTER TABLE platform_models
ADD COLUMN IF NOT EXISTS model_type_next jsonb NOT NULL DEFAULT '[]'::jsonb;
UPDATE platform_models
SET model_type_next = CASE trim(model_type)
WHEN 'chat' THEN '["text_generate"]'::jsonb
WHEN 'text' THEN '["text_generate"]'::jsonb
WHEN 'responses' THEN '["text_generate"]'::jsonb
WHEN 'image' THEN '["image_generate","image_edit"]'::jsonb
WHEN 'images.generations' THEN '["image_generate"]'::jsonb
WHEN 'images.edits' THEN '["image_edit"]'::jsonb
WHEN 'video' THEN '["video_generate"]'::jsonb
WHEN 'videos.generations' THEN '["video_generate"]'::jsonb
ELSE jsonb_build_array(trim(model_type))
END;
WITH ranked AS (
SELECT id,
first_value(id) OVER (
PARTITION BY platform_id, model_name
ORDER BY created_at ASC, id ASC
) AS keep_id,
row_number() OVER (
PARTITION BY platform_id, model_name
ORDER BY created_at ASC, id ASC
) AS row_number
FROM platform_models
),
merged AS (
SELECT ranked.keep_id,
jsonb_agg(DISTINCT type_value ORDER BY type_value) AS model_type
FROM ranked
JOIN platform_models model_row ON model_row.id = ranked.id
CROSS JOIN LATERAL jsonb_array_elements_text(model_row.model_type_next) AS type_item(type_value)
GROUP BY ranked.keep_id
)
UPDATE platform_models target
SET model_type_next = merged.model_type
FROM merged
WHERE target.id = merged.keep_id;
WITH ranked AS (
SELECT id,
first_value(id) OVER (
PARTITION BY platform_id, model_name
ORDER BY created_at ASC, id ASC
) AS keep_id,
row_number() OVER (
PARTITION BY platform_id, model_name
ORDER BY created_at ASC, id ASC
) AS row_number
FROM platform_models
)
UPDATE gateway_access_rules rules
SET resource_id = ranked.keep_id
FROM ranked
WHERE ranked.row_number > 1
AND rules.resource_type = 'platform_model'
AND rules.resource_id = ranked.id;
WITH ranked AS (
SELECT id,
row_number() OVER (
PARTITION BY platform_id, model_name
ORDER BY created_at ASC, id ASC
) AS row_number
FROM platform_models
)
DELETE FROM platform_models model_row
USING ranked
WHERE model_row.id = ranked.id
AND ranked.row_number > 1;
ALTER TABLE platform_models DROP COLUMN model_type;
ALTER TABLE platform_models RENAME COLUMN model_type_next TO model_type;
END IF;
END $$;
UPDATE platform_models
SET model_type = CASE
WHEN jsonb_typeof(model_type) = 'array' THEN model_type
WHEN jsonb_typeof(model_type) = 'string' THEN jsonb_build_array(model_type #>> '{}')
ELSE '[]'::jsonb
END;
UPDATE platform_models
SET model_type = COALESCE((
SELECT jsonb_agg(DISTINCT normalized_type ORDER BY normalized_type)
FROM jsonb_array_elements_text(platform_models.model_type) AS item(model_type_value)
CROSS JOIN LATERAL (
VALUES
(CASE item.model_type_value
WHEN 'chat' THEN 'text_generate'
WHEN 'text' THEN 'text_generate'
WHEN 'responses' THEN 'text_generate'
WHEN 'images.generations' THEN 'image_generate'
WHEN 'images.edits' THEN 'image_edit'
WHEN 'video' THEN 'video_generate'
WHEN 'videos.generations' THEN 'video_generate'
ELSE item.model_type_value
END)
) AS normalized(normalized_type)
WHERE trim(normalized_type) <> ''
), '[]'::jsonb);
UPDATE platform_models
SET model_type = '["image_generate","image_edit"]'::jsonb
WHERE model_type = '["image"]'::jsonb;
ALTER TABLE platform_models
ALTER COLUMN model_type SET NOT NULL,
ALTER COLUMN model_type SET DEFAULT '[]'::jsonb;
DROP INDEX IF EXISTS idx_platform_models_lookup;
CREATE INDEX IF NOT EXISTS idx_platform_models_lookup
ON platform_models(model_name, enabled);
CREATE INDEX IF NOT EXISTS idx_platform_models_model_type
ON platform_models USING gin(model_type);
CREATE UNIQUE INDEX IF NOT EXISTS idx_platform_models_platform_model_name
ON platform_models(platform_id, model_name);
@@ -0,0 +1,11 @@
UPDATE platform_models m
SET rate_limit_policy = '{}'::jsonb,
updated_at = now()
FROM base_model_catalog b,
model_runtime_policy_sets rp
WHERE m.base_model_id = b.id
AND m.runtime_policy_set_id = rp.id
AND m.runtime_policy_set_id IS NOT NULL
AND m.runtime_policy_set_id IS DISTINCT FROM b.runtime_policy_set_id
AND m.rate_limit_policy = b.default_rate_limit_policy
AND rp.rate_limit_policy <> '{}'::jsonb;
@@ -0,0 +1,108 @@
DO $$
DECLARE
column_type text;
BEGIN
SELECT data_type
INTO column_type
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'base_model_catalog'
AND column_name = 'model_type';
IF column_type IS DISTINCT FROM 'jsonb' THEN
DROP INDEX IF EXISTS idx_base_model_catalog_type;
ALTER TABLE base_model_catalog
ADD COLUMN IF NOT EXISTS model_type_next jsonb NOT NULL DEFAULT '[]'::jsonb;
WITH source_types AS (
SELECT id,
CASE
WHEN jsonb_typeof(capabilities->'originalTypes') = 'array' THEN capabilities->'originalTypes'
WHEN jsonb_typeof(metadata->'originalTypes') = 'array' THEN metadata->'originalTypes'
ELSE jsonb_build_array(model_type)
END AS model_types
FROM base_model_catalog
),
normalized_types AS (
SELECT source_types.id, normalized.model_type
FROM source_types
CROSS JOIN LATERAL jsonb_array_elements_text(source_types.model_types) AS raw_type(model_type)
CROSS JOIN LATERAL (
SELECT 'text_generate' AS model_type
WHERE raw_type.model_type IN ('chat', 'text', 'responses')
UNION ALL SELECT 'image_generate'
WHERE raw_type.model_type = 'image'
UNION ALL SELECT 'image_edit'
WHERE raw_type.model_type = 'image'
UNION ALL SELECT 'image_generate'
WHERE raw_type.model_type = 'images.generations'
UNION ALL SELECT 'image_edit'
WHERE raw_type.model_type = 'images.edits'
UNION ALL SELECT 'video_generate'
WHERE raw_type.model_type IN ('video', 'videos.generations')
UNION ALL SELECT raw_type.model_type
WHERE trim(raw_type.model_type) <> ''
AND raw_type.model_type NOT IN (
'chat', 'text', 'responses', 'image', 'images.generations', 'images.edits', 'video', 'videos.generations'
)
) AS normalized
)
UPDATE base_model_catalog target
SET model_type_next = COALESCE((
SELECT jsonb_agg(DISTINCT normalized_types.model_type ORDER BY normalized_types.model_type)
FROM normalized_types
WHERE normalized_types.id = target.id
), '[]'::jsonb);
ALTER TABLE base_model_catalog DROP COLUMN model_type;
ALTER TABLE base_model_catalog RENAME COLUMN model_type_next TO model_type;
END IF;
END $$;
UPDATE base_model_catalog
SET model_type = CASE
WHEN jsonb_typeof(model_type) = 'array' THEN model_type
WHEN jsonb_typeof(model_type) = 'string' THEN jsonb_build_array(model_type #>> '{}')
ELSE '[]'::jsonb
END;
UPDATE base_model_catalog
SET default_snapshot = default_snapshot || jsonb_build_object('modelType', model_type)
WHERE catalog_type = 'system'
AND COALESCE(default_snapshot, '{}'::jsonb) <> '{}'::jsonb;
ALTER TABLE base_model_catalog
ALTER COLUMN model_type SET NOT NULL,
ALTER COLUMN model_type SET DEFAULT '[]'::jsonb;
CREATE INDEX IF NOT EXISTS idx_base_model_catalog_type
ON base_model_catalog(provider_key, status);
CREATE INDEX IF NOT EXISTS idx_base_model_catalog_model_type
ON base_model_catalog USING gin(model_type);
CREATE OR REPLACE FUNCTION fill_system_base_model_default_snapshot()
RETURNS trigger AS $$
BEGIN
IF NEW.catalog_type = 'system' AND NEW.default_snapshot IS NULL THEN
NEW.default_snapshot = jsonb_build_object(
'providerKey', NEW.provider_key,
'canonicalModelKey', NEW.canonical_model_key,
'providerModelName', NEW.provider_model_name,
'modelType', NEW.model_type,
'modelAlias', NEW.display_name,
'capabilities', NEW.capabilities,
'baseBillingConfig', NEW.base_billing_config,
'defaultRateLimitPolicy', NEW.default_rate_limit_policy,
'pricingRuleSetId', COALESCE(NEW.pricing_rule_set_id::text, ''),
'runtimePolicySetId', COALESCE(NEW.runtime_policy_set_id::text, ''),
'runtimePolicyOverride', NEW.runtime_policy_override,
'metadata', NEW.metadata,
'pricingVersion', NEW.pricing_version,
'status', NEW.status
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
@@ -0,0 +1,9 @@
UPDATE platform_models m
SET model_type = b.model_type,
updated_at = now()
FROM base_model_catalog b
WHERE m.base_model_id = b.id
AND jsonb_typeof(b.model_type) = 'array'
AND jsonb_array_length(b.model_type) > 0
AND m.model_type <> b.model_type
AND COALESCE(m.capabilities, '{}'::jsonb) = COALESCE(b.capabilities, '{}'::jsonb);
@@ -0,0 +1,48 @@
ALTER TABLE gateway_wallet_accounts
ADD COLUMN IF NOT EXISTS total_recharged numeric NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS total_spent numeric NOT NULL DEFAULT 0;
ALTER TABLE gateway_wallet_transactions
ADD COLUMN IF NOT EXISTS account_id uuid REFERENCES gateway_wallet_accounts(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS direction text,
ADD COLUMN IF NOT EXISTS balance_before numeric,
ADD COLUMN IF NOT EXISTS idempotency_key text;
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'gateway_wallet_transactions'
AND column_name = 'wallet_account_id'
) THEN
EXECUTE 'UPDATE gateway_wallet_transactions SET account_id = wallet_account_id WHERE account_id IS NULL';
END IF;
END $$;
UPDATE gateway_wallet_transactions
SET direction = CASE
WHEN transaction_type IN ('recharge', 'refund', 'credit') THEN 'credit'
ELSE 'debit'
END
WHERE direction IS NULL;
UPDATE gateway_wallet_transactions
SET balance_before = COALESCE(balance_after, 0) + CASE
WHEN direction = 'debit' THEN COALESCE(amount, 0)
ELSE -COALESCE(amount, 0)
END
WHERE balance_before IS NULL;
ALTER TABLE gateway_wallet_transactions
ALTER COLUMN direction SET DEFAULT 'debit',
ALTER COLUMN direction SET NOT NULL,
ALTER COLUMN balance_before SET DEFAULT 0,
ALTER COLUMN balance_before SET NOT NULL;
CREATE INDEX IF NOT EXISTS idx_gateway_wallet_transactions_account
ON gateway_wallet_transactions(account_id, created_at DESC);
CREATE UNIQUE INDEX IF NOT EXISTS uniq_gateway_wallet_tx_idempotency
ON gateway_wallet_transactions(account_id, idempotency_key)
WHERE idempotency_key IS NOT NULL;