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.
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
- 01CRM: staff and agent screens
- 02REST API: JWT, role guards
- 03Database: MySQL via Prisma
- 04PDF rendering: templates to forms
- 05Object storage: S3-compatible
- 06Scheduler: time-based runs
- 07Email: mail and codes
- 08Feature flags: off means refused
- 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.
Decisions
The engineering calls that shaped the build, and why each one was made.
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.
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.
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.
Risky modules ship dark.
Discontinuance and demise handling sit behind flags. Switched off, the API refuses the request.
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.
| Figure | What it counts |
|---|---|
| 14 | states in the application lifecycle |
| 5 | review gates on a sale, plus one for agent KYC |
| 19 | user roles |
| 48 | hand-written SQL schema files |
| 387 | test spec files |
Built with
- NestJS
- Prisma
- MySQL
- Nuxt 4
- Tailwind CSS
- DaisyUI
- Puppeteer
- Handlebars
- S3-compatible storage
- Swagger
- Jest