ahmad@abboud.dev · bash
tty1
Last login: just now on ttys001
ahmad@abboud.dev · portfolio shell v1.0
▸ mounting /projects … ok
▸ loading identity … ok
▸ 2 phone lines, 1 timezone, 0 recruiters harmed
 
Ahmad Abboud
Senior Full-Stack Engineer · Tech Lead
I drag factories into the 21st century.
 
You have a shell. Most of it is locked.
Type -h to see what you can run.
// no javascript. the shell is off, but everything it prints is written out beside it.
type -h and hit enter
ahmad@abboud:~$ open ydm

YDM Lean Platform

Lead Software Engineer · 2024 to present

Imagine Office 365, except every tool actually talks to the others, there are 22 of them, and the whole thing exists to out-lean the factories that invented lean

95% of the repo22 modulesdatabase per tenant

Screenshots

Operational excellence teams run on a dozen disconnected tools: 5S audits in one spreadsheet, kaizen suggestions in another, the QMS in a binder, and OKRs in a slide deck nobody opens twice. YDM is one platform where all of it is the same data model, so a near-miss report can become a corrective action, which can close a finding on an internal audit, without anyone re-typing anything.

Twenty-two modules ship in the box: 5S and gemba, HES/SEC/YAK safety cycles, kaizen workshops and idea pipelines, OKRs and KPI deviation, the full QMS (audits, plans, question pools, non-conformities, change requests, corrective actions), supplier management with 8D and a supplier portal, calibration with reminders, meetings and decisions, surveys, tickets, tasks, and the executive dashboard that reads across all of them.

One instance, one database each

Every tenant gets its own MySQL database. Not a tenant_id column but an actual separate database, so there is no query in the codebase that could leak a row across customers even if someone forgot a where.

Routing makes that invisible. Traefik terminates TLS for *.ydmsoftware.com with automatic Let’s Encrypt certificates and forwards to the Next.js frontend. The API reads the host, resolves the tenant slug, and injects a Prisma client built from that tenant’s connection string, cached in an LRU pool so the hundredth request doesn’t open the hundredth connection. Adding a customer is a row in the master database and a migration run; no restart, no redeploy, no per-tenant container.

The parts that are actually hard

Keeping schemas in step. Twenty-two modules under active development, times every tenant database. Migrations run across all active tenants from one script, and the tenant iterator that powers it is the same one the cron jobs use.

Work that has to happen whether or not anyone logs in. Audits generate themselves on their cycle. Meetings open and auto-close. Field tours and workshops close on schedule. Calibration reminders and supplier 8D chasers go out by email. All of it iterates every active tenant, which means every job is written as if it will be run against a hundred databases, because it will be.

Not being the reason a factory stops. Helmet, a CORS allowlist, Redis-backed sessions and rate limiting, JWT with refresh, bcrypt, an API-key gate in front of the API, IP blocking managed from the superadmin, request size and timeout guards, and an audit log on anything sensitive. Health endpoints report Redis liveness and the tenant connection-pool stats, because “is it up” and “is it about to fall over” are different questions.

Around the edges

A separate upload service normalises images to WebP with Sharp and writes them under a per-tenant path that Nginx serves from a CDN subdomain, so business logic never holds a binary. A superadmin dashboard governs tenants, partners, API keys, exhibition leads, support tickets and blocked IPs across the whole platform. Socket.IO carries notifications. Gemini and Groq power summaries and generative helpers inside the reporting modules.

Backups dump the master database, every tenant database, and the whole upload tree, then rotate. It is the least interesting script in the repository and the one I would miss first.

▸ stack

TypeScriptExpressPrismaMySQLRedisNext.jsMUISocket.IOTraefikNginxDocker Compose