80 lines
2.7 KiB
Go
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
|
|
}
|