2026 03 12 User Management Module Modernization Design
Documentation for Plans
User Management Module Modernization — Design Spec
Session: 210 Date: 2026-03-12 Status: Draft Approach: Rebuild from gmodules1 base into gspace1
1. Objective
Modernize the modules/core-modules/user-management module to:
- Use real
backend-apiendpoints instead of mock data - Replace NGXS state management with
autoFlashStatefrom@storage-system - Incorporate the corrected L0-L4 user tier hierarchy (Session 209)
- Support both SystemRoles (immutable, platform-wide) and custom space-level roles
- Remain portable across gspace1 AND dynamically generated platforms
2. Architecture
2.1 Data Access: Dual-Layer Pattern
Components (UI)
↓ inject
Repository (domain queries, caching, events)
↓ delegates
API Service (HTTP, configurable base URL)
↓ HTTP
Backend API (port 3000 or platform backend)
Why dual-layer: The module must work in both gspace1 (where backend-api is at API_BASE_URL) and generated platforms (where the backend URL varies). The Repository abstracts this so components never know the URL.
2.2 State Management: autoFlashState
Replace NGXS @State + actions with autoFlashState signals:
// UI state — persisted locally, cross-tab synced
const userListState = autoFlashState<UserListState>('user-mgmt-list', {
filters: {},
pagination: { page: 1, pageSize: 20 },
viewMode: 'table',
bulkSelection: [],
}, {
database: 'user-management',
collection: 'ui-state',
priority: 'normal',
ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
debounceMs: 300,
});
Data (users, roles) is NOT stored in autoFlashState — it comes from the backend via API Service + Repository. Only UI preferences (filters, view mode, pagination, selection) are persisted locally.
Transient state (loading, error, selectedUser, total) lives as plain signal() values in the component or a lightweight service — NOT persisted. These reset on page load, which is correct behavior.
2.3 Tier Hierarchy Integration
There are TWO orthogonal classification systems. Both live on the User object:
System A: role: UserRole — Controls access level (what you CAN DO)
| UserRole | Level | Used For |
|---|---|---|
| system | 100 | Backend automation only |
| executive | 90 | Governance (Vitto core values, directives, persona defaults) |
| developer | 80 | Full technical access (engine settings, debug, tools) |
| admin | 60 | Platform operations (user CRUD, space management) |
| manager | 40 | Space-level management (modules, workflows) |
| editor | 20 | Content management within a space |
| viewer | 10 | Read-only access |
| guest | 0 | Unauthenticated / temp |
System B: userType: RegistrationUserType — Describes WHO you are (your tier)
| Tier | userType | Default Role | Description |
|---|---|---|---|
| L0 | (assigned internally) | executive | Founders/C-suite |
| L1 | (assigned internally) | developer or admin | GoGrab engineering & ops |
| L2 | space_builder | manager | Creates & manages spaces |
| L2 | space_admin | manager | CEO/executive of a space |
| L3 | space_user | editor | Team member within a space |
| L4 | platform_user | viewer | End customer of generated platform |
Key: role determines access control enforcement (what the RolesGuard checks). userType determines the user's category in the platform hierarchy. A space_builder (L2) gets manager role by default but could be promoted to admin by an executive.
Rule: A user can only manage users with a LOWER role level than their own. Enforced at both API and UI level via the hierarchy-aware RolesGuard.
3. Module Structure (Target)
modules/core-modules/user-management/src/lib/
├── models/
│ ├── user.model.ts # User, UserStatus, USER_PERMISSIONS
│ ├── user-api.model.ts # Request/response types (CRUD, bulk, audit)
│ ├── user-filters.model.ts # UserFilters, UserPagination
│ ├── role.model.ts # Role, SystemRoles, ROLE_PERMISSIONS
│ ├── role-api.model.ts # CreateRoleRequest, UpdateRoleRequest
│ └── index.ts
├── services/
│ ├── user-api.service.ts # HTTP calls to /api/users (extends BaseCrudApiService)
│ ├── role-api.service.ts # HTTP calls to /api/roles
│ └── index.ts
├── data/
│ └── repositories/
│ ├── user.repository.ts # Domain queries, caching, event publishing
│ ├── role.repository.ts # Role domain queries
│ └── index.ts
├── state/
│ ├── user-list.state.ts # autoFlashState for user list UI
│ └── index.ts
├── components/
│ ├── user-list/ # Main list (table/grid/cards, pagination, bulk)
│ ├── user-form/ # Create/edit form (reactive forms, role selector)
│ ├── user-detail/ # Profile view + workflow state machine
│ ├── user-role-manager/ # Role assignment with hierarchy enforcement
│ ├── multi-role-lookup/ # Multi-select role dropdown
│ ├── user-card/ # Card display
│ ├── user-avatar/ # Avatar with initials fallback
│ ├── user-filters/ # Filter panel (search, role, status, date)
│ ├── user-delete-confirm/ # Delete confirmation dialog
│ ├── role-list/ # System + custom roles list
│ ├── role-form/ # Create/edit role with permissions
│ ├── role-detail/ # Role details view
│ └── index.ts
├── actions/
│ ├── create-user.action.ts # Public action (cross-module)
│ ├── update-user.action.ts
│ ├── delete-user.action.ts
│ ├── navigate-to-user.action.ts
│ ├── bulk-update-roles.action.ts
│ └── index.ts
├── events/
│ ├── user.events.ts # UserCreated, UserUpdated, UserDeleted, etc.
│ ├── role.events.ts # RoleAssigned, RoleRemoved
│ └── index.ts
├── workflow/
│ ├── user-workflow.definition.ts # Status state machine (pending→active→suspended→inactive)
│ └── index.ts
├── embeddables/
│ ├── user-list-widget/ # Compact embeddable widget
│ └── index.ts
├── mocks/
│ ├── user-mock-api.ts # Mock API (dev/test only)
│ ├── user-mock-data.ts # Generated test data
│ └── index.ts
├── user-management.module.ts # Module manifest, lifecycle, navigation
├── user-management.routes.ts # User routes with guards
├── role-management.routes.ts # Role routes with guards
└── index.ts # Public API
13 components + 1 embeddable widget.
4. Backend API Endpoints
4.1 Existing Endpoints (backend-api:3000)
These already exist and the module will wire to them:
| Method | Path | Purpose |
|---|---|---|
| POST | /api/auth/register |
Self-register (L2+ only: space_builder, space_admin, space_user) |
| POST | /api/auth/login |
Login |
| GET | /api/auth/profile |
Current user profile |
| GET | /api/admin/users |
List all users (admin) |
| POST | /api/spaces/:spaceId/universes/:universeId/users |
Create platform user (L4) |
| GET | /api/spaces/:spaceId/universes/:universeId/users |
List platform users |
| PUT | /api/spaces/:spaceId/universes/:universeId/users/:id |
Update platform user |
| DELETE | /api/spaces/:spaceId/universes/:universeId/users/:id |
Delete platform user |
4.2 Endpoints to Add (backend-api enhancements)
Controller: New AdminUsersController at /api/admin/users to avoid route conflicts with the existing UsersController (which handles /api/users/me/*, /api/users/resolve-channel, etc.). All endpoints use @UseGuards(JwtAuthGuard, RolesGuard) at class level.
| Method | Path | Purpose | Decorator |
|---|---|---|---|
| GET | /api/admin/users |
List users with pagination/filters | @Roles(SystemRoles.ADMIN) |
| GET | /api/admin/users/:id |
Get user by ID | @Roles(SystemRoles.ADMIN) |
| PUT | /api/admin/users/:id |
Update user (name, role, status) | @Roles(SystemRoles.ADMIN) |
| DELETE | /api/admin/users/:id |
Soft-delete user | @Roles(SystemRoles.ADMIN) |
| PUT | /api/admin/users/:id/status |
Change user status | @Roles(SystemRoles.ADMIN) |
| PUT | /api/admin/users/:id/roles |
Assign/change role | @Roles(SystemRoles.ADMIN) + service-level check: assigning L0-L1 roles requires executive |
| POST | /api/admin/users/bulk |
Bulk operations | @Roles(SystemRoles.ADMIN) |
| GET | /api/admin/roles |
List roles (system + custom) | @Roles(SystemRoles.ADMIN) |
| POST | /api/admin/roles |
Create custom role | @Roles(SystemRoles.ADMIN) |
| PUT | /api/admin/roles/:id |
Update custom role | @Roles(SystemRoles.ADMIN) |
| DELETE | /api/admin/roles/:id |
Delete custom role (not system) | @Roles(SystemRoles.ADMIN) |
Note: The existing GET /api/admin/users in AdminStatsController already supports limit, skip, search. We extend it or replace it with the new AdminUsersController that adds pagination, filters, sorting, and full CRUD.
4.3 Backend Schema Enhancements
The existing User schema needs:
- Confirm
roles: string[]supports multiple roles (it does — default['user']) - Add
statusfield:'active' | 'inactive' | 'suspended' | 'pending'(the existingUserHierarchyStatususes'deleted'instead of'inactive'— we standardize on'inactive'for soft-delete,'deleted'is reserved for hard-delete/GDPR purge) - Add
userType: RegistrationUserTypefield alignment with frontend types
New schema needed:
Roleschema for custom roles (name, description, permissions[], isSystemRole, priority, spaceId?)
4.4 Pagination Strategy
All list endpoints use offset pagination (matching existing PlatformUsersController pattern):
- Query params:
page(1-based),limit(1-100, default 20),search,sortBy,sortOrder - Response:
{ data: T[], pagination: { page, pageSize, total, totalPages, hasNext, hasPrevious } }
5. Component Design
5.1 All Components
All components follow:
- Standalone:
standalone: truewith explicit imports - OnPush:
changeDetection: ChangeDetectionStrategy.OnPush - DI:
inject()function, never constructor injection - Signals: Component state via
signal(),computed(),effect() - Async: RxJS Observables only,
takeUntilDestroyed()for cleanup - Separate files:
.ts,.html,.scss— no inline templates
5.2 Key Component Details
UserListComponent:
- Reads from
UserRepository.findAll()(Observable) - UI state (filters, pagination, view mode, selection) from
autoFlashState - Supports table, grid, and card views
- Bulk operations: activate, deactivate, delete, change role
- Permission-gated actions (users:create, users:delete, etc.)
UserFormComponent:
- Reactive Forms with validation
- Mode:
'create' | 'edit'(input) - Multi-role selector via
MultiRoleLookupComponent - Status dropdown (only visible for edit mode, requires
users:statuspermission) - Avatar upload
UserDetailComponent:
- Displays full user profile
- Embedded
UserRoleManagerComponentfor role assignment - Workflow state machine visualization (pending → active → suspended → inactive)
- Status transitions with confirmation dialogs
- Workflow history timeline
UserRoleManagerComponent:
- Shows current roles with hierarchy levels
- Enforces: can only assign roles BELOW your own level
- Distinguishes system roles (immutable) from custom roles
- Primary role selector
RoleListComponent / RoleFormComponent / RoleDetailComponent:
- CRUD for custom roles within a space
- System roles shown as read-only (marked with badge)
- Permission picker for custom roles
5.3 Workflow State Machine
[pending] ──approve──→ [active] ──suspend──→ [suspended]
│ │
deactivate reactivate
↓ ↓
[inactive] ←──reactivate──+
Transitions require users:status permission. Confirmation dialogs for destructive transitions (suspend, deactivate).
6. Route Configuration
User Routes (/users)
| Path | Component | Guards | Permissions |
|---|---|---|---|
/list |
UserListComponent | authGuard, permissionGuard | users:read |
/create |
UserFormComponent | authGuard, permissionGuard | users:create |
/:id |
UserDetailComponent | authGuard, permissionGuard | users:read |
/:id/edit |
UserFormComponent | authGuard, permissionGuard | users:update |
/admin/manage |
UserListComponent | authGuard, roleGuard(ADMIN) | users:manage |
Role Routes (/users/roles)
| Path | Component | Guards | Permissions |
|---|---|---|---|
/list |
RoleListComponent | authGuard, permissionGuard | roles:read |
/create |
RoleFormComponent | authGuard, permissionGuard | roles:create |
/:id |
RoleDetailComponent | authGuard, permissionGuard | roles:read |
/:id/edit |
RoleFormComponent | authGuard, permissionGuard | roles:update |
7. Domain Events
| Event | Payload | Priority | When |
|---|---|---|---|
| UserCreatedEvent | user, createdBy, tenantId | normal | After successful creation |
| UserUpdatedEvent | user, previousData, updatedBy, tenantId | normal | After update |
| UserDeletedEvent | userId, userEmail, deletedBy, tenantId | high | After deletion |
| UserStatusChangedEvent | user, previousStatus, newStatus, changedBy | normal | Status transition |
| UserBulkOperationEvent | operation, userIds[], counts, executedBy | normal | After bulk op |
| RoleAssignedEvent | roleId, userId, tenantId, assignedBy | normal | Role assignment |
| RoleRemovedEvent | roleId, userId, tenantId, removedBy | normal | Role removal |
Events extend DomainEvent<T> from the module system's event bus.
8. Public Actions (Cross-Module API)
| Action ID | Permissions | Input | Output |
|---|---|---|---|
users:create-user |
users:create | email, firstName, lastName, roleId, tenantId | success, userId |
users:update-user |
users:update | userId, partial fields | success, updatedFields[] |
users:delete-user |
users:delete | userId, hardDelete?, reason? | success, deletionType |
users:navigate-to-user |
users:read | userId, mode? | success, targetRoute |
users:bulk-update-roles |
users:bulk | assignments[], continueOnError? | total, successful, failed |
9. Module Manifest
@ModuleDecorator({
id: 'user-management',
version: '2.0.0',
basePath: 'users',
repositories: [UserRepository, RoleRepository],
permissions: [
'users:read', 'users:create', 'users:update', 'users:delete',
'users:status', 'users:role', 'users:bulk', 'users:manage',
'roles:read', 'roles:create', 'roles:update', 'roles:delete', 'roles:manage',
],
navigation: {
menuItems: [
{ label: 'Users', icon: 'people', route: 'list', permission: 'users:read',
children: [
{ label: 'All Users', icon: 'list', route: 'list' },
{ label: 'Create User', icon: 'person_add', route: 'create' },
{ label: 'Admin Management', icon: 'shield', route: 'admin/manage' },
]
},
{ label: 'Roles', icon: 'admin_panel_settings', route: 'roles/list', permission: 'roles:read',
children: [
{ label: 'All Roles', icon: 'list', route: 'roles/list' },
{ label: 'Create Role', icon: 'add', route: 'roles/create' },
]
},
]
}
})
10. Portability Considerations
When embedded in gspace1:
- Base URL resolves via
API_BASE_URLinjection token (port 3000) - Auth interceptor adds JWT from
AuthService - Tenant context from
ContextInterceptor - SystemRoles hierarchy available
When embedded in a generated platform:
- Base URL resolves via platform's
provideApiConfig()call - Auth interceptor from platform's auth module
- Tenant context = the platform's universe
- Custom roles only (SystemRoles reduced to platform-relevant subset)
Key abstraction points:
UserApiServicebase URL is injected, never hardcoded- Permission constants are exported, not hard-wired to specific guard implementations
- Module contract interface (
BrowserModuleContract) ensures lifecycle compatibility
11. What We Are NOT Doing
- Not building a permissions editor UI (that's a separate module)
- Not implementing user groups/teams (future scope)
- Not adding 2FA management UI (backend schema exists but no UI needed yet)
- Not building CSV import/export (future scope)
- Not adding user impersonation (admin feature, separate scope)
12. Risks and Mitigations
| Risk | Mitigation |
|---|---|
| gmodules1 imports may not resolve in gspace1 | Verify all @module/* paths exist in tsconfig; use gspace1's path aliases |
| Backend-api missing CRUD endpoints for L1/L2 users | Add them as part of this spec (Section 4.2) |
| autoFlashState hydration timing on first load | Use .hydrated signal to gate operations that depend on persisted state |
| Role hierarchy enforcement gap at API level | Backend validates that requesting user's level > target user's level |
| Mock API still referenced in dev | Keep mock API in mocks/ for offline development; toggle via environment flag |