NestJS Modules
Every domain area is a NestJS module. All modules are imported in app.module.ts. The live interactive API reference is generated from the NestJS Swagger output (/api-docs-json) and available at /api-reference when the OpenAPI spec is populated.
Global Setup (app.module.ts)
ConfigModule.forRoot({ isGlobal: true })— env vars available everywhereScheduleModule.forRoot()— enables@Cron()decorators (warranty expiry cron)ThrottlerModule— 100 req/min/IP default; overridable per-endpoint via@Throttle()- Global
ThrottlerGuardviaAPP_GUARD - Express-level rate limiters on auth paths (bypasses NestJS interceptor overhead):
signInRateLimit: 10 req/15min/IP onPOST api/auth/sign-in/emailsignUpRateLimit: 5 req/1hr/IP onPOST api/auth/sign-up/email
Auth
Purpose: Mounts Better Auth at api/auth/*. All auth operations (sign-in, sign-up, session, 2FA, password reset, email verification, Google OAuth) are handled here.
Key files:
src/auth/auth.ts— Better Auth config (see Auth docs)src/auth/auth.controller.ts—@All('*path')catch-all that converts Express requests to Web APIRequestobjects and forwards them toauth.handler()
Endpoints: POST /api/auth/sign-in/email, POST /api/auth/sign-up/email, GET /api/auth/get-session, POST /api/auth/two-factor/verify-totp, and all other Better Auth paths.
Orders
Purpose: Core business logic. Order CRUD, state machine, status transitions, line item management, invoicing, archiving.
Key service methods (OrdersService):
| Method | Description |
|---|---|
create(dto, staffId) | Creates order; generates claimCode when no userId; sends quoteCreatedTemplate if published with known user |
findAll(filters?, ownerUserId?) | Paginated (20/page, max 100) for staff; non-archived published own orders for users |
findOne(id, ownerUserId?) | User path enforces ownership + published + non-archived |
update(id, dto, staffId) | Validates transitions via order-transitions.ts; packaging gate checks 9 photo slots + QA checklist; creates OrderStatusHistory entries; on completed activates warranty items; calls sendStatusEmail |
convertToOrder(id) | Flips type → order, advances orderStatus → confirmed if still in quote statuses |
publish(id) | Sets isPublished = true; sends quoteCreatedTemplate to customer |
archive(id) / unarchive(id) | Sets/clears archivedAt + archivedById |
generateClaimCode() | randomInt-based NNN-NNNN-NNN format (~10M combinations) |
Endpoints (/orders):
| Method | Path | Roles |
|---|---|---|
| GET | /orders | admin, superadmin, user |
| GET | /orders/:id | admin, superadmin, user |
| PATCH | /orders/:id | admin, superadmin |
| POST | /orders/:id/convert | admin, superadmin |
| POST | /orders/:id/archive | admin, superadmin |
| POST | /orders/:id/unarchive | admin, superadmin |
| POST | /orders/:id/publish | admin, superadmin |
| DELETE | /orders/:id | superadmin only |
| GET | /orders/:id/attachments | admin, superadmin, user |
| DELETE | /orders/:id/attachments/:id | admin, superadmin |
| GET | /orders/:id/invoice-attachment | admin, superadmin, user |
| POST | /orders/:id/invoice-attachment | admin, superadmin |
| DELETE | /orders/:id/invoice-attachment | admin, superadmin |
Dependencies: Attachments, Mail, OrderTotals, Prisma
OrderTotals
Purpose: Recalculates Order.subtotal, Order.taxAmount, Order.total after any line item mutation.
Key method: recalc(orderId) — queries all 7 line item tables in parallel, sums discountedSubtotal, lineTax, lineTotal, updates the order.
Called by: Every order item module (OrderItems, OrderOSItems, etc.) after any create/update/delete/reorder.
Order Item Modules (7 modules)
Each follows the same pattern: controller, service, module, DTOs.
| Module | Table | Notes |
|---|---|---|
order-items | OrderComponent | Links to Component catalog; stores serialNumbers: String[] and specSnapshot: Json? |
order-os-items | OrderOS | Links to OperatingSystem catalog; stores licenseKey |
order-service-items | OrderService | Links to Service catalog |
order-warranty-items | OrderWarranty | Links to Warranty catalog; startDate/endDate activated on order completed |
order-shipping-items | OrderShipping | Links to Shipping catalog |
order-peripheral-items | OrderPeripheral | Links to Peripheral catalog |
order-software-items | OrderSoftware | Links to Software catalog; stores licenseKey |
All items store:
displayName(snapshot of catalog name at order time)unitPrice,vatRate(default 24%),discountPercent- Computed:
lineSubtotal,discountAmount,discountedSubtotal,lineTax,lineTotal sortOrderfor display ordering
All modules nest their routes under orders/:orderId.
Builds
Purpose: Build lifecycle management — creation, status tracking, photo uploads, QA, booklet generation.
Key service methods (BuildsService):
| Method | Description |
|---|---|
create(dto) | Validates order exists, enforces 1 build per order |
update(id, dto) | Auto-sets startedAt/finishedAt timestamps; updates Order.fulfillmentStatus via BUILD_TO_FULFILLMENT map; on completed triggers booklet generation |
uploadPhoto(buildId, slot, file) | Replaces existing attachment for named slot, uploads to storage, updates build field |
deletePhoto(buildId, slot) | Removes attachment, nulls the photo field |
regenerateBooklets(buildId) | Fire-and-forget call to BookletsService |
Build status → fulfillment mapping:
in_progress→buildingtesting→testingcompleted→readyfailed→ not mapped (transient state; staff fix and re-transition)
Photo slots (10): coverPhoto, mboIoPhoto, gpuIoPhoto, caseIoPhoto, psuPhoto, powerBtnPhoto, panelAPhoto, panelBPhoto, foamPhoto, thermalPhoto
Dependencies: Attachments, Booklets, Mail, Prisma, Orders
Build Sub-modules
| Module | Description |
|---|---|
build-logs | Event log entries on a build (BuildEventLog model). Types: note, incident, correction, observation, measurement, issue, fix, info. Severities: info, warning, error, critical |
build-timeline | Timeline step entries (BuildTimelineStep model). Types: start, parts_received, assembly, cable_management, os_install, software_setup, testing, packaging, shipped, milestone, update |
test-runs | Benchmark/stress test records (TestRun model). Types: benchmark, stress_test, memory_test, thermal_check, storage_test, stability_test, incident |
Payments
Purpose: Viva Smart Checkout integration. See Payment Flow for the full flow.
Key methods:
| Method | Description |
|---|---|
getAccessToken() | OAuth2 client_credentials; cached 25s |
createOrder(orderId, userId) | Creates Viva payment order; saves vivaOrderCode |
verifyPayment(transactionId, orderCode, orderId) | Idempotent; verifies statusId === 'F', amount (in euros), merchantTrns; updates paymentStatus → paid |
handleWebhook(body) | Handles async Viva events; never throws (Viva retries on non-200) |
verifySignature(body, signature) | HMAC-SHA256 timing-safe compare |
Endpoints:
| Method | Path | Auth |
|---|---|---|
| POST | /payments/create-order | Session required, user/admin/superadmin |
| POST | /payments/webhook | No auth; signature-verified |
| GET | /payments/webhook | No auth; returns { Key: VIVA_WEBHOOK_KEY } |
| POST | /payments/verify | Session required, user/admin/superadmin |
Portal
Purpose: Customer-facing endpoints that require session but not staff role.
Key method: PortalOrdersService.acceptQuote(userId, orderId) — allows a customer to signal acceptance of a quote. Does NOT change orderStatus — creates a status history entry with "Customer accepted the quote via portal" and sends a staff notification email to STAFF_NOTIFY_EMAIL.
Why no status change on accept? A quote being accepted is a signal, not a state change. Staff must still convertToOrder to move to the confirmed state.
Claim
Purpose: Allows customers to claim an order using a claimCode without being logged in.
Flow:
- Customer receives claim link with
NNN-NNNN-NNNcode - POSTs to
POST /claim(unauthenticated, throttled 10/15min) ClaimService.claim()looks up order byclaimCode- If already claimed by another user →
ForbiddenException - Advances
orderStatus → claimedif currentlydraftorquote - Sets
userId, nullsclaimCode, sendsorderClaimedTemplate
Attachments
Purpose: Polymorphic file storage for orders, builds, timeline steps, event logs, test runs.
Key methods: upload(), findByEntity(), findByOrder(), remove(), findInvoiceAttachment(), resolvePath()
MIME whitelist: image/jpeg, image/png, image/webp, image/gif, application/pdf. Uses file-type library to verify magic bytes (not just the declared MIME header).
Dependencies: Storage (IStorageService), Prisma
Booklets
Purpose: PDF generation for completed builds. See Booklets for details.
Mail
Purpose: Transactional email via Nodemailer + Zoho SMTP. See Email for all triggers and templates.
Admin
Purpose: Staff-only user management and system queries.
Key methods (AdminUsersService):
| Method | Description |
|---|---|
findAll(opts) | Paginated users; filters: role, banned, search (email/name) |
findOne(id) | Full user detail with order/session/build counts |
ban(id, dto) | Only bans role = user; revokes all sessions |
unban(id) | Clears ban fields |
changeRole(id, dto) | Prevents self-role-change; audit-logged |
Additional endpoints:
GET /admin/me— returns current authenticated staff userGET /admin/warranties/expiring?days=30— active warranties expiring within N days (1–365)GET /admin/users/search?email=<q>— email autocomplete (10 results max)
Settings
Purpose: Profile management available to all authenticated users.
Endpoints: Profile update, password change, email change request. Available to user, admin, superadmin.
Catalog Modules (6 modules)
| Module | Table | Notes |
|---|---|---|
components | Component | Has per-category spec JSON, price history (ComponentPriceHistory) |
operating-systems | OperatingSystem | |
services | Service | |
warranties | Warranty | durationMonths field |
shipping | Shipping | |
peripherals | Peripheral | |
software | Software | licenseType: perpetual, subscription, oem |
All catalog items have active: boolean and basePrice: Decimal.
Quotes (Public)
Purpose: Unauthenticated endpoint to view a quote by claimCode. Used on the public /quote/[claimCode] page before a customer has an account.
WarrantyNotifications
Purpose: Daily cron that sends expiry reminders.
Schedule: 0 9 * * * (09:00 Athens time, Europe/Athens timezone)
Logic: Queries OrderWarranty where active = true and endDate is between now and now+30 days. Cap: 500 per run. 200ms delay between sends to respect Zoho SMTP rate limits.
Urgency: Color changes in the email at ≤7 days remaining.
Health
Purpose: Unauthenticated GET /health for Uptime Kuma monitoring.
Waitlist
Purpose: Public email capture for the coming-soon page. Sends notification email to WAITLIST_NOTIFY_EMAIL (comma-separated list).
Prisma
Purpose: Singleton PrismaService extending PrismaClient. Injected throughout the app.
Note: Better Auth uses its own separate PrismaClient instance (with PrismaPg adapter) for its own operations — it does not share the app's PrismaService.