import type { AuthenticatedMedusaRequest, MedusaRequest, MedusaResponse } from "@medusajs/framework"; import { ContainerRegistrationKeys, MedusaError } from "@medusajs/framework/utils"; import { PRODUCT_REVIEW_MODULE } from "../../../modules/product-review"; import type ProductReviewModuleService from "../../../modules/product-review/service"; // GET /store/reviews?product_id=... -- reseñas APROBADAS de un producto, // mas promedio y cantidad. Publica (solo publishable key). export async function GET(req: MedusaRequest, res: MedusaResponse) { const productId = typeof req.query.product_id === "string" ? req.query.product_id : ""; if (!productId) { throw new MedusaError(MedusaError.Types.INVALID_DATA, "product_id es obligatorio"); } const reviews = req.scope.resolve(PRODUCT_REVIEW_MODULE); const list = await reviews.listProductReviews( { product_id: productId, status: "approved" }, { order: { created_at: "DESC" }, take: 50 }, ); const count = list.length; const average = count ? list.reduce((sum, review) => sum + review.rating, 0) / count : 0; res.json({ reviews: list.map((review) => ({ id: review.id, author_name: review.author_name, rating: review.rating, title: review.title, content: review.content, created_at: review.created_at, verified_purchase: true, })), average: Math.round(average * 10) / 10, count, }); } type CreateReviewBody = { product_id?: string; order_id?: string; rating?: number; title?: string; content?: string; }; // POST /store/reviews -- solo clientes autenticados (middleware) y solo // si el pedido indicado es SUYO, no esta cancelado y contiene el // producto: eso es lo que respalda el sello "Compra verificada". Nace // "pending" hasta que el dueño la apruebe en el admin. export async function POST(req: AuthenticatedMedusaRequest, res: MedusaResponse) { const customerId = req.auth_context.actor_id; const { product_id, order_id, title } = req.body ?? {}; const rating = Number(req.body?.rating); const content = (req.body?.content ?? "").trim(); if (!product_id || !order_id) { throw new MedusaError(MedusaError.Types.INVALID_DATA, "product_id y order_id son obligatorios"); } if (!Number.isInteger(rating) || rating < 1 || rating > 5) { throw new MedusaError(MedusaError.Types.INVALID_DATA, "La calificación debe ser un número entero de 1 a 5"); } if (content.length < 10 || content.length > 2000) { throw new MedusaError(MedusaError.Types.INVALID_DATA, "El comentario debe tener entre 10 y 2000 caracteres"); } const query = req.scope.resolve(ContainerRegistrationKeys.QUERY); const { data: orders } = await query.graph({ entity: "order", fields: ["id", "customer_id", "status", "items.product_id"], filters: { id: order_id }, }); const order = orders[0]; const purchased = order && order.customer_id === customerId && order.status !== "canceled" && (order.items ?? []).some((item) => item?.product_id === product_id); if (!purchased) { throw new MedusaError(MedusaError.Types.NOT_ALLOWED, "Solo puedes reseñar productos que compraste"); } const reviews = req.scope.resolve(PRODUCT_REVIEW_MODULE); const existing = await reviews.listProductReviews({ product_id, customer_id: customerId }); if (existing.length > 0) { throw new MedusaError(MedusaError.Types.DUPLICATE_ERROR, "Ya dejaste una reseña para este producto"); } const { data: customers } = await query.graph({ entity: "customer", fields: ["first_name", "last_name"], filters: { id: customerId }, }); const customer = customers[0]; const lastInitial = customer?.last_name ? ` ${customer.last_name.trim().charAt(0)}.` : ""; const authorName = `${customer?.first_name?.trim() || "Cliente"}${lastInitial}`; const review = await reviews.createProductReviews({ product_id, order_id, customer_id: customerId, author_name: authorName, rating, title: title?.trim() || null, content, status: "pending", }); res.status(201).json({ review: { id: review.id, status: review.status } }); }