listing-owner-view.js

Overview

services/api/src/modules/listings/listing-owner-view.js defines the public masking rule for the owner of a Listing. It replaces createdBy with a neutral identity when listing.lifecycle.status is anonymized or purged, and leaves the listing unchanged for normal and protection. The module only reshapes the response; it does not modify User or Listing documents.

[!NOTE] This masking rule follows listing.lifecycle.status, not the populated User.accountStatus. The source comments state that the listing owns the public visibility policy.

This helper is part of the public listing serialization flow. The broader listing serializer uses it before returning public data, so feed and detail views stay consistent. fileciteturn0file5turn0file14

How it works

flowchart TB
  A[Listing input] --> B{listing exists}
  B -- No --> C[Return original value]
  B -- Yes --> D{lifecycle status is anonymized or purged}
  D -- No --> C
  D -- Yes --> E[buildMaskedOwner]
  E --> F[Copy listing and replace createdBy]

The flow is intentionally small and deterministic. First, maskListingOwner returns immediately if the input is falsy. Then it checks listing.lifecycle.status against a fixed set of masked statuses. If the status matches, it returns a shallow copy with createdBy replaced by buildMaskedOwner(listing).

buildMaskedOwner constructs a stable public identity. It uses listing.lifecycle.publicOwnerLabel when present, and otherwise falls back to "Anunciante de Plano". The returned object always hides the real owner data with null or empty values.

Key components

{
    "title": "maskListingOwner",
    "content": "[badge:public] Exported function. Returns the original listing unchanged unless lifecycle.status is anonymized or purged. In those cases, it returns a shallow copy with createdBy replaced by a neutral public owner.",
    "type": "function",
    "filePath": "services/api/src/modules/listings/listing-owner-view.js",
    "badges": [
        "public"
    ]
}
{
    "title": "buildMaskedOwner",
    "content": "Internal helper. Builds the neutral createdBy object with null identity fields, empty profile data, and isAnonymized true.",
    "type": "function",
    "filePath": "services/api/src/modules/listings/listing-owner-view.js",
    "badges": []
}

Data & fields

[
    {
        "name": "MASKED_OWNER_STATUSES",
        "type": "Set<string>",
        "required": true,
        "description": "Statuses that trigger owner masking: anonymized and purged."
    },
    {
        "name": "FALLBACK_OWNER_LABEL",
        "type": "string",
        "required": true,
        "description": "Default public label used when lifecycle.publicOwnerLabel is empty."
    }
]
[
    {
        "name": "_id",
        "type": "null",
        "required": true,
        "description": "Always null in the masked owner."
    },
    {
        "name": "name",
        "type": "string",
        "required": true,
        "description": "Uses lifecycle.publicOwnerLabel or FALLBACK_OWNER_LABEL."
    },
    {
        "name": "username",
        "type": "null",
        "required": true,
        "description": "Always null."
    },
    {
        "name": "bio",
        "type": "string",
        "required": true,
        "description": "Always empty."
    },
    {
        "name": "photo",
        "type": "string",
        "required": true,
        "description": "Always empty."
    },
    {
        "name": "coverPhoto",
        "type": "string",
        "required": true,
        "description": "Always empty."
    },
    {
        "name": "website",
        "type": "string",
        "required": true,
        "description": "Always empty."
    },
    {
        "name": "location",
        "type": "string",
        "required": true,
        "description": "Always empty."
    },
    {
        "name": "isAnonymized",
        "type": "boolean",
        "required": true,
        "description": "Always true."
    }
]

These fields describe the exact neutral owner shape returned by buildMaskedOwner. The object is stable and intentionally sparse. It avoids exposing any real User fields.

Behaviors

  • buildMaskedOwner(listing) — Returns a neutral owner object built from listing.lifecycle.publicOwnerLabel or the fallback label.
  • maskListingOwner(listing) — Returns the input unchanged for non-masked statuses, or returns a copy with createdBy replaced.
  • maskListingOwner(listing) — Preserves the original listing object when listing is falsy.
  • maskListingOwner(listing) — Masks purged listings as well, even if another layer already excludes them.

Gotchas & edge cases

[!WARNING] The function does not inspect the populated User record. It only reads listing.lifecycle.status. If that status is not anonymized or purged, no masking happens.

[!IMPORTANT] The helper is defensive. It still masks purged listings if they reach it directly. This matches the source comments and the test coverage around defense in depth. fileciteturn0file0turn0file9

[!NOTE] The returned object is a shallow copy of the listing. The original input is not mutated. The public serializer relies on that behavior. fileciteturn0file0turn0file14

services/api/src/modules/listings/listing-public-view.js uses maskListingOwner as part of the public listing serialization pipeline. That module combines owner masking with contact and location sanitization. fileciteturn0file5turn0file14

services/api/src/utils/publicUserIdentity.js uses a different rule. It masks a populated User based on accountStatus, not on listing.lifecycle.status. The source comments explicitly separate both policies. fileciteturn0file5turn0file6

anonymizeExpiredAccounts.js is the job mentioned in the comments as keeping User.accountStatus and Listing.lifecycle.status synchronized. This module depends on that lifecycle state for public display only.