Skip to main content

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 everywhere
  • ScheduleModule.forRoot() — enables @Cron() decorators (warranty expiry cron)
  • ThrottlerModule — 100 req/min/IP default; overridable per-endpoint via @Throttle()
  • Global ThrottlerGuard via APP_GUARD
  • Express-level rate limiters on auth paths (bypasses NestJS interceptor overhead):
    • signInRateLimit: 10 req/15min/IP on POST api/auth/sign-in/email
    • signUpRateLimit: 5 req/1hr/IP on POST 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 API Request objects and forwards them to auth.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):

MethodDescription
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):

MethodPathRoles
GET/ordersadmin, superadmin, user
GET/orders/:idadmin, superadmin, user
PATCH/orders/:idadmin, superadmin
POST/orders/:id/convertadmin, superadmin
POST/orders/:id/archiveadmin, superadmin
POST/orders/:id/unarchiveadmin, superadmin
POST/orders/:id/publishadmin, superadmin
DELETE/orders/:idsuperadmin only
GET/orders/:id/attachmentsadmin, superadmin, user
DELETE/orders/:id/attachments/:idadmin, superadmin
GET/orders/:id/invoice-attachmentadmin, superadmin, user
POST/orders/:id/invoice-attachmentadmin, superadmin
DELETE/orders/:id/invoice-attachmentadmin, 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.

ModuleTableNotes
order-itemsOrderComponentLinks to Component catalog; stores serialNumbers: String[] and specSnapshot: Json?
order-os-itemsOrderOSLinks to OperatingSystem catalog; stores licenseKey
order-service-itemsOrderServiceLinks to Service catalog
order-warranty-itemsOrderWarrantyLinks to Warranty catalog; startDate/endDate activated on order completed
order-shipping-itemsOrderShippingLinks to Shipping catalog
order-peripheral-itemsOrderPeripheralLinks to Peripheral catalog
order-software-itemsOrderSoftwareLinks 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
  • sortOrder for 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):

MethodDescription
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_progressbuilding
  • testingtesting
  • completedready
  • failed → 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

ModuleDescription
build-logsEvent log entries on a build (BuildEventLog model). Types: note, incident, correction, observation, measurement, issue, fix, info. Severities: info, warning, error, critical
build-timelineTimeline step entries (BuildTimelineStep model). Types: start, parts_received, assembly, cable_management, os_install, software_setup, testing, packaging, shipped, milestone, update
test-runsBenchmark/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:

MethodDescription
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:

MethodPathAuth
POST/payments/create-orderSession required, user/admin/superadmin
POST/payments/webhookNo auth; signature-verified
GET/payments/webhookNo auth; returns { Key: VIVA_WEBHOOK_KEY }
POST/payments/verifySession 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:

  1. Customer receives claim link with NNN-NNNN-NNN code
  2. POSTs to POST /claim (unauthenticated, throttled 10/15min)
  3. ClaimService.claim() looks up order by claimCode
  4. If already claimed by another user → ForbiddenException
  5. Advances orderStatus → claimed if currently draft or quote
  6. Sets userId, nulls claimCode, sends orderClaimedTemplate

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):

MethodDescription
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 user
  • GET /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)

ModuleTableNotes
componentsComponentHas per-category spec JSON, price history (ComponentPriceHistory)
operating-systemsOperatingSystem
servicesService
warrantiesWarrantydurationMonths field
shippingShipping
peripheralsPeripheral
softwareSoftwarelicenseType: 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.