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 populatedUser.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. fileciteturn0file5turn0file14
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 fromlisting.lifecycle.publicOwnerLabelor the fallback label.maskListingOwner(listing)— Returns the input unchanged for non-masked statuses, or returns a copy withcreatedByreplaced.maskListingOwner(listing)— Preserves the originallistingobject whenlistingis falsy.maskListingOwner(listing)— Maskspurgedlistings as well, even if another layer already excludes them.
Gotchas & edge cases
[!WARNING] The function does not inspect the populated
Userrecord. It only readslisting.lifecycle.status. If that status is notanonymizedorpurged, no masking happens.
[!IMPORTANT] The helper is defensive. It still masks
purgedlistings if they reach it directly. This matches the source comments and the test coverage around defense in depth. fileciteturn0file0turn0file9
[!NOTE] The returned object is a shallow copy of the listing. The original input is not mutated. The public serializer relies on that behavior. fileciteturn0file0turn0file14
Related
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. fileciteturn0file5turn0file14
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. fileciteturn0file5turn0file6
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.