feat(teams): server-enrich is_support_member on team responses#56
Merged
Merged
Conversation
Adds a server-side `is_support_member: bool` flag on each `TeamUser` returned from the team endpoints, populated by cross-referencing each member's pubkey against the Redis `support_admins` set. Restaurant owners viewing their team during a support session can now distinguish support agents from permanent members in the UI without holding any admin role themselves. Closes the gap left by the original support-users keycast spec §8.3, which had punted this responsibility to the client. The client-side fallback (GET /api/admin/support-admins) is full-admin gated and therefore unusable for owners and support agents — making the server-enriched path the only viable one. Implementation - `TeamUser` gains an `is_support_member` field with `#[sqlx(default)]` and `#[serde(default)]` so DB FromRow and JSON deserialization remain backwards-compatible (default `false`). - `fetch_support_admin_set` reads the Redis set once per request and returns a HashSet; pure function `mark_support_members` flags matching rows. Both safe no-ops when Redis is unavailable. - Wired into `list_teams`, `get_team`, and `add_user`. Single SMEMBERS per request; HashSet lookup is O(1) per row. - Unit tests cover: only set members are flagged, idempotent, empty set is no-op, preexisting `true` is preserved. Caller compatibility: existing clients that ignore unknown fields are unaffected; clients that read the field get the enrichment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…spec Updates docs/synvya/support-users.md to reflect the server-side enrichment now landing alongside this spec change: - New §3.5 documenting the enrichment behavior on GET /teams, GET /teams/:id, and POST /teams/:id/users — single SMEMBERS per request, HashSet lookup, no DB change, capability downgrade when Redis is unavailable. - §8.3 updated: replaces the original "client-side, out of scope for Keycast" punt with the new server-owned data path. The visual treatment remains a client UX decision; the data is now Keycast's responsibility. - §9 implementation checklist marked complete for the items landed in PR #54 plus this PR; the deferred HTTP-handler tests are noted separately. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the gap in the support-users feature where the client could not reliably distinguish support agents from permanent team members in the UI.
The original Keycast spec (§8.3) had punted this label as "applied client-side via
GET /api/admin/support-admins." That endpoint is full-admin gated, which leaves the actual viewers (restaurant owners and the support agents themselves) without a working data path. This PR makes Keycast the source of truth: everyTeamUserreturned to clients now carries anis_support_member: boolflag computed at request time from the Redissupport_adminsset.Behavior
Endpoints affected (additive — no breaking changes):
GET /api/teamsVec<TeamWithRelations>— eachteam_usersrow carriesis_support_member.GET /api/teams/:idTeamWithRelations— same enrichment.POST /api/teams/:id/usersTeamUser— single row, enriched.SMEMBERS support_adminsper request. Result is hashed once, then cross-referenced O(1) per row.is_support_member: falsefor all rows. Capability downgrade, never a hard failure.Commits
feat(teams): server-enrich is_support_member on team responses—TeamUsergains the field with#[sqlx(default)]+#[serde(default)]so DB FromRow and JSON deserialization stay backwards-compatible. Addsfetch_support_admin_setand the pure helpermark_support_members. Wired intolist_teams,get_team, andadd_user. Unit tests cover: only-set-members flagged, idempotent, empty-set no-op, preexisting flag preserved (4 passing tests).docs(synvya): document is_support_member enrichment— adds §3.5 to the support-users spec describing the enrichment behavior, updates §8.3 to replace the original client-side punt with the new server-owned path, and marks the §9 checklist accordingly.Companion PR
Client-side spec resolution: Synvya/client (forthcoming) closes the corresponding open question (Q4) in the client
support-users.mdspec.Test plan
cargo fmt --all -- --checkcargo clippy --all-targets -- -D warningscargo test -p keycast_api --lib mark_support_members(4/4 pass)POST /api/admin/support-admins.GET /api/teams. Confirm members in the support set returnis_support_member: true; others returnfalse.falseon next request.is_support_member: falseplus a warning in logs (not a 5xx).🤖 Generated with Claude Code