Utenti e team
L'amministrazione di chi accede e cosa può fare: utenti,
ruoli (con permessi per modulo e per campo) e team (per
lo scope team della visibilità).
Questi endpoint richiedono il ruolo amministratore
(requireAdminRole): un utente autenticato ma non admin riceve
403. Unica eccezione:
GET /api/users/assignable, disponibile a
ogni utente autenticato. Il profilo del proprio account si legge e aggiorna
da /auth/me. Ogni richiesta richiede l'header
Authorization: Bearer <accessToken>.
01Gli oggetti
Le tre risorse di questa pagina. Le date sono stringhe ISO 8601 in UTC.
L'password_hash non è mai esposto dall'API.
| Attributo | Tipo | Descrizione |
|---|---|---|
| id | integer | Identificativo univoco dell'utente. |
| username | string | Nome utente univoco (3–100 caratteri: lettere, numeri, . _ -). |
| string | Email univoca. | |
| first_name | string | null | Nome. |
| last_name | string | null | Cognome. |
| is_active | boolean | Se false, l'utente non può autenticarsi. |
| roles | array | Ruoli assegnati, come { id, name }. |
| created_at | string | Data/ora di creazione (ISO 8601). |
| updated_at | string | Data/ora ultima modifica (ISO 8601). |
{ "id": 7, "username": "m.rossi", "email": "m.rossi@acme.it", "first_name": "Marco", "last_name": "Rossi", "is_active": true, "roles": [ { "id": 2, "name": "sales" } ], "created_at": "2026-07-07T15:24:00.000Z", "updated_at": "2026-07-07T15:24:00.000Z" }
| Attributo | Tipo | Descrizione |
|---|---|---|
| id | integer | Identificativo univoco del ruolo. |
| name | string | Nome univoco del ruolo (max 80 caratteri). |
| description | string | null | Descrizione. |
| is_system | boolean | Ruolo di sistema (es. admin): non eliminabile. |
| parent_id | integer | null | Ruolo padre nella gerarchia; null se radice. |
| Attributo | Tipo | Descrizione |
|---|---|---|
| id | integer | Identificativo univoco del team. |
| name | string | Nome univoco del team (2–120 caratteri). |
| description | string | null | Descrizione (max 1000 caratteri). |
| isActive | boolean | Team attivo. |
| members | array | Membri, come { userId, username, email, firstName, lastName, fullName, roles[], joinedAt }. |
| createdAt | string | Data/ora di creazione (ISO 8601). |
| updatedAt | string | Data/ora ultima modifica (ISO 8601). |
02Utenti
| Metodo | Path | Cosa fa |
|---|---|---|
| GET | /api/users | Elenco utenti (con search, limit, offset). |
| GET | /api/users/:id | Un singolo utente. |
| POST | /api/users | Crea un utente. |
| PATCH | /api/users/:id | Modifica (ruoli, stato, dati). |
| DELETE | /api/users/:id | Elimina (riassegnando i record). |
| POST | /api/users/:id/reset-password | Reimposta la password. |
| GET | /api/users/assignable | Utenti assegnabili dal chiamante: non richiede admin. |
| Parametro | Tipo | Descrizione | |
|---|---|---|---|
| search | string | opzionale | Filtra per username, email o nome. |
| limit | integer | opzionale | Righe per pagina. |
| offset | integer | opzionale | Righe da saltare. |
curl "https://crm.tuodominio.it/api/users?search=rossi" \ -H "Authorization: Bearer <accessToken>"
const res = await fetch("https://crm.tuodominio.it/api/users?search=rossi", { headers: { Authorization: `Bearer ${accessToken}` }, }); const { data } = await res.json();
| Stato | Corpo |
|---|---|
| 200 | Elenco degli utenti nell'envelope data. |
| 403 | FORBIDDEN: l'utente non è amministratore. |
| Campo | Tipo | Note | |
|---|---|---|---|
| username | string | richiesto | 3–100 caratteri: lettere, numeri, . _ -. Univoco. |
| string | richiesto | Email valida e univoca. | |
| password | string | richiesto | Deve rispettare la policy password. |
| firstName | string | opzionale | Nome. |
| lastName | string | opzionale | Cognome. |
| isActive | boolean | opzionale | Default true. |
| roleIds | integer[] | opzionale | Ruoli da assegnare. Default vuoto. |
curl -X POST https://crm.tuodominio.it/api/users \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "username": "m.rossi", "email": "m.rossi@acme.it", "password": "…", "roleIds": [2] }'
const res = await fetch("https://crm.tuodominio.it/api/users", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ username: "m.rossi", email: "m.rossi@acme.it", password: "…", roleIds: [2] }), }); const { data } = await res.json();
{ "success": true, "data": { "user": { "id": 7, "username": "m.rossi", … } } }
| Stato | Codice | Quando |
|---|---|---|
| 201 | · | Utente creato; in data.user l'oggetto completo. |
| 400 | BAD_REQUEST | Username/email non validi o già in uso, password fuori policy. |
| 403 | FORBIDDEN | L'utente non è amministratore. |
Modifica parziale (username, email,
firstName, lastName, isActive,
roleIds). Per sicurezza non puoi rimuovere a te stesso il ruolo admin,
disattivarti, né togliere l'ultimo amministratore attivo (403).
Elimina l'utente riassegnando i record posseduti/assegnati all'admin chiamante.
Non puoi eliminare il tuo account né l'ultimo admin attivo. Risponde 200
{ deleted: true }.
Body { "password": "…" } (deve rispettare la policy).
Risponde 200 { updated: true }.
03Assegnazione dei record (gerarchia)
La gerarchia dei ruoli governa anche a chi puoi intestare un record
(owner_user_id / assigned_user_id, su ogni
modulo base e custom): un utente può assegnare solo a sé stesso o a un
sottoposto (utente con un ruolo discendente del suo), e può riassegnare solo record
già suoi o di un sottoposto. L'admin assegna a chiunque. Un target fuori
gerarchia risponde 403.
Questo vincolo è ortogonale allo scope: lo scope
(own/team/all) decide quali record raggiungi, la gerarchia decide quali valori
puoi mettere in owner/assigned. Avere scope all senza ruoli
figli permette di modificare i record, ma non di riassegnarli.
Gli utenti a cui il chiamante può assegnare un record: sé stesso + i sottoposti (admin: tutti gli utenti attivi). Alimenta il picker di owner/assegnatario. Non richiede il ruolo admin.
Risposta · 200{ "success": true, "data": { "items": [{ "id": "20", "name": "Luca Bianchi" }] } }
04Ruoli e permessi
I ruoli sono gerarchici: le autorizzazioni di un ruolo figlio sono un sottoinsieme di quelle del padre (materializzate e imposte nel service). A ogni ruolo si assegnano i permessi per modulo (azione + scope) e, opzionalmente, i permessi per campo. Alla cancellazione di un padre, i figli diventano radici. La stessa gerarchia governa la riassegnazione dei record.
| Metodo | Path | Cosa fa |
|---|---|---|
| GET | /api/roles | Elenco ruoli. |
| POST | /api/roles | Crea un ruolo. |
| GET | /api/roles/:id | Un singolo ruolo. |
| PATCH | /api/roles/:id | Rinomina / sposta nella gerarchia (parentId). |
| DELETE | /api/roles/:id | Elimina. |
| GET | /api/roles/:id/permissions | Permessi per modulo del ruolo. |
| PUT | /api/roles/:id/permissions | Sostituisce i permessi per modulo. |
| GET | /api/roles/:id/field-permissions | Permessi per campo. |
| PUT | /api/roles/:id/field-permissions | Sostituisce i permessi per campo. |
I permessi e i permessi-campo si scrivono con PUT: l'insieme che
invii sostituisce interamente quello esistente. Leggi prima
con il GET corrispondente e reinvia la matrice completa, per non
perdere assegnazioni.
Sostituisce l'intero set di permessi per modulo del ruolo. Body: la lista completa degli id dei permessi da assegnare.
Parametri body| Campo | Tipo | Note | |
|---|---|---|---|
| permissionIds | integer[] | richiesto | Set completo di permessi. Un array vuoto revoca tutto. |
curl -X PUT https://crm.tuodominio.it/api/roles/2/permissions \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "permissionIds": [1, 4, 5, 9] }'
// leggi prima la matrice corrente, poi reinvia il set completo const res = await fetch("https://crm.tuodominio.it/api/roles/2/permissions", { method: "PUT", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ permissionIds: [1, 4, 5, 9] }), }); const { data } = await res.json();
| Stato | Corpo |
|---|---|
| 200 | La matrice aggiornata nell'envelope data. |
| 403 | FORBIDDEN: non-admin, o permessi eccedenti il ceiling del padre. |
PUT /field-permissions segue lo stesso schema, con body
{ "items": [ … ] } (set completo dei permessi per campo).
05Team
I team servono allo scope team della visibilità dei record.
| Metodo | Path | Cosa fa |
|---|---|---|
| GET | /api/teams | Elenco team (con i membri). |
| GET | /api/teams/:id | Un singolo team. |
| POST | /api/teams | Crea un team. |
| PATCH | /api/teams/:id | Modifica. |
| DELETE | /api/teams/:id | Elimina. |
| Campo | Tipo | Note | |
|---|---|---|---|
| name | string | richiesto | Nome del team. 2–120 caratteri. Univoco. |
| description | string | opzionale | Max 1000 caratteri. |
| isActive | boolean | opzionale | Default true. |
| userIds | integer[] | opzionale | Membri iniziali (interi positivi, max 200). Devono essere utenti attivi esistenti. |
curl -X POST https://crm.tuodominio.it/api/teams \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{ "name": "Vendite Nord", "userIds": [7, 12] }'
const res = await fetch("https://crm.tuodominio.it/api/teams", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Vendite Nord", userIds: [7, 12] }), }); const { data } = await res.json();
{ "success": true, "data": { "item": { "id": 3, "name": "Vendite Nord", "isActive": true, "members": [ … ] } } }
| Stato | Codice | Quando |
|---|---|---|
| 201 | · | Team creato; in data.item l'oggetto completo con i membri. |
| 400 | BAD_REQUEST | Uno o più userIds inesistenti o non attivi. |
| 422 | VALIDATION_ERROR | name fuori misura (2–120) o payload non valido. |
| 403 | FORBIDDEN | L'utente non è amministratore. |
PATCH /api/teams/:id accetta gli stessi campi (tutti opzionali,
almeno uno). Inviare userIds sostituisce l'intero
elenco dei membri. DELETE risponde 200
{ deleted: true, item }.