GET /api/marketplace/reviews/:subjectType/:subjectId
Rating Flow
After an order is completed, the buyer rates the seller and (optionally) leaves a review; the seller can rate the buyer in the reciprocal flow. Reviews are scoped by subjectType (seller | template) and constrained by the seller's ShopSettings.
Actors
Buyer (typical reviewer).
Seller (subject; can also be a reviewer in the reciprocal direction).
System — enforces uniqueness and moderation rules.
The associated PurchaseRequest is completed (or finalized).
The reviewer is the buyer of that request (for isVerifiedBuyer to be true).
The subject's ShopSettings.allowSellerReviews / allowTemplateReviews is not false (reviewRoutes.ts:15-31).
Step-by-step narrative
From the request detail or seller profile, the buyer clicks "Leave review". The form captures rating (1–5) and comment (≤ 1000 chars).
Frontend POSTs POST /api/marketplace/reviews with { subjectType: 'seller' | 'template', subjectId, rating, comment, purchaseRequestId? }.
Backend route handler:
Validates payload.
Calls isReviewsAllowed(subjectType, subjectId) — checks the seller's ShopSettings (for sellers, look up the seller directly; for templates, look up the template's owning seller).
Sets isVerifiedBuyer = true if the user owns a completed purchase request from that seller.
Defaults status: 'published' (no moderation queue today).
Inserts a Review document. Unique index { subjectType, subjectId, reviewerId } prevents a user from reviewing the same subject twice (Review.ts:34).
Aggregated stats are recomputed on read via computeStats (reviewRoutes.ts:33-62) — count, average, per-star histogram. No denormalised counter on User today; everything is computed at read-time.
Visibility
Public — anyone hitting GET /api/marketplace/reviews/seller/:sellerId sees status: 'published' reviews paginated by 10.
If ShopSettings.allowSellerReviews === false (or allowTemplateReviews === false), reads return 403 Reviews are disabled by seller.
The seller can flag a review for moderation (planned — current statuses include pending and rejected, but no UI to flip them today; admin can update via direct DB).
Sequence diagram
sequenceDiagram
autonumber
actor B as Buyer
participant FE as Frontend
participant BE as Backend
participant DB as MongoDB
B->>FE: Open seller profile / request detail
B->>FE: Click "Leave review", choose stars + comment
FE->>BE: POST /api/marketplace/reviews
BE->>DB: ShopSettings.findOne({sellerId}) → allowSellerReviews?
alt allowed
BE->>DB: PurchaseRequest.exists({buyer, seller, status:"completed"})?
BE->>DB: Review.create({status:"published", isVerifiedBuyer})
BE-->>FE: 201 { review }
else disabled
BE-->>FE: 403 Reviews disabled
end
Note over FE: Public visitor:
FE->>BE: GET /api/marketplace/reviews/seller/{id}
BE->>DB: Review.find / computeStats aggregate
BE-->>FE: { items, pagination, stats:{count, avg, histogram} }
API calls
Method
Endpoint
Purpose
POST
/api/marketplace/reviews
Submit review
GET
/api/marketplace/reviews/:subjectType/:subjectId
List reviews + stats
PATCH
/api/marketplace/reviews/:id
Edit own review (within edit window)
DELETE
/api/marketplace/reviews/:id
Delete own review
Database writes
reviews — insert on submission; one document per (subjectType, subjectId, reviewerId).
shopsettings — read-only here; the seller controls allowSellerReviews / allowTemplateReviews in their shop settings.
Socket events emitted
None today. A new-review event broadcast to user-{sellerId} would be a useful enhancement so sellers see reviews appear live.
Side effects
Recompute aggregate on every list call — fine for small volumes; consider caching stats per subject when the review count grows.
Order rating field also stamps metadata.rating on the purchase request when the marketplace endpoint accepts ratings inline (see routes.ts references in backend/src/services/marketplace/routes.ts).
Error / edge cases
Duplicate review → MongoDB E11000 from the unique index; surface as 409 Already reviewed.
Subject disabled reviews → 403.
Reviewer not a verified buyer → review is still allowed but isVerifiedBuyer = false. Display this in the UI.
Rating out of 1–5 → Mongoose schema validator rejects.
Comment > 1000 chars → schema-level rejection.
Seller toggles allowSellerReviews=false after reviews exist → existing reviews remain stored but become unreadable via the public GET (reviewRoutes.ts:81-83).
Spam / abuse → no automatic moderation; admin can flip status to rejected to hide.
[!tip] Verified-buyer badge
The isVerifiedBuyer flag is the most credible signal for prospective buyers. Always render a "Verified buyer" pill next to reviews where this is true.