ListingComment en services/api/src/models/ListingComment.js

Overview

Este archivo define el modelo persistente de comentarios para listings. Guarda comentarios raíz y respuestas en una sola colección, con soporte para moderación, sentimiento y contexto de ciclo de cuenta. El esquema usa timestamps: true y persiste en la colección listing_comments.

El modelo enlaza cada comentario con un Listing, con el usuario propietario del listing, con el autor del comentario y con un posible comentario padre. También conserva banderas de contexto para casos de protección, anonimización y purga del autor.

[!NOTE] createdAt y updatedAt se generan automáticamente por Mongoose cuando timestamps: true está activo. El archivo no define esos campos manualmente.

How it works

erDiagram
    Listing ||--o{ ListingComment : listingId
    User ||--o{ ListingComment : ownerId
    User ||--o{ ListingComment : authorId
    User ||--o{ ListingComment : replyToUserId
    ListingComment ||--o{ ListingComment : parentCommentId

    ListingComment {
        string listingId
        string ownerId
        string authorId
        string parentCommentId
        string replyToUserId
        string text
        string status
        string moderationReason
        string sentiment
        boolean createdDuringProtection
        boolean createdDuringAnonymization
        string ownerStatusAtCreation
        boolean authorPurged
        date createdAt
        date updatedAt
    }

El esquema modela una conversación en hilo dentro de una única colección. listingId conecta el comentario con el anuncio, mientras que parentCommentId permite encadenar respuestas sobre otro comentario. ownerId y authorId apuntan a User, y replyToUserId identifica al destinatario de una respuesta cuando existe.

Key components

{
    "title": "ListingComment",
    "content": "Modelo de Mongoose que persiste comentarios de listings, respuestas en hilo, estado de moderaci\u00f3n, sentimiento y banderas de contexto de ciclo de cuenta.",
    "type": "class",
    "filePath": "services/api/src/models/ListingComment.js",
    "badges": [
        "public"
    ]
}

ListingCommentSchema define todos los campos del documento, sus referencias y sus restricciones. ListingCommentSchema.index(...) agrega los índices de lectura y unicidad. mongoose.model("ListingComment", ListingCommentSchema) registra y exporta el modelo.

Data & fields

El esquema usa referencias ObjectId, campos de estado y metadatos de moderación. También incluye banderas para conservar contexto histórico cuando cambian el estado del owner o el estado de purga del autor.

[
    {
        "name": "listingId",
        "type": "Schema.Types.ObjectId",
        "required": true,
        "description": "Referencia obligatoria al Listing asociado. Está indexado."
    },
    {
        "name": "ownerId",
        "type": "Schema.Types.ObjectId",
        "required": true,
        "description": "Referencia obligatoria al User propietario del listing. Está indexado."
    },
    {
        "name": "authorId",
        "type": "Schema.Types.ObjectId",
        "required": true,
        "description": "Referencia obligatoria al User que escribió el comentario. Está indexado."
    },
    {
        "name": "parentCommentId",
        "type": "Schema.Types.ObjectId",
        "description": "Referencia al comentario padre dentro de la misma colección. Por defecto es null y está indexado."
    },
    {
        "name": "replyToUserId",
        "type": "Schema.Types.ObjectId",
        "description": "Referencia opcional al User destinatario de la respuesta. Por defecto es null."
    },
    {
        "name": "text",
        "type": "String",
        "required": true,
        "description": "Texto del comentario. Se recorta y tiene longitud máxima de 1000 caracteres."
    },
    {
        "name": "status",
        "type": "String",
        "description": "Estado de moderación. Valores permitidos: visible, archived, removed. Valor por defecto: visible."
    },
    {
        "name": "moderationReason",
        "type": "String",
        "description": "Razón de moderación. Se recorta y por defecto es una cadena vacía."
    },
    {
        "name": "sentiment",
        "type": "String",
        "description": "Clasificación de sentimiento. Valores permitidos: positive, neutral, negative. Valor por defecto: neutral."
    },
    {
        "name": "createdDuringProtection",
        "type": "Boolean",
        "description": "Marca si el comentario nació durante el ciclo de protección del owner. Conserva contexto histórico."
    },
    {
        "name": "createdDuringAnonymization",
        "type": "Boolean",
        "description": "Marca si el comentario nació durante el proceso de anonimización del owner."
    },
    {
        "name": "ownerStatusAtCreation",
        "type": "String",
        "description": "Estado del owner cuando se creó el comentario. Valores permitidos: active, pending_deletion, anonymized_pending_purge, purged, disabled."
    },
    {
        "name": "authorPurged",
        "type": "Boolean",
        "description": "Marca si el autor fue purgado. La lectura puede sustituir el autor por Usuario eliminado."
    },
    {
        "name": "createdAt",
        "type": "date",
        "description": "Marca temporal automática de creación. Solo lectura."
    },
    {
        "name": "updatedAt",
        "type": "date",
        "description": "Marca temporal automática de última actualización. Solo lectura."
    }
]

text es el único contenido obligatorio del comentario, y status y sentiment usan valores enumerados cerrados. Las banderas createdDuringProtection, createdDuringAnonymization, ownerStatusAtCreation y authorPurged guardan el contexto operativo del comentario sin depender del estado actual de los usuarios relacionados.

Estos campos no describen el contenido del comentario. Conservan información histórica para lecturas posteriores y procesos de purga.

  • createdDuringProtection registra si el comentario se creó durante el ciclo de protección de 60 días.
  • createdDuringAnonymization registra si el comentario se creó durante la anonimización del owner.
  • ownerStatusAtCreation fija el estado del owner en el momento de la creación.
  • authorPurged marca que el autor fue purgado y que la lectura debe mostrar un autor eliminado.

Behaviors

Este archivo no define métodos propios de negocio. Su comportamiento relevante está en la construcción del esquema, en los índices y en el registro del modelo.

  • ListingCommentSchema.index({ listingId: 1, createdAt: -1 }) — optimiza la lectura de comentarios de un listing en orden descendente de creación.
  • ListingCommentSchema.index({ listingId: 1, parentCommentId: 1, authorId: 1 }, { unique: true, partialFilterExpression: { parentCommentId: { $type: "objectId" } } }) — impone unicidad para respuestas con parentCommentId válido.
  • mongoose.model("ListingComment", ListingCommentSchema) — registra el modelo ListingComment en Mongoose y lo exporta para uso en otras capas.

Gotchas & edge cases

[!WARNING] La unicidad parcial solo aplica cuando parentCommentId tiene tipo objectId. Eso significa que la regla protege respuestas encadenadas, no comentarios raíz. fileciteturn0file0turn0file15

[!NOTE] replyToUserId no está indexado. El esquema lo guarda como referencia opcional, pero no crea un índice específico para ese campo.

[!IMPORTANT] Los campos createdDuringProtection, createdDuringAnonymization, ownerStatusAtCreation y authorPurged existen para conservar contexto histórico. El archivo documenta su uso como soporte para estados posteriores del owner y del autor.

Este modelo se conecta con Listing y User por medio de referencias ObjectId, y se refiere a sí mismo para soportar respuestas en hilo. También aparece como base de la ruta de comentarios de listings, que trabaja sobre el mismo contrato de datos. fileciteturn0file0turn0file5turn0file6

  • Listing — anuncio asociado al comentario.
  • User — owner, author y posible destinatario de respuesta.
  • ListingComment — comentario padre dentro del mismo hilo.
  • services/api/src/modules/listing-comments/listing-comments.routes.js — consumo directo del modelo en la ruta de comentarios. fileciteturn0file5turn0file6