FULL STACK · NESTJS + REACT + SUPABASE · PHASE 2
CovershiftShift swaps, secured down to the database.
Covershift is a multi-tenant platform for scheduling shifts in operational teams such as airport security, retail or healthcare. Managers build the week, staff swap shifts and a manager approves, under one rule that cannot be bypassed: nobody works a position without a certification valid until the shift ends. I set it up as a B2B product, in phases with written reports and architecture decisions, in a TypeScript monorepo developed with Claude Code.
- Role
- Personal AI-assisted project: architecture, review, testing and documented decisions
- Stack
- NestJS · React 19 · TypeScript · Supabase (Postgres + Auth) · TypeORM · Zod · TanStack Query
- Quality
- Over 380 tests · 14 Playwright e2e tests with axe (WCAG 2.1 AA) · CodeQL, gitleaks, Trivy
- Status
- Phases 1 and 2 complete · AWS deployment prepared · code on GitHub
The goal
In operational teams shifts change hands all the time, and every swap must follow precise rules: whoever takes a shift must be cleared for the position and hold a certification that does not expire before it ends. The goal was to make swapping easy for staff and verifiable for managers, in an application where one organisation’s data cannot leak into another, not even through a backend bug.
How it works
A pnpm monorepo with three packages: the NestJS API, the React web app and shared Zod contracts. Supabase provides Postgres and authentication, but all data goes through the API: it verifies the token, then runs every request in a transaction that impersonates the user, so Postgres Row Level Security policies decide which rows exist for them. Every domain rule has a readable error in the service and a database constraint that cannot be skipped.
Features
Weekly schedule
A Monday-to-Monday week in the organisation’s time zone, correct across daylight saving changes. The organisation lives in the URL and is checked against the user’s profile; one click switches between everyone and my shifts.
Shifts with eligibility checks
A manager can only assign a shift to an active member who is cleared for the position, holds a certification valid until the shift ends and has no overlaps. An override needs a 5–500 character reason, stored on the shift.
Swaps between colleagues
A direct offer to a colleague or an open one, with accept, decline and cancel, and an inbox of received offers. The swap follows a state machine that lives in a database trigger.
Manager approvals
The manager approves or rejects accepted swaps, but not ones they are part of. If a role is revoked while the page is open, the first 403 reloads the profile and the page turns read-only, showing the reason returned by the API.
Documented API
OpenAPI at /docs generated from the same schemas used for validation, keyset pagination that stays correct with identical timestamps, and RFC 9457 errors with a stable code on every path, including unknown routes and oversized bodies (413 rather than 500).
Tests under the real policy
Components tested with MSW mocks validated by the API’s own schemas. In Playwright, a full swap between three users, from offer to reassignment, with an axe check on every run and a strict Content Security Policy active in tests too, which fail on any violation.
Screens



Real screenshots of the web app, October 2026, generated by a Playwright script from the seeded demo data and under the same CSP as the end-to-end tests. Names and organisation are fictitious.
Technical decisions
The database is the last line of defence
NestJS verifies the Supabase token via JWKS, with no shared secret, and opens a transaction with the user’s role and claims. The API connects as a role with no privileges of its own: a query outside that transaction fails instead of leaking data. Turning RLS off makes the matching tests fail.
An explained rule, an unskippable constraint
The service returns readable errors, such as an expired certification or an overlap. The database enforces the same rules anyway: composite foreign keys to isolate tenants, an exclusion constraint against double-booking and the swap state machine in a trigger.
One contract, three uses
The shared Zod schemas produce validation, OpenAPI documentation and frontend types. The openapi.json file is committed, and a test fails CI if the code changes without updating it.
Reveal nothing
A resource from another organisation returns 404, just like one that does not exist: the response does not reveal whether it is there. The post-login return parameter only accepts same-origin paths, and a custom ESLint rule fails the build if an SQL query is built by string concatenation.
A single CI gate
A single required check depends on every job and fails even if one is skipped or cancelled. Around it: CodeQL, gitleaks over the full history, dependency audits, Trivy on the Docker image, PL/pgSQL linting and SHA-pinned Actions. Major choices are recorded in ADRs and phase reports.