src/app/api/panier/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
14 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
4 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
2 exports
GETPOST
Code source· typescript
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { getSession } from "@/lib/auth/session";
import { getTenant } from "@/lib/auth/get-tenant";
import { cookies } from "next/headers";
function getSessionId(cookieStore: Awaited<ReturnType<typeof cookies>>) {
let sessionId = cookieStore.get("panier_session")?.value;
return sessionId ?? null;
}
export async function GET(req: NextRequest) {
try {
const tenant = await getTenant();
if (!tenant) return NextResponse.json({ error: "Tenant introuvable" }, { status: 404 });
const session = await getSession();
const cookieStore = await cookies();
const sessionId = getSessionId(cookieStore);
const where = session?.role === "CLIENT"
? { tenantId: tenant.id, clientId: session.userId, statut: "ACTIF" as const }
: sessionId
? { tenantId: tenant.id, sessionId, statut: "ACTIF" as const }
: null;
if (!where) return NextResponse.json({ panier: null, lignes: [] });
const panier = await prisma.panier.findFirst({
where,
include: {
lignes: true,
},
});
if (!panier) return NextResponse.json({ panier: null, lignes: [] });
// Enrichir avec les infos produit
const produitIds = panier.lignes.map((l) => l.produitId);
const produits = await prisma.produit.findMany({
where: { id: { in: produitIds } },
include: { medias: { orderBy: { ordre: "asc" }, take: 1 } },
});
// Récupérer les variantes référencées
const varianteIds = panier.lignes
.map((l) => l.varianteId)
.filter((v): v is string => v !== null);
const variantes = varianteIds.length
? await prisma.varianteProduit.findMany({ where: { id: { in: varianteIds } } })
: [];
const lignesEnrichies = panier.lignes.map((ligne) => ({
...ligne,
produit: produits.find((p) => p.id === ligne.produitId) ?? null,
variante: variantes.find((v) => v.id === ligne.varianteId) ?? null,
}));
return NextResponse.json({ panier, lignes: lignesEnrichies });
} catch (error) {
console.error("panier GET error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}
export async function POST(req: NextRequest) {
try {
const tenant = await getTenant();
if (!tenant) return NextResponse.json({ error: "Tenant introuvable" }, { status: 404 });
const { produitId, varianteId = null, quantite = 1 } = await req.json();
if (!produitId) return NextResponse.json({ error: "produitId requis" }, { status: 400 });
// Vérifier que le produit existe et appartient au tenant
const produit = await prisma.produit.findFirst({
where: { id: produitId, tenantId: tenant.id, disponible: true },
});
if (!produit) return NextResponse.json({ error: "Produit introuvable" }, { status: 404 });
// Vérifier variante si fournie
let prixFinal = produit.prix;
if (varianteId) {
const variante = await prisma.varianteProduit.findFirst({
where: { id: varianteId, produitId },
});
if (!variante) return NextResponse.json({ error: "Variante introuvable" }, { status: 404 });
if (variante.prix !== null && variante.prix !== undefined) prixFinal = variante.prix;
}
const session = await getSession();
const cookieStore = await cookies();
let sessionId = getSessionId(cookieStore);
// Créer un sessionId anonyme si besoin
const response = NextResponse.json({ success: true });
if (!session && !sessionId) {
sessionId = crypto.randomUUID();
response.cookies.set("panier_session", sessionId, {
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30, // 30 jours
path: "/",
});
}
const panierWhere = session?.role === "CLIENT"
? { tenantId: tenant.id, clientId: session.userId, statut: "ACTIF" as const }
: { tenantId: tenant.id, sessionId: sessionId!, statut: "ACTIF" as const };
// Trouver ou créer le panier
// Chercher un panier existant (actif ou non) pour éviter les conflits de contrainte unique
let panier = await prisma.panier.findFirst({
where: session?.role === "CLIENT"
? { tenantId: tenant.id, clientId: session.userId }
: { tenantId: tenant.id, sessionId: sessionId! },
});
if (!panier) {
panier = await prisma.panier.create({
data: {
tenantId: tenant.id,
clientId: session?.role === "CLIENT" ? session.userId : null,
sessionId: session?.role === "CLIENT" ? null : sessionId,
statut: "ACTIF",
},
});
} else if (panier.statut !== "ACTIF") {
// Réactiver le panier si converti/abandonné
panier = await prisma.panier.update({
where: { id: panier.id },
data: { statut: "ACTIF" },
});
}
// Ajouter ou mettre à jour la ligne
// Prisma findUnique ne supporte pas null dans les composite keys → findFirst si varianteId null
const ligneExistante = varianteId
? await prisma.panierLigne.findUnique({
where: {
panierId_produitId_varianteId: {
panierId: panier.id,
produitId,
varianteId,
},
},
})
: await prisma.panierLigne.findFirst({
where: {
panierId: panier.id,
produitId,
varianteId: null,
},
});
if (ligneExistante) {
await prisma.panierLigne.update({
where: { id: ligneExistante.id },
data: { quantite: ligneExistante.quantite + quantite },
});
} else {
await prisma.panierLigne.create({
data: {
panierId: panier.id,
produitId,
varianteId: varianteId ?? null,
quantite,
prixSnapshot: prixFinal,
devise: produit.devise,
},
});
}
// Compter les lignes pour le badge panier
const nbLignes = await prisma.panierLigne.count({ where: { panierId: panier.id } });
const finalResponse = NextResponse.json({ success: true, nbLignes });
if (!session && sessionId && !getSessionId(cookieStore)) {
finalResponse.cookies.set("panier_session", sessionId, {
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30,
path: "/",
});
}
return finalResponse;
} catch (error) {
console.error("panier POST error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { getSession } from "@/lib/auth/session";
import { getTenant } from "@/lib/auth/get-tenant";
import { cookies } from "next/headers";
function getSessionId(cookieStore: Awaited<ReturnType<typeof cookies>>) {
let sessionId = cookieStore.get("panier_session")?.value;
return sessionId ?? null;
}
export async function GET(req: NextRequest) {
try {
const tenant = await getTenant();
if (!tenant) return NextResponse.json({ error: "Tenant introuvable" }, { status: 404 });
const session = await getSession();
const cookieStore = await cookies();
const sessionId = getSessionId(cookieStore);
const where = session?.role === "CLIENT"
? { tenantId: tenant.id, clientId: session.userId, statut: "ACTIF" as const }
: sessionId
? { tenantId: tenant.id, sessionId, statut: "ACTIF" as const }
: null;
if (!where) return NextResponse.json({ panier: null, lignes: [] });
const panier = await prisma.panier.findFirst({
where,
include: {
lignes: true,
},
});
if (!panier) return NextResponse.json({ panier: null, lignes: [] });
// Enrichir avec les infos produit
const produitIds = panier.lignes.map((l) => l.produitId);
const produits = await prisma.produit.findMany({
where: { id: { in: produitIds } },
include: { medias: { orderBy: { ordre: "asc" }, take: 1 } },
});
// Récupérer les variantes référencées
const varianteIds = panier.lignes
.map((l) => l.varianteId)
.filter((v): v is string => v !== null);
const variantes = varianteIds.length
? await prisma.varianteProduit.findMany({ where: { id: { in: varianteIds } } })
: [];
const lignesEnrichies = panier.lignes.map((ligne) => ({
...ligne,
produit: produits.find((p) => p.id === ligne.produitId) ?? null,
variante: variantes.find((v) => v.id === ligne.varianteId) ?? null,
}));
return NextResponse.json({ panier, lignes: lignesEnrichies });
} catch (error) {
console.error("panier GET error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}
export async function POST(req: NextRequest) {
try {
const tenant = await getTenant();
if (!tenant) return NextResponse.json({ error: "Tenant introuvable" }, { status: 404 });
const { produitId, varianteId = null, quantite = 1 } = await req.json();
if (!produitId) return NextResponse.json({ error: "produitId requis" }, { status: 400 });
// Vérifier que le produit existe et appartient au tenant
const produit = await prisma.produit.findFirst({
where: { id: produitId, tenantId: tenant.id, disponible: true },
});
if (!produit) return NextResponse.json({ error: "Produit introuvable" }, { status: 404 });
// Vérifier variante si fournie
let prixFinal = produit.prix;
if (varianteId) {
const variante = await prisma.varianteProduit.findFirst({
where: { id: varianteId, produitId },
});
if (!variante) return NextResponse.json({ error: "Variante introuvable" }, { status: 404 });
if (variante.prix !== null && variante.prix !== undefined) prixFinal = variante.prix;
}
const session = await getSession();
const cookieStore = await cookies();
let sessionId = getSessionId(cookieStore);
// Créer un sessionId anonyme si besoin
const response = NextResponse.json({ success: true });
if (!session && !sessionId) {
sessionId = crypto.randomUUID();
response.cookies.set("panier_session", sessionId, {
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30, // 30 jours
path: "/",
});
}
const panierWhere = session?.role === "CLIENT"
? { tenantId: tenant.id, clientId: session.userId, statut: "ACTIF" as const }
: { tenantId: tenant.id, sessionId: sessionId!, statut: "ACTIF" as const };
// Trouver ou créer le panier
// Chercher un panier existant (actif ou non) pour éviter les conflits de contrainte unique
let panier = await prisma.panier.findFirst({
where: session?.role === "CLIENT"
? { tenantId: tenant.id, clientId: session.userId }
: { tenantId: tenant.id, sessionId: sessionId! },
});
if (!panier) {
panier = await prisma.panier.create({
data: {
tenantId: tenant.id,
clientId: session?.role === "CLIENT" ? session.userId : null,
sessionId: session?.role === "CLIENT" ? null : sessionId,
statut: "ACTIF",
},
});
} else if (panier.statut !== "ACTIF") {
// Réactiver le panier si converti/abandonné
panier = await prisma.panier.update({
where: { id: panier.id },
data: { statut: "ACTIF" },
});
}
// Ajouter ou mettre à jour la ligne
// Prisma findUnique ne supporte pas null dans les composite keys → findFirst si varianteId null
const ligneExistante = varianteId
? await prisma.panierLigne.findUnique({
where: {
panierId_produitId_varianteId: {
panierId: panier.id,
produitId,
varianteId,
},
},
})
: await prisma.panierLigne.findFirst({
where: {
panierId: panier.id,
produitId,
varianteId: null,
},
});
if (ligneExistante) {
await prisma.panierLigne.update({
where: { id: ligneExistante.id },
data: { quantite: ligneExistante.quantite + quantite },
});
} else {
await prisma.panierLigne.create({
data: {
panierId: panier.id,
produitId,
varianteId: varianteId ?? null,
quantite,
prixSnapshot: prixFinal,
devise: produit.devise,
},
});
}
// Compter les lignes pour le badge panier
const nbLignes = await prisma.panierLigne.count({ where: { panierId: panier.id } });
const finalResponse = NextResponse.json({ success: true, nbLignes });
if (!session && sessionId && !getSessionId(cookieStore)) {
finalResponse.cookies.set("panier_session", sessionId, {
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30,
path: "/",
});
}
return finalResponse;
} catch (error) {
console.error("panier POST error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}