Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import { UserRepository } from '@domain/ports/out/user.repository';
|
||
import { OrganizationRepository } from '@domain/ports/out/organization.repository';
|
||
import { CsvRateSearchService } from '@domain/services/csv-rate-search.service';
|
||
import { Capability } from '../capability';
|
||
|
||
/** Role d'administration de la plateforme. Les inscriptions creent des MANAGER. */
|
||
const ADMIN_ONLY = ['ADMIN'] as const;
|
||
|
||
/**
|
||
* Capacites d'administration de la plateforme.
|
||
*
|
||
* Elles franchissent la frontiere de l'organisation — c'est precisement ce qui
|
||
* les distingue du reste du catalogue — et sont donc reservees au role ADMIN,
|
||
* verifie dans le processus et non dans un prompt.
|
||
*
|
||
* Elles sont **en lecture seule**. Modifier un utilisateur, valider un SIRET ou
|
||
* remplacer une grille tarifaire touche des comptes clients et de l'argent :
|
||
* ces actions restent a la main d'une personne, dans l'espace d'administration,
|
||
* tant qu'un mecanisme de confirmation explicite n'existe pas cote agent.
|
||
*/
|
||
export function adminCapabilities(
|
||
users: UserRepository,
|
||
organizations: OrganizationRepository,
|
||
rateSearch: CsvRateSearchService
|
||
): Capability[] {
|
||
return [
|
||
{
|
||
policy: { name: 'admin_list_users', scope: 'read', roles: ADMIN_ONLY },
|
||
description:
|
||
"Liste les comptes de la plateforme, toutes organisations confondues. Réservé à l'administration.",
|
||
inputSchema: {
|
||
type: 'object',
|
||
properties: {
|
||
role: {
|
||
type: 'string',
|
||
description: 'Ne garder que ce rôle.',
|
||
enum: ['ADMIN', 'MANAGER', 'USER', 'VIEWER', 'CARRIER'],
|
||
},
|
||
search: {
|
||
type: 'string',
|
||
description: 'Filtre sur l’adresse e-mail ou le nom.',
|
||
maxLength: 120,
|
||
},
|
||
limit: {
|
||
type: 'integer',
|
||
description: 'Nombre maximum de comptes.',
|
||
minimum: 1,
|
||
maximum: 100,
|
||
default: 25,
|
||
},
|
||
},
|
||
additionalProperties: false,
|
||
},
|
||
handler: async input => {
|
||
const all = input.role
|
||
? await users.findByRole(input.role as string)
|
||
: await users.findAll();
|
||
|
||
const term = (input.search as string | undefined)?.toLowerCase();
|
||
const matching = term
|
||
? all.filter(user =>
|
||
`${user.email} ${user.firstName} ${user.lastName}`.toLowerCase().includes(term)
|
||
)
|
||
: all;
|
||
|
||
return {
|
||
total: matching.length,
|
||
users: matching.slice(0, (input.limit as number) ?? 25).map(user => ({
|
||
id: user.id,
|
||
email: user.email,
|
||
firstName: user.firstName,
|
||
lastName: user.lastName,
|
||
role: user.role,
|
||
organizationId: user.organizationId,
|
||
isActive: user.isActive,
|
||
})),
|
||
};
|
||
},
|
||
},
|
||
|
||
{
|
||
policy: { name: 'admin_list_organizations', scope: 'read', roles: ADMIN_ONLY },
|
||
description:
|
||
"Liste les organisations de la plateforme, avec leur nombre de comptes. Réservé à l'administration.",
|
||
inputSchema: {
|
||
type: 'object',
|
||
properties: {
|
||
limit: {
|
||
type: 'integer',
|
||
description: "Nombre maximum d'organisations.",
|
||
minimum: 1,
|
||
maximum: 100,
|
||
default: 25,
|
||
},
|
||
},
|
||
additionalProperties: false,
|
||
},
|
||
handler: async input => {
|
||
const all = await organizations.findAll();
|
||
const page = all.slice(0, (input.limit as number) ?? 25);
|
||
|
||
return {
|
||
total: all.length,
|
||
organizations: await Promise.all(
|
||
page.map(async organization => ({
|
||
id: organization.id,
|
||
name: organization.name,
|
||
userCount: await users.countByOrganization(organization.id),
|
||
}))
|
||
),
|
||
};
|
||
},
|
||
},
|
||
|
||
{
|
||
policy: { name: 'admin_rate_grid_overview', scope: 'read', roles: ADMIN_ONLY },
|
||
description:
|
||
"État des grilles tarifaires chargées : transporteurs et types de conteneurs disponibles. Réservé à l'administration.",
|
||
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
||
handler: async () => ({
|
||
carriers: await rateSearch.getAvailableCompanies(),
|
||
containerTypes: await rateSearch.getAvailableContainerTypes(),
|
||
}),
|
||
},
|
||
];
|
||
}
|