listing-public-assembly.js

Overview

Este módulo centraliza el ensamblaje público canónico de Listings ya obtenidos desde Mongo, con createdBy ya poblado por el caller. Extrae un pipeline compartido para que GET /api/listings, GET /api/listings/:id y el consumidor de Match usen exactamente la misma transformación pública. El archivo no decide qué Listings cargar, ni en qué orden, ni con qué filtro o paginación; solo transforma resultados ya recuperados.

[!IMPORTANT] Este módulo solo transforma Listings ya obtenidas. La query, el orden y la paginación siguen siendo responsabilidad del caller.

[!NOTE] attachRelatedOfferSummaries, ListingLike.find y sanitizeListingForPublicView se usan como operaciones de solo lectura aquí. El módulo no muta el listing recibido.

How it works

flowchart TD
    Caller[Caller]
    Assemble[assemblePublicListingsForViewer]
    Attach[attachRelatedOfferSummaries]
    Sanitize[sanitizeListingForPublicView]
    Output[Public listings]
    LikeMap[buildLikedByCurrentUserMap]
    ListingLikeFind[ListingLike find]
    MapOut[Map listingId true]

    Caller -->|listings| Assemble
    Assemble -->|empty input| Output
    Assemble -->|enrich| Attach
    Attach -->|sanitize| Sanitize
    Sanitize --> Output

    Caller -->|listingIds and viewerUserId| LikeMap
    LikeMap -->|single batch query| ListingLikeFind
    ListingLikeFind --> MapOut

El flujo principal toma un arreglo de Listings ya cargados, llama primero a attachRelatedOfferSummaries, y después aplica sanitizeListingForPublicView a cada elemento. El segundo flujo es independiente y construye un Map con los likes del usuario actual usando una sola consulta batched. Ambos caminos evitan lógica de escritura y conservan el comportamiento de solo lectura.

  • assemblePublicListingsForViewer normaliza la entrada con Array.isArray.
  • Si el arreglo queda vacío, retorna [] inmediatamente.
  • La enriquecimiento de Offer relacionado ocurre antes del saneamiento público.
  • El saneamiento conserva el orden original del arreglo.
  • buildLikedByCurrentUserMap retorna un Map vacío si no hay viewerUserId o si no hay IDs.
  • La consulta de likes usa ListingLike.find con $in, select("listingId") y lean().

Key components

{
    "title": "assemblePublicListingsForViewer",
    "content": "Aplica el pipeline p\u00fablico compartido sobre Listings ya obtenidas. Primero enriquece con attachRelatedOfferSummaries y luego sanea cada elemento con sanitizeListingForPublicView. Conserva el orden de entrada.",
    "type": "function",
    "filePath": "services/api/src/modules/listings/listing-public-assembly.js",
    "badges": [
        "public"
    ]
}
{
    "title": "buildLikedByCurrentUserMap",
    "content": "Construye un Map<string, boolean> con los Listings que el usuario ya dio like. Usa una sola consulta batched sobre ListingLike y no consulta Mongo si faltan el viewer o los IDs.",
    "type": "function",
    "filePath": "services/api/src/modules/listings/listing-public-assembly.js",
    "badges": [
        "public"
    ]
}

Este módulo depende de tres piezas visibles en el archivo:

  • ListingLike desde services/api/src/models/ListingLike.js.
  • attachRelatedOfferSummaries desde listing-referral.js.
  • sanitizeListingForPublicView desde listing-public-view.js.

Data & fields

Parámetros de assemblePublicListingsForViewer

[
    {
        "name": "listings",
        "type": "Array<object>",
        "required": true,
        "description": "Listings lean ya obtenidos por el caller. El helper conserva el orden original."
    },
    {
        "name": "options",
        "type": "object",
        "description": "Opciones para el ensamblaje público.",
        "children": [
            {
                "name": "viewerUserId",
                "type": "string|null",
                "description": "Identidad opcional del usuario visor. Se pasa a los helpers internos."
            }
        ]
    }
]

Parámetros de buildLikedByCurrentUserMap

[
    {
        "name": "listingIds",
        "type": "Array<string|object>",
        "required": true,
        "description": "IDs de Listings o valores convertibles a string para la consulta batched."
    },
    {
        "name": "viewerUserId",
        "type": "string|null",
        "description": "Identidad del usuario visor. Si falta, no se consulta Mongo."
    }
]

Salidas

  • assemblePublicListingsForViewer [badge:public] devuelve Promise<Array<object>>.
  • buildLikedByCurrentUserMap [badge:public] devuelve Promise<Map<string, boolean>>.
  • El orden de salida de assemblePublicListingsForViewer coincide con el arreglo de entrada.
  • Las claves del Map de likes se guardan como String(like.listingId).

Behaviors

  • assemblePublicListingsForViewer [badge:public] — normaliza la entrada, aplica attachRelatedOfferSummaries, y luego aplica sanitizeListingForPublicView a cada listing. Devuelve el arreglo público final.
  • buildLikedByCurrentUserMap [badge:public] — consulta ListingLike en un solo batch para saber qué listings ya fueron liked por viewerUserId. Devuelve un Map con listingId -> true.
  • assemblePublicListingsForViewer — no añade likedByCurrentUser. Ese campo se maneja por separado en superficies tipo lista.
  • buildLikedByCurrentUserMap — evita toda consulta si falta viewerUserId o si no hay IDs.
  • buildLikedByCurrentUserMap — usa select("listingId") y lean(), y solo recupera el dato mínimo necesario.

Workflows

Este archivo no define un flujo de usuario completo ni un endpoint propio. Su trabajo es componer dos rutas de datos: el ensamblaje público de Listings y el mapa de likes del usuario actual.

  • Ensamblaje público: Listings ya cargados → attachRelatedOfferSummaries → sanitizeListingForPublicView → respuesta pública.
  • Mapa de likes: listingIds + viewerUserId → consulta única en ListingLike → Map<string, boolean>.

Gotchas & edge cases

[!WARNING] buildLikedByCurrentUserMap no hace consultas si no recibe viewerUserId o si el arreglo de IDs está vacío. En ese caso retorna un Map vacío.

[!NOTE] attachRelatedOfferSummaries es un no-op seguro para Listings sin requirement.referral. El propio comentario del archivo lo describe como barato y sin consulta para esos casos.

[!IMPORTANT] likedByCurrentUser no forma parte de la forma canónica de un Listing. El archivo indica que ese campo se compone aparte cuando la superficie lo necesita.

[!CAUTION] El módulo asume que el caller ya decidió qué Listings hidratar y con qué orden. No corrige ni reconstruye esa decisión.

  • services/api/src/models/ListingLike.js — modelo usado para recuperar likes del usuario con una consulta batched.
  • services/api/src/modules/listings/listing-referral.js — aporta attachRelatedOfferSummaries.
  • services/api/src/modules/listings/listing-public-view.js — aporta sanitizeListingForPublicView.
  • services/api/src/modules/listings/listings.routes.js — este ensamblaje se extrajo para compartirlo con las rutas públicas de Listings, según el comentario del módulo.
  • services/api/test/listing-public-assembly.test.js — verifica en aislamiento que el helper preserva el orden y evita N+1 en la consulta de likes.

Este archivo existe para evitar divergencias entre superficies públicas que muestran la misma Listing. Su función es mantener una sola forma canónica de salida pública, mientras cada caller conserva su propia query de Mongo, su filtro y su paginación. Esa separación aparece explícitamente en los comentarios del archivo y en las notas de integración del repositorio.