listing-write-sanitizer.js
Overview
Este archivo define la barrera de escritura para listings en services/api/src/modules/listings/listing-write-sanitizer.js. Su función es limpiar el cuerpo enviado por el cliente antes de que llegue a POST y PATCH /api/listings.
El sanitizador aplica una allowlist explícita. Solo conserva campos permitidos y descarta el resto. Así evita que el cliente reasigne campos que pertenecen al servidor o que no forman parte del contrato de escritura.
[!IMPORTANT] El comentario del archivo marca varios campos como propiedad del servidor. Esos campos no deben venir desde el cliente, aunque estén presentes en el body.
_idcreatedBylifecyclemetricssubmissionIdsourceDocumentscreatedAtupdatedAtpublication.verifiedpdf.url
[!NOTE] El archivo no depende del modo strict de Mongoose. La restricción se aplica antes de
Listing.createyfindOneAndUpdate.
How it works
flowchart TB
A[Incoming body] --> B[isPlainObject check]
B -->|No| C[Return empty object]
B -->|Yes| D[pick allowed root fields]
D --> E[Sanitize publication]
D --> F[Sanitize pdf]
E --> G[Return payload]
F --> G[Return payload]
El flujo empieza con una validación básica del valor recibido. Si el body no es un objeto plano, el archivo devuelve un objeto vacío. Si sí lo es, copia solo los campos de primer nivel permitidos.
Después, el sanitizador trata dos objetos anidados de forma separada: publication y pdf. En ambos casos, solo conserva los subcampos autorizados. Todo valor extra se elimina en el resultado final.
Key components
{
"title": "sanitizeClientListingPayload",
"content": "Funci\u00f3n exportada que recibe el body del cliente, conserva solo los campos permitidos y devuelve un payload limpio para escritura.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-write-sanitizer.js",
"badges": [
"public"
]
}
{
"title": "isPlainObject",
"content": "Funci\u00f3n interna que comprueba si un valor es un objeto plano y no un array.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-write-sanitizer.js",
"badges": []
}
{
"title": "pick",
"content": "Funci\u00f3n interna que copia solo las claves permitidas y solo si existen con valor distinto de undefined.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-write-sanitizer.js",
"badges": []
}
Data & fields
El archivo define tres allowlists. Dos controlan objetos anidados, y una controla los campos de primer nivel. El sanitizador no valida tipos de negocio. Solo filtra claves y conserva valores tal como llegan.
CLIENT_WRITABLE_FIELDS[badge:read-only]string[]— campos de primer nivel que el cliente puede enviar.CLIENT_WRITABLE_PUBLICATION_FIELDS[badge:read-only]string[]— subcampos permitidos dentro depublication.CLIENT_WRITABLE_PDF_FIELDS[badge:read-only]string[]— subcampos permitidos dentro depdf.
[
{
"name": "title",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "intent",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "price",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "location",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "description",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "contact",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "media",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "pdf",
"type": "object",
"description": "Objeto opcional. Si no es un objeto plano, se reemplaza por un objeto vacío.",
"children": [
{
"name": "fileName",
"type": "any",
"description": "Nombre del archivo PDF generado."
},
{
"name": "generatedAt",
"type": "any",
"description": "Marca temporal de generación del PDF."
}
]
},
{
"name": "meta",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "requirement",
"type": "any",
"description": "Campo de primer nivel conservado desde el body del cliente."
},
{
"name": "publication",
"type": "object",
"description": "Objeto opcional. Si no es un objeto plano, se reemplaza por un objeto vacío.",
"children": [
{
"name": "status",
"type": "any",
"description": "Estado de publicación permitido por el cliente."
},
{
"name": "role",
"type": "any",
"description": "Rol de publicación permitido por el cliente."
}
]
}
]
titleintentpricelocationdescriptioncontactmediapdfmetarequirementpublication
statusrole
fileNamegeneratedAt
Behaviors
isPlainObject(value)— devuelvetruesolo para objetos no nulos y no array.pick(source, keys)— crea un objeto nuevo con las claves permitidas cuyo valor no esundefined.sanitizeClientListingPayload(body)[badge:public] — devuelve{}si el body no es un objeto plano; si lo es, filtra campos raíz y sanitizapublicationypdf.
Gotchas & edge cases
[!WARNING] Si
bodyno es un objeto plano, el resultado es{}. Esto incluye arrays,null,undefinedy valores primitivos.
[!WARNING]
publicationy{}.
[!CAUTION] El sanitizador no convierte tipos ni valida contenido semántico. Solo elimina claves no permitidas.
[!IMPORTANT]
sourceDocumentsno se escribe desde este sanitizador. El comentario del archivo indica que solo la subruta/source-documentstrata ese dato.
Related
Este archivo actúa como control previo a la persistencia de listings. El comentario lo vincula con POST/PATCH /api/listings, Listing.create y findOneAndUpdate. También separa claramente el manejo de sourceDocuments en la subruta /source-documents.
La relación funcional principal es esta:
- Entrada del cliente en
POSToPATCH /api/listings - Limpieza con
sanitizeClientListingPayload - Persistencia con un payload ya filtrado
- Exclusión de campos del servidor y de subcampos no autorizados