feat(group): 1st implementation of Groups
this implements first version (manageable only by admin right now)
routes:
GET /api/groups
List subject groups (paginated). Admin-only.
POST /api/groups
Create a new ReBAC subject group. Admin-only. The name must match the RFC 5321 local-part shape and be globally unique (case-insensitive).
GET /api/groups/search
Search non-virtual groups by name substring. Authenticated only (no admin role required) — backs the share-dialog recipient autocomplete.
GET /api/groups/{id}
Fetch a single group's details. Admin-only.
DELETE /api/groups/{id}
Delete a group. Cascades to `subject_group_members` (FK) and to `access_grants` rows referencing this group as a subject. Admin-only.
PATCH /api/groups/{id}
Update a group's metadata. Admin-only. v1 only persists name renames.
GET /api/groups/{id}/effective-members
List every user transitively reached through this group (members of members of members, etc.). Used by admin / audit tooling. Admin-only.
GET /api/groups/{id}/members
List the *direct* members of a group (one level only). Admin-only.
POST /api/groups/{id}/members
Add a member to a group. Exactly one of `user_id` / `group_id` must be provided. Adding a group-member runs a write-time cycle check and a nesting-depth check (max 8). Admin-only.
DELETE /api/groups/{id}/members/group/{gid}
Remove a nested group-member from a group. Admin-only.
DELETE /api/groups/{id}/members/user/{uid}
Remove a user-member from a group. Admin-only.
fix hurl
groups
round
groups
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Display helpers for ReBAC subject groups.
|
||||
*
|
||||
* Server-side names of virtual groups (`Internal`, future `Everyone`, …) are
|
||||
* fixed RFC 5321 local-part strings so they can be email-addressable. The UI
|
||||
* surfaces them with a localised, capitalised label and a distinct icon.
|
||||
*
|
||||
* "Add a new virtual group" — frontend cost is:
|
||||
* 1. Add an entry to `VIRTUAL_NAME_KEYS` mapping the well-known UUID to an
|
||||
* `i18n` key.
|
||||
* 2. Add the i18n key + translations in the 16 locale files.
|
||||
*
|
||||
* Everything else (search results, vignettes, member rows, autocomplete)
|
||||
* picks the new group up automatically because the backend now returns
|
||||
* virtual groups in `/api/groups/search`.
|
||||
*/
|
||||
|
||||
import { i18n } from '../core/i18n.js';
|
||||
import { INTERNAL_GROUP_ID } from '../model/groups.js';
|
||||
|
||||
/**
|
||||
* Map of well-known virtual-group UUIDs → i18n key for the human-readable
|
||||
* display name. Anything not in this map falls back to `group.name`.
|
||||
*
|
||||
* @type {Record<string, string>}
|
||||
*/
|
||||
const VIRTUAL_NAME_KEYS = {
|
||||
[INTERNAL_GROUP_ID]: 'groups.virtual_internal_name'
|
||||
};
|
||||
|
||||
/**
|
||||
* Minimal shape needed by the display helpers. Both `GroupItem` (from
|
||||
* `/api/groups`) and shareModal's `GroupSuggestion` satisfy it, so callers
|
||||
* can pass either without an awkward upcast.
|
||||
*
|
||||
* @typedef {{id: string, name: string, is_virtual: boolean}} GroupDisplay
|
||||
*/
|
||||
|
||||
/**
|
||||
* Human-readable display name for a group. Virtual groups get a translated
|
||||
* label; user-defined groups display their raw name.
|
||||
*
|
||||
* @param {GroupDisplay} group
|
||||
* @returns {string}
|
||||
*/
|
||||
export function groupDisplayName(group) {
|
||||
if (group.is_virtual) {
|
||||
const key = VIRTUAL_NAME_KEYS[group.id];
|
||||
if (key) return i18n.t(key, group.name);
|
||||
}
|
||||
return group.name;
|
||||
}
|
||||
|
||||
/** FA class for system-managed (virtual) groups. `fa-people-roof` evokes a
|
||||
* shared roof / community, distinguishing virtual instance-wide groups
|
||||
* (Internal, future Everyone, …) from user-defined groups. Change here to
|
||||
* re-skin every virtual-group surface in the app in one place. */
|
||||
const VIRTUAL_ICON = 'fa-people-roof';
|
||||
|
||||
/** FA class for user-defined groups. */
|
||||
const REGULAR_ICON = 'fa-user-group';
|
||||
|
||||
/**
|
||||
* Pick the Font Awesome icon class for a group vignette. Virtual groups use
|
||||
* `VIRTUAL_ICON`; user-defined groups use `REGULAR_ICON`.
|
||||
*
|
||||
* @param {GroupDisplay} group
|
||||
* @returns {string}
|
||||
*/
|
||||
export function groupIconClass(group) {
|
||||
return group.is_virtual ? VIRTUAL_ICON : REGULAR_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `groupIconClass` but for call sites that hold only the
|
||||
* `is_virtual` boolean — e.g. `MemberEntry._isVirtual` in shareModal,
|
||||
* where the full `GroupItem` isn't kept around.
|
||||
*
|
||||
* @param {boolean | undefined} isVirtual
|
||||
* @returns {string}
|
||||
*/
|
||||
export function groupIconClassByVirtual(isVirtual) {
|
||||
return isVirtual ? VIRTUAL_ICON : REGULAR_ICON;
|
||||
}
|
||||
Reference in New Issue
Block a user