70 lines
2.5 KiB
Go
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
|
|
}
|