Files
nick-doc/02 - Data Models/Review.md
2026-05-23 20:35:34 +03:30

96 lines
3.6 KiB
Markdown

---
title: Review
tags: [data-model, mongoose]
aliases: [Rating, IReview]
---
# Review
Polymorphic 1-5 star review. The `subjectType` discriminator (`seller` or `template`) plus `subjectId` identifies what is being reviewed. `sellerId` is always present so per-seller aggregations work regardless of subject. A compound unique index on `(subjectType, subjectId, reviewerId)` prevents a reviewer from posting two reviews for the same subject.
> [!note] Source
> `backend/src/models/Review.ts:19` — schema definition
> `backend/src/models/Review.ts:38` — model export
## Schema
| Field | Type | Required | Default | Validation | Index | Description |
| --- | --- | --- | --- | --- | --- | --- |
| `subjectType` | String | yes | — | enum: `seller` / `template` | yes (compound) | Discriminator. |
| `subjectId` | ObjectId | yes | — | — | yes (compound) | Id of the seller [[User]] or [[RequestTemplate]]. |
| `sellerId` | ObjectId → [[User]] | yes | — | — | — | Seller associated with the review (always populated). |
| `reviewerId` | ObjectId → [[User]] | yes | — | — | yes (compound + unique) | Author. |
| `rating` | Number | yes | — | min 1, max 5 | — | Star rating. |
| `comment` | String | no | `""` | maxlength 1000 | — | Free-form comment. |
| `isVerifiedBuyer` | Boolean | no | `false` | — | — | Whether the reviewer actually bought from this seller. |
| `purchaseRequestId` | ObjectId → [[PurchaseRequest]] | no | `null` | — | — | Source request (if any). |
| `status` | String | no | `published` | enum: `published` / `pending` / `rejected` | — | Moderation status. |
| `createdAt` | Date | auto | — | — | yes (compound, desc) | Mongoose timestamp. |
| `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. |
## Virtuals
None defined.
## Indexes
Defined at `backend/src/models/Review.ts:34-36`:
- `{ subjectType: 1, subjectId: 1, createdAt: -1 }` — listing for a subject.
- `{ reviewerId: 1, subjectType: 1 }` — reviewer history.
- `{ subjectType: 1, subjectId: 1, reviewerId: 1 }`**unique**, one review per reviewer per subject.
## Pre/Post Hooks
None declared.
## Instance Methods
None defined.
## Static Methods
None defined.
## Relationships
- **References**: [[User]] (`sellerId`, `reviewerId`, and `subjectId` when `subjectType === 'seller'`), [[RequestTemplate]] (`subjectId` when `subjectType === 'template'`), [[PurchaseRequest]] (`purchaseRequestId`).
- **Referenced by**: none.
## State Transitions
```mermaid
stateDiagram-v2
[*] --> published : default
[*] --> pending : moderation required
pending --> published : approved
pending --> rejected : rejected
published --> rejected : flagged
rejected --> [*]
```
## Common Queries
```ts
// All reviews for a seller
Review.find({ subjectType: 'seller', subjectId: sellerUserId, status: 'published' })
.sort({ createdAt: -1 });
// Average rating per seller
Review.aggregate([
{ $match: { subjectType: 'seller', subjectId: sellerUserId, status: 'published' } },
{ $group: { _id: null, avg: { $avg: '$rating' }, count: { $sum: 1 } } }
]);
// Reviews written by a user
Review.find({ reviewerId: userId }).sort({ createdAt: -1 });
// Reviews for a template
Review.find({ subjectType: 'template', subjectId: templateId, status: 'published' });
```
> [!warning] Duplicate prevention
> Attempting to insert a second review for the same `(subjectType, subjectId, reviewerId)` will fail with a `E11000 duplicate key` error from MongoDB. Application code should treat that as "already reviewed".
Related: [[User]], [[RequestTemplate]], [[PurchaseRequest]].