feat(materiel): connect catalogue and detail to api

This commit is contained in:
SaidSoighiri94
2026-07-07 13:07:47 +02:00
parent 90d5a65f29
commit 9564dd1ade
15 changed files with 368 additions and 101 deletions
+3 -1
View File
@@ -10,7 +10,9 @@ import { apiRouter } from './routes';
const app = express();
app.use(cors({ origin: env.frontendUrl }));
// En développement, on accepte toute origine locale (le front Flutter tourne sur
// un port variable) ; en production, seule l'origine du frontend est autorisée.
app.use(cors({ origin: env.nodeEnv === 'development' ? true : env.frontendUrl }));
app.use(express.json());
app.use(requestLogger);
@@ -1,7 +1,7 @@
import { Request, Response } from 'express';
import { AppError } from '../errors/app-error';
import { listerCatalogue } from '../services/materiel.service';
import { toMaterielResponse } from '../dtos/materiel.dto';
import { listerCatalogue, obtenirDetailMateriel } from '../services/materiel.service';
import { toMaterielDetailResponse, toMaterielResponse } from '../dtos/materiel.dto';
function parseCategorieId(value: unknown): number | undefined {
if (value === undefined) {
@@ -16,6 +16,18 @@ function parseCategorieId(value: unknown): number | undefined {
return parsed;
}
function parseId(value: unknown): number {
if (typeof value !== 'string') {
throw new AppError(400, 'id invalide');
}
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0) {
throw new AppError(400, 'id invalide');
}
return parsed;
}
export async function getCatalogue(req: Request, res: Response): Promise<void> {
const user = req.user;
if (!user) {
@@ -28,3 +40,13 @@ export async function getCatalogue(req: Request, res: Response): Promise<void> {
const materiels = await listerCatalogue(user.campusId, { categorieId, recherche });
res.json({ data: materiels.map(toMaterielResponse) });
}
export async function getMaterielDetail(req: Request, res: Response): Promise<void> {
const user = req.user;
if (!user) {
throw new AppError(401, 'Authentification requise');
}
const materiel = await obtenirDetailMateriel(parseId(req.params.id), user.campusId);
res.json({ data: toMaterielDetailResponse(materiel) });
}
+22 -1
View File
@@ -1,4 +1,4 @@
import { MaterielAvecCategorie } from '../repositories/materiel.repository';
import { MaterielAvecCategorie, MaterielDetail } from '../repositories/materiel.repository';
export interface CategorieResponse {
id: number;
@@ -16,6 +16,15 @@ export interface MaterielResponse {
categorie: CategorieResponse;
}
export interface MaterielDetailResponse extends MaterielResponse {
campus: {
id: number;
nom: string;
ville: string;
};
accessoires: string[];
}
export function toMaterielResponse(materiel: MaterielAvecCategorie): MaterielResponse {
return {
id: materiel.id,
@@ -31,3 +40,15 @@ export function toMaterielResponse(materiel: MaterielAvecCategorie): MaterielRes
},
};
}
export function toMaterielDetailResponse(materiel: MaterielDetail): MaterielDetailResponse {
return {
...toMaterielResponse(materiel),
campus: {
id: materiel.campus.id,
nom: materiel.campus.nom,
ville: materiel.campus.ville,
},
accessoires: materiel.accessoires.map((liaison) => liaison.accessoire.nom),
};
}
@@ -2,6 +2,13 @@ import { Materiel, Prisma } from '@prisma/client';
import { prisma } from '../db/prisma';
export type MaterielAvecCategorie = Prisma.MaterielGetPayload<{ include: { categorie: true } }>;
export type MaterielDetail = Prisma.MaterielGetPayload<{
include: {
categorie: true;
campus: true;
accessoires: { include: { accessoire: true } };
};
}>;
export interface CatalogueFiltres {
categorieId?: number;
@@ -39,3 +46,17 @@ export function findDisponiblesParCampus(
export function findById(id: number): Promise<Materiel | null> {
return prisma.materiel.findUnique({ where: { id } });
}
export function findDetailParCampus(id: number, campusId: number): Promise<MaterielDetail | null> {
return prisma.materiel.findFirst({
where: { id, campusId, actif: true },
include: {
categorie: true,
campus: true,
accessoires: {
include: { accessoire: true },
orderBy: { accessoire: { nom: 'asc' } },
},
},
});
}
+2 -1
View File
@@ -1,6 +1,7 @@
import { Router } from 'express';
import { getCatalogue } from '../controllers/materiel.controller';
import { getCatalogue, getMaterielDetail } from '../controllers/materiel.controller';
export const materielRoutes: Router = Router();
materielRoutes.get('/', getCatalogue);
materielRoutes.get('/:id', getMaterielDetail);
@@ -1,8 +1,11 @@
import {
findDisponiblesParCampus,
findDetailParCampus,
CatalogueFiltres,
MaterielDetail,
MaterielAvecCategorie,
} from '../repositories/materiel.repository';
import { AppError } from '../errors/app-error';
/* RG10 : après identification, seuls les matériels disponibles du campus de
l'étudiant sont affichés. */
@@ -12,3 +15,11 @@ export function listerCatalogue(
): Promise<MaterielAvecCategorie[]> {
return findDisponiblesParCampus(campusId, filtres);
}
export async function obtenirDetailMateriel(id: number, campusId: number): Promise<MaterielDetail> {
const materiel = await findDetailParCampus(id, campusId);
if (!materiel) {
throw new AppError(404, 'Materiel introuvable');
}
return materiel;
}