--- title: RequestTemplate tags: [data-model, mongoose] aliases: [Template, Request Template, IRequestTemplate] --- # RequestTemplate A reusable template authored by a seller. When a buyer visits the template's `shareableLink`, the front-end pre-fills a new [[PurchaseRequest]] with the template's category, urgency, specs, delivery info, and an optional default seller `proposal`. The schema mirrors `PurchaseRequest` for fast cloning, plus template-specific bookkeeping (`isActive`, `usageCount`, `maxUsage`, `expiresAt`). > [!note] Source > `backend/src/models/RequestTemplate.ts:65` — schema definition > `backend/src/models/RequestTemplate.ts:295` — model export ## Schema | Field | Type | Required | Default | Validation | Index | Description | | --- | --- | --- | --- | --- | --- | --- | | `sellerId` | ObjectId → [[User]] | yes | — | — | yes (compound) | Template author. | | `title` | String | yes | — | trim, maxlength 200 | — | Headline. | | `description` | String | yes | — | trim, maxlength 2000 | — | Description. | | `categoryId` | ObjectId → [[Category]] | yes | — | — | yes (compound) | Category. | | `productType` | String | no | `physical_product` | enum: `physical_product` / `digital_product` / `service` / `consultation` | yes (compound) | Fulfilment type. | | `productLink` | String | no | — | URL regex | — | Reference URL. | | `size` | String | no | — | trim, maxlength 100 | — | Size. | | `color` | String | no | — | trim, maxlength 100 | — | Color. | | `brand` | String | no | — | trim, maxlength 100 | — | Brand. | | `quantity` | Number | no | `1` | min 1 | — | Default unit count. | | `budget.min` | Number | no | — | min 0 | — | Lower bound. | | `budget.max` | Number | no | — | min 0 | — | Upper bound. | | `budget.currency` | String | no | `USDT` | enum: `USD` / `EUR` / `IRR` / `USDT` / `USDC` | — | Currency. Shared with [[PurchaseRequest]] so a template can be converted without enum drift. | | `urgency` | String | no | `medium` | enum: `low` / `medium` / `high` / `urgent` | — | Urgency. | | `tags[]` | String[] | no | — | trim | — | Tags. | | `specifications[].key` | String | yes | — | trim | — | Spec key. | | `specifications[].value` | String | yes | — | trim | — | Spec value. | | `specifications[].label` | String | no | — | trim | — | Human label. | | `deliveryInfo.deliveryType` | String | no | `physical` | enum: `physical` / `online` | — | Delivery channel. | | `deliveryInfo.notes` | String | no | — | — | — | Notes. | | `deliveryInfo.email` | String | no | — | email regex | — | Digital delivery email. | | `serviceInfo.duration` | Number | no | — | min 0.5 | — | Hours. | | `serviceInfo.sessionType` | String | no | — | enum: `online` / `in_person` / `hybrid` | — | Session type. | | `serviceInfo.location` | String | no | — | trim, maxlength 200 | — | Location. | | `serviceInfo.requirements[]` | String[] | no | — | trim | — | Pre-requisites. | | `proposal.title` | String | no | — | trim, maxlength 200 | — | Default offer title. | | `proposal.price` | Number | no | — | min 0.01 | — | Default offer price. | | `proposal.deliveryTime` | Number | no | — | min 1, max 365 | — | Default ETA in days. | | `proposal.description` | String | no | — | trim, maxlength 1000 | — | Default offer description. | | `attachments[]` | String[] | no | — | — | — | File URLs. | | `images[]` | String[] | no | — | trim | — | Image URLs. | | `metadata.source` | String | no | `manual` | enum: `manual` / `template` / `api` | — | Origin. | | `metadata.templateId` | String | no | — | trim | — | Originating template id. | | `metadata.version` | String | no | — | trim | — | Schema version. | | `isActive` | Boolean | no | `true` | — | yes (single + compound) | Active flag. | | `shareableLink` | String | yes | — | trim | unique (+ compound) | Public link slug. | | `usageCount` | Number | no | `0` | min 0 | — | Number of times used. | | `maxUsage` | Number | no | `null` | min 1 | — | Optional cap. | | `expiresAt` | Date | no | `null` | — | yes | Optional expiry. | | `createdAt` | Date | auto | — | — | yes (desc) | Mongoose timestamp. | | `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. | ## Virtuals None defined. ## Indexes Defined at `backend/src/models/RequestTemplate.ts:283-293`: - `{ categoryId: 1 }` - `{ productType: 1 }` - `{ isActive: 1 }` - `{ createdAt: -1 }` - `{ expiresAt: 1 }` - `{ sellerId: 1, isActive: 1 }` - `{ shareableLink: 1, isActive: 1 }` - `{ productType: 1, isActive: 1 }` - `{ categoryId: 1, productType: 1 }` `shareableLink` and `sellerId` already get indexes from `unique: true` / field-level conventions (see source comment at line 282). ## Pre/Post Hooks None declared. ## Instance Methods None defined. ## Static Methods None defined. ## Relationships - **References**: [[User]] (`sellerId`), [[Category]] (`categoryId`). - **Referenced by**: [[PurchaseRequest]] (`metadata.templateId` as string), [[Review]] (`subjectId` when `subjectType === 'template'`). ## State Transitions ```mermaid stateDiagram-v2 [*] --> active : created active --> inactive : seller toggles off inactive --> active : seller toggles on active --> expired : expiresAt passed active --> capped : usageCount == maxUsage expired --> [*] capped --> [*] ``` > [!note] Soft state > Only `isActive` is persisted directly. `expired` and `capped` are derived at query time using `expiresAt` and `usageCount`. ## Common Queries ```ts // Seller's active templates RequestTemplate.find({ sellerId, isActive: true }).sort({ createdAt: -1 }); // Public template by slug RequestTemplate.findOne({ shareableLink: slug, isActive: true }); // Bump usage atomically RequestTemplate.findOneAndUpdate( { _id, isActive: true, $or: [{ maxUsage: null }, { $expr: { $lt: ['$usageCount', '$maxUsage'] } }] }, { $inc: { usageCount: 1 } }, { new: true } ); // Cleanup expired RequestTemplate.find({ expiresAt: { $lt: new Date() }, isActive: true }); ``` Related: [[PurchaseRequest]], [[User]], [[Category]], [[Review]].