Files
creator-hub/internal/account/deletion.go
T
rogee 0879d9fd25 refactor(schema): 迁移 043 schema 收敛——42→19 张表,统一迁移 runner
- 迁移 043 一次到位:runtime_instance/environment_binding 并入 browser_env(账号即环境)、
  credential_reference 并入 social_account、work_source/metric_plan 并入 creator_work、
  sync 租约并入 creator_collection_checkpoint(kind='sync')、audit_event 瘦列、
  creator_settings 去转写列、TRUNCATE 账号域(竞品采集数据无损保留)
- 统一迁移 runner:environment.Open 串起 001 账号基座 + hub 链 + 038 creator 版本回填
  (v+1000) + creator 1017-1042 + 043;account/creator Open 不再迁移
- 三域 store SQL 全量适配:CreateBoundEnv 就绪门禁、SetRuntimeCleanupPending fence
  语义(未知代 MissingRuntimeID 允许登记并释放活跃实例)、metric_plan 列并入 work、
  checkpoint sync 租约、deletion 链路重写
- api 适配:envView 去 runtime_instance_id、audit filter 去任务列、browser_env 单表查询
- 测试:迁移 043 形态探针、迁移不可变映射新路径、legacy 迁移重放子测试重写、
  fence/出口生命周期/单来源 upsert 语义覆盖;CREATORHUB_POSTGRES_TEST_URL 全绿,
  environment 70%/creator 67.5% 覆盖率,tsc 通过
2026-09-28 19:48:55 +08:00

109 lines
3.5 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 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
WHERE account_id = $1 AND 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 id FROM social_account WHERE 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
WHERE account_id = $1 AND 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 = $1
OR browser_env_alias IN (
SELECT alias FROM browser_env WHERE account_id = $1
)`, accountID); err != nil {
return errors.New("delete account audit data")
}
if _, err := tx.ExecContext(ctx, `
UPDATE browser_env SET runtime_id = NULL, runtime_lease_until = NULL, runtime_network_id = NULL, runtime_node_id = NULL
WHERE 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 id = $1
FOR UPDATE`, accountID).Scan(&reference.Provider, &key); err != nil {
return rowError(err)
}
if _, err := tx.ExecContext(ctx, `DELETE FROM social_account WHERE 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
}