Skip to main content

Discovery Module

Public-facing kiosk/catalog API for product browsing and quote requests. All endpoints are unauthenticated (@IsPublic()).

Architecture

discovery/
├── domain/
│ └── discovery-repository.domain.ts # Port interface + domain types
├── application/
│ └── discovery.service.ts # Use cases (orchestration)
├── infrastructure/
│ └── discovery.repository.ts # Kysely adapter (implements port)
└── interfaces/
├── discovery.controller.ts # HTTP routes
└── dtos/
├── discovery-products-query.dto.ts
├── create-quote.dto.ts
└── required-location-id.query.ts

Dependency flow: Controller → Service → Domain Interface ← Repository (adapter)

Domain Concepts

ConceptDescription
Discovery ConfigPer-location settings: enabled flag, low-stock threshold, polling interval
Catalog ProductVariant-level item with price, availability status, and label
Availability Statusin_stock (qty > threshold), low_stock (0 < qty ≤ threshold), out_of_stock
Availability MatrixPer-location stock breakdown for a product (current location listed first)
Kiosk QuoteCustomer-initiated quote request from a public kiosk terminal

API Endpoints

All endpoints are public (no auth required).

MethodPathDescription
GET/discovery/config?locationId=Get discovery config for a location
GET/discovery/products?locationId=&...Paginated catalog with filters
GET/discovery/products/:productId?locationId=Product detail + availability matrix
POST/discovery/quotesCreate kiosk quote (rate-limited: 5/hr/IP)

Query Parameters — Catalog Listing

ParamTypeRequiredDefaultDescription
locationIdUUIDYesScopes availability to this location
searchstringNoSearch name, SKU, description
categoryIdUUIDNoFilter by category
collectionIdUUIDNoFilter by merchandising collection
inStockOnlybooleanNoOnly items with stock > 0
onSaleOnlybooleanNoOnly items marked on sale
pagenumberNo1Page number (1-based)
limitnumberNo24Items per page (max 100)

Quote Request Body

{
"locationId": "uuid",
"customerName": "John Doe",
"customerEmail": "john@example.com",
"customerPhone": "+50212345678",
"notes": "Optional notes",
"items": [
{
"productId": "uuid",
"productName": "Product Name",
"quantity": 2,
"locationId": "uuid (optional)",
"unitPrice": 25.99
}
]
}

At least one of customerEmail or customerPhone is required (enforced by QuotesService).

Design Decisions

  1. Variant-level catalog: Each product variant appears as a separate catalog item (one row per size/color), not grouped by parent product.

  2. Availability thresholds: Configured per-location via discoveryConfig table. Defaults: low-stock threshold = 5, polling interval = 60s.

  3. Quote delegation: Quote creation delegates to QuotesService.createQuoteFromKiosk() — the discovery module does not own quote persistence.

  4. Rate limiting: The quote endpoint uses NestJS Throttler (5 requests/hour/IP) to prevent abuse from public kiosks.

  5. Current location first: In the availability matrix, the requesting location appears first, followed by other locations sorted alphabetically.