Files

80 lines
2.7 KiB
Go

package db
import (
"context"
"fmt"
"log/slog"
)
// DeleteUserDataFull performs complete user deletion including non-cascading tables.
// This is the main function for GDPR account deletion.
//
// Order of operations:
// 1. Delete hold membership data (non-cascading tables)
// 2. Delete OAuth sessions
// 3. Delete user (cascades to manifests, tags, stars, repo_pages, etc.)
//
// This should be called AFTER remote cleanup (hold services, PDS records)
// since we need the OAuth tokens to authenticate those requests.
func DeleteUserDataFull(db DBTX, oauthStore *OAuthStore, did string) error {
slog.Info("Starting full user data deletion", "did", did)
// 1. Delete non-cascading hold membership tables
if err := deleteHoldMembershipData(db, did); err != nil {
slog.Error("Failed to delete hold membership data", "did", did, "error", err)
return fmt.Errorf("failed to delete hold membership data: %w", err)
}
// 2. Delete OAuth sessions
if oauthStore != nil {
if err := oauthStore.DeleteSessionsForDID(context.Background(), did); err != nil {
slog.Warn("Failed to delete OAuth sessions", "did", did, "error", err)
// Continue - not critical
} else {
slog.Debug("Deleted OAuth sessions", "did", did)
}
}
// 3. Delete user (cascades to manifests, tags, stars, annotations, etc.)
if _, err := DeleteUserData(db, did); err != nil {
slog.Error("Failed to delete user data", "did", did, "error", err)
return fmt.Errorf("failed to delete user data: %w", err)
}
slog.Info("User data deletion completed", "did", did)
return nil
}
// deleteHoldMembershipData deletes non-cascading hold membership tables.
// These tables don't have foreign keys to the users table.
func deleteHoldMembershipData(db DBTX, did string) error {
// Delete from hold_crew_approvals (where user is the approved member)
result, err := db.Exec(`DELETE FROM hold_crew_approvals WHERE user_did = ?`, did)
if err != nil {
return fmt.Errorf("failed to delete crew approvals: %w", err)
}
approvalsDeleted, _ := result.RowsAffected()
// Delete from hold_crew_denials (where user was denied)
result, err = db.Exec(`DELETE FROM hold_crew_denials WHERE user_did = ?`, did)
if err != nil {
return fmt.Errorf("failed to delete crew denials: %w", err)
}
denialsDeleted, _ := result.RowsAffected()
// Delete from hold_crew_members (cached crew memberships)
result, err = db.Exec(`DELETE FROM hold_crew_members WHERE member_did = ?`, did)
if err != nil {
return fmt.Errorf("failed to delete crew members: %w", err)
}
membersDeleted, _ := result.RowsAffected()
slog.Debug("Deleted hold membership data",
"did", did,
"approvals_deleted", approvalsDeleted,
"denials_deleted", denialsDeleted,
"members_deleted", membersDeleted)
return nil
}