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 runtime_instance WHERE account_id = $1 AND released_at IS NULL UNION ALL SELECT 1 FROM operation_task WHERE account_id = $1 AND state = 'executing' )`, 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 runtime_instance WHERE account_id = $1 AND released_at IS NULL UNION ALL SELECT 1 FROM operation_task WHERE account_id = $1 AND state = 'executing' )`, 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 confirmation_id IN (SELECT id FROM confirmation WHERE account_id = $1) OR task_id IN (SELECT id FROM operation_task WHERE account_id = $1) OR attempt_id IN ( SELECT id FROM execution_attempt WHERE task_id IN (SELECT id FROM operation_task WHERE account_id = $1) ) OR browser_env_alias IN ( SELECT browser_env_alias FROM environment_binding WHERE account_id = $1 ) OR runtime_instance_id IN (SELECT id FROM runtime_instance WHERE account_id = $1)`, accountID); err != nil { return errors.New("delete account audit data") } if _, err := tx.ExecContext(ctx, `UPDATE operation_task SET current_attempt_id = NULL WHERE account_id = $1`, accountID); err != nil { return errors.New("detach account task attempts") } if _, err := tx.ExecContext(ctx, ` DELETE FROM execution_attempt WHERE task_id IN (SELECT id FROM operation_task WHERE account_id = $1)`, accountID); err != nil { return errors.New("delete account task attempts") } if _, err := tx.ExecContext(ctx, `DELETE FROM operation_task WHERE account_id = $1`, accountID); err != nil { return errors.New("delete account tasks") } if _, err := tx.ExecContext(ctx, `DELETE FROM confirmation WHERE account_id = $1`, accountID); err != nil { return errors.New("delete account confirmations") } if _, err := tx.ExecContext(ctx, `DELETE FROM content_draft WHERE account_id = $1`, accountID); err != nil { return errors.New("delete account drafts") } if _, err := tx.ExecContext(ctx, `DELETE FROM runtime_instance WHERE account_id = $1`, accountID); err != nil { return errors.New("delete account runtime records") } 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() var reference CredentialReference var key string if err := tx.QueryRowContext(ctx, ` SELECT credential.id, credential.provider, credential.reference_key FROM social_account account JOIN credential_reference credential ON credential.id = account.credential_reference_id WHERE account.id = $1 FOR UPDATE`, accountID).Scan(&reference.ID, &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.ExecContext(ctx, `DELETE FROM credential_reference WHERE id = $1`, reference.ID); 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 }