mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
Trouvé en recette : GET /assets/options n'avait aucun filtre — tout Demandeur voyait le parc complet dans le sélecteur d'équipement de "Nouvelle demande", sur le web ET le mobile (même endpoint partagé). Risque réel : signaler accidentellement une panne sur un ascenseur qu'on ne gère pas. Corrigé à la racine, sur les deux plateformes à la fois : - Relation many-to-many User↔Location (assignedSites/assignedUsers, migration r6_demandeur_sites, même style que Team.members) — vide = aucune restriction, comportement historique inchangé pour tous les rôles sauf un Demandeur affecté à un site. - AssetsService.allowedLocationIds(user) : sites + zones filles autorisés, ou null si aucune restriction — réutilisée par options() ET par RequestsService.create (défense en profondeur : un assetId soumis directement hors périmètre est rejeté, 400). - UsersService : assertTopLevelSites (un Demandeur est affecté à un site, jamais une zone) ; invite()/update() branchent locationIds (remplace l'affectation, comme teamIds). - Web (personnes.tsx) : ModaleInvitation affiche les sites à cocher pour un rôle Demandeur ; colonne "Sites" éditable via une modale dédiée. - Mobile (formulaire-demande.tsx) : bouton "Scanner l'étiquette" en raccourci — résout uniquement contre les options déjà chargées (déjà filtrées), jamais de repli sur le parc complet qui annulerait la restriction. Aucun changement à useAssetOptions() : le filtrage serveur profite automatiquement au formulaire mobile. - Seed : Karim Doukkali (démo) rattaché à Tour Atlas. Bug trouvé en vérification avant tout commit : create() comparait allowed.includes(dto.assetId), mais allowed est une liste d'ids de sites/zones, pas d'ids d'appareils — aurait rejeté à tort tout signalement d'un Demandeur affecté, y compris dans son propre périmètre. Corrigé (comparaison sur asset.locationId) ; méthode renommée allowedAssetIds → allowedLocationIds pour que le nom dise ce qu'elle retourne. exploitation.e2e-spec.ts mis à jour (A1/C1 → A2/B1, dans le site de Karim — sinon rejetés par la nouvelle règle, comportement voulu). 79/80 tests API verts, le seul échec (documents-analytics, monthCost) est le flake calendaire déjà identifié cette session, sans rapport. Typecheck/tests/lint verts sur les 4 paquets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83 lines
2.2 KiB
TypeScript
83 lines
2.2 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpCode,
|
|
Param,
|
|
ParseUUIDPipe,
|
|
Patch,
|
|
Post,
|
|
} from '@nestjs/common';
|
|
import {
|
|
AssetComponentCreateSchema,
|
|
AssetCreateSchema,
|
|
AssetUpdateSchema,
|
|
type AssetComponentCreate,
|
|
type AssetCreate,
|
|
type AssetUpdate,
|
|
} from '@siop/shared';
|
|
import { type AuthenticatedUser, CurrentUser } from '../auth/current-user.decorator';
|
|
import { ZodValidationPipe } from '../common/zod-validation.pipe';
|
|
import { RequirePermission } from '../permissions/require-permission.decorator';
|
|
import { AssetsService } from './assets.service';
|
|
|
|
@Controller('assets')
|
|
export class AssetsController {
|
|
constructor(private readonly assetsService: AssetsService) {}
|
|
|
|
@Get()
|
|
@RequirePermission('ASSETS', 'view')
|
|
list() {
|
|
return this.assetsService.list();
|
|
}
|
|
|
|
/** Avant ':id' (ordre des routes) — authentification seule : le demandeur
|
|
* doit pouvoir désigner l'appareil qu'il signale (filtré à son site s'il
|
|
* en a un, R6.6). */
|
|
@Get('options')
|
|
options(@CurrentUser() user: AuthenticatedUser) {
|
|
return this.assetsService.options(user);
|
|
}
|
|
|
|
@Get(':id')
|
|
@RequirePermission('ASSETS', 'view')
|
|
get(@Param('id', ParseUUIDPipe) id: string) {
|
|
return this.assetsService.get(id);
|
|
}
|
|
|
|
@Post()
|
|
@RequirePermission('ASSETS', 'create')
|
|
create(@Body(new ZodValidationPipe(AssetCreateSchema)) body: AssetCreate) {
|
|
return this.assetsService.create(body);
|
|
}
|
|
|
|
@Patch(':id')
|
|
@RequirePermission('ASSETS', 'edit')
|
|
update(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body(new ZodValidationPipe(AssetUpdateSchema)) body: AssetUpdate,
|
|
) {
|
|
return this.assetsService.update(id, body);
|
|
}
|
|
|
|
@Post(':id/components')
|
|
@RequirePermission('ASSETS', 'edit')
|
|
addComponent(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body(new ZodValidationPipe(AssetComponentCreateSchema)) body: AssetComponentCreate,
|
|
) {
|
|
return this.assetsService.addComponent(id, body);
|
|
}
|
|
|
|
@Delete(':id/components/:componentId')
|
|
@RequirePermission('ASSETS', 'edit')
|
|
@HttpCode(204)
|
|
removeComponent(
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Param('componentId', ParseUUIDPipe) componentId: string,
|
|
) {
|
|
return this.assetsService.removeComponent(id, componentId);
|
|
}
|
|
}
|