src/app/api/mobile/conversations/route.ts

route·app·5.3 KB · 127 lignes· Voir l'itinéraire
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.

2 exports

GETPOST

Code source· typescript

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { getSessionFromRequest } from "@/lib/auth/session";

// GET /api/mobile/conversations
// CLIENT  : liste des conversations du client (tous tenants)
// GERANT  : liste des conversations du tenant
export async function GET(req: NextRequest) {
  const session = await getSessionFromRequest(req);
  if (!session) return NextResponse.json({ error: "Non autorisé" }, { status: 401 });

  const where =
    session.role === "CLIENT"
      ? { clientAccountId: session.userId }
      : session.role === "TENANT_ADMIN" && session.tenantId
      ? { tenantId: session.tenantId }
      : null;
  if (!where) return NextResponse.json({ error: "Rôle non supporté" }, { status: 403 });

  const conversations = await prisma.conversation.findMany({
    where,
    orderBy: { derniereMessageAt: "desc" },
    take: 100,
    include: {
      tenant: { select: { id: true, nom: true, subdomain: true, logoUrl: true } },
      clientAccount: { select: { id: true, prenom: true, nom: true, name: true, photoUrl: true } },
      produit: { select: { id: true, nom: true, prix: true, devise: true, medias: { take: 1, select: { url: true } } } },
      prestation: { select: { id: true, nom: true, prixMin: true, prixMax: true, devise: true, medias: { take: 1, select: { url: true } } } },
      messages: { orderBy: { createdAt: "desc" }, take: 1, select: { texte: true, auteur: true, createdAt: true } },
    },
  });

  return NextResponse.json({
    conversations: conversations.map((c) => ({
      id: c.id,
      tenant: c.tenant,
      client: c.clientAccount,
      contexte: c.contexte,
      produit: c.produit,
      prestation: c.prestation,
      derniereMessageAt: c.derniereMessageAt.toISOString(),
      nonLu: session.role === "CLIENT" ? c.nonLuClient : c.nonLuTenant,
      dernierMessage: c.messages[0]
        ? { texte: c.messages[0].texte, auteur: c.messages[0].auteur, createdAt: c.messages[0].createdAt.toISOString() }
        : null,
    })),
  });
}

// POST /api/mobile/conversations
// CLIENT uniquement (le gérant répond à une conv existante, il n'en ouvre pas).
// Body: { tenantId, contexte?, produitId?, prestationId? }. Upsert : retourne existing si déjà créée.
export async function POST(req: NextRequest) {
  const session = await getSessionFromRequest(req);
  if (!session || session.role !== "CLIENT") {
    return NextResponse.json({ error: "Authentification client requise" }, { status: 401 });
  }

  let body: { tenantId?: string; contexte?: string; produitId?: string; prestationId?: string };
  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ error: "JSON invalide" }, { status: 400 });
  }

  const tenantId = typeof body.tenantId === "string" ? body.tenantId : null;
  if (!tenantId) return NextResponse.json({ error: "tenantId requis" }, { status: 400 });

  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { id: true, actif: true, messagerieActive: true },
  });
  if (!tenant || !tenant.actif) return NextResponse.json({ error: "Tenant introuvable" }, { status: 404 });
  if (!tenant.messagerieActive) return NextResponse.json({ error: "Messagerie désactivée par ce vendeur" }, { status: 403 });

  const contexte = typeof body.contexte === "string" && body.contexte.trim() ? body.contexte.trim().slice(0, 200) : null;
  const produitId = typeof body.produitId === "string" && body.produitId.trim() ? body.produitId.trim() : null;
  const prestationId = typeof body.prestationId === "string" && body.prestationId.trim() ? body.prestationId.trim() : null;

  if (produitId) {
    const p = await prisma.produit.findUnique({ where: { id: produitId }, select: { tenantId: true } });
    if (!p || p.tenantId !== tenantId) {
      return NextResponse.json({ error: "Produit introuvable ou n'appartient pas à ce vendeur" }, { status: 400 });
    }
  }
  if (prestationId) {
    const p = await prisma.prestation.findUnique({ where: { id: prestationId }, select: { tenantId: true } });
    if (!p || p.tenantId !== tenantId) {
      return NextResponse.json({ error: "Prestation introuvable ou n'appartient pas à ce vendeur" }, { status: 400 });
    }
  }

  const updateData: { contexte?: string; produitId?: string; prestationId?: string } = {};
  if (contexte) updateData.contexte = contexte;
  if (produitId) updateData.produitId = produitId;
  if (prestationId) updateData.prestationId = prestationId;

  const conv = await prisma.conversation.upsert({
    where: { clientAccountId_tenantId: { clientAccountId: session.userId, tenantId } },
    create: {
      clientAccountId: session.userId,
      tenantId,
      contexte,
      produitId,
      prestationId,
    },
    update: updateData,
    include: {
      tenant: { select: { id: true, nom: true, subdomain: true, logoUrl: true } },
      produit: { select: { id: true, nom: true, prix: true, devise: true, medias: { take: 1, select: { url: true } } } },
      prestation: { select: { id: true, nom: true, prixMin: true, prixMax: true, devise: true, medias: { take: 1, select: { url: true } } } },
    },
  });

  return NextResponse.json({
    conversation: {
      id: conv.id,
      tenant: conv.tenant,
      contexte: conv.contexte,
      produit: conv.produit,
      prestation: conv.prestation,
      derniereMessageAt: conv.derniereMessageAt.toISOString(),
      nonLu: 0,
    },
  });
}