Repository: appwrite/appwrite
File: app/controllers/api/teams.php (PHP)

Code before:
        }

        if ($membership->getAttribute('teamInternalId') !== $team->getInternalId()) {
            throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
        }

        if ($project->getId() === 'console') {
            // Quick check:
            // fetch up to 2 owners to determine if only one exists
            $ownersCount = $dbForProject->count(
                collection: 'memberships',
                queries: [
                    Query::contains('roles', ['owner']),
                    Query::equal('teamInternalId', [$team->getInternalId()])
                ],
                max: 2
            );

            // Is the deletion being requested by the user on their own membership?
            $isCurrentUserAnOwner =  $user->getInternalId() === $membership->getAttribute('userInternalId');

            if ($ownersCount === 1 && $isCurrentUserAnOwner) {
                /* Prevent removal if the user is the only owner. */
                throw new Exception(
                    Exception::GENERAL_ARGUMENT_INVALID,
                    'There must be at least one owner in the organization.'
                );
            }
        }

        try {
            $dbForProject->deleteDocument('memberships', $membership->getId());
        } catch (AuthorizationException $exception) {
            throw new Exception(Exception::USER_UNAUTHORIZED);
        } catch (\Throwable $exception) {
            throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB');
        }

        $dbForProject->purgeCachedDocument('users', $profile->getId());

        if ($membership->getAttribute('confirm')) { // Count only confirmed members
            Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
        }

        $queueForEvents
            ->setParam('teamId', $team->getId())
            ->setParam('userId', $profile->getId())
            ->setParam('membershipId', $membership->getId())
            ->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP))
        ;


Review comment (suggestion):
Should be bad request, or it's own custom exception type. The message should also mention team not org, since at this scope it's a team

Code after:
        }

        if ($membership->getAttribute('teamInternalId') !== $team->getInternalId()) {
            throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
        }

        if ($project->getId() === 'console') {
            // Quick check:
            // fetch up to 2 owners to determine if only one exists
            $ownersCount = $dbForProject->count(
                collection: 'memberships',
                queries: [
                    Query::contains('roles', ['owner']),
                    Query::equal('teamInternalId', [$team->getInternalId()])
                ],
                max: 2
            );

            // Is the deletion being requested by the user on their own membership?
            $isCurrentUserAnOwner =  $user->getInternalId() === $membership->getAttribute('userInternalId');

            if ($ownersCount === 1 && $isCurrentUserAnOwner) {
                /* Prevent removal if the user is the only owner. */
                throw new Exception(Exception::MEMBERSHIP_DELETION_PROHIBITED, 'There must be at least one owner in the organization.');
            }
        }

        try {
            $dbForProject->deleteDocument('memberships', $membership->getId());
        } catch (AuthorizationException $exception) {
            throw new Exception(Exception::USER_UNAUTHORIZED);
        } catch (\Throwable $exception) {
            throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB');
        }

        $dbForProject->purgeCachedDocument('users', $profile->getId());

        if ($membership->getAttribute('confirm')) { // Count only confirmed members
            Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
        }

        $queueForEvents
            ->setParam('teamId', $team->getId())
            ->setParam('userId', $profile->getId())
            ->setParam('membershipId', $membership->getId())
            ->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP))
        ;

        $response->noContent();
    });