Skip to content

200OK

Application lifecycle and review system for a trust business

Multi-desk review, signed documents and strict visibility rules for a trust business selling through an agent network.

Kind
Regulated workflow for a trust business
Our part
A partner collaboration on a shared codebase. This page describes our contributions only.

404 Not Found

What was missing

A trust-product business sells placements through an agent network. Every application moves from draft through several review desks (compliance, admin, trustee, finance, signed documents) to approval, completion, maturity or early termination. Commission, payouts, statutory documents and notices all depend on that path. What was missing was a lifecycle that stays correct and auditable while strict rules decide who may see what. We worked inside a shared codebase, alongside a partner team.

Constraints

What the system had to hold to, before a line was written.

Review
Maker-checker review gates, signed documents as the KYC record, and steps that cannot be undone once taken.
Roles
19 roles, each with its own view of the same records.
Visibility
A hard visibility rule: an agent sees every downline name and code, the sales of direct recruits only, and their own settlors only.
Branches
Another team pushes to the same repositories, so our work is cut from the remote base branch each day.
Schema
Live data: schema changes are hand-written SQL, run by the database owner, some before the API deploys.

The system

A Nuxt CRM talks to a NestJS REST API over HTTPS, with JWT sign-in and role guards. Prisma maps the API to MySQL. Forms are filled from Handlebars templates, rendered to PDF by Puppeteer and stored in S3-compatible object storage, which the browser reads through presigned links. Transactional email and one-time passwords go through an email provider, with in-app notifications alongside. A scheduler runs time-based work such as discontinuance. Risky modules ship dark behind environment flags. Schema changes travel out of band: sectioned SQL files, indexed with the order they must run in, applied by hand before the deploy that needs them.

Figure 1Drawn from the build
HTTPSqueriesfillstoreuploadpresigned readrunssendgatebefore deploy01CRMstaff and agent screens02REST APIJWT, role guards03DatabaseMySQL via Prisma04PDF renderingtemplates to forms05Object storageS3-compatible06Schedulertime-based runs07Emailmail and codes08Feature flagsoff means refused09SQL filessectioned, run by hand

CRM → REST API → MySQL, with PDF rendering, object storage and email beside the API, and SQL files applied out of band before each deploy.

  • Built by us
  • Data held
  • Third party
  • Offline or out of band
Read the drawing as text

Parts

  1. 01CRM: staff and agent screens
  2. 02REST API: JWT, role guards
  3. 03Database: MySQL via Prisma
  4. 04PDF rendering: templates to forms
  5. 05Object storage: S3-compatible
  6. 06Scheduler: time-based runs
  7. 07Email: mail and codes
  8. 08Feature flags: off means refused
  9. 09SQL files: sectioned, run by hand

Flows

  • CRM to REST API: HTTPS
  • REST API to Database: queries
  • REST API to PDF rendering: fill
  • PDF rendering to Object storage: store
  • REST API to Object storage: upload
  • CRM to Object storage: presigned read (alternate or out of band)
  • Scheduler to REST API: runs
  • REST API to Email: send
  • Feature flags to REST API: gate
  • SQL files to Database: before deploy (alternate or out of band)

The screen

Most of this system sits behind a login, so its key screen is redrawn here as a schematic. Bars stand in for every name and figure.

DraftSubmittedReviewApprovedCompletedReview gatesComplianceAdminTrusteeFinanceSigned documents
An application record as a schematic: step tabs from draft to completion, and the review gates with their flags.Schematic, no real data

Decisions

The engineering calls that shaped the build, and why each one was made.

  1. Image attachments through a registry, not a new column.

    A remark is a column, and a file can only point at a row, so attachments key on table, row and field. A central registry declares which pairs may carry images, and its "withheld from" rule is required: leaving it out does not compile.

  2. A partial success is deliberately not retryable.

    A group upload lands wherever it can. A retry would duplicate files on rows that already took them, and per-row refusals are deterministic, so nothing is lost. Files attach only to rows that succeeded, never to rows that were merely selected.

  3. A new state instead of a holder column.

    Send-backs go to the agent's person-in-charge first. A new state was the safer choice: the existing allowlists refuse a state they do not know, but would silently ignore a new column.

  4. Risky modules ship dark.

    Discontinuance and demise handling sit behind flags. Switched off, the API refuses the request.

  5. Schema by hand, in order.

    Each change is a sectioned SQL file, and an index says which file runs before which deploy. No generator, and nothing pushed to the database from a laptop.

200OK

Shipped

Our contributions across the lifecycle: image attachments on remarks, a reconciliation step for send-backs, a document library with a delete lock, a two-phase review with traffic-light flags, and a preview page that renders every system message on both email and in-app channels. Each shipped on the shared branch, alongside the partner team's own work.

Counts of the shared codebase, taken from its code. They are not our share of it. Counted on 3 November 2026. No figure here is a business result.
FigureWhat it counts
14states in the application lifecycle
5review gates on a sale, plus one for agent KYC
19user roles
48hand-written SQL schema files
387test spec files

Built with

  • NestJS
  • Prisma
  • MySQL
  • Nuxt 4
  • Tailwind CSS
  • DaisyUI
  • Puppeteer
  • Handlebars
  • S3-compatible storage
  • Swagger
  • Jest