From 3d33a1f4833bef024e5dc7a2ed66f339065d2062 Mon Sep 17 00:00:00 2001 From: Trey t Date: Thu, 4 Dec 2025 16:20:59 -0600 Subject: [PATCH] Fix clear all data - preserve superusers properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issues fixed: 1. Added missing user_applesocialauth deletion (Sign in with Apple) 2. Added missing residence_residencesharecode deletion 3. Simplified user deletion to always use WHERE is_superuser = false (GORM's Exec with IN clause and slice was unreliable) The delete query now directly filters on is_superuser column instead of using NOT IN with a precomputed list of IDs, which is both simpler and guaranteed to preserve all superuser accounts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/admin/handlers/settings_handler.go | 155 +++++++++++--------- 1 file changed, 85 insertions(+), 70 deletions(-) diff --git a/internal/admin/handlers/settings_handler.go b/internal/admin/handlers/settings_handler.go index 21a05f4..424ef25 100644 --- a/internal/admin/handlers/settings_handler.go +++ b/internal/admin/handlers/settings_handler.go @@ -276,56 +276,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { return } - // 3. Delete document images - if err := tx.Exec("DELETE FROM task_documentimage").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete document images: " + err.Error()}) - return - } - - // 4. Delete documents - if err := tx.Exec("DELETE FROM task_document").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete documents: " + err.Error()}) - return - } - - // 5. Delete tasks (must be before contractors since tasks reference contractors) - if err := tx.Exec("DELETE FROM task_task").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete tasks: " + err.Error()}) - return - } - - // 6. Delete contractor specialties (many-to-many) - if err := tx.Exec("DELETE FROM task_contractor_specialties").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractor specialties: " + err.Error()}) - return - } - - // 7. Delete contractors - if err := tx.Exec("DELETE FROM task_contractor").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractors: " + err.Error()}) - return - } - - // 8. Delete residence_users (many-to-many for shared residences) - if err := tx.Exec("DELETE FROM residence_residence_users").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residence users: " + err.Error()}) - return - } - - // 9. Delete residences - if err := tx.Exec("DELETE FROM residence_residence").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residences: " + err.Error()}) - return - } - - // 10. Delete notifications for non-superusers + // 3. Delete notifications (must be before tasks since notifications have task_id FK) if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM notifications_notification WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -340,7 +291,63 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 11. Delete push devices for non-superusers (both APNS and GCM) + // 4. Delete document images + if err := tx.Exec("DELETE FROM task_documentimage").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete document images: " + err.Error()}) + return + } + + // 5. Delete documents + if err := tx.Exec("DELETE FROM task_document").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete documents: " + err.Error()}) + return + } + + // 6. Delete tasks (must be before contractors since tasks reference contractors) + if err := tx.Exec("DELETE FROM task_task").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete tasks: " + err.Error()}) + return + } + + // 7. Delete contractor specialties (many-to-many) + if err := tx.Exec("DELETE FROM task_contractor_specialties").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractor specialties: " + err.Error()}) + return + } + + // 8. Delete contractors + if err := tx.Exec("DELETE FROM task_contractor").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete contractors: " + err.Error()}) + return + } + + // 9. Delete residence_users (many-to-many for shared residences) + if err := tx.Exec("DELETE FROM residence_residence_users").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residence users: " + err.Error()}) + return + } + + // 10. Delete residence share codes (must be before residences since share codes have residence_id FK) + if err := tx.Exec("DELETE FROM residence_residencesharecode").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residence share codes: " + err.Error()}) + return + } + + // 11. Delete residences + if err := tx.Exec("DELETE FROM residence_residence").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete residences: " + err.Error()}) + return + } + + // 12. Delete push devices for non-superusers (both APNS and GCM) if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM push_notifications_apnsdevice WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -365,7 +372,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 12. Delete notification preferences for non-superusers + // 13. Delete notification preferences for non-superusers if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM notifications_notificationpreference WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -380,7 +387,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 13. Delete user subscriptions for non-superusers + // 14. Delete user subscriptions for non-superusers if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM subscription_usersubscription WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -395,7 +402,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 14. Delete password reset codes for non-superusers + // 15. Delete password reset codes for non-superusers if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM user_passwordresetcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -410,7 +417,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 15. Delete confirmation codes for non-superusers + // 16. Delete confirmation codes for non-superusers if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM user_confirmationcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -425,7 +432,7 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 16. Delete auth tokens for non-superusers + // 17. Delete auth tokens for non-superusers if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM user_authtoken WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -440,7 +447,22 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 17. Delete user profiles for non-superusers + // 18. Delete Apple social auth for non-superusers (Sign in with Apple) + if len(preservedUserIDs) > 0 { + if err := tx.Exec("DELETE FROM user_applesocialauth WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete Apple social auth: " + err.Error()}) + return + } + } else { + if err := tx.Exec("DELETE FROM user_applesocialauth").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete Apple social auth: " + err.Error()}) + return + } + } + + // 19. Delete user profiles for non-superusers if len(preservedUserIDs) > 0 { if err := tx.Exec("DELETE FROM user_userprofile WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { tx.Rollback() @@ -455,19 +477,12 @@ func (h *AdminSettingsHandler) ClearAllData(c *gin.Context) { } } - // 18. Finally, delete non-superuser users - if len(preservedUserIDs) > 0 { - if err := tx.Exec("DELETE FROM auth_user WHERE id NOT IN (?)", preservedUserIDs).Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete users: " + err.Error()}) - return - } - } else { - if err := tx.Exec("DELETE FROM auth_user WHERE is_superuser = false").Error; err != nil { - tx.Rollback() - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete users: " + err.Error()}) - return - } + // 20. Finally, delete non-superuser users + // Always filter by is_superuser to be safe, regardless of preservedUserIDs + if err := tx.Exec("DELETE FROM auth_user WHERE is_superuser = false").Error; err != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete users: " + err.Error()}) + return } // Commit the transaction