Project 4 // _qcode
QCode
A prescription as a QR code — issued by one person, carried by another, redeemed by a third, and provable afterwards.
client
private client — Swiss healthcare · confirm before publishing
timeline
25 months · Aug 2021 → Sep 2023 · shipped as medicodeapp.com, since retired
role
full-stack developer
scope
## the-problem
A paper prescription is a bearer token with no memory. It doesn't know how much has already been collected, it can't tell you who dispensed it, and once it leaves the issuer's hands the issuer is blind. Digitising it naively just moves that blindness to a screen.
The hard part isn't the QR code — it's that four different people touch one record and none of them may see the same thing. The issuer needs their own patients and nobody else's. The patient needs the code and the count. The dispenser needs today's entitlement, not the medical history. The operator needs all of it, to audit. Get that wrong once and it's a data-protection incident, not a bug report.
On top of that, the products aren't free text. Swiss dispensing runs on a national article catalogue, so a code that says "painkillers" is worthless — it has to resolve to the exact article number the counter will scan.
## the-approach
- ▸five roles — Superadmin, Admin, Adminclient, Reader, Client — declared per endpoint, so the role list sits next to the handler rather than in a middleware table nobody reads
- ▸made the authorization check hit the database, not the token: the JWT carries an id, the role is re-read from the user record on every request, so a demotion takes effect immediately
- ▸modelled the code as a lifecycle, not a flag — issued, sent, given — with a date range, a per-day limit, a renewable flag and the issuer's signature on the record
- ▸consent by scan: a patient's own QR is a signed token, and an issuer scanning it adds that patient to their allowed list — the issuer's client list is that list, so access is granted by the patient, in person
- ▸every collection appends to a log with a per-day counter, so "three times a day" is an enforceable number rather than a note
- ▸products resolve against the Swiss Documedis article catalogue by article number, cached locally on first use — searched live, stored once, referenced by id from then on
- ▸delivery on two channels, email and in-app notification, with an Expo client for the patient that carries the code, the count and the alerts
- ▸an operator console on top: account oversight, catalogue import, an AVS registry import and a value-per-patient rollup over everything dispensed
// authorization — the token proves who, the row decides what
type Role =| "Superadmin" | "Admin" | "Adminclient" | "Reader" | "Client";const VerifyToken = (roles: Role[], next: Handler) =>async (req: Request, params: any) => {const token = req.headers.get("authorization")?.split(" ")[1];if (!token) return json({ msg: "authorization denied" }, 401);const { id } = jwt.verify(token, process.env.JWT_SECRET);req.user = id;// The token says who you are. What you may do is re-read from// the row every request, so a demotion lands on the next call// instead of whenever the last-issued token happens to expire.if (roles.length) {const q = { _id: id, role: { $in: roles } };if (!(await User.findOne(q))) return json({ msg: "denied" }, 401);}return next(req, params);};// Declared at the endpoint, in view of the handler it guards.export const POST = VerifyToken(["Reader"], markCodeGiven);
## the-architecture
// architecture — one code, four roles, one audit trail
issuer & operator
Next.js dashboards
- issue, send, revise a code
- catalogue + account oversight
patient app
Expo · React Native
- carries the code
- push alerts, print/share
dispenser
in-browser QR scan
- scan, read entitlement
- mark as given
role gate
per-route, DB-backed
- 5 roles declared per endpoint
- role re-read from the row
API
Next.js route handlers
- 41 endpoints
- codes, products, users, AVS
code lifecycle
issued → sent → given
- date range + per-day limit
- collection log with counter
MongoDB
codes · products · users
- signature + revision history
- allowed-clients consent list
Documedis
Swiss article catalogue
- search by article number
- cached locally on first use
code delivery
- the QR, sent to the patient
## the-results
5
roles, re-checked at every endpoint
41
API routes in one Next.js app
25 mo
from first screen to retirement
- ✓one record serves four roles without any of them seeing the others' view of it
- ✓the prescription carries its own history — issued by, sent, dispensed by, collected when and how often
- ✓codes reference real catalogue articles, so what's issued is what the counter dispenses
- ✓shipped web, mobile and operator console off one Next.js app
// want something like this built for you?
start-a-project