Files
nick-doc/02 - Data Models/RequestTemplate.md

135 lines
6.0 KiB
Markdown

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