Skip to main content

interaction-module

# Interaction & Review Module

The high-throughput, concurrency-safe engine powering product reviews, threaded comments, and dynamic mathematical aggregations.

MongoDB Zod Security Architecture

1. Overview

The Interaction Module (src/modules/interactions/) handles the lifecycle of user reviews, threaded comments, and helpful/unhelpful voting. It is optimised to decouple heavy write‑operations (math calculations) from read‑operations (catalog browsing).

Base Route: /api/v1/interactions

Key capabilities:

  • Polymorphic validation – reviews require a rating; comments forbid ratings.
  • Verified purchase badge – cross‑module trust layer with Orders collection.
  • Asynchronous aggregation – average rating and star distribution are updated in the background.
  • Concurrency‑safe voting – atomic MongoDB array operators prevent duplicate votes.
  • Pagination with hard limit – prevents memory exhaustion (CWE‑400).

2. Schema Architecture & Design Decisions

The Adjacency List Pattern (Threading)

To support infinite nesting (e.g., replying to a review, replying to a reply), the Interaction schema uses the Adjacency List Pattern:

  • parentId references the _id of another document in the same collection.
  • Top‑level reviews have parentId: null.
FieldTypeDescription
_idObjectIdUnique identifier.
productIdObjectIdReferenced product (indexed).
userObjectIdAuthor of the interaction.
typeenumREVIEW or COMMENT.
ratingnumber (1-5)Required for REVIEW, forbidden for COMMENT.
contentstringThe review or comment text.
parentIdObjectIdParent interaction (null for top‑level).
likesObjectId[]Array of user IDs who upvoted.
dislikesObjectId[]Array of user IDs who downvoted.
isVerifiedPurchasebooleanSet automatically based on order history.
createdAt / updatedAtDateTimestamps.

Indexes:

  • { productId: 1, type: 1, createdAt: -1 } – optimises public review listing.
  • { parentId: 1 } – for fetching comment threads.

Polymorphic Zod Validation

The Zod DTO uses superRefine to enforce context‑aware business rules:

if (type === "REVIEW" && !rating) {
ctx.addIssue({ message: "Rating required for reviews" });
}
if (type === "COMMENT" && rating) {
ctx.addIssue({ message: "Rating forbidden on comments" });
}

This prevents users from manipulating average ratings through nested threads.


3. Core Business Logic

Cross‑Module Trust Layer (Verified Purchases)

The system does not trust the frontend to declare a "Verified Purchase". During creation, the service layer queries the Orders collection:

const hasDeliveredOrder = await Order.exists({
user: userId,
"items.product": productId,
orderStatus: "DELIVERED"
});
isVerifiedPurchase = !!hasDeliveredOrder;

The flag is permanently locked to true and never updated again.

Asynchronous Aggregation Engine

Recalculating a product’s average rating and 5‑star distribution curve is computationally expensive. Therefore:

  • The aggregation pipeline is pushed to the background via setImmediate().
  • The HTTP 201 Created response returns to the client instantly.
  • The background pipeline uses MongoDB $cond to calculate the counts for 1‑star, 2‑star, etc., in a single database pass.
  • After the aggregation, the product’s ratingsMetadata is updated atomically.

Sequence Diagram:

Concurrency‑Safe Voting

Users can upvote or downvote an interaction. To handle race conditions (e.g., double‑clicking), the voting system uses atomic MongoDB array operators:

  • Like: $addToSet adds the user ID to the likes array, $pull removes from dislikes.
  • Dislike: $addToSet adds to dislikes, $pull removes from likes.

This guarantees that even if the frontend sends duplicate requests, the user cannot vote twice.

Code snippet:

if (action === "LIKE") {
update = { $addToSet: { likes: userId }, $pull: { dislikes: userId } };
} else if (action === "DISLIKE") {
update = { $addToSet: { dislikes: userId }, $pull: { likes: userId } };
}
await Interaction.findOneAndUpdate({ _id: interactionId }, update);

4. Security Firewalls

ThreatMitigation
CWE‑400: Memory exhaustionPublic GET route uses Math.min(limit, 50) to cap pagination.
CWE‑117: Log injectionAll BSON ObjectIds are cast to hex strings; error messages stripped of \r\n.
CWE‑943: NoSQL injectionExplicit $eq wrappers for all query parameters (not shown, but standard practice).
CWE‑1321: Prototype pollutionZod .strict() schemas reject undocumented fields.
Review bombingPartial unique index on { productId, user, type: "REVIEW" } ensures one review per user per product.

5. API Endpoints

MethodEndpointAccessDescription
GET/interactions/product/:productIdPublicFetch paginated top‑level reviews (limit ≤ 50).
POST/interactionsProtectedCreate a review or threaded comment.
PATCH/interactions/:interactionId/voteProtectedUpvote or downvote an interaction.

Pagination example:

GET /interactions/product/64a7b...?page=2&limit=20

The limit is automatically reduced to 50 if a higher value is sent.


FilePurpose
src/modules/interactions/interaction.controller.tsHTTP layer.
src/modules/interactions/interaction.service.tsCore logic – verified purchase, aggregation, voting.
src/modules/interactions/interaction.model.tsMongoose schema, indexes.
src/modules/interactions/interaction.routes.tsRoute definitions with rate limiting and auth.
src/modules/interactions/dtos/create-interaction.dto.tsZod validation with superRefine.
src/modules/interactions/dtos/vote-interaction.dto.tsValidation for vote action.
src/modules/interactions/interfaces/interaction.interface.tsTypeScript interfaces.
src/modules/products/models/base-product.model.tsStores ratingsMetadata.

7. Sequence Diagram (Full Lifecycle)


See Also


The Reshma-Core Team