Skip to content

feat: add PATCH /api/groups/members for atomic membership level management#298

Open
TX-RX wants to merge 1 commit intobrian7704:masterfrom
TX-RX:feature/group-membership-level-api
Open

feat: add PATCH /api/groups/members for atomic membership level management#298
TX-RX wants to merge 1 commit intobrian7704:masterfrom
TX-RX:feature/group-membership-level-api

Conversation

@TX-RX
Copy link
Copy Markdown
Contributor

@TX-RX TX-RX commented Apr 28, 2026

Summary

  • Adds PATCH /api/groups/members endpoint to atomically set a user's group membership level via a single level parameter (full, listen, transmit, or none)
  • Makes direction optional on DELETE /api/groups/members — omitting it now removes all direction rows for a user+group pair in one call
  • Both operations correctly handle RabbitMQ queue unbinds for any OUT rows removed

Background

TAK group memberships use a composite primary key of (user_id, group_id, direction), which means a full group member legitimately has two rows — one IN (can transmit) and one OUT (can receive). This is intentional: the TAK protocol and the existing RabbitMQ binding logic in client_controller.py depend on this model.

Previously, setting a user's effective access level required knowing the current state and making separate PUT/DELETE calls per direction. There was no atomic way to express "make this user listen-only" or "remove them from the group entirely."

New Endpoint: PATCH /api/groups/members

Request body:

{
  "username": "alice",
  "group_name": "TeamA",
  "level": "listen"
}

Membership levels:

Level DB rows Effect
full IN + OUT User can transmit and receive
listen OUT only User receives traffic, cannot transmit
transmit IN only User can transmit, does not receive
none (none) User removed from group entirely

The endpoint reads existing rows, adds any missing directions, and removes extra directions — all in one transaction. RabbitMQ unbinds are issued only when OUT rows are removed.

Updated Endpoint: DELETE /api/groups/members

direction is now optional. When omitted, all direction rows for the user+group pair are deleted and both IN and OUT routing keys are unbound from RabbitMQ.

Relation to Mumble Voice Enforcement

These levels map directly to how PermissionEnforcementCallback in mumble_ice_app.py enforces voice channel permissions:

  • Full members (IN+OUT) → can speak in the voice channel
  • Listen-only members (OUT only) → muted via the suppress flag
  • The enforcement callback prefers IN when both rows exist, so the PATCH endpoint's level semantics translate cleanly to Mumble behavior

Test Plan

  • Verified PATCH level=listen removes IN row, leaves OUT — user becomes listen-only
  • Verified PATCH level=full adds IN row back — user becomes full member
  • Verified PATCH level=none removes all rows — user fully removed from group
  • Verified DELETE without direction removes both IN and OUT rows
  • Verified DELETE with direction still removes only the specified row
  • Service restart clean, no syntax errors, all 8 routes registered correctly
  • Confirm RabbitMQ unbind behavior with connected EUD devices (requires live TAK device test)
  • UI integration (separate PR for OpenTAKServer-UI)

🤖 Generated with Claude Code

…ement

Adds a PATCH endpoint to atomically set a user's group membership level
rather than requiring separate PUT calls per direction. Also makes the
direction parameter optional on DELETE, allowing removal of all direction
rows for a user+group pair in a single call.

Membership levels:
- full (IN+OUT): user can transmit and receive in the group/voice channel
- listen (OUT only): user receives traffic but cannot transmit
- transmit (IN only): user can transmit but does not receive
- none: user has no membership in the group

The PATCH endpoint reads existing rows, adds any missing directions, and
removes directions no longer in the target set — all in one transaction.
RabbitMQ queue unbinds are issued for any OUT rows that are removed,
keeping subscription state consistent with the database.

These levels map directly to how the Mumble PermissionEnforcementCallback
enforces voice permissions: full members (IN+OUT) can speak, listen-only
members (OUT only) are muted via the suppress flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@brian7704
Copy link
Copy Markdown
Owner

Does the PUT /api/groups endpoint not work for your use case? The endpoint added in this PR seems to do pretty much the same thing in a slightly different way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants