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(), }), }, ]; }