campaigns.routes.js
Overview
Este archivo define el router HTTP de campañas para la API. Expone operaciones de listado, consulta, creación, actualización, cambio de estado y eliminación.
Todas las rutas requieren autenticación. El módulo usa requireAuth para obtener req.auth.userId y aplicar control de propiedad sobre cada campaña.
[!IMPORTANT] Las operaciones solo actúan sobre campañas creadas por el usuario autenticado.
How it works
El router combina validación de autenticación, validación de payload y acceso a MongoDB mediante Campaign y mongoose.
flowchart TD
Client[Client]
subgraph CampaignRoutes[campaigns routes]
Auth[requireAuth]
List[List campaigns]
Detail[Get campaign by id]
Create[Create campaign]
Update[Update campaign]
Status[Update status]
Delete[Delete campaign]
Validator[validateCampaignPayload]
ObjectIdCheck[ObjectId validation]
CampaignModel[Campaign model]
end
Client --> Auth
Auth --> List
Auth --> Detail
Auth --> Create
Auth --> Update
Auth --> Status
Auth --> Delete
List --> ObjectIdCheck
Detail --> ObjectIdCheck
Create --> Validator
Create --> ObjectIdCheck
Update --> Validator
Update --> ObjectIdCheck
Status --> ObjectIdCheck
Delete --> ObjectIdCheck
List --> CampaignModel
Detail --> CampaignModel
Create --> CampaignModel
Update --> CampaignModel
Status --> CampaignModel
Delete --> CampaignModel
Cada handler verifica la identidad del usuario primero. Luego filtra por createdBy. Las rutas de escritura validan datos antes de tocar la base de datos.
Key components
{
"title": "campaigns.routes.js",
"content": "Express router que registra las rutas protegidas de campa\u00f1as. Agrupa lectura, escritura, cambios de estado y eliminaci\u00f3n.",
"type": "function",
"filePath": "services/api/src/modules/campaigns/campaigns.routes.js",
"badges": [
"public"
]
}
{
"title": "requireAuth",
"content": "Middleware de autenticaci\u00f3n usado por todas las rutas del m\u00f3dulo. Debe exponer `req.auth.userId` para las comprobaciones de propiedad.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.middleware.js",
"badges": [
"public"
]
}
{
"title": "validateCampaignPayload",
"content": "Valida el cuerpo de creaci\u00f3n y actualizaci\u00f3n de campa\u00f1as. El handler de PATCH usa validaci\u00f3n parcial.",
"type": "function",
"filePath": "services/api/src/modules/campaigns/campaigns.validators.js",
"badges": [
"public"
]
}
{
"title": "Campaign",
"content": "Modelo de Mongoose usado para buscar, contar, crear, actualizar y eliminar campa\u00f1as.",
"type": "class",
"filePath": "services/api/src/models/Campaign.js",
"badges": [
"public"
]
}
Data & fields
Este módulo trabaja con parámetros de consulta, parámetros de ruta y cuerpos JSON. El campo createdBy nunca lo envía el cliente. El router lo inyecta desde la sesión autenticada.
Query parameters del listado
[
{
"name": "page",
"type": "number",
"description": "Página solicitada. Si es inválida, se usa 1."
},
{
"name": "limit",
"type": "number",
"description": "Cantidad de resultados por página. Si es inválida, se usa 10. El máximo permitido es 50."
},
{
"name": "status",
"type": "string",
"description": "Filtra campañas por estado exacto."
},
{
"name": "listingId",
"type": "string",
"description": "Filtra por el listado asociado. Solo se aplica si es un ObjectId válido."
}
]
Path parameters
[
{
"name": "id",
"type": "string",
"required": true,
"description": "Identificador de la campaña. Debe ser un ObjectId válido."
}
]
Body fields de estado
[
{
"name": "status",
"type": "string",
"required": true,
"description": "Estado permitido para el cambio manual. Solo admite draft, paused o cancelled."
}
]
Campos protegidos en actualización
createdBy[badge:read-only] se elimina del cuerpo antes de actualizar.paymentOrderId[badge:read-only] se elimina del cuerpo antes de actualizar.metrics[badge:read-only] se elimina del cuerpo antes de actualizar.
[!NOTE] La validación de creación y edición depende de
validateCampaignPayload. Este archivo no define el esquema completo del payload.
Forma de respuesta
success[badge:required]boolean— Indica éxito o fallo.message[badge:optional]string— Mensaje humano para operaciones y errores.count[badge:optional]number— Cantidad de campañas devueltas en la página actual.total[badge:optional]number— Total de campañas que cumplen el filtro.page[badge:optional]number— Página actual.pages[badge:optional]number— Total de páginas calculadas.data[badge:optional]array | object— Lista o documento de campaña.errors[badge:optional]array— Detalle de errores de validación.
Behaviors
GET /api/campaigns— Devuelve campañas del usuario autenticado, con paginación y filtros opcionales.GET /api/campaigns/:id— Devuelve una sola campaña si pertenece al usuario.POST /api/campaigns— Valida el payload, agregacreatedByy crea una campaña nueva.PATCH /api/campaigns/:id— Valida parcialmente el payload y actualiza una campaña propia.PATCH /api/campaigns/:id/status— Cambia el estado manual de la campaña dentro de un conjunto permitido.DELETE /api/campaigns/:id— Elimina una campaña propia solo en estados permitidos.
API
Cada endpoint tiene su propio bloque. Todos están protegidos por autenticación.
GET /api/campaigns
Lista las campañas del usuario autenticado. El resultado incluye paginación, total y filtros por status y listingId.
{
"title": "List Campaigns",
"description": "Returns the authenticated user's campaigns with pagination and optional filters.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/campaigns",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [
{
"key": "page",
"value": "1",
"required": false
},
{
"key": "limit",
"value": "10",
"required": false
},
{
"key": "status",
"value": "draft",
"required": false
},
{
"key": "listingId",
"value": "64f1c2a1e8f2d5b9c4a12345",
"required": false
}
],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Success",
"body": "{\n \"success\": true,\n \"count\": 1,\n \"total\": 3,\n \"page\": 1,\n \"pages\": 3,\n \"data\": [\n {\n \"_id\": \"66a1c2a1e8f2d5b9c4a67890\",\n \"createdBy\": \"64f0c2a1e8f2d5b9c4a11111\",\n \"status\": \"draft\",\n \"listingId\": {\n \"_id\": \"64f1c2a1e8f2d5b9c4a12345\",\n \"title\": \"Listado demo\",\n \"meta\": {\n \"entityType\": \"listing\"\n },\n \"publication\": {\n \"status\": \"published\"\n }\n }\n }\n ]\n}"
},
"500": {
"description": "Server error",
"body": "{\n \"success\": false,\n \"message\": \"No se pudieron obtener las campa\u00f1as.\"\n}"
}
}
}
GET /api/campaigns/:id
Devuelve una campaña concreta del usuario autenticado. Si el id no es válido, responde con 400.
{
"title": "Get Campaign",
"description": "Returns a single campaign owned by the authenticated user.",
"method": "GET",
"baseUrl": "/api",
"endpoint": "/campaigns/:id",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [
{
"key": "id",
"value": "66a1c2a1e8f2d5b9c4a67890",
"required": true
}
],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Success",
"body": "{\n \"success\": true,\n \"data\": {\n \"_id\": \"66a1c2a1e8f2d5b9c4a67890\",\n \"createdBy\": \"64f0c2a1e8f2d5b9c4a11111\",\n \"listingId\": {\n \"_id\": \"64f1c2a1e8f2d5b9c4a12345\",\n \"title\": \"Listado demo\",\n \"meta\": {\n \"entityType\": \"listing\"\n },\n \"publication\": {\n \"status\": \"published\"\n },\n \"location\": {\n \"city\": \"Madrid\"\n }\n }\n }\n}"
},
"400": {
"description": "Invalid ID",
"body": "{\n \"success\": false,\n \"message\": \"ID inv\u00e1lido.\"\n}"
},
"404": {
"description": "Not found or forbidden",
"body": "{\n \"success\": false,\n \"message\": \"Campa\u00f1a no encontrada o sin permisos.\"\n}"
},
"500": {
"description": "Server error",
"body": "{\n \"success\": false,\n \"message\": \"No se pudo obtener la campa\u00f1a.\"\n}"
}
}
}
POST /api/campaigns
Crea una nueva campaña. El handler agrega createdBy desde la autenticación y valida el payload completo.
{
"title": "Create Campaign",
"description": "Creates a new campaign for the authenticated user after validating the payload.",
"method": "POST",
"baseUrl": "/api",
"endpoint": "/campaigns",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "json",
"requestBody": "{\n \"listingId\": \"64f1c2a1e8f2d5b9c4a12345\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"201": {
"description": "Created",
"body": "{\n \"success\": true,\n \"message\": \"Campa\u00f1a creada correctamente.\",\n \"data\": {\n \"_id\": \"66a1c2a1e8f2d5b9c4a67890\",\n \"createdBy\": \"64f0c2a1e8f2d5b9c4a11111\",\n \"listingId\": \"64f1c2a1e8f2d5b9c4a12345\"\n }\n}"
},
"400": {
"description": "Invalid payload or listingId",
"body": "{\n \"success\": false,\n \"message\": \"Payload inv\u00e1lido.\",\n \"errors\": []\n}"
},
"500": {
"description": "Server error",
"body": "{\n \"success\": false,\n \"message\": \"No se pudo crear la campa\u00f1a.\"\n}"
}
}
}
PATCH /api/campaigns/:id
Actualiza una campaña propia. El handler usa validación parcial. También elimina createdBy, paymentOrderId y metrics del cuerpo recibido.
{
"title": "Update Campaign",
"description": "Updates an owned campaign using partial validation and protected field stripping.",
"method": "PATCH",
"baseUrl": "/api",
"endpoint": "/campaigns/:id",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [
{
"key": "id",
"value": "66a1c2a1e8f2d5b9c4a67890",
"required": true
}
],
"bodyType": "json",
"requestBody": "{\n \"status\": \"paused\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Updated",
"body": "{\n \"success\": true,\n \"message\": \"Campa\u00f1a actualizada correctamente.\",\n \"data\": {\n \"_id\": \"66a1c2a1e8f2d5b9c4a67890\",\n \"createdBy\": \"64f0c2a1e8f2d5b9c4a11111\",\n \"status\": \"paused\"\n }\n}"
},
"400": {
"description": "Invalid request",
"body": "{\n \"success\": false,\n \"message\": \"Payload inv\u00e1lido.\",\n \"errors\": []\n}"
},
"404": {
"description": "Not found or forbidden",
"body": "{\n \"success\": false,\n \"message\": \"Campa\u00f1a no encontrada o sin permisos.\"\n}"
},
"500": {
"description": "Server error",
"body": "{\n \"success\": false,\n \"message\": \"No se pudo actualizar la campa\u00f1a.\"\n}"
}
}
}
PATCH /api/campaigns/:id/status
Cambia el estado de la campaña. Solo acepta draft, paused y cancelled. El comentario del código indica que active y completed los controla el sistema de pagos y webhooks.
{
"title": "Update Campaign Status",
"description": "Changes the campaign status to a user-allowed value only.",
"method": "PATCH",
"baseUrl": "/api",
"endpoint": "/campaigns/:id/status",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [
{
"key": "id",
"value": "66a1c2a1e8f2d5b9c4a67890",
"required": true
}
],
"bodyType": "json",
"requestBody": "{\n \"status\": \"paused\"\n}",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Status updated",
"body": "{\n \"success\": true,\n \"message\": \"Campa\u00f1a paused.\",\n \"data\": {\n \"_id\": \"66a1c2a1e8f2d5b9c4a67890\",\n \"status\": \"paused\"\n }\n}"
},
"400": {
"description": "Invalid ID or invalid status",
"body": "{\n \"success\": false,\n \"message\": \"Transici\u00f3n inv\u00e1lida. El usuario puede establecer: draft, paused, cancelled.\"\n}"
},
"404": {
"description": "Not found or forbidden",
"body": "{\n \"success\": false,\n \"message\": \"Campa\u00f1a no encontrada o sin permisos.\"\n}"
},
"500": {
"description": "Server error",
"body": "{\n \"success\": false,\n \"message\": \"No se pudo actualizar el estado.\"\n}"
}
}
}
DELETE /api/campaigns/:id
Elimina una campaña propia. Solo permite borrar campañas en estado draft o cancelled.
{
"title": "Delete Campaign",
"description": "Deletes an owned campaign only when the current status allows removal.",
"method": "DELETE",
"baseUrl": "/api",
"endpoint": "/campaigns/:id",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [
{
"key": "id",
"value": "66a1c2a1e8f2d5b9c4a67890",
"required": true
}
],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Deleted",
"body": "{\n \"success\": true,\n \"message\": \"Campa\u00f1a eliminada correctamente.\"\n}"
},
"400": {
"description": "Invalid ID",
"body": "{\n \"success\": false,\n \"message\": \"ID inv\u00e1lido.\"\n}"
},
"404": {
"description": "Not found or forbidden",
"body": "{\n \"success\": false,\n \"message\": \"Campa\u00f1a no encontrada o sin permisos.\"\n}"
},
"409": {
"description": "Status not allowed",
"body": "{\n \"success\": false,\n \"message\": \"Solo se pueden eliminar campa\u00f1as en estado draft o cancelled.\"\n}"
},
"500": {
"description": "Server error",
"body": "{\n \"success\": false,\n \"message\": \"No se pudo eliminar la campa\u00f1a.\"\n}"
}
}
}
Workflows
La secuencia general es la misma para todas las rutas. Cambian la validación y la operación de Mongoose.
sequenceDiagram
participant Client
participant Routes as campaigns routes
participant Auth as requireAuth
participant Validator as validateCampaignPayload
participant Model as Campaign
Client->>Routes: HTTP request
Routes->>Auth: authenticate request
Auth-->>Routes: req auth user id
alt GET list or detail or delete
Routes->>Model: query owned campaigns
Model-->>Routes: documents
else POST create
Routes->>Validator: validate payload
Validator-->>Routes: valid or errors
Routes->>Model: create campaign
Model-->>Routes: campaign
else PATCH update
Routes->>Validator: validate partial payload
Validator-->>Routes: valid or errors
Routes->>Model: update owned campaign
Model-->>Routes: campaign
else PATCH status
Routes->>Model: update status
Model-->>Routes: campaign
end
Routes-->>Client: JSON response
Gotchas & edge cases
[!WARNING]
GET /api/campaignsno falla silistingIdes inválido en la consulta. Solo ignora ese filtro.
[!WARNING]
limittiene tope de 50. Valores mayores se reemplazan por 10.
[!NOTE]
pagesse calcula conMath.ceil(total / limit). Si no hay resultados, puede devolver0.
[!CAUTION]
PATCH /api/campaigns/:id/statussolo aceptadraft,pausedycancelled. Cualquier otro valor devuelve 400.
[!CAUTION]
DELETE /api/campaigns/:idsolo funciona endraftocancelled. Otros estados devuelven 409.
[!IMPORTANT]
PATCH /api/campaigns/:idborracreatedBy,paymentOrderIdymetricsdel cuerpo. Esos campos no se actualizan desde el cliente.
Related
Este archivo se conecta con piezas concretas del backend de campañas y autenticación.
services/api/src/modules/campaigns/campaigns.validators.js— valida los datos de entrada.services/api/src/modules/auth/auth.middleware.js— protege las rutas y aportareq.auth.userId.services/api/src/models/Campaign.js— persiste y consulta campañas en MongoDB.mongoose— validaObjectId, ejecuta consultas y aplicapopulate.express— crea elroutery registra los handlers HTTP.