289 lines
9.8 KiB
Go
289 lines
9.8 KiB
Go
package creator
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
func (s *Store) CheckOwnedAccountDeletion(ctx context.Context, accountID string) error {
|
|
if 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 databaseError(err)
|
|
}
|
|
if !exists {
|
|
return ErrNotFound
|
|
}
|
|
var active bool
|
|
if err := s.db.QueryRowContext(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM creator_operation
|
|
WHERE account_id = $1 AND state = 'processing'
|
|
UNION ALL
|
|
SELECT 1 FROM creator_event
|
|
WHERE (receiving_account_id = $1 OR execution_account_id = $1) AND state = 'processing'
|
|
UNION ALL
|
|
SELECT 1 FROM creator_collection_checkpoint
|
|
WHERE source_type = 'owned' AND source_id = $1 AND status = 'running'
|
|
UNION ALL
|
|
SELECT 1 FROM creator_source_sync_lease
|
|
WHERE source_type = 'owned' AND source_id = $1 AND lease_until > now()
|
|
)`, accountID).Scan(&active); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if active {
|
|
return ErrConflict
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) DeleteOwnedAccountData(ctx context.Context, accountID string) error {
|
|
if accountID == "" {
|
|
return ErrInvalid
|
|
}
|
|
tx, err := s.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return errors.New("begin creator account deletion")
|
|
}
|
|
defer tx.Rollback()
|
|
if err := lockCreatorAccount(ctx, tx, accountID); err != nil {
|
|
return err
|
|
}
|
|
if err := checkOwnedAccountDeletionTx(ctx, tx, accountID); err != nil {
|
|
return err
|
|
}
|
|
var secretID, secretKey string
|
|
var hasSecret bool
|
|
if err := tx.QueryRowContext(ctx, `
|
|
SELECT secret_reference_id, secret_key
|
|
FROM creator_account_password
|
|
WHERE account_id = $1`, accountID).Scan(&secretID, &secretKey); err == nil {
|
|
hasSecret = true
|
|
} else if !errors.Is(err, sql.ErrNoRows) {
|
|
return databaseError(err)
|
|
}
|
|
if hasSecret && s.secrets == nil {
|
|
return errors.New("creator secret bridge is unavailable")
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_operation
|
|
WHERE account_id = $1
|
|
OR strategy_id IN (SELECT id FROM creator_strategy WHERE big_account_id = $1 OR execution_account_id = $1)
|
|
OR event_id IN (SELECT id FROM creator_event WHERE receiving_account_id = $1 OR execution_account_id = $1)`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_event
|
|
WHERE receiving_account_id = $1 OR execution_account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_cooldown
|
|
WHERE big_account_id = $1 OR execution_account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_strategy
|
|
WHERE big_account_id = $1 OR execution_account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_relation
|
|
WHERE big_account_id = $1 OR small_account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_conversation WHERE account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_listener_boundary WHERE account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_listener_state WHERE account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if err := deleteCreatorSourceTx(ctx, tx, SourceOwned, accountID); err != nil {
|
|
return err
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_account_password WHERE account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_account_profile WHERE account_id = $1`, accountID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return errors.New("commit creator account deletion")
|
|
}
|
|
if hasSecret {
|
|
if err := s.secrets.Delete(context.WithoutCancel(ctx), SecretReference{ID: secretID, Provider: "os_keyring"}, secretKey); err != nil {
|
|
return errors.Join(errors.New("delete creator account password"), err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) DeleteCompetitor(ctx context.Context, competitorID string) error {
|
|
if competitorID == "" {
|
|
return ErrInvalid
|
|
}
|
|
tx, err := s.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return errors.New("begin competitor deletion")
|
|
}
|
|
defer tx.Rollback()
|
|
var syncStatus string
|
|
var syncLease sql.NullTime
|
|
if err := tx.QueryRowContext(ctx, `
|
|
SELECT sync_status, sync_lease_until
|
|
FROM creator_competitor
|
|
WHERE id = $1
|
|
FOR UPDATE`, competitorID).Scan(&syncStatus, &syncLease); err != nil {
|
|
return rowError(err)
|
|
}
|
|
if syncStatus == "running" || (syncLease.Valid && syncLease.Time.After(time.Now())) {
|
|
return ErrConflict
|
|
}
|
|
var active bool
|
|
if err := tx.QueryRowContext(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM creator_collection_checkpoint
|
|
WHERE source_type = 'competitor' AND source_id = $1
|
|
AND (status = 'running' OR (lease_until IS NOT NULL AND lease_until > now()))
|
|
UNION ALL
|
|
SELECT 1 FROM creator_source_sync_lease
|
|
WHERE source_type = 'competitor' AND source_id = $1 AND lease_until > now()
|
|
)`, competitorID).Scan(&active); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if active {
|
|
return ErrConflict
|
|
}
|
|
if err := deleteCreatorSourceTx(ctx, tx, SourceCompetitor, competitorID); err != nil {
|
|
return err
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_competitor WHERE id = $1`, competitorID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return errors.New("commit competitor deletion")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func lockCreatorAccount(ctx context.Context, tx *sql.Tx, accountID string) error {
|
|
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)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkOwnedAccountDeletionTx(ctx context.Context, tx *sql.Tx, accountID string) error {
|
|
var active bool
|
|
if err := tx.QueryRowContext(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM creator_operation
|
|
WHERE account_id = $1 AND state = 'processing'
|
|
UNION ALL
|
|
SELECT 1 FROM creator_event
|
|
WHERE (receiving_account_id = $1 OR execution_account_id = $1) AND state = 'processing'
|
|
UNION ALL
|
|
SELECT 1 FROM creator_collection_checkpoint
|
|
WHERE source_type = 'owned' AND source_id = $1 AND status = 'running'
|
|
UNION ALL
|
|
SELECT 1 FROM creator_source_sync_lease
|
|
WHERE source_type = 'owned' AND source_id = $1 AND lease_until > now()
|
|
)`, accountID).Scan(&active); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if active {
|
|
return ErrConflict
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deleteCreatorSourceTx(ctx context.Context, tx *sql.Tx, sourceType, sourceID string) error {
|
|
if _, err := tx.ExecContext(ctx, `
|
|
CREATE TEMP TABLE creator_delete_work_ids (id text PRIMARY KEY) ON COMMIT DROP`); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
INSERT INTO creator_delete_work_ids (id)
|
|
SELECT id FROM creator_work WHERE source_type = $1 AND source_id = $2
|
|
UNION
|
|
SELECT work_id FROM creator_work_source WHERE source_type = $1 AND source_id = $2`, sourceType, sourceID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
var active bool
|
|
if err := tx.QueryRowContext(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM creator_operation
|
|
WHERE state = 'processing'
|
|
AND (target_work_id IN (SELECT id FROM creator_delete_work_ids)
|
|
OR target_comment_id IN (SELECT id FROM creator_comment WHERE work_id IN (SELECT id FROM creator_delete_work_ids)))
|
|
UNION ALL
|
|
SELECT 1 FROM creator_event
|
|
WHERE state = 'processing' AND work_id IN (SELECT id FROM creator_delete_work_ids)
|
|
)`).Scan(&active); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if active {
|
|
return ErrConflict
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_work_source WHERE source_type = $1 AND source_id = $2`, sourceType, sourceID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_source_sync_lease WHERE source_type = $1 AND source_id = $2`, sourceType, sourceID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `DELETE FROM creator_collection_checkpoint WHERE source_type = $1 AND source_id = $2`, sourceType, sourceID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
UPDATE creator_work work
|
|
SET source_type = source.source_type, source_id = source.source_id
|
|
FROM (
|
|
SELECT DISTINCT ON (work_id) work_id, source_type, source_id
|
|
FROM creator_work_source
|
|
ORDER BY work_id, created_at, source_type, source_id
|
|
) source
|
|
WHERE work.id = source.work_id AND work.source_type = $1 AND work.source_id = $2`, sourceType, sourceID); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_operation
|
|
WHERE target_work_id IN (
|
|
SELECT id FROM creator_delete_work_ids
|
|
WHERE NOT EXISTS (SELECT 1 FROM creator_work_source WHERE work_id = creator_delete_work_ids.id)
|
|
)
|
|
OR target_comment_id IN (
|
|
SELECT comment.id FROM creator_comment comment
|
|
WHERE comment.work_id IN (
|
|
SELECT id FROM creator_delete_work_ids
|
|
WHERE NOT EXISTS (SELECT 1 FROM creator_work_source WHERE work_id = creator_delete_work_ids.id)
|
|
)
|
|
)`); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_event
|
|
WHERE work_id IN (
|
|
SELECT id FROM creator_delete_work_ids
|
|
WHERE NOT EXISTS (SELECT 1 FROM creator_work_source WHERE work_id = creator_delete_work_ids.id)
|
|
)`); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `
|
|
DELETE FROM creator_work
|
|
WHERE id IN (
|
|
SELECT id FROM creator_delete_work_ids
|
|
WHERE NOT EXISTS (SELECT 1 FROM creator_work_source WHERE work_id = creator_delete_work_ids.id)
|
|
)`); err != nil {
|
|
return databaseError(err)
|
|
}
|
|
return nil
|
|
}
|