Please hold on for a moment while the content loads.
Please hold on for a moment while the content loads.
Full-Stack LMS with DRM, Payments & QR
Production learning management system with DRM-protected video delivery, bKash tokenized checkout, QR code attendance tracking, ABAC authorization, and an append-only activity audit log, serving 13,820+ leads and 898+ active users.
A full-stack learning management system handling course delivery, event registration with bKash payments, DRM-protected video streaming, QR code attendance tracking, WhatsApp group management, and bootcamp lead capture. The backend is Express.js + TypeScript with MongoDB, Redis caching (13 domain-specific adapters), and ABAC authorization. The frontend is React 19 + TanStack Router + Zustand + shadcn/ui with strict 3-layer architecture (Service → Hook → Component).
Designed and built the Express + TypeScript API: ABAC authorization, bKash tokenized checkout with idempotency, DRM video delivery, QR attendance, and the audit log.
Built the React 19 frontend with a strict Service → Hook → Component architecture, plus the 50-test MSW-based frontend suite.
Owned the 85-test suite and production monitoring: rate limiting abuse control, cache-adaptor design, non-blocking audit writes, and failure-mode planning.
Deployment and runtime maintenance through a versioned CI/CD pipeline, with 24/7 alerting on service health and payment failures.
Full-stack architecture: React SPA → Express API → MongoDB/Redis → external payment and video services
Tokenized checkout flow with idempotency guards and Redis-cached grant tokens
Full course lifecycle: milestones → modules → units (video/assignment/post). Course outlines, backup snapshots, enrollment with level-based restrictions, and per-user progress tracking.
Full bKash integration: grant token (cached 55min), create → execute → query payment flow with idempotency guards, duplicate transaction detection, and admin verification with audit trail.
Attribute-Based Access Control with 5 roles (super_admin, admin, editor, qr, student) across 13 resource types. Hierarchical permissions: super_admin bypasses all checks, admin includes editor + qr.
Token-based video access via external DRM API with IP-based restrictions, video status checking, and DRM token caching to minimize external API calls.
Nanoid-generated short IDs for QR codes. Contact QRs (vCard-style), scan logging with device/IP/user-agent tracking, and rate limiting (25 scans per 10 minutes per IP).
WhatsApp group management with rotating invite links and max join counts. Full event lifecycle (hackathons/workshops) with registration, dynamic forms, capacity limits, and CSV export.
Simple role-permission matrices don't scale when you have 5 roles across 13 resource types with inherited permissions (admin includes editor + qr). Adding a new resource requires updating every role.
ABAC with hierarchical grantAccess middleware: each role defines (action, resource) pairs. super_admin bypasses all checks. Admin = mergePermissions(editor, qr, additional grants). Adding a new resource requires one permission definition file.
// Hierarchical ABAC: each role defines (action, resource) pairs.
// super_admin bypasses all checks.
const rolePermissions: Record<Role, Permission[]> = {
super_admin: [{ action: "*", resource: "*" }],
admin: mergePermissions(editorPerms, qrPerms, [
{ action: "manage", resource: "payments" },
]),
editor: [{ action: "create", resource: "course" },
{ action: "update", resource: "content" }],
qr: [{ action: "scan", resource: "attendance" }],
student: [{ action: "read", resource: "course" }],
}
function grantAccess(role: Role, action: string, resource: string): boolean {
const perms = rolePermissions[role]
if (perms.some(p => p.action === "*")) return true
return perms.some(p => p.action === action && p.resource === resource)
}Read-heavy endpoints (courses, enrollments, dashboard, QR codes, analytics) hit MongoDB repeatedly. Each domain has different access patterns and freshness requirements.
13 domain-specific cache adapters with per-layer TTLs: QR codes (1hr), enrollments (1hr), courses (30min), units (2min), bKash tokens (55min), analytics (5-10min). Versioned key names (v1) for safe schema migrations. Feature flags per layer enable/disable caching independently.
bKash is the dominant payment method in Bangladesh. The tokenized checkout flow requires: grant token → create payment → user approves on bKash → execute payment → query for verification.
Full integration with idempotency guards (prevents double-processing), duplicate transaction ID detection, rate limiting (1 attempt/min/user+event), and admin verification API. Grant token cached in Redis for 55 minutes to avoid re-authentication.
Audit logging must capture before/after diffs for compliance but cannot block request handling. Writing to MongoDB synchronously adds latency to every mutation.
Append-only immutable audit events written via setImmediate (fire-and-forget). Secret auto-redaction (passwords, tokens, OTPs). Graceful shutdown drain ensures pending logs are flushed before process exit.
Money flows must never double-charge a student, even when a user double-submits, a network request retries, or bKash's server behaves non-deterministically. The bKash tokenized flow has four hops grant token, create payment, execute, query each of which can fail or be retried. The design below is how the system stays consistent across retries.
Four-hop tokenized flow; retry-safety is guaranteed at create and execute steps
Generated once per enrollment, persisted, and reused on every retry of the same checkout. If the create step is retried, bKash sees the same invoice number and returns the existing payment instead of creating a duplicate.
On webhook and query responses, the transaction ID is checked against already-processed payments before any state mutation. A repeat notification for an already-marked-paid enrollment is a no-op, not a second charge.
A per-user, per-event attempt limiter stops automated double-submits and brute-force retries at the boundary before they reach bKash.
Re-uses a valid bKash token across payments instead of re-authenticating on every checkout, cutting latency and avoiding rate-limit friction with the provider.
Tokenized checkout is slower than one-step payment links (extra user-approval hop) but keeps cards/accounts off our servers and is the standard bKash integration. The cost is more states to manage which is exactly why the idempotency layer exists. A distributed lock or unique DB index would add a second guard at the data layer; we rely on the application-level guard plus the rate limiter today.
MongoDB is the primary store. The non-obvious decisions are about write patterns, freshness, and how read-heavy paths avoid expensive queries.
Analytics dashboards, leaderboards, and enrollment counts are the hottest reads. Recomputing them from source documents on every request burns CPU and creates locking contention. The platform instead maintains denormalized counters and snapshots that are updated on write and served on read, with the per-domain Redis adapters (30min courses, 5-10min analytics) absorbing the rest. This keeps p95 read latency flat as the lead volume grows.
The activity log is immutable by design: events are inserted, never updated, and carry a before/after diff. Passwords, tokens, and OTPs are redacted before write so secrets never land in the log. Writes are fire-and-forget (non-blocking) with a graceful-shutdown drain so the last few events survive process restarts.
Every cache adapter namespaces its keys with a version (v1) so a schema change or cache-payload change can invalidate cleanly by bumping the version no cross-version deserialization bugs, no manual flush choreography.
Granular permission checks scale with resources. Adding a new resource requires one permission definition.
More complex to reason about. Hierarchical grants can produce unexpected permission combinations.
Per-domain TTLs match access patterns. QR codes cached for 1hr, units for 2min. Feature flags enable/disable independently.
More code to maintain. Cache invalidation logic duplicated across adapters. Risk of stale data if TTLs are wrong.
Prevents unauthorized video sharing. IP-based access control adds a second auth layer.
Adds latency to video start. DRM token caching is required but creates a window for unauthorized access.
bKash payment timeout
Payment initiated but callback never arrives. User charged but registration incomplete.
Idempotency key prevents double-processing on retry. Query API checks transaction status. Admin verification endpoint for manual reconciliation.
DRM token expiry
Video playback fails mid-session. Student loses access to content they paid for.
DRM tokens cached with shorter TTL than expiry window. Re-request with cached credentials on first playback error. Fallback: manual token refresh via admin.
Redis cache stampede
Popular course details hit MongoDB simultaneously when TTL expires. Database overload.
TTL jitter (±10% randomness) prevents synchronized expiry. Feature flags can disable caching per domain instantly.
QR scan abuse
Automated scripts scanning QR codes to harvest attendance data or generate fake check-ins.
Rate limiting: 25 scans per 10 minutes per IP. Scan lock mechanism prevents concurrent scans. Device/IP/user-agent logging for audit trail.
Activity log write failure
Audit trail loses events. Compliance gap for production mutations.
Non-blocking writes via setImmediate: log failures don't affect request handling. Graceful shutdown drain ensures pending logs are flushed before process exit.

Services and utils: enrollment, analytics (5 sub-services), bKash payment, activity log, QR codes, sessions, courses, forms, assignments, WhatsApp groups
Vitest with mocked Mongoose queries and Redis stubs. External APIs (bKash, video DRM, auth service) mocked via vi.mock('axios'). Fake timers for date-dependent logic. No database connections in tests.
7 integration tests (auth/RBAC, enrollment, course management, course player, assignment review, bootcamp leads, QR management), 15 hook tests, 12 service tests, 3 store tests, 4 utility tests, 3 component tests, 2 permission tests
Vitest + MSW (Mock Service Worker) for HTTP mocking. Testing Library for React component rendering. Test factories and auth helpers for consistent test data. CI runs on every push via GitHub Actions.
Push/PR to staging → lint → typecheck → test → build. Release-please for automated versioning.
GitHub Actions with pnpm, Node.js 24. Docker multi-stage build for backend (production replicas with health checks). Cloudflare Pages for frontend deployment.
Attribute-based checks scale better than role-permission matrices. Adding a new resource requires one permission definition, not updating every role.
setImmediate prevents activity logging from blocking request handling. Fire-and-forget with graceful shutdown drain is the right pattern for audit trails.
Duplicate transactions happen. State machines with idempotency keys prevent double-processing. Redis-cached grant tokens avoid re-authentication on every request.
13 domain-specific cache adapters with per-layer TTLs outperform a single global cache. Each domain has different freshness requirements. Units need 2min, courses need 30min.


This is an open-source production system. The details above reflect actual development work. Evidence available: