Fix: Deletion Deadlocks (Owner, User)

This commit is contained in:
2026-03-03 23:22:50 +05:45
parent 34f2dc070c
commit ea1bacc794
4 changed files with 215 additions and 35 deletions
+45 -20
View File
@@ -220,48 +220,73 @@ def update_organization(org_id):
@api_v1_bp.route("/organizations/<org_id>", methods=["DELETE"])
@login_required
@require_owner
@full_access_required
def delete_organization(org_id):
"""
Delete organization (soft delete).
The owner may only delete the organization if they are the *sole* remaining
member. If other active members exist they must first transfer ownership
(or remove all other members) before deleting the organization.
Only the OWNER of the organization may call this endpoint.
When the organization has other active members the caller must explicitly
confirm the deletion by sending ``{"confirm": true}`` in the request body.
All members (and their memberships) are soft-deleted together with the org
in a single atomic transaction so no orphaned data is left behind.
Args:
org_id: Organization ID
Request body (JSON, optional):
confirm (bool): Required when the org has other active members.
Returns:
200: Organization deleted successfully
400: Organization has other members but confirm was not true
401: Not authenticated
403: Not the owner
404: Organization not found
409: Organization still has other members — transfer ownership first
"""
from gatehouse_app.models.organization.organization_member import OrganizationMember as _OrgMember
from gatehouse_app.utils.constants import OrganizationRole as _OrgRole
caller = g.current_user
org = OrganizationService.get_organization_by_id(org_id)
# Guard: block deletion while non-owner members still exist so ownership
# can be transferred rather than silently orphaning them.
active_member_count = org.get_member_count()
if active_member_count > 1:
# Only the owner may delete the organization.
caller_membership = _OrgMember.query.filter_by(
user_id=caller.id,
organization_id=org.id,
deleted_at=None,
).first()
if not caller_membership or caller_membership.role != _OrgRole.OWNER:
return api_response(
success=False,
message=(
"This organization still has other members. "
"Please transfer ownership to another member or remove all "
"other members before deleting the organization."
),
status=409,
error_type="ORG_HAS_MEMBERS",
error_details={"member_count": active_member_count},
message="Only the organization owner can delete the organization.",
status=403,
error_type="AUTHORIZATION_ERROR",
)
OrganizationService.delete_organization(
# If other members exist, require explicit confirmation to avoid accidents.
active_member_count = org.get_member_count()
if active_member_count > 1:
data = request.get_json(silent=True) or {}
if not data.get("confirm"):
return api_response(
success=False,
message=(
f"This organization has {active_member_count} active members. "
"Deleting it will remove all members and their data. "
'Send {"confirm": true} to confirm.'
),
status=400,
error_type="CONFIRMATION_REQUIRED",
error_details={"member_count": active_member_count},
)
OrganizationService.force_delete_organization(
org=org,
user_id=g.current_user.id,
soft=True,
user_id=caller.id,
)
return api_response(