Files
creator-hub/internal/account/deletion.go
T
rogee 65d0801290 feat(schema): 044 数字主键——全表 bigint IDENTITY、文本标识降级 UNIQUE、FK 重指
- 迁移 044:9 张表换 bigint IDENTITY 主键,account_id/exit_id/competitor_id/work_id/comment_id/job_id/rule_id 降级 UNIQUE;
  引用表 FK 重指 bigint;checkpoint 删派生文本 id;creator_settings 布尔 PK 换 bigint identity + 单行表达式唯一索引;
  social_account.profile_id 死列删除
- seed 派生改 SQL:CreateBoundEnv 用 jsonb_set(account.id+1000),deriveSeed 删除;Fingerprint.Validate 允许 Seed=0(派生态)
- 三域 store SQL 列名适配;appendAudit/审计过滤经 JOIN 读回文本标识;UPDATE..RETURNING..FROM 拆两段式
- 对外 API 契约不变:模型/JSON/URL 全部保持文本 ID
- account 域补 PG 生命周期集成测试(列表/凭据解析/暂停吊销恢复/审计过滤/删除链路),覆盖率 22.4%→73.9%
2026-09-28 22:30:14 +08:00

113 lines
4.0 KiB
Go

package account
import (
"context"
"errors"
)
// CheckAccountDeletion verifies that deleting an account will not interrupt an active run.
func (s *Store) CheckAccountDeletion(ctx context.Context, accountID string) error {
if !idPattern.MatchString(accountID) {
return ErrInvalid
}
var exists bool
if err := s.db.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM social_account WHERE account_id = $1)`, accountID).Scan(&exists); err != nil {
return errors.New("check account deletion state")
}
if !exists {
return ErrNotFound
}
var active bool
if err := s.db.QueryRowContext(ctx, `
SELECT EXISTS (
SELECT 1 FROM browser_env environment
JOIN social_account account ON account.id = environment.account_id
WHERE account.account_id = $1 AND environment.runtime_id IS NOT NULL
)`, accountID).Scan(&active); err != nil {
return errors.New("check account deletion state")
}
if active {
return ErrConflict
}
return nil
}
// DeleteAccountData removes Phase A data while keeping the account row for the final deletion step.
func (s *Store) DeleteAccountData(ctx context.Context, accountID string) error {
if !idPattern.MatchString(accountID) {
return ErrInvalid
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return errors.New("begin account deletion transaction")
}
defer tx.Rollback()
var lockedID string
if err := tx.QueryRowContext(ctx, `SELECT account_id FROM social_account WHERE account_id = $1 FOR UPDATE`, accountID).Scan(&lockedID); err != nil {
return rowError(err)
}
var active bool
if err := tx.QueryRowContext(ctx, `
SELECT EXISTS (
SELECT 1 FROM browser_env environment
JOIN social_account account ON account.id = environment.account_id
WHERE account.account_id = $1 AND environment.runtime_id IS NOT NULL
)`, accountID).Scan(&active); err != nil {
return errors.New("check account deletion state")
}
if active {
return ErrConflict
}
if _, err := tx.ExecContext(ctx, `SELECT set_config('creatorhub.account_deletion', 'on', true)`); err != nil {
return errors.New("enable account deletion audit cleanup")
}
if _, err := tx.ExecContext(ctx, `
DELETE FROM audit_event
WHERE account_id = (SELECT account.id FROM social_account account WHERE account.account_id = $1)
OR browser_env_id IN (
SELECT environment.id FROM browser_env environment
JOIN social_account account ON account.id = environment.account_id
WHERE account.account_id = $1
)`, accountID); err != nil {
return errors.New("delete account audit data")
}
if _, err := tx.ExecContext(ctx, `
UPDATE browser_env environment SET runtime_id = NULL, runtime_lease_until = NULL, runtime_network_id = NULL, runtime_node_id = NULL
FROM social_account account WHERE account.id = environment.account_id AND account.account_id = $1`, accountID); err != nil {
return errors.New("clear account runtime state")
}
return commit(tx)
}
// DeleteAccount removes the account row and its external cookie credential.
// Call DeleteAccountData and delete account-owned creator/environment data first.
func (s *Store) DeleteAccount(ctx context.Context, accountID string, credentials CredentialBridge) error {
if !idPattern.MatchString(accountID) || credentials == nil {
return ErrInvalid
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return errors.New("begin account record deletion")
}
defer tx.Rollback()
reference := CredentialReference{ID: accountID, Provider: ""}
var key string
if err := tx.QueryRowContext(ctx, `
SELECT credential_provider, credential_key
FROM social_account
WHERE account_id = $1
FOR UPDATE`, accountID).Scan(&reference.Provider, &key); err != nil {
return rowError(err)
}
if _, err := tx.ExecContext(ctx, `DELETE FROM social_account WHERE account_id = $1`, accountID); err != nil {
return publicDatabaseError(err)
}
if err := tx.Commit(); err != nil {
return errors.New("commit account record deletion")
}
if err := credentials.Delete(context.WithoutCancel(ctx), reference, key); err != nil {
return errors.Join(errors.New("delete account credential"), err)
}
return nil
}