--- 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]].