8.4 KiB
title, tags, aliases
| title | tags | aliases | ||||||
|---|---|---|---|---|---|---|---|---|
| RequestTemplate |
|
|
RequestTemplate
Last updated: 2026-06-01 — Postgres schema and backfill surface documented.
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, seller-selected delivery mode, payment rail allowlist, 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:83— Mongoose schema definitionbackend/src/models/RequestTemplate.ts:335— model exportbackend/src/db/schema/requestTemplate.ts:35— Drizzle table definitionbackend/src/db/backfill/backfill-requestTemplates.ts:1— Mongo → Postgres backfill
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 |
— | Seller-selected delivery channel. Buyers cannot override this at checkout. |
deliveryInfo.notes |
String | no | — | — | — | Seller notes about delivery. |
deliveryInfo.email |
String | no | — | email regex when non-empty | — | Legacy/optional field. Template checkout now asks the buyer for a receiving email when deliveryType === "online". |
paymentConfig.useShopDefault |
Boolean | no | true |
— | — | When false, the template's own chain/token allowlist overrides ShopSettings. New template UI defaults this to false so sellers choose rails explicitly. |
paymentConfig.allowedChains[] |
Number[] | no | [1, 56] |
must contain at least one positive chain id | — | Chain ids accepted for this template, e.g. 1 Ethereum, 56 BSC. Empty arrays are rejected. |
paymentConfig.allowedTokens[] |
String[] | no | ["USDC", "USDT"] |
must contain at least one non-empty token symbol | — | Settlement tokens accepted for this template. Empty arrays are rejected. |
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).
Postgres migration 0010_request_templates.sql creates request_templates with:
request_templates_legacy_object_id_uq: idempotent Mongo bridge for backfill.request_templates_shareable_link_uq: public slug uniqueness.- FK columns
seller_id → users.idandcategory_id → categories.id. - Matching single/compound indexes for seller, category, product type, active state, expiry, and public slug lookups.
- JSONB
specificationsand scalar/array columns for delivery, service, proposal, payment rails, images, and attachments.
Runtime service wiring is not cut over yet; RequestTemplateService still uses Mongoose directly.
Pre/Post Hooks
None declared.
Instance Methods
None defined.
Static Methods
None defined.
Relationships
- References: User (
sellerId), Category (categoryId). - Referenced by: PurchaseRequest (
metadata.templateIdas string), Review (subjectIdwhensubjectType === 'template').
Checkout Semantics
- The seller chooses
deliveryInfo.deliveryTypeon the template. The buyer checkout step only collects the required fulfillment details: a physical address forphysical, a receiving email foronline, and both when a cart mixes physical and online templates. batch-convertcopies the seller's delivery mode into each generated PurchaseRequest and overlays the buyer-supplied billing/email details.- Payment checkout resolves allowed rails through
paymentConfig: template override first, then ShopSettings, then the global supported default. A template with an explicit empty chain or token list is invalid.
State Transitions
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
isActiveis persisted directly.expiredandcappedare derived at query time usingexpiresAtandusageCount.
Common Queries
// 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.