Files
nick-doc/02 - Data Models/RequestTemplate.md
2026-06-01 19:02:03 +04:00

8.4 KiB

title, tags, aliases
title tags aliases
RequestTemplate
data-model
mongoose
postgres
Template
Request Template
IRequestTemplate

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 definition backend/src/models/RequestTemplate.ts:335 — model export backend/src/db/schema/requestTemplate.ts:35 — Drizzle table definition backend/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.id and category_id → categories.id.
  • Matching single/compound indexes for seller, category, product type, active state, expiry, and public slug lookups.
  • JSONB specifications and 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.templateId as string), Review (subjectId when subjectType === 'template').

Checkout Semantics

  • The seller chooses deliveryInfo.deliveryType on the template. The buyer checkout step only collects the required fulfillment details: a physical address for physical, a receiving email for online, and both when a cart mixes physical and online templates.
  • batch-convert copies 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 isActive is persisted directly. expired and capped are derived at query time using expiresAt and usageCount.

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.