Skip to content
Ali Hassan — home

Work

FleetOps360

Shipments, dispatch, drivers, vehicles, proof of delivery, invoicing and reports in one application, with six roles each seeing only their own work.

Role
Full-stack engineer
Status
Built
  • Next.js
  • React
  • TypeScript
  • PostgreSQL
  • Prisma
  • Tailwind CSS
  • shadcn/ui
  • TanStack Table
  • Recharts
  • S3-compatible storage
  • Docker Compose
Fleet dashboard with shipments by status, the dispatch queue and invoices in a role-based logistics app.

The problem

Context
A freight business runs customers, shipments, dispatch, drivers, vehicles, proof of delivery, invoices and reports, usually across several tools. Each person needs a different slice of it: a dispatcher assigns work, a driver sees today's jobs, finance sees invoices, a customer sees only their own shipments, and anyone with a reference number can check a delivery's progress.
Constraints
One Next.js application with business logic in Server Actions and no separate API. Custom authentication rather than a third-party service, and the whole stack (database, file storage and mail) had to come up locally with one command so it can be self-hosted.
What was at stake
One missed authorisation check would show a customer another customer's shipments, prices or internal notes, or expose drivers' details and private delivery photos and signatures. The public tracking page has to reveal progress and nothing else.

What I built

Access control in three layers

  • Route check, data layer, action guard

    A lightweight check at the edge only redirects signed-out visitors and makes no decision about what anyone may do. A data-access layer resolves the user and their permissions from the database once per request, and every Server Action checks the specific permission before touching data.

  • Permissions as data, not role names

    Six roles (Super Admin, Admin, Dispatcher, Driver, Customer and Finance) map to resource-and-action permissions defined in one place and stored in the database. Feature code asks for a permission, never for a role by name, so changing what a role can do is a one-line change.

  • Ownership checked separately from role

    Whether someone may see a particular shipment is answered by one function: staff with the right permission, the customer who owns it, or the driver assigned to it. Detail pages and file downloads use the same answer instead of re-deriving it.

Files and personal data

  • No public links to private files

    Proof-of-delivery photos, signatures and documents live in a private bucket. They are served only through an authorised download route that works out which record a file belongs to, checks access to that record, and refuses anything it does not recognise.

  • A tracking page that withholds by design

    The public tracking page selects only the reference, status, cities, timings and a trimmed timeline. Prices, internal notes, contacts and addresses are never fetched, so they cannot leak through it.

  • Sessions that are useless if stolen from the database

    Only a hash of each session token is stored; the token itself lives in an HTTP-only cookie. Passwords are hashed with bcrypt, sign-in takes the same time whether or not the email exists, and password-reset tokens are single-use and expire after an hour.

Data integrity

  • Status changes as state machines

    Invoices can only move along allowed transitions, from draft to sent to paid or cancelled, with the timestamps stamped on each move. Shipment statuses have locked and terminal states, and validation rejects negative amounts and back-to-front delivery windows.

  • Reference numbers that tolerate collisions

    Shipment and invoice references follow a dated sequence on a unique column, and a clash between two requests at the same moment is retried automatically instead of failing the save.

Operations

  • One command to run everything

    The app, database, file storage and a mail catcher start together with one Docker Compose command, so the full stack runs locally without any outside services.

Tell me what you’re building and where it’s stuck.

I’ll tell you the cleanest path forward, including if it’s “don’t build that.”

Or write tocontact@alihassan.dev

Ali Hassan in a dark winter jacket, looking off to one side, standing in a stone courtyard with a minaret and cloudy sky behind him.