Fix ClearAllData to delete task_reminderlog table

The new task_reminderlog table has a foreign key to task_task, so it
must be deleted before tasks to avoid FK constraint violations.

Gracefully handles the case where the migration hasn't been run yet
by ignoring "relation does not exist" errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-19 23:39:32 -06:00
parent 51afa0837d
commit 8cd41a145f

View File

@@ -597,43 +597,54 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete documents: " + err.Error()}) return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete documents: " + err.Error()})
} }
// 6. Delete tasks (must be before contractors since tasks reference contractors) // 6. Delete task reminder logs (must be before tasks since reminder logs have task_id FK)
// Check if table exists first to avoid aborting transaction
var tableExists bool
tx.Raw("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'task_reminderlog')").Scan(&tableExists)
if tableExists {
if err := tx.Exec("DELETE FROM task_reminderlog").Error; err != nil {
tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete task reminder logs: " + err.Error()})
}
}
// 7. Delete tasks (must be before contractors since tasks reference contractors)
if err := tx.Exec("DELETE FROM task_task").Error; err != nil { if err := tx.Exec("DELETE FROM task_task").Error; err != nil {
tx.Rollback() tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete tasks: " + err.Error()}) return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete tasks: " + err.Error()})
} }
// 7. Delete contractor specialties (many-to-many) // 8. Delete contractor specialties (many-to-many)
if err := tx.Exec("DELETE FROM task_contractor_specialties").Error; err != nil { if err := tx.Exec("DELETE FROM task_contractor_specialties").Error; err != nil {
tx.Rollback() tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractor specialties: " + err.Error()}) return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractor specialties: " + err.Error()})
} }
// 8. Delete contractors // 9. Delete contractors
if err := tx.Exec("DELETE FROM task_contractor").Error; err != nil { if err := tx.Exec("DELETE FROM task_contractor").Error; err != nil {
tx.Rollback() tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractors: " + err.Error()}) return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete contractors: " + err.Error()})
} }
// 9. Delete residence_users (many-to-many for shared residences) // 10. Delete residence_users (many-to-many for shared residences)
if err := tx.Exec("DELETE FROM residence_residence_users").Error; err != nil { if err := tx.Exec("DELETE FROM residence_residence_users").Error; err != nil {
tx.Rollback() tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence users: " + err.Error()}) return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence users: " + err.Error()})
} }
// 10. Delete residence share codes (must be before residences since share codes have residence_id FK) // 11. 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 { if err := tx.Exec("DELETE FROM residence_residencesharecode").Error; err != nil {
tx.Rollback() tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence share codes: " + err.Error()}) return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residence share codes: " + err.Error()})
} }
// 11. Delete residences // 12. Delete residences
if err := tx.Exec("DELETE FROM residence_residence").Error; err != nil { if err := tx.Exec("DELETE FROM residence_residence").Error; err != nil {
tx.Rollback() tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residences: " + err.Error()}) return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete residences: " + err.Error()})
} }
// 12. Delete push devices for non-superusers (both APNS and GCM) // 13. Delete push devices for non-superusers (both APNS and GCM)
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM push_notifications_apnsdevice WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM push_notifications_apnsdevice WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -654,7 +665,7 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 13. Delete notification preferences for non-superusers // 14. Delete notification preferences for non-superusers
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM notifications_notificationpreference WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM notifications_notificationpreference WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -667,7 +678,7 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 14. Delete user subscriptions for non-superusers // 15. Delete user subscriptions for non-superusers
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM subscription_usersubscription WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM subscription_usersubscription WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -680,7 +691,7 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 15. Delete password reset codes for non-superusers // 16. Delete password reset codes for non-superusers
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM user_passwordresetcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM user_passwordresetcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -693,7 +704,7 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 16. Delete confirmation codes for non-superusers // 17. Delete confirmation codes for non-superusers
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM user_confirmationcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM user_confirmationcode WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -706,7 +717,7 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 17. Delete auth tokens for non-superusers // 18. Delete auth tokens for non-superusers
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM user_authtoken WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM user_authtoken WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -719,7 +730,7 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 18. Delete Apple social auth for non-superusers (Sign in with Apple) // 19. Delete Apple social auth for non-superusers (Sign in with Apple)
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM user_applesocialauth WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM user_applesocialauth WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -732,7 +743,7 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 19. Delete user profiles for non-superusers // 20. Delete user profiles for non-superusers
if len(preservedUserIDs) > 0 { if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM user_userprofile WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil { if err := tx.Exec("DELETE FROM user_userprofile WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback() tx.Rollback()
@@ -745,7 +756,24 @@ func (h *AdminSettingsHandler) ClearAllData(c echo.Context) error {
} }
} }
// 20. Finally, delete non-superuser users // 21. Delete onboarding emails for non-superusers
var onboardingEmailsExists bool
tx.Raw("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'onboarding_emails')").Scan(&onboardingEmailsExists)
if onboardingEmailsExists {
if len(preservedUserIDs) > 0 {
if err := tx.Exec("DELETE FROM onboarding_emails WHERE user_id NOT IN (?)", preservedUserIDs).Error; err != nil {
tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete onboarding emails: " + err.Error()})
}
} else {
if err := tx.Exec("DELETE FROM onboarding_emails").Error; err != nil {
tx.Rollback()
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": "Failed to delete onboarding emails: " + err.Error()})
}
}
}
// 22. Finally, delete non-superuser users
// Always filter by is_superuser to be safe, regardless of preservedUserIDs // 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 { if err := tx.Exec("DELETE FROM auth_user WHERE is_superuser = false").Error; err != nil {
tx.Rollback() tx.Rollback()