Skip to main content
Ahmed Salama

Corporate training · gamification

HNI Gamified Training

An enterprise training platform delivering gamified programmes to corporate cohorts, through three independently authenticated portals.

Context

HNI is a corporate training and people-development company in the UAE, and gamification is a service it sells rather than a flourish on top of one. This is the platform that delivers it. Training runs as games rather than modules: an administrator builds a cohort, attaches games to it, assigns the staff of a client company, and the platform carries it from there. Three audiences use it and none of them shares a code path with the others: trainees work through the material assigned to them, trainers oversee the cohorts they have been given, and administrators build everything the other two see. Underneath, an administrator creates a cohort with a date window, attaches training games to it, assigns trainees and a trainer, and the platform tracks what happens next. The content itself lives elsewhere. A game here is a catalogue record pointing at a hosted experience by embed, link or QR code, with its own screenshots and two separate document libraries, one for trainees and one for trainers. Everything a learner or a trainer reads is authored in both English and Arabic.

The problem

Three audiences with genuinely different authority over the same records is the constraint that shapes the whole system. A trainee, a trainer and an administrator can all be looking at one cohort, and what each is allowed to see and change is different at every level: a trainer sees across a cohort but cannot create one, a trainee sees only their own assignments, and material written for trainers must never be reachable by the people they train. Enforcing that with a role flag on one user table is how systems of this shape leak, because every query then carries the responsibility and one missing clause is a disclosure. The second constraint is that the client is a training business rather than a software one. Programmes get retuned, cohorts get rebuilt, and staff rosters arrive as spreadsheets from corporate customers, so anything that requires an engineer to change is a bottleneck in somebody else business.

The solution

The separation is structural rather than conditional. Trainees and trainers are different entities with different tables, different authentication guards and different tokens, so a trainer is not a user with a flag set. Trainer material and trainee material live in two separate document libraries rather than one table with a visibility column, which means the question of whether a trainee can reach a trainer document is answered by the schema instead of by a query. Each module then carries three parallel controller and resource stacks, one per audience, so the same cohort is shaped differently for whoever asked for it. Around that sit the things a training business needs to run itself: staff rosters imported from spreadsheets as queued, chunked, validated jobs; a permission catalogue assembled from every module and presented to administrators as a checkbox tree; soft deletion with trash and restore on everything content-bearing, stamped with who did it; and notifications reaching people in the app, by email and by push.

Architecture

One Laravel 12 deployment, divided into ten modules over a single shared database, with three JWT guards running side by side: one for trainees, one for trainers, one for administrators. Each module resolves its own routes per guard, which is why the games module runs to roughly a hundred and forty files for seven tables. That is the cost of the design and it is paid deliberately: three audiences that never share a controller cannot accidentally share a response. The catalogue holds cohorts, games, categories and the documents attached to them, and the games themselves are hosted outside the platform, so a trainee opening one leaves for an embed or a QR target rather than being served content from here. Work that should not sit on a request is queued, roster imports and notification fan-out among it, and the queue runs on the database driver in the checked-out configuration with Redis in production. Activity is written to an append-only log across three event streams, carrying IP and user agent, pruned at six months. Alongside it five counters are incremented as events arrive, for logins, game views, game plays, document views and cohort views, which is what the dashboards read instead of aggregating the log on every request.

System diagram: HNI Gamified Training topologyNext.js. Where a learner sees the cohorts and games assigned to them and the documents that come with them.CLIENTTrainee portalNext.js, and a separate identity rather than a user with a role: its own table, its own guard, its own token.CLIENTTrainer portalNext.js. Builds the cohorts, the catalogue and the permissions, and imports staff rosters from spreadsheets.CLIENTAdmin dashboardA modular monolith of ten modules over one shared database, running three JWT guards side by side. Each module carries a controller and resource stack per audience, so no two audiences share a response.SERVICELaravel 12 APIThe training experiences themselves, which live outside the platform. A trainee reaches one by embed, link or QR code rather than being served it from here.EXTERNALHosted gamesOne shared database behind all ten modules, holding cohorts, the game catalogue, categories, documents and both identity tables, with bilingual translation tables alongside five of them.DATAMySQLAppend-only telemetry across three event streams with IP and user agent, pruned at six months. Five counters are incremented as events arrive and are what the dashboards read.DATAActivity logCarries what must not sit on a request: chunked roster imports and notification fan-out. Runs on the database driver in the checked-out configuration, with Redis in production.QUEUEQueueCloud Messaging, carrying push to trainees and trainers alongside in-app and email delivery.EXTERNALFirebaseassignments, documentscohort oversightcohorts, catalogue,rostersembed or QRread / writeevents + countersimports, notificationspush delivery
Read this diagram as text
Trainee portalClient
Next.js. Where a learner sees the cohorts and games assigned to them and the documents that come with them.
Trainer portalClient
Next.js, and a separate identity rather than a user with a role: its own table, its own guard, its own token.
Admin dashboardClient
Next.js. Builds the cohorts, the catalogue and the permissions, and imports staff rosters from spreadsheets.
Laravel 12 APIService
A modular monolith of ten modules over one shared database, running three JWT guards side by side. Each module carries a controller and resource stack per audience, so no two audiences share a response.
Hosted gamesExternal
The training experiences themselves, which live outside the platform. A trainee reaches one by embed, link or QR code rather than being served it from here.
MySQLData
One shared database behind all ten modules, holding cohorts, the game catalogue, categories, documents and both identity tables, with bilingual translation tables alongside five of them.
Activity logData
Append-only telemetry across three event streams with IP and user agent, pruned at six months. Five counters are incremented as events arrive and are what the dashboards read.
QueueQueue
Carries what must not sit on a request: chunked roster imports and notification fan-out. Runs on the database driver in the checked-out configuration, with Redis in production.
FirebaseExternal
Cloud Messaging, carrying push to trainees and trainers alongside in-app and email delivery.

Connections

  • Trainee portal → Laravel 12 API (assignments, documents)
  • Trainer portal → Laravel 12 API (cohort oversight)
  • Admin dashboard → Laravel 12 API (cohorts, catalogue, rosters)
  • Trainee portal → Hosted games (embed or QR)
  • Laravel 12 API → MySQL (read / write)
  • Laravel 12 API → Activity log (events + counters)
  • Laravel 12 API → Queue (imports, notifications)
  • Queue → Firebase (push delivery)

What makes it interesting

The parts a system like this is actually judged on.

  • Three audiences are separated by structure rather than by condition, and that is the decision the rest of the system follows from. Trainees and trainers are different tables behind different guards, trainer and trainee documents are different libraries rather than one table with a visibility flag, and every module carries a controller and resource stack per audience. The cost is real, roughly a hundred and forty files for seven tables in the games module alone, and what it buys is that a missing where clause cannot leak a trainer document to a trainee, because there is no query that could return one.
  • The platform is a delivery and tracking layer rather than a content host. A game is a record with an embed URL, a source link and a QR code, so the thing a trainee actually opens is somebody else system. That keeps the platform out of the business of running interactive content and puts it firmly in the business of knowing who was assigned what, who opened it, and when, which is the half a training company is actually paid for.
  • Counters are incremented as activity is written while the log behind them is pruned at six months, and nothing decrements them. The two are guaranteed to diverge over time, which is correct for what they are: lifetime tallies feeding dashboards, not a ledger. It is worth being explicit that they are a running total rather than a queryable fact, because the failure mode is somebody later treating them as reconcilable.
  • The permission catalogue is assembled at boot by merging a configuration file from every module, so a module ships its own permissions rather than registering them in a central list. Administrators then see the union as a checkbox tree. It keeps modules genuinely self-contained, at the price that the catalogue only exists at runtime and nothing type-checks a permission string.
  • Roster import is the largest single feature in either identity module, and it is what makes the platform sellable to a corporate client. Spreadsheets arrive, so imports run as queued chunked jobs with per-row validation, upsert on email then username, timeouts, retries and temp-file cleanup on both the success and failure paths. None of that is visible in the product, and without it onboarding a client company is a support ticket rather than an upload.
  • Bilingual authoring is a schema decision rather than an interface one. Five entities carry translation tables, so a cohort or a game exists once with a name in each language rather than twice, and the taxonomy does not fork per locale. English and Arabic are the two, which is what a UAE training provider needs.

Engineering challenges

  • Three audiences with different authority over the same records, separated by schema rather than by query
  • Trainer material that must be unreachable by trainees by construction, not by a visibility flag
  • A training business that retunes programmes faster than software can be deployed
  • Corporate staff rosters arriving as spreadsheets rather than through an API
  • Counters that feed dashboards while the log beneath them is pruned
  • Bilingual content across every entity a learner or trainer reads

Integrations

  • Firebase Cloud Messaging: push notification delivery, alongside in-app and email
  • Externally hosted training games, reached by embed, link or QR code rather than served by the platform

Technology

  • Laravel 12
  • PHP 8.2
  • MySQL
  • Redis
  • Next.js
  • TypeScript
  • Tailwind CSS
  • JWT
  • Firebase Cloud Messaging
  • DigitalOcean
  • Docker
  • GitLab CI

These are the projects my confidentiality agreements allow me to publish, so this is a subset of the work rather than all of it. Every one of them comes from a full-time role, and I was on the project from its start through to its end. Freelance and consulting engagements are not published here.

The write-ups still describe systems rather than individual credit. Where the source material does not establish who did what inside a team, a case study says nothing rather than implying credit it cannot support.

Let’s talk

Have a product, platform or delivery challenge? Let’s talk about turning it into a structured, scalable solution.

Open to technical leadership, product delivery and senior engineering roles, and available for architecture consulting, technical reviews and mentorship. Engagements run as project-based work, contracts, consulting, freelance engagements, remote collaboration and long-term partnerships.

Based in Cairo, Egypt, working remotely with clients across the MENA region and internationally.

Also on LinkedIn (opens in a new tab)