Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { orderRouter, userOrderRouter } from "./modules/users/orders/order.route
import { errorHandler } from "./middlewares/errorHandler.js";
import { NotFoundError } from "./lib/errors.js";

import { setupSwagger } from "./docs/swagger.config.js";
import { setupSwagger } from "./config/swagger.config.js";

const app = express();

Expand Down
80 changes: 80 additions & 0 deletions src/config/swagger.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import swaggerJsdoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import {
commonSchemas,
// /commerce
addressSchemas,
storeSchemas,
productTagSchemas,
productReviewSchemas,
productSchemas,
// /session
sessionSchemas,
} from "../docs/schemas/index.js";

const options = {
definition: {
openapi: "3.0.0",
info: {
title: "OpenMarket API",
version: "1.0.0",
description: "Documentación de la API del OpenMarket"
},
servers: [
{
url: "http://localhost:3000",
description: "Servidor local"
},
{
url: "https://backendmarketplace-test.onrender.com",
description: "Servidor de testeo en Render"
},
{
url: "https://backendmarketplace-4cgb.onrender.com",
description: "Servidor de producción en Render"
}
],
tags: [
{ name: "Session", description: "Autenticación y sesión" },
{ name: "Commerces", description: "Gestión de comercios" },
{ name: "Products", description: "Gestión de productos" },
{ name: "Reviews", description: "Reseñas de productos" },
{ name: "Users", description: "Gestión de usuarios" },
{ name: "Orders", description: "Gestión de pedidos" },
],
components: {
securitySchemes: {
cookieAuth: {
type: "apiKey",
in: "cookie",
name: "userToken"
}
},
schemas: {
...commonSchemas,
// /commerce/
...addressSchemas,
...storeSchemas,
...productReviewSchemas,
...productTagSchemas,
...productSchemas,

// /session
...sessionSchemas,
}
}
},
// Rutas donde swagger-jsdoc va a buscar los comentarios @swagger
apis: ["./src/modules/**/*.routes.js", "./src/modules/**/routes/*.routes.js"]
};

export const swaggerSpec = swaggerJsdoc(options);

export const setupSwagger = (app) => {
app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
customSiteTitle: "Marketplace API Docs",
swaggerOptions: {
persistAuthorization: true
}
}));
};
44 changes: 44 additions & 0 deletions src/docs/schemas/commerce/address.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export const addressSchemas = {
AddressResponse: {
type: "object",
properties: {
id_address: { type: "integer", example: 1 },
fk_user: { type: "integer", example: 1 },
fk_store: { type: "integer", example: 3 },
address: { type: "string", example: "Av. España 1234" },
city: { type: "string", example: "Asunción" },
region: { type: "string", example: "Villa Morra" },
postal_code: { type: "string", nullable: true, example: "1209" },
status: { type: "boolean", example: true },
created_at: { type: "string", format: "date-time" },
updated_at: { type: "string", format: "date-time" }
}
},
CreateAddressRequest: {
type: "object",
required: ["address", "city", "region"],
properties: {
address: { type: "string", example: "Av. España 1234" },
city: { type: "string", maxLength: 100, example: "Asunción" },
region: { type: "string", maxLength: 100, example: "Villa Morra" },
postal_code: { type: "string", maxLength: 20, nullable: true, example: "1209" }
}
},
UpdateAddressRequest: {
type: "object",
properties: {
address: { type: "string", example: "Av. España 1234" },
city: { type: "string", maxLength: 100, example: "Asunción" },
region: { type: "string", maxLength: 100, example: "Villa Morra" },
postal_code: { type: "string", maxLength: 20, nullable: true, example: "1209" }
}
},
DeleteAddressResponse: {
type: "object",
properties: {
id_address: { type: "integer", example: 1 },
status: { type: "boolean", example: false },
updated_at: { type: "string", format: "date-time" }
}
},
}
51 changes: 51 additions & 0 deletions src/docs/schemas/commerce/product-review.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export const productReviewSchemas = {
// ─── REQUESTS ──────────────────────────────────────────────────
CreateReviewRequest: {
type: "object",
required: ["rating"],
properties: {
rating: { type: "integer", minimum: 1, maximum: 5, example: 4 },
comment: { type: "string", nullable: true, example: "Muy buen producto" }
}
},

// ─── RESPONSES ─────────────────────────────────────────────────
ReviewItemResponse: {
type: "object",
properties: {
id: { type: "integer", example: 1 },
customerName: { type: "string", example: "Juan Pérez" },
rating: { type: "integer", minimum: 1, maximum: 5, example: 4 },
comment: { type: "string", nullable: true, example: "Muy buen producto" },
date: { type: "string", format: "date-time" },
isVerified: { type: "boolean", example: true }
}
},

ReviewsListResponse: {
type: "object",
properties: {
stats: {
type: "object",
properties: {
averageRating: { type: "number", nullable: true, example: 4.2 },
totalReviews: { type: "integer", example: 25 },
verifiedReviews: { type: "integer", example: 20 }
}
},
pagination: {
type: "object",
properties: {
page: { type: "integer", example: 1 },
limit: { type: "integer", example: 10 },
totalReviews: { type: "integer", example: 25 },
totalPages: { type: "integer", example: 3 }
}
},
reviews: {
type: "array",
items: { $ref: "#/components/schemas/ReviewItemResponse" }
}
}
}
};
11 changes: 11 additions & 0 deletions src/docs/schemas/commerce/product-tag.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const productTagSchemas = {
ProductTagResponse: {
type: "object",
properties: {
id: { type: "integer", example: 1 },
name: { type: "string", example: "oferta" },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" }
}
}
};
170 changes: 170 additions & 0 deletions src/docs/schemas/commerce/product.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
export const productSchemas = {
// ─── REQUESTS ──────────────────────────────────────────────────
CreateProductRequest: {
type: "object",
required: ["name", "price", "categoryId"],
properties: {
name: { type: "string", maxLength: 100, example: "Auriculares Bluetooth" },
price: { type: "number", example: 150000 },
categoryId: { type: "integer", example: 3 },
description: { type: "string", nullable: true, example: "Auriculares de alta calidad" },
quantity: { type: "integer", nullable: true, example: 50 },
tags: {
type: "array",
items: { type: "integer" },
example: [1, 2]
},
visible: { type: "boolean", example: true }
}
},

UpdateProductRequest: {
type: "object",
properties: {
name: { type: "string", maxLength: 100, example: "Auriculares Bluetooth Pro" },
price: { type: "number", example: 200000 },
categoryId: { type: "integer", example: 3 },
description: { type: "string", nullable: true },
quantity: { type: "integer", nullable: true },
tags: { type: "array", items: { type: "integer" }, example: [1] },
visible: { type: "boolean", example: true }
}
},

// ─── RESPONSES ─────────────────────────────────────────────────
ProductResponse: {
type: "object",
properties: {
id: { type: "integer", example: 1 },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
name: { type: "string", example: "Auriculares Bluetooth" },
description: { type: "string", nullable: true },
price: { type: "number", example: 150000 },
quantity: { type: "integer", nullable: true, example: 50 },
categoryId: { type: "integer", example: 3 },
category: {
type: "object",
nullable: true,
properties: {
id: { type: "integer", example: 3 },
name: { type: "string", example: "Audio" },
status: { type: "boolean", example: true }
}
},
tags: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "integer", example: 1 },
name: { type: "string", example: "oferta" }
}
}
},
commerce: {
type: "object",
nullable: true,
properties: {
id: { type: "integer", example: 1 },
name: { type: "string", example: "Mi Comercio" }
}
},
averageRating: { type: "number", nullable: true, example: 4.5 },
reviewCount: { type: "integer", example: 10 },
visible: { type: "boolean", example: true },
status: { type: "string", enum: ["active", "pending"], example: "active" },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" }
}
},

ProductSearchItemResponse: {
type: "object",
properties: {
id_product: { type: "integer", example: 1 },
name: { type: "string", example: "Auriculares Bluetooth" },
description: { type: "string", nullable: true },
price: { type: "number", example: 150000 },
store: {
type: "object",
nullable: true,
properties: {
id_store: { type: "integer", example: 1 },
name: { type: "string", example: "Mi Comercio" }
}
}
}
},

ProductSearchResponse: {
type: "object",
properties: {
products: {
type: "array",
items: { $ref: "#/components/schemas/ProductSearchItemResponse" }
},
pagination: {
type: "object",
properties: {
totalProducts: { type: "integer", example: 100 },
page: { type: "integer", example: 1 },
limit: { type: "integer", example: 20 },
totalPages: { type: "integer", example: 5 }
}
}
}
},

CompareProductsResponse: {
type: "object",
properties: {
product: {
type: "object",
nullable: true,
properties: {
productId: { type: "integer", example: 1 },
name: { type: "string", example: "Auriculares Bluetooth" },
description: { type: "string", nullable: true },
price: { type: "number", example: 150000 },
store: {
type: "object",
nullable: true,
properties: {
id: { type: "integer", example: 1 },
name: { type: "string", example: "Mi Comercio" }
}
}
}
},
offers: {
type: "array",
items: {
type: "object",
properties: {
productId: { type: "integer", example: 2 },
name: { type: "string", example: "Auriculares Bluetooth" },
description: { type: "string", nullable: true },
price: { type: "number", example: 120000 },
store: {
type: "object",
nullable: true,
properties: {
id: { type: "integer", example: 2 },
name: { type: "string", example: "Otro Comercio" }
}
}
}
}
},
pagination: {
type: "object",
nullable: true,
properties: {
totalProducts: { type: "integer", example: 5 },
page: { type: "integer", example: 1 },
limit: { type: "integer", example: 50 },
totalPages: { type: "integer", example: 1 }
}
}
}
}
};
Loading
Loading