api-staff-member
Vue d'ensemble
Le service api-staff-member est le cœur du système RBAC de la plateforme Roomee. C'est le service le plus complexe avec 18 controllers différents gérant les membres, workspaces, rôles, permissions et toute la logique multi-tenant.
| Caractéristique | Détail |
|---|---|
| Port | 3001 |
| Stack | JavaScript (Legacy), Express.js, Prisma |
| Base de données | MongoDB (roomee_members) |
| Architecture | Legacy (JS + Repository Pattern) |
| Complexité | ⭐⭐⭐⭐⭐ (Le plus complexe) |
Statistiques
| Métrique | Valeur |
|---|---|
| Controllers | 18 |
| Routes | 120+ |
| Modèles DB | 18 |
| Middlewares | 8 |
| Services | 15+ |
Architecture du Service
graph TB
subgraph "api-staff-member"
ROUTES[Routes API]
subgraph "Controllers (18)"
MEMBER[MemberController]
WORKSPACE[WorkspaceController]
ROLE[RoleController]
PERMISSION[PermissionController]
TEAM[TeamController]
DEPT[DepartmentController]
ONBOARD[OnboardingController]
ECOSYSTEM[EcosystemController]
end
subgraph "Services"
MEMBER_SVC[MemberService]
RBAC_SVC[RBACService]
SYNC_SVC[SyncService]
end
subgraph "Repository Layer"
MEMBER_REPO[MemberRepository]
WORKSPACE_REPO[WorkspaceRepository]
ROLE_REPO[RoleRepository]
end
ROUTES --> MEMBER
ROUTES --> WORKSPACE
ROUTES --> ROLE
MEMBER --> MEMBER_SVC
WORKSPACE --> MEMBER_SVC
ROLE --> RBAC_SVC
MEMBER_SVC --> MEMBER_REPO
RBAC_SVC --> ROLE_REPO
MEMBER_REPO --> PRISMA[(Prisma Client)]
end
subgraph "Services Externes"
AUTH[api-authentication]
NOTIF[api-notification]
end
MEMBER_SVC --> AUTH
MEMBER_SVC --> NOTIFStructure du Projet
api-staff-member/
├── app.js
├── loader.js
├── controllers/ # 18 Controllers
│ ├── member.controller.js # CRUD membres
│ ├── workspace.controller.js # Gestion workspaces
│ ├── workspaceMember.controller.js # Membres dans workspaces
│ ├── role.controller.js # Gestion rôles
│ ├── permission.controller.js # Gestion permissions
│ ├── team.controller.js # Équipes
│ ├── department.controller.js # Départements
│ ├── onboarding.controller.js # Processus d'intégration
│ ├── ecosystem.controller.js # Écosystèmes
│ ├── ecosystemMembership.controller.js
│ ├── setting.controller.js # Paramètres utilisateur
│ ├── preference.controller.js # Préférences
│ ├── invitation.controller.js # Invitations
│ ├── hierarchy.controller.js # Hiérarchie manager/subordinés
│ ├── analytics.controller.js # Statistiques
│ ├── sync.controller.js # Synchronisation
│ ├── bulk.controller.js # Opérations en masse
│ └── export.controller.js # Export de données
├── routes/ # Routes correspondantes
├── repository/ # Couche d'accès données
├── services/ # Business logic
│ ├── member.service.js
│ ├── rbac.service.js
│ ├── workspace.service.js
│ ├── hierarchy.service.js
│ ├── onboarding.service.js
│ └── sync.service.js
├── middlewares/
│ ├── auth.middleware.js
│ ├── permission.middleware.js # Vérification permissions
│ ├── workspace.middleware.js # Isolation workspace
│ └── rateLimiter.middleware.js
├── utils/
│ ├── ApiError.js
│ ├── catchAsync.js
│ ├── rbacHelper.js # Helpers RBAC
│ └── hierarchyHelper.js
├── socket/
│ └── memberSocket.js # Events temps réel
├── amqp/
│ └── memberEvents.js # Events AMQP
├── prisma/
│ └── schema.prisma # Schéma complexe (18 modèles)
└── __tests/Modèles de Données (Sélection)
members
model members {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String? @db.ObjectId // Lien vers User (api-authentication)
// Informations personnelles
firstName String
lastName String
email String @unique
phone String?
photo_url String?
// Hiérarchie
isSuperAdmin Boolean @default(false)
superior String? @db.ObjectId // Manager ID
manager members? @relation("ManagerToMembers", fields: [superior], references: [id], onDelete: NoAction, onUpdate: NoAction)
subordinates members[] @relation("ManagerToMembers")
// Paramètres
dashboardAccess Boolean @default(false)
isActive Boolean @default(true)
// Relations
workspaces workspaceMember[]
ecosystems ecosystemMembership[]
teams TeamMember[]
departments DepartmentMember[]
settings memberSettings?
preferences memberPreference[]
// Metadata
deleted_at DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
@@index([userId])
@@index([superior])
@@index([isSuperAdmin])
}workspaces
model workspaces {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
description String?
// Relations écosystème
ecosystemId String @db.ObjectId // hotelId
ecosystemType String @default("hotel") // hotel, resort, etc.
// Configuration
logo_url String?
settings Json? // Paramètres workspace
isDefault Boolean @default(false)
isActive Boolean @default(true)
// Relations
members workspaceMember[]
teams Team[]
departments Department[]
roles workspaceRole[]
// Metadata
deleted_at DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([ecosystemId])
@@index([isActive])
}workspaceMember
model workspaceMember {
id String @id @default(auto()) @map("_id") @db.ObjectId
memberId String @db.ObjectId
member members @relation(fields: [memberId], references: [id], onDelete: Cascade)
workspaceId String @db.ObjectId
workspace workspaces @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
// Rôle dans le workspace
workspaceRole String // "admin", "manager", "member", "viewer"
permissionSlugs String[] // Liste des permissions
// Status
status String @default("active") // active, pending, suspended
isAdmin Boolean @default(false)
// Dates
joinedAt DateTime @default(now())
leftAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([memberId, workspaceId])
@@index([memberId])
@@index([workspaceId])
@@index([workspaceRole])
}workspaceRole
model workspaceRole {
id String @id @default(auto()) @map("_id") @db.ObjectId
workspaceId String @db.ObjectId
workspace workspaces @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String // "Manager", "Staff", "Supervisor"
slug String // "manager", "staff"
description String?
// Permissions
permissions rolePermission[]
// Système ou custom
isSystem Boolean @default(false) // Rôles système non modifiables
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([workspaceId, slug])
@@index([workspaceId])
}permission
model permission {
id String @id @default(auto()) @map("_id") @db.ObjectId
slug String @unique // "manage_members", "create_content"
name String // "Gérer les membres"
description String?
// Catégorisation
category String // "members", "content", "settings"
resource String // "member", "page", "workspace"
action String // "create", "read", "update", "delete", "manage"
// Niveau
level String @default("workspace") // "ecosystem", "workspace"
// Relations
rolePermissions rolePermission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([category])
@@index([level])
}rolePermission
model rolePermission {
id String @id @default(auto()) @map("_id") @db.ObjectId
roleId String @db.ObjectId
role workspaceRole @relation(fields: [roleId], references: [id], onDelete: Cascade)
permissionId String @db.ObjectId
permission permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
// Options
conditions Json? // Conditions spécifiques
createdAt DateTime @default(now())
@@unique([roleId, permissionId])
@@index([roleId])
@@index([permissionId])
}ecosystemMembership
model ecosystemMembership {
id String @id @default(auto()) @map("_id") @db.ObjectId
memberId String @db.ObjectId
member members @relation(fields: [memberId], references: [id], onDelete: Cascade)
ecosystemId String // hotelId
ecosystemType String @default("hotel")
// Permissions niveau écosystème (générales)
permissions String[] // ["access_live_chat", "screenshot_permission"]
role String @default("member") // "owner", "admin", "member"
isOwner Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([memberId, ecosystemId])
@@index([memberId])
@@index([ecosystemId])
}Team
model Team {
id String @id @default(auto()) @map("_id") @db.ObjectId
workspaceId String @db.ObjectId
workspace workspaces @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String
description String?
color String? // Couleur d'identification
icon String?
// Relations
members TeamMember[]
leaderId String? @db.ObjectId // Team leader
isActive Boolean @default(true)
deleted_at DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([workspaceId])
}Department
model Department {
id String @id @default(auto()) @map("_id") @db.ObjectId
workspaceId String @db.ObjectId
workspace workspaces @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String // "Réception", "Housekeeping", "Restaurant"
description String?
code String? // Code département
// Hiérarchie
parentId String? @db.ObjectId
parent Department? @relation("DepartmentHierarchy", fields: [parentId], references: [id], onDelete: NoAction, onUpdate: NoAction)
subDepartments Department[] @relation("DepartmentHierarchy")
// Relations
members DepartmentMember[]
managerId String? @db.ObjectId // Department manager
isActive Boolean @default(true)
deleted_at DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([workspaceId])
@@index([parentId])
}onboarding
model onboarding {
id String @id @default(auto()) @map("_id") @db.ObjectId
// Membre invité
email String
firstName String?
lastName String?
// Invitation
token String @unique
invitedBy String @db.ObjectId // ID du membre inviteur
// Contexte
workspaceId String @db.ObjectId
ecosystemId String
workspaceRole String @default("member")
permissions String[]
// Status
status String @default("pending") // pending, accepted, expired, cancelled
acceptedAt DateTime?
expiresAt DateTime
// Metadata
invitationData Json? // Données supplémentaires
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
@@index([token])
@@index([workspaceId])
@@index([status])
}Routes API (Sélection des principales)
👤 Membres (/members)
POST /members/
Créer un nouveau membre.
Request :
{
"firstName": "Marie",
"lastName": "Dubois",
"email": "marie.dubois@hotel.com",
"phone": "+33 6 12 34 56 78",
"workspaceId": "65f1234567890abcdef12345",
"workspaceRole": "manager",
"superior": "65f9876543210fedcba98765",
"dashboardAccess": true
}Response :
{
"id": "65faaaaaaaaaaaaaaaaaaaaa",
"firstName": "Marie",
"lastName": "Dubois",
"email": "marie.dubois@hotel.com",
"workspaces": [
{
"workspaceId": "65f1234567890abcdef12345",
"workspaceRole": "manager",
"permissionSlugs": ["manage_team", "view_reports"]
}
],
"superior": "65f9876543210fedcba98765",
"dashboardAccess": true
}GET /members/get/all
Récupérer tous les membres (avec filtres).
Query Params :
workspaceId: Filtrer par workspaceworkspaceRole: Filtrer par rôleisActive: Filtrer par statutsearch: Recherche nom/emailpage,limit: Pagination
Response :
{
"data": [
{
"id": "65faaaaaaaaaaaaaaaaaaaaa",
"firstName": "Marie",
"lastName": "Dubois",
"email": "marie.dubois@hotel.com",
"photo_url": "https://...",
"workspaces": [...],
"isActive": true
}
],
"meta": {
"total": 150,
"page": 1,
"limit": 20
}
}GET /members/get/:id
Récupérer un membre par son ID avec toutes ses relations.
Response :
{
"id": "65faaaaaaaaaaaaaaaaaaaaa",
"firstName": "Marie",
"lastName": "Dubois",
"email": "marie.dubois@hotel.com",
"phone": "+33 6 12 34 56 78",
"photo_url": "https://...",
"isSuperAdmin": false,
"dashboardAccess": true,
"manager": {
"id": "65f9876543210fedcba98765",
"firstName": "Jean",
"lastName": "Martin"
},
"subordinates": [
{
"id": "65fbbbbbbbbbbbbbbbbbbbbb",
"firstName": "Paul",
"lastName": "Durand"
}
],
"workspaces": [
{
"workspaceId": "65f1234567890abcdef12345",
"workspaceName": "Grand Hotel Paris",
"workspaceRole": "manager",
"permissions": ["manage_team", "view_reports"],
"isAdmin": false
}
],
"ecosystems": [
{
"ecosystemId": "65fccccccccccccccccccccc",
"role": "admin",
"permissions": ["access_live_chat", "screenshot_permission"]
}
],
"teams": [
{
"teamId": "65fddddddddddddddddddddd",
"teamName": "Équipe Réception",
"role": "member"
}
],
"departments": [
{
"departmentId": "65feeeeeeeeeeeeeeeeeeeeee",
"departmentName": "Réception",
"role": "staff"
}
]
}PATCH /members/update/:id
Mettre à jour un membre.
DELETE /members/delete/:id
Supprimer un membre (soft delete).
GET /members/get/with/:email
Récupérer un membre par son email (utilisé par api-authentication).
POST /members/add/hotel/member
Créer un administrateur d'hôtel (appelé par api-hotel).
Request :
{
"hotelId": "65f1111111111111111111111",
"email": "admin@hotel.com",
"firstName": "Admin",
"lastName": "Hotel",
"dashboardAccess": true,
"isAdmin": true
}GET /members/get/by/hotel_id/:id
Récupérer tous les membres d'un hôtel.
GET /members/hierarchy/:id
Récupérer la hiérarchie complète d'un membre (manager + subordinés récursifs).
Response :
{
"member": {
"id": "65faaaaaaaaaaaaaaaaaaaaa",
"firstName": "Marie",
"lastName": "Dubois"
},
"manager": {
"id": "65f9876543210fedcba98765",
"firstName": "Jean",
"lastName": "Martin"
},
"subordinates": [
{
"id": "65fbbbbbbbbbbbbbbbbbbbbb",
"firstName": "Paul",
"lastName": "Durand",
"subordinates": [...]
}
]
}🏢 Workspaces (/workspaces)
POST /workspaces/
Créer un nouveau workspace.
Request :
{
"name": "Grand Hotel Lyon",
"description": "Workspace principal",
"ecosystemId": "65f1111111111111111111111",
"ecosystemType": "hotel",
"isDefault": true,
"settings": {
"timezone": "Europe/Paris",
"language": "fr"
}
}GET /workspaces/get/all
Récupérer tous les workspaces.
GET /workspaces/get/:id
Récupérer un workspace avec ses membres.
Response :
{
"id": "65f1234567890abcdef12345",
"name": "Grand Hotel Lyon",
"description": "Workspace principal",
"ecosystemId": "65f1111111111111111111111",
"logo_url": "https://...",
"isActive": true,
"members": [
{
"memberId": "65faaaaaaaaaaaaaaaaaaaaa",
"firstName": "Marie",
"lastName": "Dubois",
"workspaceRole": "admin",
"permissions": ["manage_members", "manage_content"]
}
],
"teams": [
{
"id": "65fddddddddddddddddddddd",
"name": "Équipe Réception",
"membersCount": 5
}
],
"departments": [
{
"id": "65feeeeeeeeeeeeeeeeeeeeee",
"name": "Réception",
"membersCount": 8
}
],
"roles": [
{
"id": "65fffffffffffffffffffff",
"name": "Manager",
"slug": "manager",
"isSystem": true
}
]
}POST /workspaces/:id/add-member
Ajouter un membre à un workspace.
Request :
{
"memberId": "65faaaaaaaaaaaaaaaaaaaaa",
"workspaceRole": "manager",
"permissions": ["manage_team", "view_reports"]
}DELETE /workspaces/:workspaceId/remove-member/:memberId
Retirer un membre d'un workspace.
🎭 Rôles (/roles)
POST /roles/
Créer un rôle personnalisé.
Request :
{
"workspaceId": "65f1234567890abcdef12345",
"name": "Superviseur",
"slug": "supervisor",
"description": "Supervise une équipe",
"permissions": [
"view_members",
"view_reports",
"manage_team"
]
}GET /roles/workspace/:workspaceId
Récupérer tous les rôles d'un workspace.
Response :
{
"data": [
{
"id": "65fffffffffffffffffffff",
"name": "Admin",
"slug": "admin",
"isSystem": true,
"isDefault": false,
"permissions": [
{
"slug": "manage_members",
"name": "Gérer les membres",
"category": "members"
},
{
"slug": "manage_content",
"name": "Gérer le contenu",
"category": "content"
}
]
}
]
}PATCH /roles/:id
Mettre à jour un rôle (seulement les rôles custom).
POST /roles/:id/add-permission
Ajouter une permission à un rôle.
Request :
{
"permissionSlug": "create_content"
}DELETE /roles/:id/remove-permission/:permissionSlug
Retirer une permission d'un rôle.
🔑 Permissions (/permissions)
GET /permissions/all
Récupérer toutes les permissions disponibles.
Response :
{
"data": [
{
"id": "65f0000000000000000000001",
"slug": "manage_members",
"name": "Gérer les membres",
"description": "Créer, modifier et supprimer des membres",
"category": "members",
"resource": "member",
"action": "manage",
"level": "workspace"
},
{
"id": "65f0000000000000000000002",
"slug": "create_content",
"name": "Créer du contenu",
"category": "content",
"resource": "page",
"action": "create",
"level": "workspace"
}
],
"categories": {
"members": ["manage_members", "view_members"],
"content": ["create_content", "edit_content", "delete_content"],
"settings": ["manage_settings", "view_settings"],
"communication": ["send_notifications", "access_chat"]
}
}POST /permissions/
Créer une permission personnalisée.
GET /permissions/member/:memberId/workspace/:workspaceId
Récupérer toutes les permissions effectives d'un membre dans un workspace.
Response :
{
"memberId": "65faaaaaaaaaaaaaaaaaaaaa",
"workspaceId": "65f1234567890abcdef12345",
"workspaceRole": "manager",
"permissions": [
"manage_team",
"view_reports",
"create_content",
"edit_content"
],
"source": {
"role": ["manage_team", "view_reports"],
"direct": ["create_content", "edit_content"]
}
}👥 Équipes (/teams)
POST /teams/
Créer une équipe.
Request :
{
"workspaceId": "65f1234567890abcdef12345",
"name": "Équipe Réception",
"description": "Équipe de la réception",
"color": "#3B82F6",
"icon": "🏨",
"leaderId": "65faaaaaaaaaaaaaaaaaaaaa"
}GET /teams/workspace/:workspaceId
Récupérer toutes les équipes d'un workspace.
POST /teams/:id/add-member
Ajouter un membre à une équipe.
GET /teams/:id/members
Récupérer les membres d'une équipe.
🏢 Départements (/departments)
POST /departments/
Créer un département.
Request :
{
"workspaceId": "65f1234567890abcdef12345",
"name": "Réception",
"description": "Service de réception",
"code": "RCP",
"parentId": null,
"managerId": "65faaaaaaaaaaaaaaaaaaaaa"
}GET /departments/workspace/:workspaceId
Récupérer tous les départements d'un workspace (hiérarchie).
Response :
{
"data": [
{
"id": "65feeeeeeeeeeeeeeeeeeeeee",
"name": "Réception",
"code": "RCP",
"manager": {
"id": "65faaaaaaaaaaaaaaaaaaaaa",
"firstName": "Marie",
"lastName": "Dubois"
},
"membersCount": 8,
"subDepartments": [
{
"id": "65fffffffffffffffffffffe",
"name": "Réception - Nuit",
"code": "RCP-N",
"membersCount": 3
}
]
}
]
}🎓 Onboarding (/onboarding)
POST /onboarding/invite
Envoyer une invitation d'intégration.
Request :
{
"email": "nouveau@hotel.com",
"firstName": "Nouveau",
"lastName": "Membre",
"workspaceId": "65f1234567890abcdef12345",
"ecosystemId": "65f1111111111111111111111",
"workspaceRole": "member",
"permissions": ["view_content"]
}Response :
{
"id": "65f2222222222222222222222",
"email": "nouveau@hotel.com",
"token": "onboarding-token-abc123",
"status": "pending",
"expiresAt": "2025-01-27T10:00:00.000Z",
"invitationLink": "https://app.roomee.io/onboarding?token=..."
}Logique :
- Création du record onboarding
- Génération d'un token unique (7 jours)
- Émission événement AMQP
member.onboarding - api-notification envoie l'email d'invitation
GET /onboarding/get/all
Récupérer toutes les invitations (filtres par status).
POST /onboarding/accept/:token
Accepter une invitation (appelé après inscription user).
DELETE /onboarding/cancel/:id
Annuler une invitation.
⚙️ Paramètres (/settings)
GET /settings/member/:id
Récupérer les paramètres d'un membre.
PATCH /settings/member/:id
Mettre à jour les paramètres.
📊 Analytics (/analytics)
GET /analytics/workspace/:id
Statistiques d'un workspace.
Response :
{
"workspaceId": "65f1234567890abcdef12345",
"members": {
"total": 45,
"active": 42,
"pending": 3,
"byRole": {
"admin": 2,
"manager": 8,
"member": 35
}
},
"teams": {
"total": 6,
"averageSize": 7.5
},
"departments": {
"total": 4
},
"activity": {
"newMembersThisMonth": 5,
"activeToday": 38
}
}🔄 Synchronisation (/sync)
POST /sync/members
Synchroniser les membres (appelé par api-news et api-notification).
Request :
{
"workspaceId": "65f1234567890abcdef12345"
}Response :
{
"members": [
{
"id": "65faaaaaaaaaaaaaaaaaaaaa",
"firstName": "Marie",
"lastName": "Dubois",
"email": "marie.dubois@hotel.com",
"photo_url": "https://..."
}
],
"syncedAt": "2025-01-21T10:00:00.000Z"
}📥 Export (/export)
GET /export/members/workspace/:workspaceId
Exporter les membres d'un workspace (CSV/Excel).
Query Params :
format:csvouxlsx
🔢 Bulk Operations (/bulk)
POST /bulk/add-members
Ajouter plusieurs membres en masse.
Request :
{
"workspaceId": "65f1234567890abcdef12345",
"members": [
{
"email": "user1@hotel.com",
"firstName": "User",
"lastName": "One",
"workspaceRole": "member"
},
{
"email": "user2@hotel.com",
"firstName": "User",
"lastName": "Two",
"workspaceRole": "member"
}
]
}POST /bulk/update-roles
Modifier les rôles de plusieurs membres.
POST /bulk/assign-permissions
Assigner des permissions à plusieurs membres.
Middlewares
Permission Middleware
// middlewares/permission.middleware.js
const requirePermission = (permissionSlug) => {
return async (req, res, next) => {
const { userId } = req.user
const { workspaceId } = req.params || req.body
// Récupérer les permissions du membre dans le workspace
const permissions = await rbacService.getMemberPermissions(
userId,
workspaceId
)
if (!permissions.includes(permissionSlug)) {
throw new ApiError(403, 'Permission denied')
}
next()
}
}
// Usage
router.post(
'/members/',
authMiddleware.middleware,
requirePermission('manage_members'),
memberController.create
)Workspace Isolation Middleware
// middlewares/workspace.middleware.js
const ensureWorkspaceAccess = async (req, res, next) => {
const { userId } = req.user
const { workspaceId } = req.params || req.body
const hasAccess = await workspaceService.hasMemberAccess(
userId,
workspaceId
)
if (!hasAccess) {
throw new ApiError(403, 'Workspace access denied')
}
next()
}Système RBAC
Architecture à 2 Niveaux
graph TB
subgraph "Niveau 1: Écosystème"
ECO_PERM[Permissions Générales]
LIVE_CHAT[access_live_chat]
SCREENSHOT[screenshot_permission]
ECOSYSTEM_MANAGE[manage_ecosystem]
end
subgraph "Niveau 2: Workspace"
WS_ROLE[Rôle Workspace]
WS_PERM[Permissions Workspace]
ADMIN[Admin]
MANAGER[Manager]
MEMBER[Member]
MANAGE_MEMBERS[manage_members]
CREATE_CONTENT[create_content]
VIEW_REPORTS[view_reports]
end
ECO_PERM --> LIVE_CHAT
ECO_PERM --> SCREENSHOT
WS_ROLE --> ADMIN
WS_ROLE --> MANAGER
WS_ROLE --> MEMBER
ADMIN --> MANAGE_MEMBERS
ADMIN --> CREATE_CONTENT
MANAGER --> CREATE_CONTENT
MANAGER --> VIEW_REPORTSCatégories de Permissions
const PERMISSION_CATEGORIES = {
members: [
'manage_members', // CRUD membres
'view_members', // Voir les membres
'invite_members', // Inviter des membres
'remove_members', // Retirer des membres
'manage_roles' // Gérer les rôles
],
content: [
'create_content', // Créer du contenu
'edit_content', // Modifier du contenu
'delete_content', // Supprimer du contenu
'publish_content', // Publier du contenu
'moderate_comments' // Modérer les commentaires
],
settings: [
'manage_settings', // Gérer les paramètres
'view_settings', // Voir les paramètres
'manage_integrations' // Gérer les intégrations
],
communication: [
'send_notifications', // Envoyer des notifications
'access_chat', // Accéder au chat
'manage_channels' // Gérer les canaux
],
analytics: [
'view_reports', // Voir les rapports
'export_data', // Exporter les données
'view_analytics' // Voir les analytics
]
}Vérification des Permissions
// services/rbac.service.js
class RBACService {
async getMemberPermissions(memberId, workspaceId) {
// 1. Récupérer le workspace member
const workspaceMember = await prisma.workspaceMember.findUnique({
where: {
memberId_workspaceId: { memberId, workspaceId }
},
include: {
workspace: {
include: {
roles: {
where: { slug: workspaceMember.workspaceRole },
include: {
permissions: {
include: { permission: true }
}
}
}
}
}
}
})
// 2. Permissions du rôle
const rolePermissions = workspaceMember.workspace.roles
.flatMap(role => role.permissions.map(rp => rp.permission.slug))
// 3. Permissions directes
const directPermissions = workspaceMember.permissionSlugs || []
// 4. Fusionner et dédupliquer
return [...new Set([...rolePermissions, ...directPermissions])]
}
async hasPermission(memberId, workspaceId, permissionSlug) {
const permissions = await this.getMemberPermissions(memberId, workspaceId)
return permissions.includes(permissionSlug)
}
}Événements AMQP
Événements Émis
| Routing Key | Payload | Description |
|---|---|---|
roomee.notification.member.registered | { memberId, email, workspaceId } | Nouveau membre |
roomee.notification.member.updated | { memberId, changes } | Membre mis à jour |
roomee.notification.member.deleted | { memberId, email } | Membre supprimé |
roomee.notification.workspace.created | { workspaceId, name, ecosystemId } | Workspace créé |
roomee.notification.workspace.member_joined | { memberId, workspaceId } | Membre rejoint workspace |
roomee.notification.workspace.member_left | { memberId, workspaceId } | Membre quitté workspace |
Socket.IO Events
// socket/memberSocket.js
io.on('connection', (socket) => {
// Rejoindre les rooms workspace
socket.on('joinWorkspaces', (workspaceIds) => {
workspaceIds.forEach(id => {
socket.join(`workspace:${id}`)
})
})
// Rejoindre la room personnelle
socket.on('joinMember', (memberId) => {
socket.join(`member:${memberId}`)
})
})
// Émissions serveur
io.to(`workspace:${workspaceId}`).emit('member:joined', memberData)
io.to(`member:${memberId}`).emit('role:updated', roleData)Configuration
# Server
PORT=3001
# Database
DATABASE_URL=mongodb://localhost:27017/roomee_members
# JWT
JWT_ACCESS_SECRET=your-jwt-secret
# AMQP
AMQP_GATEWAY_URL=amqp://localhost:5672
AMQP_EXCHANGE_NAME=roomee_events
AMQP_ROUTING_KEY_BASE=roomee.notification.member
# Service URLs
AUTH_API_URL=http://localhost:3000
# API Keys
AUTH_SERVICE_API_KEY=your-auth-api-keyBonnes Pratiques
1. Toujours vérifier l'isolation workspace
const members = await prisma.members.findMany({
where: {
workspaces: {
some: {
workspaceId: req.user.workspaceId
}
},
deleted_at: null
}
})2. Vérifier les permissions avant chaque action
const hasPermission = await rbacService.hasPermission(
req.user.userId,
workspaceId,
'manage_members'
)
if (!hasPermission) {
throw new ApiError(403, 'Permission denied')
}3. Gérer la hiérarchie manager/subordinés
// Un manager ne peut gérer que ses subordinés
const isSubordinate = await hierarchyService.isSubordinate(
managerId,
memberId
)
if (!isSubordinate) {
throw new ApiError(403, 'Cannot manage this member')
}Améliorations Futures
- [ ] Cache Redis pour les permissions (TTL 5 min)
- [ ] Graph API pour les hiérarchies complexes
- [ ] Webhooks pour les événements membres
- [ ] Audit trail complet des actions RBAC
- [ ] Permissions temporaires (time-bound)
- [ ] Délégation de permissions
- [ ] SSO avec SAML/LDAP pour entreprises