Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
538ae31a
Commit
538ae31a
authored
Apr 30, 2026
by
陈曦
Browse files
merge v0.1.121 and fixed conflict
parents
74828a7c
48912014
Pipeline
#82338
passed with stage
in 17 seconds
Changes
151
Pipelines
3
Hide whitespace changes
Inline
Side-by-side
backend/migration_release/096_payment_provider_instances.sql
deleted
100644 → 0
View file @
74828a7c
CREATE
TABLE
IF
NOT
EXISTS
payment_provider_instances
(
id
BIGSERIAL
PRIMARY
KEY
,
provider_key
VARCHAR
(
30
)
NOT
NULL
,
name
VARCHAR
(
100
)
NOT
NULL
DEFAULT
''
,
config
TEXT
NOT
NULL
,
supported_types
VARCHAR
(
200
)
NOT
NULL
DEFAULT
''
,
enabled
BOOLEAN
NOT
NULL
DEFAULT
TRUE
,
sort_order
INT
NOT
NULL
DEFAULT
0
,
limits
TEXT
NOT
NULL
DEFAULT
''
,
refund_enabled
BOOLEAN
NOT
NULL
DEFAULT
FALSE
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_payment_provider_instances_provider_key
ON
payment_provider_instances
(
provider_key
);
CREATE
INDEX
IF
NOT
EXISTS
idx_payment_provider_instances_enabled
ON
payment_provider_instances
(
enabled
);
backend/migration_release/097_fix_settings_updated_at_default.sql
deleted
100644 → 0
View file @
74828a7c
-- 097_fix_settings_updated_at_default.sql
--
-- 修复 settings.updated_at 列在历史实例上可能缺失 SQL DEFAULT 的问题。
--
-- 背景:
-- 早期版本曾依赖 ent 自动迁移建表(ent 的 Default(time.Now) 仅是 Go 层默认值,
-- 不会在 SQL 层落地为 DEFAULT),随后引入的 005_schema_parity.sql 使用了
-- CREATE TABLE IF NOT EXISTS,对已存在的 settings 表不会重建,导致这部分实例
-- 的 updated_at 列虽然是 NOT NULL,但缺少 SQL DEFAULT。
--
-- 后续 098_migrate_purchase_subscription_to_custom_menu.sql 是项目中唯一使用
-- 原生 SQL INSERT INTO settings 的迁移(其余 settings 写入都走 ent / Go 层),
-- 因此该 schema 缺陷直到 098 才会触发:
-- "null value in column \"updated_at\" of relation \"settings\" violates not-null constraint"
--
-- 幂等性:
-- - ALTER COLUMN ... SET DEFAULT NOW() 在已经具备相同默认值的实例上是无操作,
-- 不会报错(PostgreSQL 允许重复设置相同的默认值)。
-- - UPDATE 子句的 WHERE updated_at IS NULL 在健康实例上匹配 0 行,不影响数据。
--
-- 这样可以同时兼容:
-- 1. 从未运行过旧版迁移的全新部署(005 已经把列建对,本迁移变成 no-op)。
-- 2. 历史损坏实例(本迁移修复缺失的默认值,使后续 098 能够正常 INSERT)。
ALTER
TABLE
settings
ALTER
COLUMN
updated_at
SET
DEFAULT
NOW
();
UPDATE
settings
SET
updated_at
=
NOW
()
WHERE
updated_at
IS
NULL
;
backend/migration_release/098_migrate_purchase_subscription_to_custom_menu.sql
deleted
100644 → 0
View file @
74828a7c
-- 096_migrate_purchase_subscription_to_custom_menu.sql
--
-- Migrates the legacy purchase_subscription_url setting into custom_menu_items.
-- After migration, purchase_subscription_enabled is set to "false" and
-- purchase_subscription_url is cleared.
--
-- Idempotent: skips if custom_menu_items already contains
-- "migrated_purchase_subscription".
DO
$$
DECLARE
v_enabled
text
;
v_url
text
;
v_raw
text
;
v_items
jsonb
;
v_new_item
jsonb
;
BEGIN
-- Read legacy settings
SELECT
value
INTO
v_enabled
FROM
settings
WHERE
key
=
'purchase_subscription_enabled'
;
SELECT
value
INTO
v_url
FROM
settings
WHERE
key
=
'purchase_subscription_url'
;
-- Skip if not enabled or URL is empty
IF
COALESCE
(
v_enabled
,
''
)
<>
'true'
OR
COALESCE
(
TRIM
(
v_url
),
''
)
=
''
THEN
RETURN
;
END
IF
;
-- Read current custom_menu_items
SELECT
value
INTO
v_raw
FROM
settings
WHERE
key
=
'custom_menu_items'
;
IF
COALESCE
(
v_raw
,
''
)
=
''
OR
v_raw
=
'null'
THEN
v_items
:
=
'[]'
::
jsonb
;
ELSE
v_items
:
=
v_raw
::
jsonb
;
END
IF
;
-- Skip if already migrated (item with id "migrated_purchase_subscription" exists)
IF
EXISTS
(
SELECT
1
FROM
jsonb_array_elements
(
v_items
)
elem
WHERE
elem
->>
'id'
=
'migrated_purchase_subscription'
)
THEN
RETURN
;
END
IF
;
-- Build the new menu item
v_new_item
:
=
jsonb_build_object
(
'id'
,
'migrated_purchase_subscription'
,
'label'
,
'Purchase'
,
'icon_svg'
,
''
,
'url'
,
TRIM
(
v_url
),
'visibility'
,
'user'
,
'sort_order'
,
100
);
-- Append to array
v_items
:
=
v_items
||
jsonb_build_array
(
v_new_item
);
-- Upsert custom_menu_items
INSERT
INTO
settings
(
key
,
value
)
VALUES
(
'custom_menu_items'
,
v_items
::
text
)
ON
CONFLICT
(
key
)
DO
UPDATE
SET
value
=
EXCLUDED
.
value
;
-- Clear legacy settings
UPDATE
settings
SET
value
=
'false'
WHERE
key
=
'purchase_subscription_enabled'
;
UPDATE
settings
SET
value
=
''
WHERE
key
=
'purchase_subscription_url'
;
RAISE
NOTICE
'[migration-096] Migrated purchase_subscription_url (%) to custom_menu_items'
,
v_url
;
END
$$
;
backend/migration_release/099_fix_migrated_purchase_menu_label_icon.sql
deleted
100644 → 0
View file @
74828a7c
-- 097_fix_migrated_purchase_menu_label_icon.sql
--
-- Fixes the custom menu item created by migration 096: updates the label
-- from hardcoded English "Purchase" to "充值/订阅", and sets the icon_svg
-- to a credit-card SVG matching the sidebar CreditCardIcon.
--
-- Idempotent: only modifies items where id = 'migrated_purchase_subscription'.
DO
$$
DECLARE
v_raw
text
;
v_items
jsonb
;
v_idx
int
;
v_icon
text
;
v_elem
jsonb
;
v_i
int
:
=
0
;
BEGIN
SELECT
value
INTO
v_raw
FROM
settings
WHERE
key
=
'custom_menu_items'
;
IF
COALESCE
(
v_raw
,
''
)
=
''
OR
v_raw
=
'null'
THEN
RETURN
;
END
IF
;
v_items
:
=
v_raw
::
jsonb
;
-- Find the index of the migrated item by iterating the array
v_idx
:
=
NULL
;
FOR
v_elem
IN
SELECT
jsonb_array_elements
(
v_items
)
LOOP
IF
v_elem
->>
'id'
=
'migrated_purchase_subscription'
THEN
v_idx
:
=
v_i
;
EXIT
;
END
IF
;
v_i
:
=
v_i
+
1
;
END
LOOP
;
IF
v_idx
IS
NULL
THEN
RETURN
;
-- item not found, nothing to fix
END
IF
;
-- Credit card SVG (Heroicons outline, matches CreditCardIcon in AppSidebar)
v_icon
:
=
'<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z"/></svg>'
;
-- Update label and icon_svg
v_items
:
=
jsonb_set
(
v_items
,
ARRAY
[
v_idx
::
text
,
'label'
],
'"充值/订阅"'
::
jsonb
);
v_items
:
=
jsonb_set
(
v_items
,
ARRAY
[
v_idx
::
text
,
'icon_svg'
],
to_jsonb
(
v_icon
));
UPDATE
settings
SET
value
=
v_items
::
text
WHERE
key
=
'custom_menu_items'
;
RAISE
NOTICE
'[migration-097] Fixed migrated_purchase_subscription: label=充值/订阅, icon=CreditCard SVG'
;
END
$$
;
backend/migration_release/100_remove_easypay_from_enabled_payment_types.sql
deleted
100644 → 0
View file @
74828a7c
-- 098_remove_easypay_from_enabled_payment_types.sql
--
-- Removes "easypay" from ENABLED_PAYMENT_TYPES setting.
-- "easypay" is a provider key, not a payment type. Valid payment types
-- are: alipay, wxpay, alipay_direct, wxpay_direct, stripe.
--
-- Idempotent: safe to run multiple times.
UPDATE
settings
SET
value
=
array_to_string
(
array_remove
(
string_to_array
(
value
,
','
),
'easypay'
),
','
)
WHERE
key
=
'ENABLED_PAYMENT_TYPES'
AND
value
LIKE
'%easypay%'
;
backend/migration_release/101_add_account_stats_pricing.sql
deleted
100644 → 0
View file @
74828a7c
-- Account statistics pricing: allow channels to configure custom pricing for account cost tracking.
-- 1. Channel-level toggle
ALTER
TABLE
channels
ADD
COLUMN
IF
NOT
EXISTS
apply_pricing_to_account_stats
BOOLEAN
NOT
NULL
DEFAULT
FALSE
;
-- 2. Account stats pricing rules (ordered list per channel)
CREATE
TABLE
IF
NOT
EXISTS
channel_account_stats_pricing_rules
(
id
BIGSERIAL
PRIMARY
KEY
,
channel_id
BIGINT
NOT
NULL
REFERENCES
channels
(
id
)
ON
DELETE
CASCADE
,
name
VARCHAR
(
100
)
NOT
NULL
DEFAULT
''
,
group_ids
BIGINT
[]
NOT
NULL
DEFAULT
'{}'
,
account_ids
BIGINT
[]
NOT
NULL
DEFAULT
'{}'
,
sort_order
INT
NOT
NULL
DEFAULT
0
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_cas_pricing_rules_channel_id
ON
channel_account_stats_pricing_rules
(
channel_id
);
-- 3. Model pricing for each rule (same structure as channel_model_pricing)
CREATE
TABLE
IF
NOT
EXISTS
channel_account_stats_model_pricing
(
id
BIGSERIAL
PRIMARY
KEY
,
rule_id
BIGINT
NOT
NULL
REFERENCES
channel_account_stats_pricing_rules
(
id
)
ON
DELETE
CASCADE
,
platform
VARCHAR
(
50
)
NOT
NULL
DEFAULT
''
,
models
JSONB
NOT
NULL
DEFAULT
'[]'
,
billing_mode
VARCHAR
(
20
)
NOT
NULL
DEFAULT
'token'
,
input_price
NUMERIC
(
20
,
10
),
output_price
NUMERIC
(
20
,
10
),
cache_write_price
NUMERIC
(
20
,
10
),
cache_read_price
NUMERIC
(
20
,
10
),
image_output_price
NUMERIC
(
20
,
10
),
per_request_price
NUMERIC
(
20
,
10
),
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_cas_model_pricing_rule_id
ON
channel_account_stats_model_pricing
(
rule_id
);
-- 4. Usage logs: pre-computed account stats cost (NULL = use default formula)
ALTER
TABLE
usage_logs
ADD
COLUMN
IF
NOT
EXISTS
account_stats_cost
NUMERIC
(
20
,
10
);
backend/migration_release/101_add_balance_notify_fields.sql
deleted
100644 → 0
View file @
74828a7c
-- Balance notification user preferences
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
balance_notify_enabled
BOOLEAN
NOT
NULL
DEFAULT
true
;
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
balance_notify_threshold
DECIMAL
(
20
,
8
)
DEFAULT
NULL
;
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
balance_notify_extra_emails
TEXT
NOT
NULL
DEFAULT
'[]'
;
backend/migration_release/101_add_channel_features_config.sql
deleted
100644 → 0
View file @
74828a7c
ALTER
TABLE
channels
ADD
COLUMN
IF
NOT
EXISTS
features_config
JSONB
NOT
NULL
DEFAULT
'{}'
;
COMMENT
ON
COLUMN
channels
.
features_config
IS
'渠道特性配置(如 web_search_emulation),JSON 对象格式'
;
backend/migration_release/101_add_payment_mode.sql
deleted
100644 → 0
View file @
74828a7c
-- Add payment_mode field to payment_provider_instances
-- Values: 'redirect' (hosted page redirect), 'api' (API call for QR/payurl), '' (default/N/A)
ALTER
TABLE
payment_provider_instances
ADD
COLUMN
IF
NOT
EXISTS
payment_mode
VARCHAR
(
20
)
NOT
NULL
DEFAULT
''
;
-- Migrate existing data: easypay instances with 'easypay' in supported_types → redirect mode
-- Remove 'easypay' from supported_types and set payment_mode = 'redirect'
UPDATE
payment_provider_instances
SET
payment_mode
=
'redirect'
,
supported_types
=
TRIM
(
BOTH
','
FROM
REPLACE
(
REPLACE
(
REPLACE
(
supported_types
,
'easypay,'
,
''
),
',easypay'
,
''
),
'easypay'
,
''
))
WHERE
provider_key
=
'easypay'
AND
supported_types
LIKE
'%easypay%'
;
-- EasyPay instances without 'easypay' in supported_types → api mode
UPDATE
payment_provider_instances
SET
payment_mode
=
'api'
WHERE
provider_key
=
'easypay'
AND
payment_mode
=
''
;
backend/migration_release/102_add_balance_notify_threshold_type.sql
deleted
100644 → 0
View file @
74828a7c
-- Add threshold type support (fixed / percentage) to balance notification
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
balance_notify_threshold_type
VARCHAR
(
10
)
NOT
NULL
DEFAULT
'fixed'
;
-- Track cumulative recharge amount for percentage threshold calculation
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
total_recharged
DECIMAL
(
20
,
8
)
NOT
NULL
DEFAULT
0
;
backend/migration_release/102_add_out_trade_no_to_payment_orders.sql
deleted
100644 → 0
View file @
74828a7c
-- 100_add_out_trade_no_to_payment_orders.sql
-- Adds out_trade_no column for external order ID used with payment providers.
-- Allows webhook handlers to look up orders by external ID instead of embedding DB ID.
ALTER
TABLE
payment_orders
ADD
COLUMN
IF
NOT
EXISTS
out_trade_no
VARCHAR
(
64
)
NOT
NULL
DEFAULT
''
;
CREATE
INDEX
IF
NOT
EXISTS
paymentorder_out_trade_no
ON
payment_orders
(
out_trade_no
);
backend/migration_release/103_add_allow_user_refund.sql
deleted
100644 → 0
View file @
74828a7c
ALTER
TABLE
payment_provider_instances
ADD
COLUMN
IF
NOT
EXISTS
allow_user_refund
BOOLEAN
NOT
NULL
DEFAULT
false
;
backend/migration_release/104_migrate_notify_emails_to_struct.sql
deleted
100644 → 0
View file @
74828a7c
-- Migrate notification email lists from old []string format to new []NotifyEmailEntry format
-- Old: ["a@x.com", "b@x.com"]
-- New: [{"email":"a@x.com","disabled":false,"verified":true}, ...]
-- Existing emails are marked as verified=false (unverified), disabled=false (enabled)
-- 1. User balance notification emails
UPDATE
users
SET
balance_notify_extra_emails
=
(
SELECT
COALESCE
(
jsonb_agg
(
jsonb_build_object
(
'email'
,
elem
::
text
,
'disabled'
,
false
,
'verified'
,
false
)),
'[]'
::
jsonb
)::
text
FROM
jsonb_array_elements_text
(
balance_notify_extra_emails
::
jsonb
)
AS
elem
)
WHERE
balance_notify_extra_emails
IS
NOT
NULL
AND
balance_notify_extra_emails
<>
'[]'
AND
balance_notify_extra_emails
<>
''
AND
(
balance_notify_extra_emails
::
jsonb
->
0
)
IS
NOT
NULL
AND
jsonb_typeof
(
balance_notify_extra_emails
::
jsonb
->
0
)
=
'string'
;
-- 2. Admin account quota notification emails
UPDATE
settings
SET
value
=
(
SELECT
COALESCE
(
jsonb_agg
(
jsonb_build_object
(
'email'
,
elem
::
text
,
'disabled'
,
false
,
'verified'
,
false
)),
'[]'
::
jsonb
)::
text
FROM
jsonb_array_elements_text
(
value
::
jsonb
)
AS
elem
)
WHERE
key
=
'account_quota_notify_emails'
AND
value
IS
NOT
NULL
AND
value
<>
'[]'
AND
value
<>
''
AND
(
value
::
jsonb
->
0
)
IS
NOT
NULL
AND
jsonb_typeof
(
value
::
jsonb
->
0
)
=
'string'
;
backend/migration_release/105_migrate_websearch_emulation_to_tristate.sql
deleted
100644 → 0
View file @
74828a7c
-- Convert old boolean web_search_emulation to tri-state string
-- true → "enabled", false → remove key (becomes "default")
UPDATE
accounts
SET
extra
=
(
extra
-
'web_search_emulation'
)
||
jsonb_build_object
(
'web_search_emulation'
,
'enabled'
)
WHERE
extra
?
'web_search_emulation'
AND
extra
->>
'web_search_emulation'
=
'true'
;
UPDATE
accounts
SET
extra
=
extra
-
'web_search_emulation'
WHERE
extra
?
'web_search_emulation'
AND
extra
->>
'web_search_emulation'
=
'false'
;
backend/migration_release/106_add_account_stats_pricing_intervals.sql
deleted
100644 → 0
View file @
74828a7c
-- Add intervals table for account stats pricing rules (mirrors channel_pricing_intervals).
CREATE
TABLE
IF
NOT
EXISTS
channel_account_stats_pricing_intervals
(
id
BIGSERIAL
PRIMARY
KEY
,
pricing_id
BIGINT
NOT
NULL
REFERENCES
channel_account_stats_model_pricing
(
id
)
ON
DELETE
CASCADE
,
min_tokens
INT
NOT
NULL
DEFAULT
0
,
max_tokens
INT
,
tier_label
VARCHAR
(
50
),
input_price
NUMERIC
(
20
,
12
),
output_price
NUMERIC
(
20
,
12
),
cache_write_price
NUMERIC
(
20
,
12
),
cache_read_price
NUMERIC
(
20
,
12
),
per_request_price
NUMERIC
(
20
,
12
),
sort_order
INT
NOT
NULL
DEFAULT
0
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_account_stats_pricing_intervals_pricing_id
ON
channel_account_stats_pricing_intervals
(
pricing_id
);
backend/migration_release/107_add_account_cost_to_dashboard_tables.sql
deleted
100644 → 0
View file @
74828a7c
-- Add account_cost column to dashboard aggregation tables for admin dashboard display.
-- account_cost = SUM(COALESCE(account_stats_cost, total_cost) * COALESCE(account_rate_multiplier, 1))
ALTER
TABLE
usage_dashboard_hourly
ADD
COLUMN
IF
NOT
EXISTS
account_cost
DECIMAL
(
20
,
10
)
NOT
NULL
DEFAULT
0
;
ALTER
TABLE
usage_dashboard_daily
ADD
COLUMN
IF
NOT
EXISTS
account_cost
DECIMAL
(
20
,
10
)
NOT
NULL
DEFAULT
0
;
backend/migration_release/108_auth_identity_foundation_core.sql
deleted
100644 → 0
View file @
74828a7c
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
signup_source
VARCHAR
(
20
)
NOT
NULL
DEFAULT
'email'
,
ADD
COLUMN
IF
NOT
EXISTS
last_login_at
TIMESTAMPTZ
NULL
,
ADD
COLUMN
IF
NOT
EXISTS
last_active_at
TIMESTAMPTZ
NULL
;
UPDATE
users
SET
signup_source
=
'email'
WHERE
signup_source
IS
NULL
OR
signup_source
=
''
;
DO
$$
BEGIN
IF
NOT
EXISTS
(
SELECT
1
FROM
pg_constraint
WHERE
conname
=
'users_signup_source_check'
)
THEN
ALTER
TABLE
users
ADD
CONSTRAINT
users_signup_source_check
CHECK
(
signup_source
IN
(
'email'
,
'linuxdo'
,
'wechat'
,
'oidc'
));
END
IF
;
END
$$
;
CREATE
TABLE
IF
NOT
EXISTS
auth_identities
(
id
BIGSERIAL
PRIMARY
KEY
,
user_id
BIGINT
NOT
NULL
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
,
provider_type
VARCHAR
(
20
)
NOT
NULL
,
provider_key
TEXT
NOT
NULL
,
provider_subject
TEXT
NOT
NULL
,
verified_at
TIMESTAMPTZ
NULL
,
issuer
TEXT
NULL
,
metadata
JSONB
NOT
NULL
DEFAULT
'{}'
::
jsonb
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
auth_identities_provider_type_check
CHECK
(
provider_type
IN
(
'email'
,
'linuxdo'
,
'wechat'
,
'oidc'
))
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
auth_identities_provider_subject_key
ON
auth_identities
(
provider_type
,
provider_key
,
provider_subject
);
CREATE
INDEX
IF
NOT
EXISTS
auth_identities_user_id_idx
ON
auth_identities
(
user_id
);
CREATE
INDEX
IF
NOT
EXISTS
auth_identities_user_provider_idx
ON
auth_identities
(
user_id
,
provider_type
);
CREATE
TABLE
IF
NOT
EXISTS
auth_identity_channels
(
id
BIGSERIAL
PRIMARY
KEY
,
identity_id
BIGINT
NOT
NULL
REFERENCES
auth_identities
(
id
)
ON
DELETE
CASCADE
,
provider_type
VARCHAR
(
20
)
NOT
NULL
,
provider_key
TEXT
NOT
NULL
,
channel
VARCHAR
(
20
)
NOT
NULL
,
channel_app_id
TEXT
NOT
NULL
,
channel_subject
TEXT
NOT
NULL
,
metadata
JSONB
NOT
NULL
DEFAULT
'{}'
::
jsonb
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
auth_identity_channels_provider_type_check
CHECK
(
provider_type
IN
(
'email'
,
'linuxdo'
,
'wechat'
,
'oidc'
))
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
auth_identity_channels_channel_key
ON
auth_identity_channels
(
provider_type
,
provider_key
,
channel
,
channel_app_id
,
channel_subject
);
CREATE
INDEX
IF
NOT
EXISTS
auth_identity_channels_identity_id_idx
ON
auth_identity_channels
(
identity_id
);
CREATE
TABLE
IF
NOT
EXISTS
pending_auth_sessions
(
id
BIGSERIAL
PRIMARY
KEY
,
session_token
VARCHAR
(
255
)
NOT
NULL
,
intent
VARCHAR
(
40
)
NOT
NULL
,
provider_type
VARCHAR
(
20
)
NOT
NULL
,
provider_key
TEXT
NOT
NULL
,
provider_subject
TEXT
NOT
NULL
,
target_user_id
BIGINT
NULL
REFERENCES
users
(
id
)
ON
DELETE
SET
NULL
,
redirect_to
TEXT
NOT
NULL
DEFAULT
''
,
resolved_email
TEXT
NOT
NULL
DEFAULT
''
,
registration_password_hash
TEXT
NOT
NULL
DEFAULT
''
,
upstream_identity_claims
JSONB
NOT
NULL
DEFAULT
'{}'
::
jsonb
,
local_flow_state
JSONB
NOT
NULL
DEFAULT
'{}'
::
jsonb
,
browser_session_key
TEXT
NOT
NULL
DEFAULT
''
,
completion_code_hash
TEXT
NOT
NULL
DEFAULT
''
,
completion_code_expires_at
TIMESTAMPTZ
NULL
,
email_verified_at
TIMESTAMPTZ
NULL
,
password_verified_at
TIMESTAMPTZ
NULL
,
totp_verified_at
TIMESTAMPTZ
NULL
,
expires_at
TIMESTAMPTZ
NOT
NULL
,
consumed_at
TIMESTAMPTZ
NULL
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
pending_auth_sessions_intent_check
CHECK
(
intent
IN
(
'login'
,
'bind_current_user'
,
'adopt_existing_user_by_email'
)),
CONSTRAINT
pending_auth_sessions_provider_type_check
CHECK
(
provider_type
IN
(
'email'
,
'linuxdo'
,
'wechat'
,
'oidc'
))
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
pending_auth_sessions_session_token_key
ON
pending_auth_sessions
(
session_token
);
CREATE
INDEX
IF
NOT
EXISTS
pending_auth_sessions_target_user_id_idx
ON
pending_auth_sessions
(
target_user_id
);
CREATE
INDEX
IF
NOT
EXISTS
pending_auth_sessions_expires_at_idx
ON
pending_auth_sessions
(
expires_at
);
CREATE
INDEX
IF
NOT
EXISTS
pending_auth_sessions_provider_idx
ON
pending_auth_sessions
(
provider_type
,
provider_key
,
provider_subject
);
CREATE
INDEX
IF
NOT
EXISTS
pending_auth_sessions_completion_code_idx
ON
pending_auth_sessions
(
completion_code_hash
);
CREATE
TABLE
IF
NOT
EXISTS
identity_adoption_decisions
(
id
BIGSERIAL
PRIMARY
KEY
,
pending_auth_session_id
BIGINT
NOT
NULL
REFERENCES
pending_auth_sessions
(
id
)
ON
DELETE
CASCADE
,
identity_id
BIGINT
NULL
REFERENCES
auth_identities
(
id
)
ON
DELETE
SET
NULL
,
adopt_display_name
BOOLEAN
NOT
NULL
DEFAULT
FALSE
,
adopt_avatar
BOOLEAN
NOT
NULL
DEFAULT
FALSE
,
decided_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
identity_adoption_decisions_pending_auth_session_id_key
ON
identity_adoption_decisions
(
pending_auth_session_id
);
CREATE
INDEX
IF
NOT
EXISTS
identity_adoption_decisions_identity_id_idx
ON
identity_adoption_decisions
(
identity_id
);
CREATE
TABLE
IF
NOT
EXISTS
auth_identity_migration_reports
(
id
BIGSERIAL
PRIMARY
KEY
,
report_type
VARCHAR
(
40
)
NOT
NULL
,
report_key
TEXT
NOT
NULL
,
details
JSONB
NOT
NULL
DEFAULT
'{}'
::
jsonb
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
auth_identity_migration_reports_type_idx
ON
auth_identity_migration_reports
(
report_type
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
auth_identity_migration_reports_type_key
ON
auth_identity_migration_reports
(
report_type
,
report_key
);
backend/migration_release/108_request_capture_log.sql
deleted
100644 → 0
View file @
74828a7c
-- Add capture_requests flag to api_keys
ALTER
TABLE
api_keys
ADD
COLUMN
IF
NOT
EXISTS
capture_requests
boolean
NOT
NULL
DEFAULT
false
;
-- Create request_capture_logs table (monthly range-partitioned by created_at)
-- PRIMARY KEY must include the partition key, so we use (id, created_at).
CREATE
TABLE
IF
NOT
EXISTS
request_capture_logs
(
id
bigserial
NOT
NULL
,
api_key_id
bigint
NOT
NULL
,
user_id
bigint
NOT
NULL
,
request_id
varchar
(
64
),
path
varchar
(
100
),
method
varchar
(
10
),
ip_address
varchar
(
45
),
request_body
text
,
response_body
text
,
nfs_file_path
varchar
(
500
),
created_at
timestamptz
NOT
NULL
DEFAULT
now
(),
PRIMARY
KEY
(
id
,
created_at
)
)
PARTITION
BY
RANGE
(
created_at
);
CREATE
INDEX
IF
NOT
EXISTS
idx_rcl_api_key_created
ON
request_capture_logs
(
api_key_id
,
created_at
DESC
);
CREATE
INDEX
IF
NOT
EXISTS
idx_rcl_user_id
ON
request_capture_logs
(
user_id
);
-- Pre-create partitions for previous, current, and next month
DO
$$
DECLARE
month_start
DATE
;
prev_month
DATE
;
next_month
DATE
;
BEGIN
month_start
:
=
date_trunc
(
'month'
,
now
()
AT
TIME
ZONE
'UTC'
)::
date
;
prev_month
:
=
(
month_start
-
INTERVAL
'1 month'
)::
date
;
next_month
:
=
(
month_start
+
INTERVAL
'1 month'
)::
date
;
EXECUTE
format
(
'CREATE TABLE IF NOT EXISTS request_capture_logs_%s PARTITION OF request_capture_logs FOR VALUES FROM (%L) TO (%L)'
,
to_char
(
prev_month
,
'YYYYMM'
),
prev_month
,
month_start
);
EXECUTE
format
(
'CREATE TABLE IF NOT EXISTS request_capture_logs_%s PARTITION OF request_capture_logs FOR VALUES FROM (%L) TO (%L)'
,
to_char
(
month_start
,
'YYYYMM'
),
month_start
,
next_month
);
EXECUTE
format
(
'CREATE TABLE IF NOT EXISTS request_capture_logs_%s PARTITION OF request_capture_logs FOR VALUES FROM (%L) TO (%L)'
,
to_char
(
next_month
,
'YYYYMM'
),
next_month
,
(
next_month
+
INTERVAL
'1 month'
)::
date
);
END
$$
;
backend/migration_release/108a_widen_auth_identity_migration_report_type.sql
deleted
100644 → 0
View file @
74828a7c
DO
$$
BEGIN
IF
EXISTS
(
SELECT
1
FROM
information_schema
.
columns
WHERE
table_schema
=
'public'
AND
table_name
=
'auth_identity_migration_reports'
AND
column_name
=
'report_type'
AND
COALESCE
(
character_maximum_length
,
0
)
<
80
)
THEN
ALTER
TABLE
auth_identity_migration_reports
ALTER
COLUMN
report_type
TYPE
VARCHAR
(
80
);
END
IF
;
END
$$
;
backend/migration_release/109_auth_identity_compat_backfill.sql
deleted
100644 → 0
View file @
74828a7c
INSERT
INTO
auth_identities
(
user_id
,
provider_type
,
provider_key
,
provider_subject
,
verified_at
,
metadata
)
SELECT
u
.
id
,
'email'
,
'email'
,
LOWER
(
BTRIM
(
u
.
email
)),
COALESCE
(
u
.
updated_at
,
u
.
created_at
,
NOW
()),
jsonb_build_object
(
'backfill_source'
,
'users.email'
,
'migration'
,
'109_auth_identity_compat_backfill'
)
FROM
users
AS
u
WHERE
u
.
deleted_at
IS
NULL
AND
BTRIM
(
COALESCE
(
u
.
email
,
''
))
<>
''
AND
RIGHT
(
LOWER
(
BTRIM
(
u
.
email
)),
LENGTH
(
'@linuxdo-connect.invalid'
))
<>
'@linuxdo-connect.invalid'
AND
RIGHT
(
LOWER
(
BTRIM
(
u
.
email
)),
LENGTH
(
'@oidc-connect.invalid'
))
<>
'@oidc-connect.invalid'
AND
RIGHT
(
LOWER
(
BTRIM
(
u
.
email
)),
LENGTH
(
'@wechat-connect.invalid'
))
<>
'@wechat-connect.invalid'
ON
CONFLICT
(
provider_type
,
provider_key
,
provider_subject
)
DO
NOTHING
;
INSERT
INTO
auth_identities
(
user_id
,
provider_type
,
provider_key
,
provider_subject
,
verified_at
,
metadata
)
SELECT
u
.
id
,
'linuxdo'
,
'linuxdo'
,
SUBSTRING
(
BTRIM
(
u
.
email
)
FROM
'(?i)^linuxdo-(.+)@linuxdo-connect
\.
invalid$'
),
COALESCE
(
u
.
updated_at
,
u
.
created_at
,
NOW
()),
jsonb_build_object
(
'backfill_source'
,
'synthetic_email'
,
'legacy_email'
,
BTRIM
(
u
.
email
),
'migration'
,
'109_auth_identity_compat_backfill'
)
FROM
users
AS
u
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
u
.
email
))
~
'^linuxdo-.+@linuxdo-connect
\.
invalid$'
ON
CONFLICT
(
provider_type
,
provider_key
,
provider_subject
)
DO
NOTHING
;
INSERT
INTO
auth_identities
(
user_id
,
provider_type
,
provider_key
,
provider_subject
,
verified_at
,
metadata
)
SELECT
u
.
id
,
'wechat'
,
'wechat'
,
SUBSTRING
(
BTRIM
(
u
.
email
)
FROM
'(?i)^wechat-(.+)@wechat-connect
\.
invalid$'
),
COALESCE
(
u
.
updated_at
,
u
.
created_at
,
NOW
()),
jsonb_build_object
(
'backfill_source'
,
'synthetic_email'
,
'legacy_email'
,
BTRIM
(
u
.
email
),
'migration'
,
'109_auth_identity_compat_backfill'
)
FROM
users
AS
u
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
u
.
email
))
~
'^wechat-.+@wechat-connect
\.
invalid$'
ON
CONFLICT
(
provider_type
,
provider_key
,
provider_subject
)
DO
NOTHING
;
UPDATE
users
SET
signup_source
=
'linuxdo'
WHERE
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
COALESCE
(
email
,
''
)))
~
'^linuxdo-.+@linuxdo-connect
\.
invalid$'
;
UPDATE
users
SET
signup_source
=
'wechat'
WHERE
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
COALESCE
(
email
,
''
)))
~
'^wechat-.+@wechat-connect
\.
invalid$'
;
UPDATE
users
SET
signup_source
=
'oidc'
WHERE
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
COALESCE
(
email
,
''
)))
~
'^oidc-.+@oidc-connect
\.
invalid$'
;
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'oidc_synthetic_email_requires_manual_recovery'
,
CAST
(
u
.
id
AS
TEXT
),
jsonb_build_object
(
'user_id'
,
u
.
id
,
'email'
,
LOWER
(
BTRIM
(
u
.
email
)),
'reason'
,
'cannot recover issuer_plus_sub deterministically from synthetic email alone'
,
'migration'
,
'109_auth_identity_compat_backfill'
)
FROM
users
AS
u
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
u
.
email
))
~
'^oidc-.+@oidc-connect
\.
invalid$'
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'wechat_openid_only_requires_remediation'
,
CAST
(
u
.
id
AS
TEXT
),
jsonb_build_object
(
'user_id'
,
u
.
id
,
'email'
,
LOWER
(
BTRIM
(
u
.
email
)),
'reason'
,
'legacy wechat synthetic identity requires explicit unionid remediation if channel-only data exists'
,
'migration'
,
'109_auth_identity_compat_backfill'
)
FROM
users
AS
u
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
u
.
email
))
~
'^wechat-.+@wechat-connect
\.
invalid$'
AND
NOT
EXISTS
(
SELECT
1
FROM
auth_identities
ai
WHERE
ai
.
user_id
=
u
.
id
AND
ai
.
provider_type
=
'wechat'
AND
ai
.
provider_key
=
'wechat'
)
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
Prev
1
2
3
4
5
6
7
8
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment