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
Show whitespace changes
Inline
Side-by-side
backend/migration_release/110_pending_auth_and_provider_default_grants.sql
deleted
100644 → 0
View file @
74828a7c
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
deleted
100644 → 0
View file @
74828a7c
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
deleted
100644 → 0
View file @
74828a7c
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
deleted
100644 → 0
View file @
74828a7c
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
deleted
100644 → 0
View file @
74828a7c
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
deleted
100644 → 0
View file @
74828a7c
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
deleted
100644 → 0
View file @
74828a7c
CREATE
OR
REPLACE
FUNCTION
public
.
__migration_116_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
;
$$
;
CREATE
OR
REPLACE
FUNCTION
public
.
__migration_116_is_valid_legacy_metadata_jsonb
(
input_text
TEXT
)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
AS
$$
DECLARE
parsed
JSONB
;
BEGIN
IF
input_text
IS
NULL
OR
BTRIM
(
input_text
)
=
''
THEN
RETURN
TRUE
;
END
IF
;
parsed
:
=
input_text
::
jsonb
;
RETURN
TRUE
;
EXCEPTION
WHEN
OTHERS
THEN
RETURN
FALSE
;
END
;
$$
;
DO
$$
BEGIN
IF
to_regclass
(
'public.user_external_identities'
)
IS
NULL
THEN
RETURN
;
END
IF
;
EXECUTE
$
sql
$
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'legacy_external_identity_invalid_metadata_json'
,
'legacy_external_identity:'
||
uei
.
id
::
text
,
jsonb_build_object
(
'legacy_identity_id'
,
uei
.
id
,
'user_id'
,
uei
.
user_id
,
'provider'
,
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
))),
'provider_user_id'
,
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
)),
'provider_union_id'
,
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
)),
'reason'
,
'legacy metadata is not valid JSON; migration downgraded metadata to empty object'
,
'raw_metadata'
,
LEFT
(
BTRIM
(
COALESCE
(
uei
.
metadata
,
''
)),
1000
),
'migration'
,
'116_auth_identity_legacy_external_safety_reports'
)
FROM
user_external_identities
AS
uei
JOIN
users
AS
u
ON
u
.
id
=
uei
.
user_id
WHERE
u
.
deleted_at
IS
NULL
AND
BTRIM
(
COALESCE
(
uei
.
metadata
,
''
))
<>
''
AND
NOT
public
.
__migration_116_is_valid_legacy_metadata_jsonb
(
uei
.
metadata
)
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'legacy_external_identity_conflict'
,
'legacy_external_identity:'
||
legacy
.
id
::
text
,
legacy
.
metadata_json
||
jsonb_build_object
(
'legacy_identity_id'
,
legacy
.
id
,
'legacy_user_id'
,
legacy
.
user_id
,
'provider_type'
,
legacy
.
provider_type
,
'provider_key'
,
legacy
.
provider_key
,
'provider_subject'
,
legacy
.
provider_subject
,
'conflicting_legacy_user_ids'
,
ambiguous
.
conflicting_legacy_user_ids
,
'reason'
,
'legacy canonical identity subject belongs to multiple legacy users and cannot be auto-resolved'
,
'migration'
,
'116_auth_identity_legacy_external_safety_reports'
)
FROM
(
SELECT
uei
.
id
,
uei
.
user_id
,
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
AS
provider_type
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
'wechat-main'
ELSE
'linuxdo'
END
AS
provider_key
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
ELSE
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
END
AS
provider_subject
,
public
.
__migration_116_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
,
''
)))
IN
(
'linuxdo'
,
'wechat'
)
AND
(
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'linuxdo'
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
)
OR
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
<>
''
)
)
)
AS
legacy
JOIN
(
SELECT
provider_type
,
provider_key
,
provider_subject
,
to_jsonb
(
array_agg
(
DISTINCT
user_id
ORDER
BY
user_id
))
AS
conflicting_legacy_user_ids
FROM
(
SELECT
uei
.
user_id
,
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
AS
provider_type
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
'wechat-main'
ELSE
'linuxdo'
END
AS
provider_key
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
ELSE
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
END
AS
provider_subject
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
,
''
)))
IN
(
'linuxdo'
,
'wechat'
)
AND
(
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'linuxdo'
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
)
OR
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
<>
''
)
)
)
AS
legacy_subjects
GROUP
BY
provider_type
,
provider_key
,
provider_subject
HAVING
COUNT
(
DISTINCT
user_id
)
>
1
)
AS
ambiguous
ON
ambiguous
.
provider_type
=
legacy
.
provider_type
AND
ambiguous
.
provider_key
=
legacy
.
provider_key
AND
ambiguous
.
provider_subject
=
legacy
.
provider_subject
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'legacy_external_identity_conflict'
,
'legacy_external_identity:'
||
legacy
.
id
::
text
,
legacy
.
metadata_json
||
jsonb_build_object
(
'legacy_identity_id'
,
legacy
.
id
,
'legacy_user_id'
,
legacy
.
user_id
,
'existing_identity_id'
,
ai
.
id
,
'existing_user_id'
,
ai
.
user_id
,
'provider_type'
,
legacy
.
provider_type
,
'provider_key'
,
legacy
.
provider_key
,
'provider_subject'
,
legacy
.
provider_subject
,
'reason'
,
'legacy canonical identity subject already belongs to another user'
,
'migration'
,
'116_auth_identity_legacy_external_safety_reports'
)
FROM
(
SELECT
uei
.
id
,
uei
.
user_id
,
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
AS
provider_type
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
'wechat-main'
ELSE
'linuxdo'
END
AS
provider_key
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
ELSE
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
END
AS
provider_subject
,
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
AS
provider_user_id
,
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
AS
provider_union_id
,
BTRIM
(
COALESCE
(
uei
.
provider_username
,
''
))
AS
provider_username
,
BTRIM
(
COALESCE
(
uei
.
display_name
,
''
))
AS
display_name
,
public
.
__migration_116_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
,
''
)))
IN
(
'linuxdo'
,
'wechat'
)
AND
(
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'linuxdo'
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
)
OR
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
<>
''
)
)
)
AS
legacy
JOIN
(
SELECT
provider_type
,
provider_key
,
provider_subject
FROM
(
SELECT
uei
.
user_id
,
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
AS
provider_type
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
'wechat-main'
ELSE
'linuxdo'
END
AS
provider_key
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
ELSE
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
END
AS
provider_subject
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
,
''
)))
IN
(
'linuxdo'
,
'wechat'
)
AND
(
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'linuxdo'
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
)
OR
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
<>
''
)
)
)
AS
legacy_subjects
GROUP
BY
provider_type
,
provider_key
,
provider_subject
HAVING
COUNT
(
DISTINCT
user_id
)
=
1
)
AS
clear_subjects
ON
clear_subjects
.
provider_type
=
legacy
.
provider_type
AND
clear_subjects
.
provider_key
=
legacy
.
provider_key
AND
clear_subjects
.
provider_subject
=
legacy
.
provider_subject
JOIN
auth_identities
AS
ai
ON
ai
.
provider_type
=
legacy
.
provider_type
AND
ai
.
provider_key
=
legacy
.
provider_key
AND
ai
.
provider_subject
=
legacy
.
provider_subject
WHERE
ai
.
user_id
<>
legacy
.
user_id
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
WITH
legacy
AS
(
SELECT
uei
.
id
,
uei
.
user_id
,
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
AS
provider_type
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
'wechat-main'
ELSE
'linuxdo'
END
AS
provider_key
,
CASE
WHEN
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
THEN
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
ELSE
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
END
AS
provider_subject
,
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
AS
provider_user_id
,
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
AS
provider_union_id
,
BTRIM
(
COALESCE
(
uei
.
provider_username
,
''
))
AS
provider_username
,
BTRIM
(
COALESCE
(
uei
.
display_name
,
''
))
AS
display_name
,
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
AS
metadata_json
,
COALESCE
(
uei
.
updated_at
,
uei
.
created_at
,
NOW
())
AS
verified_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
,
''
)))
IN
(
'linuxdo'
,
'wechat'
)
AND
(
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'linuxdo'
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
)
OR
(
LOWER
(
BTRIM
(
COALESCE
(
uei
.
provider
,
''
)))
=
'wechat'
AND
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
<>
''
)
)
),
clear_subjects
AS
(
SELECT
provider_type
,
provider_key
,
provider_subject
FROM
legacy
GROUP
BY
provider_type
,
provider_key
,
provider_subject
HAVING
COUNT
(
DISTINCT
user_id
)
=
1
),
canonical_legacy
AS
(
SELECT
legacy
.
*
,
ROW_NUMBER
()
OVER
(
PARTITION
BY
legacy
.
provider_type
,
legacy
.
provider_key
,
legacy
.
provider_subject
ORDER
BY
legacy
.
verified_at
DESC
,
legacy
.
id
DESC
)
AS
canonical_row_num
FROM
legacy
JOIN
clear_subjects
ON
clear_subjects
.
provider_type
=
legacy
.
provider_type
AND
clear_subjects
.
provider_key
=
legacy
.
provider_key
AND
clear_subjects
.
provider_subject
=
legacy
.
provider_subject
)
INSERT
INTO
auth_identities
(
user_id
,
provider_type
,
provider_key
,
provider_subject
,
verified_at
,
metadata
)
SELECT
legacy
.
user_id
,
legacy
.
provider_type
,
legacy
.
provider_key
,
legacy
.
provider_subject
,
legacy
.
verified_at
,
legacy
.
metadata_json
||
jsonb_build_object
(
'legacy_identity_id'
,
legacy
.
id
,
'provider_user_id'
,
legacy
.
provider_user_id
,
'provider_union_id'
,
NULLIF
(
legacy
.
provider_union_id
,
''
),
'provider_username'
,
legacy
.
provider_username
,
'display_name'
,
legacy
.
display_name
,
'migration'
,
'116_auth_identity_legacy_external_safety_reports'
)
FROM
canonical_legacy
AS
legacy
LEFT
JOIN
auth_identities
AS
ai
ON
ai
.
provider_type
=
legacy
.
provider_type
AND
ai
.
provider_key
=
legacy
.
provider_key
AND
ai
.
provider_subject
=
legacy
.
provider_subject
WHERE
legacy
.
canonical_row_num
=
1
AND
ai
.
id
IS
NULL
ON
CONFLICT
(
provider_type
,
provider_key
,
provider_subject
)
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'legacy_external_channel_conflict'
,
'legacy_external_identity:'
||
legacy
.
id
::
text
,
legacy
.
metadata_json
||
jsonb_build_object
(
'legacy_identity_id'
,
legacy
.
id
,
'legacy_user_id'
,
legacy
.
user_id
,
'existing_channel_id'
,
channel
.
id
,
'existing_identity_id'
,
existing_ai
.
id
,
'existing_user_id'
,
existing_ai
.
user_id
,
'provider_type'
,
'wechat'
,
'provider_key'
,
'wechat-main'
,
'provider_subject'
,
legacy
.
provider_union_id
,
'channel'
,
legacy
.
channel
,
'channel_app_id'
,
legacy
.
channel_app_id
,
'channel_subject'
,
legacy
.
provider_user_id
,
'reason'
,
'legacy channel subject already belongs to another user'
,
'migration'
,
'116_auth_identity_legacy_external_safety_reports'
)
FROM
(
SELECT
uei
.
id
,
uei
.
user_id
,
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
AS
provider_user_id
,
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
AS
provider_union_id
,
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
AS
metadata_json
,
BTRIM
(
COALESCE
(
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'channel'
,
''
))
AS
channel
,
BTRIM
(
COALESCE
(
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'channel_app_id'
,
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'appid'
,
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'app_id'
,
''
))
AS
channel_app_id
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
,
''
))
<>
''
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
)
AS
legacy
JOIN
(
SELECT
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
AS
provider_subject
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
,
''
))
<>
''
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
GROUP
BY
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
HAVING
COUNT
(
DISTINCT
uei
.
user_id
)
=
1
)
AS
clear_subjects
ON
clear_subjects
.
provider_subject
=
legacy
.
provider_union_id
JOIN
auth_identities
AS
legacy_ai
ON
legacy_ai
.
user_id
=
legacy
.
user_id
AND
legacy_ai
.
provider_type
=
'wechat'
AND
legacy_ai
.
provider_key
=
'wechat-main'
AND
legacy_ai
.
provider_subject
=
legacy
.
provider_union_id
JOIN
auth_identity_channels
AS
channel
ON
channel
.
provider_type
=
'wechat'
AND
channel
.
provider_key
=
'wechat-main'
AND
channel
.
channel
=
legacy
.
channel
AND
channel
.
channel_app_id
=
legacy
.
channel_app_id
AND
channel
.
channel_subject
=
legacy
.
provider_user_id
JOIN
auth_identities
AS
existing_ai
ON
existing_ai
.
id
=
channel
.
identity_id
WHERE
legacy
.
channel
<>
''
AND
legacy
.
channel_app_id
<>
''
AND
existing_ai
.
user_id
<>
legacy
.
user_id
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
$
sql
$
;
EXECUTE
$
sql
$
WITH
legacy
AS
(
SELECT
uei
.
user_id
,
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
AS
provider_user_id
,
BTRIM
(
COALESCE
(
uei
.
provider_union_id
,
''
))
AS
provider_union_id
,
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
AS
metadata_json
,
BTRIM
(
COALESCE
(
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'channel'
,
''
))
AS
channel
,
BTRIM
(
COALESCE
(
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'channel_app_id'
,
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'appid'
,
public
.
__migration_116_safe_legacy_metadata_jsonb
(
uei
.
metadata
)
->>
'app_id'
,
''
))
AS
channel_app_id
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
,
''
))
<>
''
AND
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
<>
''
),
clear_subjects
AS
(
SELECT
provider_union_id
AS
provider_subject
FROM
legacy
GROUP
BY
provider_union_id
HAVING
COUNT
(
DISTINCT
user_id
)
=
1
)
INSERT
INTO
auth_identity_channels
(
identity_id
,
provider_type
,
provider_key
,
channel
,
channel_app_id
,
channel_subject
,
metadata
)
SELECT
legacy_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'
,
'116_auth_identity_legacy_external_safety_reports'
)
FROM
legacy
JOIN
clear_subjects
ON
clear_subjects
.
provider_subject
=
legacy
.
provider_union_id
JOIN
auth_identities
AS
legacy_ai
ON
legacy_ai
.
user_id
=
legacy
.
user_id
AND
legacy_ai
.
provider_type
=
'wechat'
AND
legacy_ai
.
provider_key
=
'wechat-main'
AND
legacy_ai
.
provider_subject
=
legacy
.
provider_union_id
LEFT
JOIN
auth_identity_channels
AS
channel
ON
channel
.
provider_type
=
'wechat'
AND
channel
.
provider_key
=
'wechat-main'
AND
channel
.
channel
=
legacy
.
channel
AND
channel
.
channel_app_id
=
legacy
.
channel_app_id
AND
channel
.
channel_subject
=
legacy
.
provider_user_id
WHERE
legacy
.
channel
<>
''
AND
legacy
.
channel_app_id
<>
''
AND
channel
.
id
IS
NULL
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'
,
'116_auth_identity_legacy_external_safety_reports'
)
FROM
(
SELECT
uei
.
id
,
uei
.
user_id
,
BTRIM
(
COALESCE
(
uei
.
provider_user_id
,
''
))
AS
provider_user_id
,
public
.
__migration_116_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
$$
;
DO
$$
BEGIN
IF
NOT
EXISTS
(
SELECT
1
FROM
pg_constraint
WHERE
conname
=
'auth_identities_metadata_is_object_check'
)
THEN
ALTER
TABLE
auth_identities
ADD
CONSTRAINT
auth_identities_metadata_is_object_check
CHECK
(
jsonb_typeof
(
metadata
)
=
'object'
);
END
IF
;
IF
NOT
EXISTS
(
SELECT
1
FROM
pg_constraint
WHERE
conname
=
'auth_identity_channels_metadata_is_object_check'
)
THEN
ALTER
TABLE
auth_identity_channels
ADD
CONSTRAINT
auth_identity_channels_metadata_is_object_check
CHECK
(
jsonb_typeof
(
metadata
)
=
'object'
);
END
IF
;
IF
NOT
EXISTS
(
SELECT
1
FROM
pg_constraint
WHERE
conname
=
'auth_identity_migration_reports_details_is_object_check'
)
THEN
ALTER
TABLE
auth_identity_migration_reports
ADD
CONSTRAINT
auth_identity_migration_reports_details_is_object_check
CHECK
(
jsonb_typeof
(
details
)
=
'object'
);
END
IF
;
END
$$
;
DROP
FUNCTION
IF
EXISTS
public
.
__migration_116_is_valid_legacy_metadata_jsonb
(
TEXT
);
DROP
FUNCTION
IF
EXISTS
public
.
__migration_116_safe_legacy_metadata_jsonb
(
TEXT
);
backend/migration_release/117_add_payment_order_provider_snapshot.sql
deleted
100644 → 0
View file @
74828a7c
ALTER
TABLE
payment_orders
ADD
COLUMN
IF
NOT
EXISTS
provider_snapshot
JSONB
;
backend/migration_release/118_wechat_dual_mode_and_auth_source_defaults.sql
deleted
100644 → 0
View file @
74828a7c
INSERT
INTO
settings
(
key
,
value
)
VALUES
(
'wechat_connect_open_enabled'
,
CASE
WHEN
NOT
EXISTS
(
SELECT
1
FROM
settings
WHERE
key
=
'wechat_connect_enabled'
)
THEN
''
WHEN
COALESCE
((
SELECT
value
FROM
settings
WHERE
key
=
'wechat_connect_enabled'
),
'false'
)
<>
'true'
THEN
'false'
WHEN
LOWER
(
TRIM
(
COALESCE
((
SELECT
value
FROM
settings
WHERE
key
=
'wechat_connect_mode'
),
'open'
)))
=
'mp'
THEN
'false'
ELSE
'true'
END
),
(
'wechat_connect_mp_enabled'
,
CASE
WHEN
NOT
EXISTS
(
SELECT
1
FROM
settings
WHERE
key
=
'wechat_connect_enabled'
)
THEN
''
WHEN
COALESCE
((
SELECT
value
FROM
settings
WHERE
key
=
'wechat_connect_enabled'
),
'false'
)
<>
'true'
THEN
'false'
WHEN
LOWER
(
TRIM
(
COALESCE
((
SELECT
value
FROM
settings
WHERE
key
=
'wechat_connect_mode'
),
'open'
)))
=
'mp'
THEN
'true'
ELSE
'false'
END
),
(
'auth_source_default_email_grant_on_signup'
,
'false'
),
(
'auth_source_default_linuxdo_grant_on_signup'
,
'false'
),
(
'auth_source_default_oidc_grant_on_signup'
,
'false'
),
(
'auth_source_default_wechat_grant_on_signup'
,
'false'
)
ON
CONFLICT
(
key
)
DO
NOTHING
;
backend/migration_release/119_enforce_payment_orders_out_trade_no_unique.sql
deleted
100644 → 0
View file @
74828a7c
-- Intentionally left as a no-op.
-- The online index rollout lives in 120_enforce_payment_orders_out_trade_no_unique_notx.sql
DO
$$
BEGIN
NULL
;
END
$$
;
backend/migration_release/120_enforce_payment_orders_out_trade_no_unique_notx.sql
deleted
100644 → 0
View file @
74828a7c
-- Build the payment order uniqueness guarantee online.
-- The migration runner performs an explicit duplicate out_trade_no precheck and
-- drops any stale invalid paymentorder_out_trade_no_unique index before retrying.
-- Create the new partial unique index concurrently first so writes keep flowing,
-- then remove the legacy index name once the replacement is ready.
CREATE
UNIQUE
INDEX
CONCURRENTLY
IF
NOT
EXISTS
paymentorder_out_trade_no_unique
ON
payment_orders
(
out_trade_no
)
WHERE
out_trade_no
<>
''
;
DROP
INDEX
CONCURRENTLY
IF
EXISTS
paymentorder_out_trade_no
;
backend/migration_release/120a_align_payment_orders_out_trade_no_index_name.sql
deleted
100644 → 0
View file @
74828a7c
DO
$$
BEGIN
IF
EXISTS
(
SELECT
1
FROM
pg_indexes
WHERE
schemaname
=
'public'
AND
tablename
=
'payment_orders'
AND
indexname
=
'paymentorder_out_trade_no_unique'
)
THEN
IF
EXISTS
(
SELECT
1
FROM
pg_indexes
WHERE
schemaname
=
'public'
AND
tablename
=
'payment_orders'
AND
indexname
=
'paymentorder_out_trade_no'
)
THEN
EXECUTE
'DROP INDEX IF EXISTS paymentorder_out_trade_no'
;
END
IF
;
EXECUTE
'ALTER INDEX paymentorder_out_trade_no_unique RENAME TO paymentorder_out_trade_no'
;
END
IF
;
END
$$
;
backend/migration_release/121_auth_identity_migration_report_type_widen.sql
deleted
100644 → 0
View file @
74828a7c
ALTER
TABLE
auth_identity_migration_reports
ALTER
COLUMN
report_type
TYPE
VARCHAR
(
80
);
backend/migration_release/122_pending_auth_completion_token_cleanup.sql
deleted
100644 → 0
View file @
74828a7c
UPDATE
pending_auth_sessions
SET
local_flow_state
=
jsonb_set
(
local_flow_state
,
'{completion_response}'
,
((
local_flow_state
->
'completion_response'
)
-
'access_token'
-
'refresh_token'
-
'expires_in'
-
'token_type'
),
true
)
WHERE
jsonb_typeof
(
local_flow_state
->
'completion_response'
)
=
'object'
AND
(
(
local_flow_state
->
'completion_response'
)
?
'access_token'
OR
(
local_flow_state
->
'completion_response'
)
?
'refresh_token'
OR
(
local_flow_state
->
'completion_response'
)
?
'expires_in'
OR
(
local_flow_state
->
'completion_response'
)
?
'token_type'
);
backend/migration_release/123_fix_legacy_auth_source_grant_on_signup_defaults.sql
deleted
100644 → 0
View file @
74828a7c
-- Auto-backfill untouched migration 110 signup-grant defaults to the corrected false value.
-- Rows still matching the migration-110 default payload and timestamp window are treated as
-- untouched legacy defaults; any remaining legacy true values are reported for manual review.
WITH
migration_110
AS
(
SELECT
applied_at
FROM
schema_migrations
WHERE
filename
=
'110_pending_auth_and_provider_default_grants.sql'
),
providers
AS
(
SELECT
provider_type
FROM
(
VALUES
(
'email'
),
(
'linuxdo'
),
(
'oidc'
),
(
'wechat'
)
)
AS
providers
(
provider_type
)
),
legacy_provider_defaults
AS
(
SELECT
providers
.
provider_type
FROM
providers
CROSS
JOIN
migration_110
JOIN
settings
balance
ON
balance
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_balance'
JOIN
settings
concurrency
ON
concurrency
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_concurrency'
JOIN
settings
subscriptions
ON
subscriptions
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_subscriptions'
JOIN
settings
grant_on_signup
ON
grant_on_signup
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_grant_on_signup'
JOIN
settings
grant_on_first_bind
ON
grant_on_first_bind
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_grant_on_first_bind'
WHERE
balance
.
value
=
'0'
AND
concurrency
.
value
=
'5'
AND
subscriptions
.
value
=
'[]'
AND
grant_on_signup
.
value
=
'true'
AND
grant_on_first_bind
.
value
=
'false'
AND
balance
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
concurrency
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
subscriptions
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
grant_on_signup
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
grant_on_first_bind
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
),
updated_signup_grants
AS
(
UPDATE
settings
SET
value
=
'false'
,
updated_at
=
NOW
()
FROM
legacy_provider_defaults
WHERE
settings
.
key
=
'auth_source_default_'
||
legacy_provider_defaults
.
provider_type
||
'_grant_on_signup'
AND
settings
.
value
=
'true'
RETURNING
legacy_provider_defaults
.
provider_type
)
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'legacy_auth_source_signup_grant_review'
,
providers
.
provider_type
,
jsonb_build_object
(
'provider_type'
,
providers
.
provider_type
,
'current_value'
,
grant_on_signup
.
value
,
'auto_backfilled'
,
FALSE
,
'reason'
,
'legacy_true_default_not_auto_backfilled'
)
FROM
providers
JOIN
settings
grant_on_signup
ON
grant_on_signup
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_grant_on_signup'
LEFT
JOIN
updated_signup_grants
ON
updated_signup_grants
.
provider_type
=
providers
.
provider_type
WHERE
grant_on_signup
.
value
=
'true'
AND
updated_signup_grants
.
provider_type
IS
NULL
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
backend/migration_release/124_backfill_legacy_oidc_security_flags.sql
deleted
100644 → 0
View file @
74828a7c
-- Preserve legacy OIDC behavior for upgraded installs that predate the
-- introduction of secure PKCE/id_token defaults. Fresh installs continue to
-- inherit runtime defaults when these rows are absent.
WITH
legacy_oidc_install
AS
(
SELECT
1
FROM
settings
WHERE
key
IN
(
'oidc_connect_enabled'
,
'oidc_connect_client_id'
,
'oidc_connect_authorize_url'
,
'oidc_connect_token_url'
,
'oidc_connect_issuer_url'
,
'oidc_connect_userinfo_url'
,
'oidc_connect_frontend_redirect_url'
)
LIMIT
1
)
INSERT
INTO
settings
(
key
,
value
)
SELECT
defaults
.
key
,
'false'
FROM
legacy_oidc_install
CROSS
JOIN
(
VALUES
(
'oidc_connect_use_pkce'
),
(
'oidc_connect_validate_id_token'
)
)
AS
defaults
(
key
)
WHERE
NOT
EXISTS
(
SELECT
1
FROM
settings
existing
WHERE
existing
.
key
=
defaults
.
key
)
ON
CONFLICT
(
key
)
DO
NOTHING
;
backend/migration_release/125_add_channel_monitors.sql
deleted
100644 → 0
View file @
74828a7c
-- Migration: 125_add_channel_monitors
-- 渠道监控 MVP:周期性对外部 provider/endpoint/api_key 做模型心跳测试。
--
-- 表结构说明:
-- - channel_monitors 渠道配置表(一行 = 一个监控对象)
-- - channel_monitor_histories 检测历史明细表(一次检测一个模型 = 一行)
--
-- 设计要点:
-- - api_key_encrypted 列存放 AES-256-GCM 密文(base64),由 service 层加密。
-- - extra_models 用 JSONB 存储字符串数组,便于扩展(后续可加权重等元数据)。
-- - history 表通过 ON DELETE CASCADE 自动清理已删除监控的历史。
-- - (enabled, last_checked_at) 索引服务于调度器扫描“到期需要检测”的监控。
-- - histories 上 (monitor_id, model, checked_at DESC) 服务用户视图聚合查询;
-- 单独的 (checked_at) 索引服务定期清理 30 天前数据的 DELETE。
CREATE
TABLE
IF
NOT
EXISTS
channel_monitors
(
id
BIGSERIAL
PRIMARY
KEY
,
name
VARCHAR
(
100
)
NOT
NULL
,
provider
VARCHAR
(
20
)
NOT
NULL
,
-- openai / anthropic / gemini
endpoint
VARCHAR
(
500
)
NOT
NULL
,
-- base origin
api_key_encrypted
TEXT
NOT
NULL
,
-- AES-256-GCM (base64)
primary_model
VARCHAR
(
200
)
NOT
NULL
,
extra_models
JSONB
NOT
NULL
DEFAULT
'[]'
::
jsonb
,
group_name
VARCHAR
(
100
)
NOT
NULL
DEFAULT
''
,
enabled
BOOLEAN
NOT
NULL
DEFAULT
TRUE
,
interval_seconds
INT
NOT
NULL
,
last_checked_at
TIMESTAMPTZ
,
created_by
BIGINT
NOT
NULL
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
channel_monitors_provider_check
CHECK
(
provider
IN
(
'openai'
,
'anthropic'
,
'gemini'
)),
CONSTRAINT
channel_monitors_interval_check
CHECK
(
interval_seconds
BETWEEN
15
AND
3600
)
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitors_enabled_last_checked
ON
channel_monitors
(
enabled
,
last_checked_at
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitors_provider
ON
channel_monitors
(
provider
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitors_group_name
ON
channel_monitors
(
group_name
);
CREATE
TABLE
IF
NOT
EXISTS
channel_monitor_histories
(
id
BIGSERIAL
PRIMARY
KEY
,
monitor_id
BIGINT
NOT
NULL
REFERENCES
channel_monitors
(
id
)
ON
DELETE
CASCADE
,
model
VARCHAR
(
200
)
NOT
NULL
,
status
VARCHAR
(
20
)
NOT
NULL
,
latency_ms
INT
,
ping_latency_ms
INT
,
message
VARCHAR
(
500
)
NOT
NULL
DEFAULT
''
,
checked_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
channel_monitor_histories_status_check
CHECK
(
status
IN
(
'operational'
,
'degraded'
,
'failed'
,
'error'
))
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_histories_monitor_model_checked
ON
channel_monitor_histories
(
monitor_id
,
model
,
checked_at
DESC
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_histories_checked_at
ON
channel_monitor_histories
(
checked_at
);
backend/migration_release/125_add_group_rpm_limit.sql
deleted
100644 → 0
View file @
74828a7c
-- Add per-group Requests-Per-Minute limit.
-- rpm_limit: 分组统一 RPM 上限(0 = 不限制)。
-- 一旦配置即接管该用户在该分组的限流,覆盖用户级 users.rpm_limit。
-- 计数键:rpm:ug:{user_id}:{group_id}:{minute}。
ALTER
TABLE
groups
ADD
COLUMN
IF
NOT
EXISTS
rpm_limit
integer
NOT
NULL
DEFAULT
0
;
COMMENT
ON
COLUMN
groups
.
rpm_limit
IS
'分组 RPM 上限;0 表示不限制;设置后接管该分组用户的限流(覆盖用户级 rpm_limit)。'
;
backend/migration_release/126_add_channel_monitor_aggregation.sql
deleted
100644 → 0
View file @
74828a7c
-- Migration: 126_add_channel_monitor_aggregation
-- 渠道监控日聚合:把 channel_monitor_histories 的明细按天聚合,明细只保留 1 天,
-- 聚合保留 30 天。明细和聚合表都用软删除(deleted_at),由 ops cleanup 任务每天
-- 凌晨随运维监控清理一起跑(共享 cron)。
--
-- 设计要点:
-- - channel_monitor_histories 加 deleted_at 软删除字段(SoftDeleteMixin 全局
-- Hook 会把 DELETE 自动改写成 UPDATE deleted_at = NOW())。
-- - channel_monitor_daily_rollups 按 (monitor_id, model, bucket_date) 唯一,
-- 用 ON CONFLICT DO UPDATE 实现幂等回填,状态分布和延迟分子分母都保留,
-- 方便后续按窗口任意求加权可用率和均值。
-- - watermark 表只有一行(id=1),记录最近一次聚合到达的日期,避免重启后重复
-- 扫全表。
-- - rollup 上 (bucket_date) 索引服务清理任务的 DELETE WHERE bucket_date < cutoff。
-- 1) 给历史明细表加软删除字段
ALTER
TABLE
channel_monitor_histories
ADD
COLUMN
IF
NOT
EXISTS
deleted_at
TIMESTAMPTZ
;
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_histories_deleted_at
ON
channel_monitor_histories
(
deleted_at
);
-- 2) 创建日聚合表
CREATE
TABLE
IF
NOT
EXISTS
channel_monitor_daily_rollups
(
id
BIGSERIAL
PRIMARY
KEY
,
monitor_id
BIGINT
NOT
NULL
REFERENCES
channel_monitors
(
id
)
ON
DELETE
CASCADE
,
model
VARCHAR
(
200
)
NOT
NULL
,
bucket_date
DATE
NOT
NULL
,
total_checks
INT
NOT
NULL
DEFAULT
0
,
ok_count
INT
NOT
NULL
DEFAULT
0
,
operational_count
INT
NOT
NULL
DEFAULT
0
,
degraded_count
INT
NOT
NULL
DEFAULT
0
,
failed_count
INT
NOT
NULL
DEFAULT
0
,
error_count
INT
NOT
NULL
DEFAULT
0
,
sum_latency_ms
BIGINT
NOT
NULL
DEFAULT
0
,
count_latency
INT
NOT
NULL
DEFAULT
0
,
sum_ping_latency_ms
BIGINT
NOT
NULL
DEFAULT
0
,
count_ping_latency
INT
NOT
NULL
DEFAULT
0
,
computed_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
deleted_at
TIMESTAMPTZ
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_daily_rollups_unique
ON
channel_monitor_daily_rollups
(
monitor_id
,
model
,
bucket_date
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_daily_rollups_bucket
ON
channel_monitor_daily_rollups
(
bucket_date
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_daily_rollups_deleted_at
ON
channel_monitor_daily_rollups
(
deleted_at
);
-- 3) 创建 watermark 表(单行:id=1)
CREATE
TABLE
IF
NOT
EXISTS
channel_monitor_aggregation_watermark
(
id
INT
PRIMARY
KEY
DEFAULT
1
,
last_aggregated_date
DATE
,
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
channel_monitor_aggregation_watermark_singleton
CHECK
(
id
=
1
)
);
INSERT
INTO
channel_monitor_aggregation_watermark
(
id
,
last_aggregated_date
,
updated_at
)
VALUES
(
1
,
NULL
,
NOW
())
ON
CONFLICT
(
id
)
DO
NOTHING
;
backend/migration_release/126_add_user_rpm_limit.sql
deleted
100644 → 0
View file @
74828a7c
-- Add per-user Requests-Per-Minute cap.
-- rpm_limit: 用户全局 RPM 兜底(0 = 不限制)。
-- 仅当所访问分组未设置 rpm_limit 且无 user-group rpm_override 时作为兜底生效。
-- 计数键:rpm:u:{user_id}:{minute}。
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
rpm_limit
integer
NOT
NULL
DEFAULT
0
;
COMMENT
ON
COLUMN
users
.
rpm_limit
IS
'用户级 RPM 兜底上限;0 表示不限制;仅当分组未设置 rpm_limit 时生效。'
;
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