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
73a8683c
Commit
73a8683c
authored
Apr 24, 2026
by
陈曦
Browse files
append migration files by v117
parent
b017f461
Pipeline
#82250
passed with stage
in 23 seconds
Changes
37
Pipelines
2
Expand all
Hide whitespace changes
Inline
Side-by-side
backend/migration_release/095_channel_features.sql
deleted
100644 → 0
View file @
b017f461
ALTER
TABLE
channels
ADD
COLUMN
IF
NOT
EXISTS
features
TEXT
NOT
NULL
DEFAULT
''
;
COMMENT
ON
COLUMN
channels
.
features
IS
'渠道特性描述,JSON 数组格式,用于支付页面展示'
;
backend/migration_release/101_add_account_stats_pricing.sql
deleted
100644 → 0
View file @
b017f461
-- 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 @
b017f461
-- 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 @
b017f461
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/102_add_balance_notify_threshold_type.sql
deleted
100644 → 0
View file @
b017f461
-- 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/103_add_allow_user_refund.sql
deleted
100644 → 0
View file @
b017f461
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 @
b017f461
-- 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 @
b017f461
-- 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 @
b017f461
-- 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 @
b017f461
-- 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
0 → 100644
View file @
73a8683c
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/108a_widen_auth_identity_migration_report_type.sql
0 → 100644
View file @
73a8683c
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
0 → 100644
View file @
73a8683c
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
;
backend/migration_release/110_pending_auth_and_provider_default_grants.sql
0 → 100644
View file @
73a8683c
CREATE
TABLE
IF
NOT
EXISTS
user_provider_default_grants
(
id
BIGSERIAL
PRIMARY
KEY
,
user_id
BIGINT
NOT
NULL
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
,
provider_type
VARCHAR
(
20
)
NOT
NULL
,
grant_reason
VARCHAR
(
20
)
NOT
NULL
DEFAULT
'first_bind'
,
granted_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
user_provider_default_grants_provider_type_check
CHECK
(
provider_type
IN
(
'email'
,
'linuxdo'
,
'wechat'
,
'oidc'
)),
CONSTRAINT
user_provider_default_grants_reason_check
CHECK
(
grant_reason
IN
(
'signup'
,
'first_bind'
))
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
user_provider_default_grants_user_provider_reason_key
ON
user_provider_default_grants
(
user_id
,
provider_type
,
grant_reason
);
CREATE
INDEX
IF
NOT
EXISTS
user_provider_default_grants_user_id_idx
ON
user_provider_default_grants
(
user_id
);
CREATE
TABLE
IF
NOT
EXISTS
user_avatars
(
id
BIGSERIAL
PRIMARY
KEY
,
user_id
BIGINT
NOT
NULL
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
,
storage_provider
VARCHAR
(
20
)
NOT
NULL
DEFAULT
'database'
,
storage_key
TEXT
NOT
NULL
DEFAULT
''
,
url
TEXT
NOT
NULL
DEFAULT
''
,
content_type
VARCHAR
(
100
)
NOT
NULL
DEFAULT
''
,
byte_size
INT
NOT
NULL
DEFAULT
0
,
sha256
VARCHAR
(
64
)
NOT
NULL
DEFAULT
''
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
user_avatars_user_id_key
ON
user_avatars
(
user_id
);
INSERT
INTO
settings
(
key
,
value
)
VALUES
(
'auth_source_default_email_balance'
,
'0'
),
(
'auth_source_default_email_concurrency'
,
'5'
),
(
'auth_source_default_email_subscriptions'
,
'[]'
),
(
'auth_source_default_email_grant_on_signup'
,
'false'
),
(
'auth_source_default_email_grant_on_first_bind'
,
'false'
),
(
'auth_source_default_linuxdo_balance'
,
'0'
),
(
'auth_source_default_linuxdo_concurrency'
,
'5'
),
(
'auth_source_default_linuxdo_subscriptions'
,
'[]'
),
(
'auth_source_default_linuxdo_grant_on_signup'
,
'false'
),
(
'auth_source_default_linuxdo_grant_on_first_bind'
,
'false'
),
(
'auth_source_default_oidc_balance'
,
'0'
),
(
'auth_source_default_oidc_concurrency'
,
'5'
),
(
'auth_source_default_oidc_subscriptions'
,
'[]'
),
(
'auth_source_default_oidc_grant_on_signup'
,
'false'
),
(
'auth_source_default_oidc_grant_on_first_bind'
,
'false'
),
(
'auth_source_default_wechat_balance'
,
'0'
),
(
'auth_source_default_wechat_concurrency'
,
'5'
),
(
'auth_source_default_wechat_subscriptions'
,
'[]'
),
(
'auth_source_default_wechat_grant_on_signup'
,
'false'
),
(
'auth_source_default_wechat_grant_on_first_bind'
,
'false'
),
(
'force_email_on_third_party_signup'
,
'false'
)
ON
CONFLICT
(
key
)
DO
NOTHING
;
backend/migration_release/111_payment_routing_and_scheduler_flags.sql
0 → 100644
View file @
73a8683c
INSERT
INTO
settings
(
key
,
value
)
VALUES
(
'payment_visible_method_alipay_source'
,
''
),
(
'payment_visible_method_wxpay_source'
,
''
),
(
'payment_visible_method_alipay_enabled'
,
'false'
),
(
'payment_visible_method_wxpay_enabled'
,
'false'
),
(
'openai_advanced_scheduler_enabled'
,
'false'
)
ON
CONFLICT
(
key
)
DO
NOTHING
;
backend/migration_release/112_add_payment_order_provider_key_snapshot.sql
0 → 100644
View file @
73a8683c
ALTER
TABLE
payment_orders
ADD
COLUMN
IF
NOT
EXISTS
provider_key
VARCHAR
(
30
);
UPDATE
payment_orders
SET
provider_key
=
(
SELECT
provider_key
FROM
payment_provider_instances
WHERE
CAST
(
id
AS
TEXT
)
=
payment_orders
.
provider_instance_id
)
WHERE
provider_key
IS
NULL
AND
provider_instance_id
IS
NOT
NULL
;
backend/migration_release/113_normalize_legacy_wechat_provider_key.sql
0 → 100644
View file @
73a8683c
UPDATE
auth_identities
AS
ai
SET
provider_key
=
'wechat-main'
,
metadata
=
COALESCE
(
ai
.
metadata
,
'{}'
::
jsonb
)
||
jsonb_build_object
(
'legacy_provider_key'
,
'wechat'
,
'normalized_by_migration'
,
'113_normalize_legacy_wechat_provider_key'
),
updated_at
=
NOW
()
WHERE
ai
.
provider_type
=
'wechat'
AND
ai
.
provider_key
=
'wechat'
AND
NOT
EXISTS
(
SELECT
1
FROM
auth_identities
AS
canon
WHERE
canon
.
provider_type
=
'wechat'
AND
canon
.
provider_key
=
'wechat-main'
AND
canon
.
provider_subject
=
ai
.
provider_subject
);
UPDATE
auth_identity_channels
AS
channel
SET
provider_key
=
'wechat-main'
,
metadata
=
COALESCE
(
channel
.
metadata
,
'{}'
::
jsonb
)
||
jsonb_build_object
(
'legacy_provider_key'
,
'wechat'
,
'normalized_by_migration'
,
'113_normalize_legacy_wechat_provider_key'
),
updated_at
=
NOW
()
WHERE
channel
.
provider_type
=
'wechat'
AND
channel
.
provider_key
=
'wechat'
AND
NOT
EXISTS
(
SELECT
1
FROM
auth_identity_channels
AS
canon
WHERE
canon
.
provider_type
=
'wechat'
AND
canon
.
provider_key
=
'wechat-main'
AND
canon
.
channel
=
channel
.
channel
AND
canon
.
channel_app_id
=
channel
.
channel_app_id
AND
canon
.
channel_subject
=
channel
.
channel_subject
);
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'wechat_provider_key_conflict'
,
CAST
(
ai
.
id
AS
TEXT
),
jsonb_build_object
(
'legacy_identity_id'
,
ai
.
id
,
'legacy_user_id'
,
ai
.
user_id
,
'provider_subject'
,
ai
.
provider_subject
,
'canonical_identity_id'
,
canon
.
id
,
'canonical_user_id'
,
canon
.
user_id
,
'same_user'
,
canon
.
user_id
=
ai
.
user_id
,
'migration'
,
'113_normalize_legacy_wechat_provider_key'
)
FROM
auth_identities
AS
ai
JOIN
auth_identities
AS
canon
ON
canon
.
provider_type
=
'wechat'
AND
canon
.
provider_key
=
'wechat-main'
AND
canon
.
provider_subject
=
ai
.
provider_subject
WHERE
ai
.
provider_type
=
'wechat'
AND
ai
.
provider_key
=
'wechat'
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'wechat_channel_provider_key_conflict'
,
CAST
(
channel
.
id
AS
TEXT
),
jsonb_build_object
(
'legacy_channel_id'
,
channel
.
id
,
'legacy_identity_id'
,
channel
.
identity_id
,
'canonical_channel_id'
,
canon
.
id
,
'canonical_identity_id'
,
canon
.
identity_id
,
'channel'
,
channel
.
channel
,
'channel_app_id'
,
channel
.
channel_app_id
,
'channel_subject'
,
channel
.
channel_subject
,
'same_user'
,
COALESCE
(
legacy_identity
.
user_id
=
canonical_identity
.
user_id
,
FALSE
),
'migration'
,
'113_normalize_legacy_wechat_provider_key'
)
FROM
auth_identity_channels
AS
channel
JOIN
auth_identity_channels
AS
canon
ON
canon
.
provider_type
=
'wechat'
AND
canon
.
provider_key
=
'wechat-main'
AND
canon
.
channel
=
channel
.
channel
AND
canon
.
channel_app_id
=
channel
.
channel_app_id
AND
canon
.
channel_subject
=
channel
.
channel_subject
LEFT
JOIN
auth_identities
AS
legacy_identity
ON
legacy_identity
.
id
=
channel
.
identity_id
LEFT
JOIN
auth_identities
AS
canonical_identity
ON
canonical_identity
.
id
=
canon
.
identity_id
WHERE
channel
.
provider_type
=
'wechat'
AND
channel
.
provider_key
=
'wechat'
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
backend/migration_release/114_auth_identity_migration_report_resolution.sql
0 → 100644
View file @
73a8683c
ALTER
TABLE
auth_identity_migration_reports
ADD
COLUMN
IF
NOT
EXISTS
resolved_at
TIMESTAMPTZ
NULL
;
ALTER
TABLE
auth_identity_migration_reports
ADD
COLUMN
IF
NOT
EXISTS
resolved_by_user_id
BIGINT
NULL
;
ALTER
TABLE
auth_identity_migration_reports
ADD
COLUMN
IF
NOT
EXISTS
resolution_note
TEXT
NOT
NULL
DEFAULT
''
;
CREATE
INDEX
IF
NOT
EXISTS
idx_auth_identity_migration_reports_resolved_at
ON
auth_identity_migration_reports
(
resolved_at
);
backend/migration_release/115_auth_identity_legacy_external_backfill.sql
0 → 100644
View file @
73a8683c
CREATE
OR
REPLACE
FUNCTION
public
.
__migration_115_safe_legacy_metadata_jsonb
(
input_text
TEXT
)
RETURNS
JSONB
LANGUAGE
plpgsql
AS
$$
DECLARE
parsed
JSONB
;
BEGIN
IF
input_text
IS
NULL
OR
BTRIM
(
input_text
)
=
''
THEN
RETURN
'{}'
::
jsonb
;
END
IF
;
BEGIN
parsed
:
=
input_text
::
jsonb
;
EXCEPTION
WHEN
OTHERS
THEN
RETURN
'{}'
::
jsonb
;
END
;
IF
jsonb_typeof
(
parsed
)
=
'object'
THEN
RETURN
parsed
;
END
IF
;
RETURN
jsonb_build_object
(
'_legacy_metadata_raw_json'
,
parsed
);
END
;
$$
;
DO
$$
BEGIN
IF
to_regclass
(
'public.user_external_identities'
)
IS
NULL
THEN
RETURN
;
END
IF
;
EXECUTE
$
sql
$
WITH
legacy
AS
(
SELECT
uei
.
id
,
uei
.
user_id
,
BTRIM
(
uei
.
provider_user_id
)
AS
provider_user_id
,
BTRIM
(
uei
.
provider_username
)
AS
provider_username
,
BTRIM
(
uei
.
display_name
)
AS
display_name
,
public
.
__migration_115_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
AS
metadata_json
,
uei
.
created_at
,
uei
.
updated_at
FROM
user_external_identities
AS
uei
JOIN
users
AS
u
ON
u
.
id
=
uei
.
user_id
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'linuxdo'
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
),
legacy_subjects
AS
(
SELECT
provider_user_id
AS
provider_subject
,
COUNT
(
DISTINCT
user_id
)
AS
distinct_user_count
FROM
legacy
GROUP
BY
provider_user_id
),
canonical_legacy
AS
(
SELECT
legacy
.
*
,
ROW_NUMBER
()
OVER
(
PARTITION
BY
legacy
.
provider_user_id
ORDER
BY
COALESCE
(
legacy
.
updated_at
,
legacy
.
created_at
,
NOW
())
DESC
,
legacy
.
id
DESC
)
AS
canonical_row_num
FROM
legacy
JOIN
legacy_subjects
AS
subjects
ON
subjects
.
provider_subject
=
legacy
.
provider_user_id
AND
subjects
.
distinct_user_count
=
1
)
INSERT
INTO
auth_identities
(
user_id
,
provider_type
,
provider_key
,
provider_subject
,
verified_at
,
metadata
)
SELECT
legacy
.
user_id
,
'linuxdo'
,
'linuxdo'
,
legacy
.
provider_user_id
,
COALESCE
(
legacy
.
updated_at
,
legacy
.
created_at
,
NOW
()),
legacy
.
metadata_json
||
jsonb_build_object
(
'legacy_identity_id'
,
legacy
.
id
,
'provider_user_id'
,
legacy
.
provider_user_id
,
'provider_username'
,
legacy
.
provider_username
,
'display_name'
,
legacy
.
display_name
,
'migration'
,
'115_auth_identity_legacy_external_backfill'
)
FROM
canonical_legacy
AS
legacy
WHERE
legacy
.
canonical_row_num
=
1
ON
CONFLICT
(
provider_type
,
provider_key
,
provider_subject
)
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
WITH
legacy
AS
(
SELECT
uei
.
id
,
uei
.
user_id
,
BTRIM
(
uei
.
provider_user_id
)
AS
provider_user_id
,
BTRIM
(
uei
.
provider_union_id
)
AS
provider_union_id
,
BTRIM
(
uei
.
provider_username
)
AS
provider_username
,
BTRIM
(
uei
.
display_name
)
AS
display_name
,
public
.
__migration_115_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
AS
metadata_json
,
uei
.
created_at
,
uei
.
updated_at
FROM
user_external_identities
AS
uei
JOIN
users
AS
u
ON
u
.
id
=
uei
.
user_id
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
<>
''
),
legacy_subjects
AS
(
SELECT
provider_union_id
AS
provider_subject
,
COUNT
(
DISTINCT
user_id
)
AS
distinct_user_count
FROM
legacy
GROUP
BY
provider_union_id
),
canonical_legacy
AS
(
SELECT
legacy
.
*
,
ROW_NUMBER
()
OVER
(
PARTITION
BY
legacy
.
provider_union_id
ORDER
BY
COALESCE
(
legacy
.
updated_at
,
legacy
.
created_at
,
NOW
())
DESC
,
legacy
.
id
DESC
)
AS
canonical_row_num
FROM
legacy
JOIN
legacy_subjects
AS
subjects
ON
subjects
.
provider_subject
=
legacy
.
provider_union_id
AND
subjects
.
distinct_user_count
=
1
)
INSERT
INTO
auth_identities
(
user_id
,
provider_type
,
provider_key
,
provider_subject
,
verified_at
,
metadata
)
SELECT
legacy
.
user_id
,
'wechat'
,
'wechat-main'
,
legacy
.
provider_union_id
,
COALESCE
(
legacy
.
updated_at
,
legacy
.
created_at
,
NOW
()),
legacy
.
metadata_json
||
jsonb_build_object
(
'legacy_identity_id'
,
legacy
.
id
,
'openid'
,
legacy
.
provider_user_id
,
'unionid'
,
legacy
.
provider_union_id
,
'provider_user_id'
,
legacy
.
provider_user_id
,
'provider_union_id'
,
legacy
.
provider_union_id
,
'provider_username'
,
legacy
.
provider_username
,
'display_name'
,
legacy
.
display_name
,
'migration'
,
'115_auth_identity_legacy_external_backfill'
)
FROM
canonical_legacy
AS
legacy
WHERE
legacy
.
canonical_row_num
=
1
ON
CONFLICT
(
provider_type
,
provider_key
,
provider_subject
)
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
WITH
legacy
AS
(
SELECT
uei
.
user_id
,
BTRIM
(
uei
.
provider_user_id
)
AS
provider_user_id
,
BTRIM
(
uei
.
provider_union_id
)
AS
provider_union_id
,
BTRIM
(
COALESCE
(
meta
.
metadata_json
->>
'channel'
,
''
))
AS
channel
,
BTRIM
(
COALESCE
(
meta
.
metadata_json
->>
'channel_app_id'
,
meta
.
metadata_json
->>
'appid'
,
meta
.
metadata_json
->>
'app_id'
,
''
))
AS
channel_app_id
,
meta
.
metadata_json
FROM
user_external_identities
AS
uei
JOIN
users
AS
u
ON
u
.
id
=
uei
.
user_id
CROSS
JOIN
LATERAL
(
SELECT
public
.
__migration_115_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
AS
metadata_json
)
AS
meta
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
<>
''
),
legacy_subjects
AS
(
SELECT
provider_union_id
AS
provider_subject
,
COUNT
(
DISTINCT
user_id
)
AS
distinct_user_count
FROM
legacy
GROUP
BY
provider_union_id
)
INSERT
INTO
auth_identity_channels
(
identity_id
,
provider_type
,
provider_key
,
channel
,
channel_app_id
,
channel_subject
,
metadata
)
SELECT
ai
.
id
,
'wechat'
,
'wechat-main'
,
legacy
.
channel
,
legacy
.
channel_app_id
,
legacy
.
provider_user_id
,
legacy
.
metadata_json
||
jsonb_build_object
(
'openid'
,
legacy
.
provider_user_id
,
'unionid'
,
legacy
.
provider_union_id
,
'migration'
,
'115_auth_identity_legacy_external_backfill'
)
FROM
legacy
JOIN
legacy_subjects
AS
subjects
ON
subjects
.
provider_subject
=
legacy
.
provider_union_id
AND
subjects
.
distinct_user_count
=
1
JOIN
auth_identities
AS
ai
ON
ai
.
user_id
=
legacy
.
user_id
AND
ai
.
provider_type
=
'wechat'
AND
ai
.
provider_key
=
'wechat-main'
AND
ai
.
provider_subject
=
legacy
.
provider_union_id
WHERE
legacy
.
channel
<>
''
AND
legacy
.
channel_app_id
<>
''
AND
legacy
.
provider_user_id
<>
''
ON
CONFLICT
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'wechat_openid_only_requires_remediation'
,
'legacy_external_identity:'
||
legacy
.
id
::
text
,
legacy
.
metadata_json
||
jsonb_build_object
(
'legacy_identity_id'
,
legacy
.
id
,
'user_id'
,
legacy
.
user_id
,
'openid'
,
legacy
.
provider_user_id
,
'reason'
,
'legacy user_external_identities row only has openid and cannot be canonicalized offline'
,
'migration'
,
'115_auth_identity_legacy_external_backfill'
)
FROM
(
SELECT
uei
.
id
,
uei
.
user_id
,
BTRIM
(
uei
.
provider_user_id
)
AS
provider_user_id
,
public
.
__migration_115_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
AS
metadata_json
FROM
user_external_identities
AS
uei
JOIN
users
AS
u
ON
u
.
id
=
uei
.
user_id
WHERE
u
.
deleted_at
IS
NULL
AND
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
=
''
)
AS
legacy
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
$
sql
$
;
END
$$
;
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'wechat_openid_only_requires_remediation'
,
'synthetic_auth_identity:'
||
ai
.
id
::
text
,
COALESCE
(
ai
.
metadata
,
'{}'
::
jsonb
)
||
jsonb_build_object
(
'auth_identity_id'
,
ai
.
id
,
'user_id'
,
ai
.
user_id
,
'provider_subject'
,
ai
.
provider_subject
,
'reason'
,
'synthetic wechat auth identity still lacks unionid metadata and needs remediation'
,
'migration'
,
'115_auth_identity_legacy_external_backfill'
)
FROM
auth_identities
AS
ai
WHERE
ai
.
provider_type
=
'wechat'
AND
COALESCE
(
ai
.
metadata
->>
'backfill_source'
,
''
)
=
'synthetic_email'
AND
BTRIM
(
COALESCE
(
ai
.
metadata
->>
'unionid'
,
''
))
=
''
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
DROP
FUNCTION
IF
EXISTS
public
.
__migration_115_safe_legacy_metadata_jsonb
(
TEXT
);
backend/migration_release/116_auth_identity_legacy_external_safety_reports.sql
0 → 100644
View file @
73a8683c
This diff is collapsed.
Click to expand it.
Prev
1
2
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