refactor(sidebar): per-enclave admin-managed room categories (LC-79 redesign) #137

Merged
nrupard merged 1 commit from refactor/lc-79-enclave-scoped-categories into main 2026-05-18 18:57:21 +02:00
Owner

Summary

Pivots LC-79 from per-user sidebar categorization (auth.db, each user organises their own sidebar in isolation) to per-enclave shared categories (chat.db, managed by enclave admin / owner / site admin, visible to every member of the enclave). Old per-user tables are dropped; existing categorizations are gone (acceptable since the feature is brand-new and only the dev instance had any).

Schema

  • auth/0017_drop_sidebar_categories_add_collapsed.sql: DROPs the per-user sidebar_categories + sidebar_category_rooms; CREATEs collapsed_categories (user_id, category_id) for the one piece of state that stays per-user (fold/expand is a personal view preference).
  • chat/0026_room_categories.sql: CREATEs room_categories (id, enclave_id, name, position, created_at) and room_category_assignments (room_id PK, category_id, position) so each room belongs to at most one category. FK CASCADEs on enclave_id, room_id, category_id keep deletion semantics correct.

Routes

All mutations moved under /enclave/{id}/sidebar/categories/... and gated by enclave_can_manage (Owner / Admin / site admin):

  • POST /enclave/{id}/sidebar/categories
  • PATCH /enclave/{id}/sidebar/categories/{cat_id} (rename)
  • DELETE /enclave/{id}/sidebar/categories/{cat_id}
  • PATCH /enclave/{id}/sidebar/categories/{cat_id}/rooms/{room_id} (assign; same-enclave check)
  • DELETE /enclave/{id}/sidebar/categories/rooms/{room_id} (unassign)
  • PATCH /enclave/{id}/sidebar/categories/positions (reorder cats)
  • PATCH /enclave/{id}/sidebar/categories/{cat_id}/positions (reorder rooms / cross-cat upsert)

Lone per-user endpoint (no admin RBAC, any enclave member may fold their own view):

  • PATCH /sidebar/categories/{cat_id}/collapse

Every shared-state mutation broadcasts a new SidebarCategoriesChanged { enclave_id } WS event to all enclave members so live tabs catch up without a manual refresh.

Plumbing

load_sidebar / load_chrome thread two new values through every view struct (~25 sites via batch regex):

  • can_manage_sidebar_categories: bool (compute once, gate UI affordances).
  • sidebar_current_enclave: Option<i64> (needed by the +Add form when no categories exist yet to know which enclave to target).

SidebarCategoryGroup gains enclave_id so the template can build admin URLs without extra context.

Template

  • Admin affordances (rename, delete, +Add) gated on can_manage_sidebar_categories.
  • Per-room move-to-category dropdown likewise gated; URLs now include the category's enclave_id.
  • Category-section drag handles only rendered when admin (members can't reorder shared state).
  • Collapse form is unconditional and lives at /sidebar/categories/{id}/collapse (per-user setting).

RBAC nuance

The user's planning answer picked "Enclave admin + moderator + owner", but lets-chat's enclave role model only has Owner / Admin / Member. Used enclave_can_manage (Owner + Admin + site admin). Adding an enclave-level Moderator role is a separate feature.

Test plan

  • just check (fmt + clippy across standalone + saas).
  • New routes_sidebar_categories.rs tests: admin creates, member 403, cross-user shared visibility, per-user collapse isolation (4/4 pass).
  • just test: green modulo the known routes_uploads::send_message_with_attachment_renders_inline_image flake documented in CLAUDE.md (passes in isolation).
  • Manual on dev server: admin in enclave A creates "Engineering" category; member in same enclave sees it via live WS update; member in enclave B does not see it.
## Summary Pivots LC-79 from per-user sidebar categorization (auth.db, each user organises their own sidebar in isolation) to per-enclave shared categories (chat.db, managed by enclave admin / owner / site admin, visible to every member of the enclave). Old per-user tables are dropped; existing categorizations are gone (acceptable since the feature is brand-new and only the dev instance had any). ## Schema - `auth/0017_drop_sidebar_categories_add_collapsed.sql`: DROPs the per-user `sidebar_categories` + `sidebar_category_rooms`; CREATEs `collapsed_categories (user_id, category_id)` for the one piece of state that stays per-user (fold/expand is a personal view preference). - `chat/0026_room_categories.sql`: CREATEs `room_categories (id, enclave_id, name, position, created_at)` and `room_category_assignments (room_id PK, category_id, position)` so each room belongs to at most one category. FK CASCADEs on enclave_id, room_id, category_id keep deletion semantics correct. ## Routes All mutations moved under `/enclave/{id}/sidebar/categories/...` and gated by `enclave_can_manage` (Owner / Admin / site admin): - `POST /enclave/{id}/sidebar/categories` - `PATCH /enclave/{id}/sidebar/categories/{cat_id}` (rename) - `DELETE /enclave/{id}/sidebar/categories/{cat_id}` - `PATCH /enclave/{id}/sidebar/categories/{cat_id}/rooms/{room_id}` (assign; same-enclave check) - `DELETE /enclave/{id}/sidebar/categories/rooms/{room_id}` (unassign) - `PATCH /enclave/{id}/sidebar/categories/positions` (reorder cats) - `PATCH /enclave/{id}/sidebar/categories/{cat_id}/positions` (reorder rooms / cross-cat upsert) Lone per-user endpoint (no admin RBAC, any enclave member may fold their own view): - `PATCH /sidebar/categories/{cat_id}/collapse` Every shared-state mutation broadcasts a new `SidebarCategoriesChanged { enclave_id }` WS event to all enclave members so live tabs catch up without a manual refresh. ## Plumbing `load_sidebar` / `load_chrome` thread two new values through every view struct (~25 sites via batch regex): - `can_manage_sidebar_categories: bool` (compute once, gate UI affordances). - `sidebar_current_enclave: Option<i64>` (needed by the +Add form when no categories exist yet to know which enclave to target). `SidebarCategoryGroup` gains `enclave_id` so the template can build admin URLs without extra context. ## Template - Admin affordances (rename, delete, +Add) gated on `can_manage_sidebar_categories`. - Per-room move-to-category dropdown likewise gated; URLs now include the category's `enclave_id`. - Category-section drag handles only rendered when admin (members can't reorder shared state). - Collapse form is unconditional and lives at `/sidebar/categories/{id}/collapse` (per-user setting). ## RBAC nuance The user's planning answer picked "Enclave admin + moderator + owner", but lets-chat's enclave role model only has Owner / Admin / Member. Used `enclave_can_manage` (Owner + Admin + site admin). Adding an enclave-level Moderator role is a separate feature. ## Test plan - [x] `just check` (fmt + clippy across standalone + saas). - [x] New `routes_sidebar_categories.rs` tests: admin creates, member 403, cross-user shared visibility, per-user collapse isolation (4/4 pass). - [x] `just test`: green modulo the known `routes_uploads::send_message_with_attachment_renders_inline_image` flake documented in CLAUDE.md (passes in isolation). - [ ] Manual on dev server: admin in enclave A creates "Engineering" category; member in same enclave sees it via live WS update; member in enclave B does not see it.
refactor(sidebar): make room categories per-enclave, admin-managed (LC-79 redesign)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 54s
7daad442be
Pivots LC-79's per-user categorization (auth.db, each user organises their own sidebar) to per-enclave shared categories (chat.db, managed by enclave admin / owner / site admin, visible to all members of the enclave). Drops the existing per-user tables and starts fresh; the old data is gone (acceptable since the feature is brand-new and only the dev instance had any categorizations).

Schema:
- auth migration 0017 DROPs `sidebar_categories` + `sidebar_category_rooms` and CREATEs `collapsed_categories (user_id, category_id)` for per-user collapsed state.
- chat migration 0026 CREATEs `room_categories (id, enclave_id, name, position, created_at)` and `room_category_assignments (room_id PK, category_id, position)` so each room belongs to at most one category. FK CASCADEs on enclave_id, room_id, category_id keep deletion semantics correct.

DB module (`db::sidebar_categories`):
- Functions now take `enclave_id` instead of `user_id` for shared state (`list_categories_for_enclave`, `create_category`, `rename_category`, `delete_category`, `set_category_positions`, `room_assignments_for_enclave`, `enclave_of_category`).
- `assign_room`, `unassign_room`, `set_room_positions` work on room_id directly (no per-user dimension).
- Per-user collapsed helpers (`list_collapsed_for_user`, `set_collapsed`) live alongside, hitting the auth.db table.

Routes:
- All mutating endpoints moved under `/enclave/{id}/sidebar/categories/...` and gated by `enclave_can_manage` (Owner / Admin / site admin). Membership-checks the room id against the same enclave on every assignment.
- `PATCH /sidebar/categories/{id}/collapse` is the lone per-user endpoint: any member of the enclave that owns the category may fold their own view.
- Every shared-state mutation broadcasts a new `SidebarCategoriesChanged { enclave_id }` event to all members so live tabs catch up without a manual refresh.

load_sidebar / load_chrome now thread two new outputs through every view struct:
- `can_manage_sidebar_categories: bool` (compute once, gate UI affordances).
- `sidebar_current_enclave: Option<i64>` (needed by the +Add form when no categories exist yet).
SidebarCategoryGroup gains `enclave_id` so the template can build admin URLs without extra context.

Template:
- Admin affordances (rename, delete, +Add) gated on `can_manage_sidebar_categories`.
- Per-room move dropdown likewise gated; URLs now include the category's `enclave_id` instead of the per-user form.
- Category-section drag handles only rendered when admin (members can't reorder shared state).
- The collapse form is unconditional (per-user setting).

WS:
- `ChatEvent::SidebarCategoriesChanged` added; ws.rs match arm re-renders the sidebar for the recipient on receipt. `ws_fragments::render_event` adds it to the per-recipient (no-fragment) bucket.

Tests:
- Old per-user tests replaced; the new file covers admin-create / member-403 / cross-user shared-visibility / per-user-collapse-isolation. 4/4 pass.
- 18 hand-rolled test files updated with auth migration 0017 + chat migration 0026 entries (CLAUDE.md test-maintenance discipline).

`just check` + `just test` green modulo the known routes_uploads concurrent-binary flake (documented in CLAUDE.md; passes in isolation).
nrupard deleted branch refactor/lc-79-enclave-scoped-categories 2026-05-18 18:57:21 +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!137
No description provided.