forked from wrenn/wrenn
Fix cascading deletion gaps for user and team cleanup
- Add ON DELETE CASCADE to users_teams, oauth_providers, admin_permissions and ON DELETE SET NULL (with nullable columns) to team_api_keys.created_by, hosts.created_by, host_tokens.created_by so HardDeleteExpiredUsers no longer fails with FK violations - User account deletion now cascades to sole-owned teams via DeleteTeamInternal, preventing orphaned teams with live sandboxes after account removal - ListActiveSandboxesByTeam now includes hibernated sandboxes so their disk snapshots are cleaned up during team deletion - Team soft-delete now hard-deletes sandbox metric points, metric snapshots, API keys, and channels to prevent data accumulation on deleted teams - Extract deleteTeamCore() to deduplicate shared logic across DeleteTeam, AdminDeleteTeam, and DeleteTeamInternal - Fix ListAPIKeysByTeamWithCreator to use LEFT JOIN after created_by became nullable, and update handler to read pgtype.Text.String for creator_email
This commit is contained in:
@ -22,6 +22,7 @@ import (
|
||||
"git.omukk.dev/wrenn/wrenn/pkg/auth/oauth"
|
||||
"git.omukk.dev/wrenn/wrenn/pkg/db"
|
||||
"git.omukk.dev/wrenn/wrenn/pkg/id"
|
||||
"git.omukk.dev/wrenn/wrenn/pkg/service"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -37,6 +38,7 @@ type meHandler struct {
|
||||
mailer email.Mailer
|
||||
oauthRegistry *oauth.Registry
|
||||
redirectURL string
|
||||
teamSvc *service.TeamService
|
||||
}
|
||||
|
||||
func newMeHandler(
|
||||
@ -47,6 +49,7 @@ func newMeHandler(
|
||||
mailer email.Mailer,
|
||||
registry *oauth.Registry,
|
||||
redirectURL string,
|
||||
teamSvc *service.TeamService,
|
||||
) *meHandler {
|
||||
return &meHandler{
|
||||
db: db,
|
||||
@ -56,6 +59,7 @@ func newMeHandler(
|
||||
mailer: mailer,
|
||||
oauthRegistry: registry,
|
||||
redirectURL: strings.TrimRight(redirectURL, "/"),
|
||||
teamSvc: teamSvc,
|
||||
}
|
||||
}
|
||||
|
||||
@ -507,6 +511,19 @@ func (h *meHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete all teams the user solely owns (no other members).
|
||||
soleTeams, err := h.db.ListSoleOwnedTeams(ctx, ac.UserID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "db_error", "failed to list owned teams")
|
||||
return
|
||||
}
|
||||
for _, teamID := range soleTeams {
|
||||
if err := h.teamSvc.DeleteTeamInternal(ctx, teamID); err != nil {
|
||||
slog.Warn("account delete: failed to delete sole-owned team",
|
||||
"team_id", id.FormatTeamID(teamID), "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.db.SoftDeleteUser(ctx, ac.UserID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "db_error", "failed to delete account")
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user