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
110702d4
Commit
110702d4
authored
Apr 27, 2026
by
陈曦
Browse files
merge v0.1.119
parent
8cc113dc
Pipeline
#82274
passed with stage
in 4 minutes and 19 seconds
Changes
31
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
backend/migration_release/108_auth_identity_foundation_core.sql
deleted
100644 → 0
View file @
8cc113dc
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
deleted
100644 → 0
View file @
8cc113dc
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 @
8cc113dc
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
deleted
100644 → 0
View file @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
This diff is collapsed.
Click to expand it.
backend/migration_release/117_add_payment_order_provider_snapshot.sql
deleted
100644 → 0
View file @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
-- 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 @
8cc113dc
-- 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 @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
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 @
8cc113dc
-- 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 @
8cc113dc
-- 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 @
8cc113dc
-- 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
);
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