121 lines
6.0 KiB
Markdown
121 lines
6.0 KiB
Markdown
---
|
||
title: Rating Flow
|
||
tags: [flow, rating, review, moderation]
|
||
related_models: ["[[Review]]", "[[ShopSettings]]", "[[PurchaseRequest]]"]
|
||
related_apis: ["POST /api/marketplace/reviews", "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.
|
||
- **Backend** — `backend/src/services/marketplace/reviewRoutes.ts`.
|
||
- **MongoDB** — `reviews` collection (`backend/src/models/Review.ts`).
|
||
|
||
## Preconditions
|
||
|
||
- 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
|
||
|
||
1. From the request detail or seller profile, the buyer clicks "Leave review". The form captures `rating` (1–5) and `comment` (≤ 1000 chars).
|
||
2. Frontend POSTs `POST /api/marketplace/reviews` with `{ subjectType: 'seller' | 'template', subjectId, rating, comment, purchaseRequestId? }`.
|
||
3. 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).
|
||
4. Inserts a `Review` document. Unique index `{ subjectType, subjectId, reviewerId }` prevents a user from reviewing the same subject twice (`Review.ts:34`).
|
||
5. 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
|
||
|
||
```mermaid
|
||
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`.
|
||
|
||
## Linked flows
|
||
|
||
- [[Purchase Request Flow]] — precursor that makes the buyer "verified".
|
||
- [[Seller Offer Flow]] — display average rating on offer cards.
|
||
- [[Dispute Flow]] — a resolved dispute could trigger a review prompt; today they are independent.
|
||
|
||
## Source files
|
||
|
||
- Backend: `backend/src/services/marketplace/reviewRoutes.ts`
|
||
- Backend: `backend/src/models/Review.ts`
|
||
- Backend: `backend/src/services/marketplace/shopSettingsController.ts` (allow flags)
|
||
- Backend: `backend/src/services/marketplace/routes.ts` (inline rating on order completion)
|
||
- Frontend: review components under `frontend/src/sections/account/` and seller profile views
|