feat(mentions): per-enclave user groups @group-name (LC-83) #143

Merged
nrupard merged 1 commit from feat/lc-83-user-groups into main 2026-05-18 20:40:06 +02:00
Owner

Summary

LC-83: per-enclave user groups for @group-name mentions. A token that doesn't match a username falls through to a per-enclave group lookup; on match, the existing @username codepath gets one MentionRef per member, so each member gets a real mentions row + notification + activity entry through the existing per-user pipeline.

Schema (chat 0027)

  • user_groups (id, enclave_id, name, description, created_by, created_at) with UNIQUE (enclave_id, name) and ON DELETE CASCADE from enclaves.
  • user_group_members (group_id, user_id, added_at, PK(group_id, user_id)) with ON DELETE CASCADE from user_groups.

Admin API (gated by enclave_can_manage)

  • POST /enclave/{id}/groups (form: name, optional description).
  • PATCH /enclave/{id}/groups/{group_id} (rename).
  • DELETE /enclave/{id}/groups/{group_id}.
  • POST /enclave/{id}/groups/{group_id}/members (form: user_id).
  • DELETE /enclave/{id}/groups/{group_id}/members/{user_id}.

Add-member refuses non-enclave-members so a group can only hold users who actually have access.

Acceptance criteria

  • Admin can create / rename / delete + add / remove members.
  • @group-name resolves to each member through the existing per-user mention pipeline.
  • Per-enclave scope: mentioning outside the group's enclave doesn't resolve (parser looks up the room's enclave_id first).
  • Removing a user from a group doesn't retroactively un-mention them in old messages (mention rows persisted at send time).
  • Deleting a group renders past @group-name as literal text (parser fails to find the group; existing mention rows stay valid).
  • Mention typeahead surfacing groups - out of scope for v1; manage via direct API / curl.

Out of scope (open questions)

  • Per-group "who can mention" permission.
  • Single hover-expand chip vs N individual chips on render.
  • API surface for bot expansion.

Test plan

  • just check (fmt + clippy across standalone + saas).
  • ./dev/cargo test -p lets-chat-server --test routes_user_groups - 3/3 pass (admin creates + adds member; member is 403'd; @designers mention writes a mentions row for the member).
  • Manual: curl-create a group, add yourself + a teammate, send @team-foo in #general - verify both get notifications + activity entries.

Drift

Chat migration 0027 added to all 6 hand-rolled test files (CLAUDE.md test-maintenance section 2).

## Summary LC-83: per-enclave user groups for `@group-name` mentions. A token that doesn't match a username falls through to a per-enclave group lookup; on match, the existing `@username` codepath gets one `MentionRef` per member, so each member gets a real `mentions` row + notification + activity entry through the existing per-user pipeline. ## Schema (chat 0027) - `user_groups (id, enclave_id, name, description, created_by, created_at)` with `UNIQUE (enclave_id, name)` and `ON DELETE CASCADE` from enclaves. - `user_group_members (group_id, user_id, added_at, PK(group_id, user_id))` with `ON DELETE CASCADE` from user_groups. ## Admin API (gated by `enclave_can_manage`) - `POST /enclave/{id}/groups` (form: `name`, optional `description`). - `PATCH /enclave/{id}/groups/{group_id}` (rename). - `DELETE /enclave/{id}/groups/{group_id}`. - `POST /enclave/{id}/groups/{group_id}/members` (form: `user_id`). - `DELETE /enclave/{id}/groups/{group_id}/members/{user_id}`. Add-member refuses non-enclave-members so a group can only hold users who actually have access. ## Acceptance criteria - [x] Admin can create / rename / delete + add / remove members. - [x] `@group-name` resolves to each member through the existing per-user mention pipeline. - [x] Per-enclave scope: mentioning outside the group's enclave doesn't resolve (parser looks up the room's enclave_id first). - [x] Removing a user from a group doesn't retroactively un-mention them in old messages (mention rows persisted at send time). - [x] Deleting a group renders past `@group-name` as literal text (parser fails to find the group; existing mention rows stay valid). - [ ] **Mention typeahead surfacing groups** - out of scope for v1; manage via direct API / curl. ## Out of scope (open questions) - Per-group "who can mention" permission. - Single hover-expand chip vs N individual chips on render. - API surface for bot expansion. ## Test plan - [x] `just check` (fmt + clippy across standalone + saas). - [x] `./dev/cargo test -p lets-chat-server --test routes_user_groups` - 3/3 pass (admin creates + adds member; member is 403'd; `@designers` mention writes a mentions row for the member). - [ ] Manual: curl-create a group, add yourself + a teammate, send `@team-foo` in #general - verify both get notifications + activity entries. ## Drift Chat migration 0027 added to all 6 hand-rolled test files (CLAUDE.md test-maintenance section 2).
feat(mentions): per-enclave user groups (@group-name) - LC-83
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 7s
09979bd4bd
Adds the on-enclave `user_groups` + `user_group_members` tables and
extends the mention parser so a token that doesn't match a username
falls through to a per-enclave group lookup. If a group matches, the
existing `@username` codepath gets one `MentionRef` per member (filtered
through the same dedup that collapses @here / @channel overlaps), so
each member gets a real `mentions` row + notification + activity entry
- AC #2 covered.

Schema:
- chat 0027 creates `user_groups (id, enclave_id, name, description,
  created_by, created_at)` with `UNIQUE (enclave_id, name)` and
  `ON DELETE CASCADE` from enclaves; plus `user_group_members
  (group_id, user_id)` with `ON DELETE CASCADE` from user_groups.

Admin API (gated by `enclave_can_manage`):
- `POST /enclave/{id}/groups` (create with name + optional description)
- `PATCH /enclave/{id}/groups/{group_id}` (rename)
- `DELETE /enclave/{id}/groups/{group_id}`
- `POST /enclave/{id}/groups/{group_id}/members` (form: user_id=...)
- `DELETE /enclave/{id}/groups/{group_id}/members/{user_id}`

Add-member endpoint refuses non-enclave-members so a group can only
hold users who can actually see the enclave's rooms.

Acceptance criteria covered:
- [x] Admin can create / rename / delete groups, add / remove members.
- [x] `@group-name` resolves to each member through the existing per-
  user mention pipeline.
- [x] Group lives per-enclave; mentioning it in a room outside that
  enclave doesn't resolve (the mention parser looks up the room's
  enclave_id first).
- [x] Removing a user from a group does NOT retroactively un-mention
  them in old messages (mentions are persisted at send time).
- [x] Deleting a group renders past `@group-name` as literal text
  (the parser fails to find the group; existing mentions rows stay).
- [ ] Mention typeahead surfacing groups (mention popover changes) -
  out of scope for v1; manage via direct API / curl.

Out of scope (open questions):
- Per-group "who can mention" permission (e.g., @everyone gating).
- Single hover-expand chip vs N individual chips on render.
- API surface for bot expansion.

Tests: 3/3 pass - admin creates group + adds member; member is 403'd
from creating; `@designers` mention in #general writes a mentions row
for the group member (exercises the parser end-to-end).

Drift maintenance: chat migration 0027 added to all hand-rolled test
files (6 of them per CLAUDE.md test-maintenance section 2).
nrupard deleted branch feat/lc-83-user-groups 2026-05-18 20:40:06 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!143
No description provided.