Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

70 lines
2.5 KiB
Go

package auth
import (
"fmt"
"gorm.io/gorm"
)
// DeleteUserCascade removes a user and all of their owned data.
//
// PostgreSQL strictly enforces every foreign key, while SQLite only enforces
// them when foreign_keys=ON. So we explicitly delete every dependent row
// instead of relying on `ON DELETE CASCADE`, otherwise:
//
// - on PostgreSQL the user delete fails with a constraint violation when
// the user authored or consumed any invite codes (the InviteCode FKs are
// declared without an OnDelete: CASCADE constraint), and
// - usage_records have no FK at all, so they would be left orphaned in any
// dialect.
//
// It also clears the in-memory quota cache for the user.
//
// Returns gorm.ErrRecordNotFound when the user does not exist.
func DeleteUserCascade(db *gorm.DB, userID string) error {
err := db.Transaction(func(tx *gorm.DB) error {
// Drop invites authored by this user; the admin who issued them is gone.
if err := tx.Where("created_by = ?", userID).Delete(&InviteCode{}).Error; err != nil {
return fmt.Errorf("delete invites created by user: %w", err)
}
// Preserve audit trail for invites consumed by this user — null the FK.
if err := tx.Model(&InviteCode{}).Where("used_by = ?", userID).Update("used_by", nil).Error; err != nil {
return fmt.Errorf("clear used_by on invites: %w", err)
}
// Wipe collected metrics; they have no FK and would otherwise orphan.
if err := tx.Where("user_id = ?", userID).Delete(&UsageRecord{}).Error; err != nil {
return fmt.Errorf("delete usage records: %w", err)
}
// Explicit deletes for the CASCADE-backed children too — they're cheap
// and keep behaviour identical across SQLite (FKs may be OFF) and
// PostgreSQL.
if err := tx.Where("user_id = ?", userID).Delete(&Session{}).Error; err != nil {
return fmt.Errorf("delete sessions: %w", err)
}
if err := tx.Where("user_id = ?", userID).Delete(&UserAPIKey{}).Error; err != nil {
return fmt.Errorf("delete api keys: %w", err)
}
if err := tx.Where("user_id = ?", userID).Delete(&UserPermission{}).Error; err != nil {
return fmt.Errorf("delete permissions: %w", err)
}
if err := tx.Where("user_id = ?", userID).Delete(&QuotaRule{}).Error; err != nil {
return fmt.Errorf("delete quota rules: %w", err)
}
result := tx.Where("id = ?", userID).Delete(&User{})
if result.Error != nil {
return fmt.Errorf("delete user: %w", result.Error)
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
})
if err != nil {
return err
}
quotaCache.invalidateUser(userID)
return nil
}