EduPrime is a multi-tenant learning platform: admissions, live classrooms, tuition billing, AI-assisted teaching and a partner API, all behind one deployment where every school's data stays provably apart from the next school's. It is already running — real students, real live classes, real tuition payments.
This page is the scope of what's built, the numbers behind it, what it means commercially, and — just as importantly — the parts that are honestly still unfinished.
394 of the 543 tests were run live alongside other suites with one incidental timeout (it passes alone in 1.3s); CI runs the full 543 on every merge to main. Size figures are counted from source, not estimated.
One Prisma client adds the tenant filter itself; a query with no tenant in context throws instead of returning everyone's rows. CI fails the build if a new table doesn't declare who owns it.
Paystack webhooks are signature-checked and idempotent; tuition runs as a per-level ledger that pays the oldest charge first.
LiveKit-based rooms with token minting, automatic recording, and moderation — no separate video vendor to integrate.
A class-notes pipeline (transcription and extraction) and an AI tutor with tested failover across three providers.
Deduped incident tracking, a "drift" check comparing what the browser actually rendered against what the database says should show, and a developer console generated from source.
Scoped keys, cursor pagination, idempotent writes, and outbound webhooks signed and retried for 17 hours.
Every query in EduPrime passes through the same Prisma client, and that client adds the tenant filter itself. A query that runs with no tenant in context throws — it doesn't quietly return every school's rows. Getting the tenant to a query several function calls deep needed AsyncLocalStorage, with the auth gate installing an empty holder first and filling it in once the session lookup returns. A CI check fails the build if a table is added without saying whether it belongs to a tenant.
The schema has 222 cascade relations, so one careless delete could quietly take payments and grades with it. Deletes on the 15 models that matter — users, students, payments, certificates — become soft deletes, and a bulk delete with no filter, or one touching more than 200 rows, is refused. Changes to money, marks and identity get a before-image in an audit table, and a Postgres trigger blocks updates and deletes on that table itself. If someone got into the app, the app would not be the tool they could use to cover their tracks.
A nightly encrypted pg_dump, plus a monthly job that restores the newest dump into a throwaway Postgres and checks the row counts against the live database. On 3 September, production was reset to an empty schema by an environment mix-up. It was caught from the failing migration step and fully restored from a point-in-time backup — about an hour end to end. A CI check now fails the build if a production database URL turns up somewhere it shouldn't.
Calls to Paystack and the AI providers used to have no timeout — a call that just hangs never trips a breaker that only counts failures. Now every outbound call goes through one wrapper: a deadline first, a breaker second. Network errors, timeouts, 5xx and 429 count against a provider; a declined card doesn't, so a few mistyped emails can't switch off payments for everyone. Neon suspends when idle, so the first query after a quiet spell can fail — that one connection error is retried, because nothing reached the server, so a retry can't write twice.
It used to be one daily function running all 36 jobs in a row inside a 300-second limit — one partner endpoint that never answered would starve everything behind it. The jobs now run in five lanes split by what they depend on (payments, delivery, nudges, housekeeping, AI, media), each with its own time budget, running at the same time. A stuck job is dropped at its lane's cap and reported as timed out; the rest of the lane carries on.
On one day, close to a terabyte moved through serverless functions because every video range request was being proxied through them. Recordings, materials and offline downloads now redirect to short-lived signed bucket URLs instead, and a CI check fails any route that streams a file without explaining why that's okay. Transfer spend went from about $55 on the worst day to roughly $1.50 a day — a cut of more than 97%, with nothing removed for the user.
The Paystack webhook checks its signature with a constant-time compare and is idempotent on the payment reference, so the same event landing twice does nothing twice. The question "is this student allowed into the portal?" had been worked out separately in 13 places, and the copies disagreed — the admin screen said open while the student saw a padlock. It's now one function, with a CI check that fails the build if a route works it out on its own.
EduPrime's first tenant is a German-language school in Nigeria — roughly 400 students, live classes, and card payments switched on for real since 2 September 2026. Everything above is what runs underneath the page below.
Happy to walk through the architecture, the numbers, or a live demo on a call.
Get in touch