Rutas de uploads en services/api/src/modules/uploads/uploads.routes.js
Descripción general
Este archivo define el router HTTP de uploads. Expone dos rutas autenticadas para ImageKit y para subida de archivos. También conecta la validación de propiedad de anuncios con la carga de medios.
[!NOTE] Las rutas son relativas al router. El punto de montaje final no aparece en este archivo.
[!IMPORTANT] Ambas rutas usan
requireAuth. Eso significa quereq.auth.userIdya existe cuando entra al handler.
Cómo funciona
El archivo crea un express.Router() y un middleware de multer en memoria. Luego registra dos endpoints: uno genera parámetros de autenticación de ImageKit, y el otro recibe un archivo multipart y lo delega a uploadAssetToImageKit. Cuando listingId viene en la petición, la ruta valida que el anuncio pertenezca al usuario autenticado antes de subir el archivo.
flowchart TB
Router[uploads routes router]
subgraph AuthEndpoint[GET imagekit auth]
AuthMW[requireAuth]
ImageKitClient[getImageKitClient helper]
PublicConfig[getImageKitPublicConfig helper]
AuthResponse[JSON response]
AuthMW --> ImageKitClient
ImageKitClient --> AuthResponse
AuthMW --> PublicConfig
PublicConfig --> AuthResponse
end
subgraph UploadEndpoint[POST upload]
Multer[multer memory storage]
ListingCheck[Listing ownership check]
ListingModel[Listing model]
MediaService[uploadAssetToImageKit]
UploadResponse[JSON response]
Multer --> ListingCheck
ListingCheck --> ListingModel
ListingCheck --> MediaService
MediaService --> UploadResponse
end
Router --> AuthMW
Router --> Multer
Componentes clave
Este módulo mezcla enrutado HTTP, autenticación y control de carga. La lógica de negocio principal se delega a helpers y servicios importados.
router— instancia deexpress.Router()que agrupa las rutas de uploads.upload— middleware demulterconmemoryStorage()y límite de 25 MB.Listing— modelo de Mongoose usado para validar ownership del anuncio.mongoose.Types.ObjectId.isValid()— valida el formato delistingIdantes de consultar la base de datos.
{
"title": "requireAuth",
"content": "Middleware de autenticaci\u00f3n usado en ambas rutas. Aporta acceso a `req.auth.userId` antes de ejecutar la l\u00f3gica del endpoint.",
"type": "function",
"filePath": "services/api/src/modules/auth/auth.middleware.js",
"badges": [
"public"
]
}
{
"title": "getImageKitClient",
"content": "Helper que devuelve el cliente de ImageKit. La ruta lo usa para generar par\u00e1metros de autenticaci\u00f3n sin exponer la clave privada.",
"type": "function",
"filePath": "services/api/src/config/imagekit.js",
"badges": [
"public"
]
}
{
"title": "getImageKitPublicConfig",
"content": "Helper que aporta la configuraci\u00f3n p\u00fablica de ImageKit. Sus valores se mezclan con los par\u00e1metros de autenticaci\u00f3n en la respuesta.",
"type": "function",
"filePath": "services/api/src/config/imagekit.js",
"badges": [
"public"
]
}
{
"title": "uploadAssetToImageKit",
"content": "Servicio que realiza la subida efectiva del archivo. Recibe el archivo, la carpeta solicitada, `listingId`, `userId` y etiquetas normalizadas.",
"type": "function",
"filePath": "services/api/src/modules/media/media.service.js",
"badges": [
"public"
]
}
Datos y campos
La ruta POST / consume datos de multipart/form-data. El archivo también usa req.auth.userId como contexto de seguridad y trazabilidad.
[
{
"name": "file",
"type": "File",
"required": true,
"description": "Archivo recibido por `multer` mediante `upload.single(\"file\")`."
},
{
"name": "listingId",
"type": "string",
"description": "Identificador opcional del anuncio. Si existe, la ruta valida que pertenezca al usuario autenticado."
},
{
"name": "folder",
"type": "string",
"description": "Carpeta solicitada para ImageKit. Si no llega, usa `listings`."
},
{
"name": "tags",
"type": "string",
"description": "Etiquetas separadas por comas. La ruta las recorta, filtra vacías y las convierte en un arreglo."
}
]
req.auth.userId[badge:read-only]string— Identificador del usuario autenticado que se pasa al servicio de subida.
La ruta toma req.body.tags, lo convierte a texto y lo separa por comas. Después aplica trim() a cada valor y elimina los vacíos con filter(Boolean). El resultado se envía como un arreglo al servicio de media.
Comportamientos
Cada handler tiene una responsabilidad concreta. La ruta de autenticación prepara el cliente para subidas futuras desde el navegador. La ruta de carga protege el anuncio y delega la subida al servicio de media.
GET /imagekit-auth— generatoken,expireysignature, y devuelve la configuración pública de ImageKit.POST /— valida ownership cuandolistingIdexiste y sube el archivo conuploadAssetToImageKit.upload.single("file")— extrae un único archivo demultipart/form-datay lo deja enreq.file.console.error("POST /api/uploads error:", error)— registra errores inesperados en la subida.res.status(error.statusCode || 500)— conserva el código de error del servicio cuando está disponible.
[!WARNING]
multer.memoryStorage()guarda el archivo en memoria. El límite configurado aquí es de 25 MB.
[!IMPORTANT] Si
listingIdviene en la petición, la ruta exige que el anuncio pertenezca al usuario autenticado.
API
El archivo define dos endpoints HTTP. Cada uno tiene su propio contrato y su propia respuesta.
GET /imagekit-auth
{
"title": "GET imagekit auth",
"description": "Genera los par\u00e1metros de autenticaci\u00f3n de ImageKit y devuelve la configuraci\u00f3n p\u00fablica disponible para el cliente.",
"method": "GET",
"baseUrl": "router mount point not shown in this file",
"endpoint": "/imagekit-auth",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "none",
"requestBody": "",
"formData": [],
"rawBody": "",
"responses": {
"200": {
"description": "Genera la autenticaci\u00f3n y devuelve datos p\u00fablicos de ImageKit.",
"body": "{\n \"success\": true,\n \"data\": {\n \"token\": \"imagekit_auth_token\",\n \"expire\": 1730000000,\n \"signature\": \"imagekit_signature\"\n }\n}"
},
"500": {
"description": "Error al generar los par\u00e1metros de autenticaci\u00f3n.",
"body": "{\n \"success\": false,\n \"message\": \"No se pudo generar autenticacion de ImageKit.\"\n}"
}
}
}
POST /
{
"title": "POST upload file",
"description": "Recibe un archivo multipart, valida ownership si llega listingId y delega la subida a uploadAssetToImageKit.",
"method": "POST",
"baseUrl": "router mount point not shown in this file",
"endpoint": "/",
"headers": [
{
"key": "Authorization",
"value": "Bearer <token>",
"required": true
},
{
"key": "Content-Type",
"value": "multipart/form-data",
"required": true
}
],
"queryParams": [],
"pathParams": [],
"bodyType": "form",
"requestBody": "",
"formData": [
{
"key": "file",
"value": "Binary file content",
"required": true
},
{
"key": "listingId",
"value": "Listing ObjectId",
"required": false
},
{
"key": "folder",
"value": "listings",
"required": false
},
{
"key": "tags",
"value": "hero,cover,public",
"required": false
}
],
"rawBody": "",
"responses": {
"200": {
"description": "Archivo subido con \u00e9xito.",
"body": "{\n \"success\": true,\n \"data\": {\n \"result\": \"uploadAssetToImageKit response\"\n }\n}"
},
"403": {
"description": "El usuario no posee el anuncio asociado.",
"body": "{\n \"success\": false,\n \"message\": \"No puedes subir archivos a este anuncio.\"\n}"
},
"500": {
"description": "Error al procesar la subida.",
"body": "{\n \"success\": false,\n \"message\": \"No se pudo subir el archivo.\"\n}"
}
}
}
Casos límite y detalles importantes
Este archivo maneja algunos casos con reglas explícitas. La validación de ownership ocurre solo cuando listingId está presente. Si el valor no es un ObjectId válido, la verificación falla y la ruta responde con 403.
[!NOTE]
GET /imagekit-authestá preparado para flujos de subida client-side futuros. El comentario del código indica que nunca exponeIMAGEKIT_PRIVATE_KEY.
[!CAUTION] Si
listingIdno es válido o no pertenece al usuario, la subida se rechaza antes de llamar al servicio de media.
[!WARNING] La ruta de subida no define aquí una validación explícita de
req.file. La extracción depende demultery del servicio delegado.
Relacionado
Estos archivos participan directamente en esta funcionalidad:
services/api/src/auth/auth.middleware.js— aportarequireAuthy el contextoreq.auth.userId.services/api/src/config/imagekit.js— aportagetImageKitClient()ygetImageKitPublicConfig().services/api/src/models/Listing.js— se consulta para verificar que el anuncio pertenece al usuario.services/api/src/modules/media/media.service.js— realiza la subida real conuploadAssetToImageKit().services/api/src/modules/uploads/uploads.routes.js— exporta el router con ambas rutas de uploads.