feat(r1.1): socle backend du référentiel — modèle, contrat, API, seed, tests

- migration r1_referentiel : Category (EQUIPMENT/COMPONENT_TYPE), Location
  (site → zone, lat/lng + colonne PostGIS générée geography(Point,4326)
  + index GIST), Asset (statut d'équipement), AssetComponent (organe sans
  emplacement PAR CONSTRUCTION), Team, invitation sur User ; migration
  autosuffisante (CREATE EXTENSION IF NOT EXISTS postgis)
- contrat : 21 nouvelles opérations (26 total), générateur OpenAPI étendu
  aux paramètres de chemin ; spec + client web régénérés dans ce commit
- API : modules categories/locations/assets/teams + gestion des personnes
  (liste, rôles, invitation lien 7 j à usage unique, activation publique
  qui connecte directement, mise à jour rôle/équipes) — tout sous
  @RequirePermission ; invariants en service (profondeur 2, kinds,
  catégorie jamais supprimée)
- seed : parc de la maquette validée (5 sites + 8 zones, 8 appareils,
  organes A1/B2, 9 catégories, 2 équipes) — idempotent
- 36 tests verts (couverture 96 % stmts / 85 % branches) : recette
  site→zone→appareil→organes, matrice vivante, invitation→activation ;
  smoke test sur build de prod
- CI : postgres → postgis/postgis:18-3.6 (la migration R1 l'exige)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-16 12:27:48 +01:00
parent 6d9aafdab4
commit 266ffaaf1b
36 changed files with 6016 additions and 13 deletions

View File

@@ -0,0 +1,73 @@
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 { 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();
}
@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);
}
}