src/app/api/mobile/catalogue/route.ts
Annotation non disponible
Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.
Concepts détectés — comprends la théorie
ORM Prisma
3 occurrencesCe fichier accède à la base de données via Prisma. Prisma est l'ORM utilisé côté backend pour les requêtes typées sur PostgreSQL.
Voir l'article général
Route API Next.js
3 occurrencesCe fichier est une route API Next.js (App Router). Voir le contrat API complet pour les conventions de réponse et d'auth.
Voir l'article général
JWT / Auth backend
1 occurrenceCe fichier touche au système d'authentification (JWT, session, getSessionFromRequest). Voir le contrat API pour la logique complète.
Voir l'article général
1 export
GET
Code source· typescript
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { filtreTenantsAccessibles, getConfigEffective } from "@/lib/acces-vitrine";
import { getSessionFromRequest } from "@/lib/auth/session";
export async function GET(req: NextRequest) {
try {
const session = await getSessionFromRequest(req);
const clientId = session?.role === "CLIENT" ? session.userId : null;
const { searchParams } = req.nextUrl;
const tenantId = searchParams.get("tenantId");
const page = Math.max(1, parseInt(searchParams.get("page") || "1"));
const limit = Math.min(50, parseInt(searchParams.get("limit") || "20"));
const categorie = searchParams.get("categorie");
const q = searchParams.get("q");
const tri = searchParams.get("tri"); // newest | prix_asc | prix_desc
const prixMin = searchParams.get("prixMin");
const prixMax = searchParams.get("prixMax");
const dispo = searchParams.get("dispo"); // disponible | sur_commande
const tagParam = searchParams.get("tag"); // slug1,slug2
const tagMode = (searchParams.get("tagMode") || "and").toLowerCase(); // and | or
const tagSlugs = tagParam
? tagParam.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
: [];
const skip = (page - 1) * limit;
const where: any = {
disponible: true,
statut: dispo === "disponible"
? "DISPONIBLE"
: dispo === "sur_commande"
? "SUR_COMMANDE"
: { in: ["DISPONIBLE", "SUR_COMMANDE"] },
tenant: { actif: true, vitrineActive: true, ...filtreTenantsAccessibles(clientId) },
};
if (tenantId) {
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tenantId);
if (isUuid) {
where.tenantId = tenantId;
} else {
where.tenant = { ...where.tenant, subdomain: tenantId };
}
}
if (q) where.OR = [
{ nom: { contains: q, mode: "insensitive" } },
{ tags: { has: q } },
{ marque: { contains: q, mode: "insensitive" } },
];
if (categorie) where.categories = { some: { categorieId: categorie } };
if (tagSlugs.length) {
if (tagMode === "or") {
where.tagsLinks = { some: { tag: { slug: { in: tagSlugs } } } };
} else {
where.AND = tagSlugs.map((slug) => ({
tagsLinks: { some: { tag: { slug } } },
}));
}
}
if (prixMin || prixMax) {
where.prix = {};
if (prixMin) where.prix.gte = parseFloat(prixMin);
if (prixMax) where.prix.lte = parseFloat(prixMax);
}
const orderBy =
tri === "prix_asc" ? [{ prix: "asc" as const }, { id: "asc" as const }] :
tri === "prix_desc" ? [{ prix: "desc" as const }, { id: "asc" as const }] :
[{ createdAt: "desc" as const }, { id: "asc" as const }];
const [produits, total] = await Promise.all([
prisma.produit.findMany({
where,
skip,
take: limit,
orderBy,
include: {
medias: { orderBy: { ordre: "asc" }, select: { url: true, ordre: true, type: true } },
categories: {
include: { categorie: { select: { id: true, nom: true } } },
},
tagsLinks: {
include: { tag: { select: { id: true, nom: true, slug: true, couleur: true } } },
},
tenant: {
select: {
id: true,
subdomain: true,
nom: true,
logoUrl: true,
whatsapp: true,
niveauAcces: true,
configAcces: true,
},
},
},
}),
prisma.produit.count({ where }),
]);
return NextResponse.json({
produits: produits.map((p) => ({
id: p.id,
nom: p.nom,
description: p.description,
prix: p.prix,
ancienPrix: p.ancienPrix,
devise: p.devise,
stock: p.stock,
disponible: p.disponible,
statut: p.statut,
marque: p.marque,
tags: p.tags,
videoUrl: p.videoUrl,
medias: p.medias,
categories: p.categories.map((c) => c.categorie),
tagsList: p.tagsLinks.map((tl) => tl.tag),
tenant: { ...p.tenant, configAcces: getConfigEffective(p.tenant.configAcces) },
})),
total,
page,
totalPages: Math.ceil(total / limit),
});
} catch (error) {
console.error("mobile/catalogue error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { filtreTenantsAccessibles, getConfigEffective } from "@/lib/acces-vitrine";
import { getSessionFromRequest } from "@/lib/auth/session";
export async function GET(req: NextRequest) {
try {
const session = await getSessionFromRequest(req);
const clientId = session?.role === "CLIENT" ? session.userId : null;
const { searchParams } = req.nextUrl;
const tenantId = searchParams.get("tenantId");
const page = Math.max(1, parseInt(searchParams.get("page") || "1"));
const limit = Math.min(50, parseInt(searchParams.get("limit") || "20"));
const categorie = searchParams.get("categorie");
const q = searchParams.get("q");
const tri = searchParams.get("tri"); // newest | prix_asc | prix_desc
const prixMin = searchParams.get("prixMin");
const prixMax = searchParams.get("prixMax");
const dispo = searchParams.get("dispo"); // disponible | sur_commande
const tagParam = searchParams.get("tag"); // slug1,slug2
const tagMode = (searchParams.get("tagMode") || "and").toLowerCase(); // and | or
const tagSlugs = tagParam
? tagParam.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
: [];
const skip = (page - 1) * limit;
const where: any = {
disponible: true,
statut: dispo === "disponible"
? "DISPONIBLE"
: dispo === "sur_commande"
? "SUR_COMMANDE"
: { in: ["DISPONIBLE", "SUR_COMMANDE"] },
tenant: { actif: true, vitrineActive: true, ...filtreTenantsAccessibles(clientId) },
};
if (tenantId) {
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tenantId);
if (isUuid) {
where.tenantId = tenantId;
} else {
where.tenant = { ...where.tenant, subdomain: tenantId };
}
}
if (q) where.OR = [
{ nom: { contains: q, mode: "insensitive" } },
{ tags: { has: q } },
{ marque: { contains: q, mode: "insensitive" } },
];
if (categorie) where.categories = { some: { categorieId: categorie } };
if (tagSlugs.length) {
if (tagMode === "or") {
where.tagsLinks = { some: { tag: { slug: { in: tagSlugs } } } };
} else {
where.AND = tagSlugs.map((slug) => ({
tagsLinks: { some: { tag: { slug } } },
}));
}
}
if (prixMin || prixMax) {
where.prix = {};
if (prixMin) where.prix.gte = parseFloat(prixMin);
if (prixMax) where.prix.lte = parseFloat(prixMax);
}
const orderBy =
tri === "prix_asc" ? [{ prix: "asc" as const }, { id: "asc" as const }] :
tri === "prix_desc" ? [{ prix: "desc" as const }, { id: "asc" as const }] :
[{ createdAt: "desc" as const }, { id: "asc" as const }];
const [produits, total] = await Promise.all([
prisma.produit.findMany({
where,
skip,
take: limit,
orderBy,
include: {
medias: { orderBy: { ordre: "asc" }, select: { url: true, ordre: true, type: true } },
categories: {
include: { categorie: { select: { id: true, nom: true } } },
},
tagsLinks: {
include: { tag: { select: { id: true, nom: true, slug: true, couleur: true } } },
},
tenant: {
select: {
id: true,
subdomain: true,
nom: true,
logoUrl: true,
whatsapp: true,
niveauAcces: true,
configAcces: true,
},
},
},
}),
prisma.produit.count({ where }),
]);
return NextResponse.json({
produits: produits.map((p) => ({
id: p.id,
nom: p.nom,
description: p.description,
prix: p.prix,
ancienPrix: p.ancienPrix,
devise: p.devise,
stock: p.stock,
disponible: p.disponible,
statut: p.statut,
marque: p.marque,
tags: p.tags,
videoUrl: p.videoUrl,
medias: p.medias,
categories: p.categories.map((c) => c.categorie),
tagsList: p.tagsLinks.map((tl) => tl.tag),
tenant: { ...p.tenant, configAcces: getConfigEffective(p.tenant.configAcces) },
})),
total,
page,
totalPages: Math.ceil(total / limit),
});
} catch (error) {
console.error("mobile/catalogue error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}