
RVNL Bilingual CMS · Rail Vikas Nigam Limited
A ground-up rebuild of the public website and content operations for Rail Vikas Nigam Limited, an Indian railway PSU. Every piece of content is fully bilingual (English / हिन्दी) and moves through a governed maker-checker workflow before it can go live — with role-based access, append-only audit trails, a separate vigilance / grievance case system for citizen complaints and formal investigations, and a signed outbound webhook layer that keeps external systems in sync. Built on Node.js + Express + Sequelize + MySQL with a React (Vite) frontend.
Bilingual content workflow Maker-checker · Per-language state machine
A publishing pipeline where authors, moderators, and approvers each hold a distinct role, and English and Hindi advance independently through the same state machine.
- Modelled a parent + per-language translation schema (e.g.
articles+article_translations) so each language carries its own workflow state — an English article can be published while its Hindi translation is still in approval. - Built a maker-checker state machine — draft → in moderation → in approval → approved → published, plus request-changes, reject, schedule, unpublish, and archive — with every transition recorded in a
workflow_transitionstable. - Supported scheduled publishing and resubmit loops, tracking each review cycle by
request_idand a per-item round counter so reviewers see the full back-and-forth history. - Integrated a machine-translation helper (Azure translator) to seed EN ↔ HI drafts, keeping a human approver in the loop before anything publishes.
Access control & audit RBAC · Section scoping · Append-only history
Fine-grained, section-scoped permissions backed by a tamper-evident record of who changed what, when, and why.
- Designed a slug-based RBAC system (
roles,permissions,role_permissions) with roles assigned globally and per section / subsection — so a tenders moderator and a careers moderator carry different reach. - Kept a field-level audit log storing
old_data/new_dataJSON snapshots on every change, plus an append-only content-history ledger that preserves actor label, role, and designation even after a user is deleted. - Secured auth with JWT access + refresh tokens, bcrypt password hashing, and token-version invalidation on logout / password change to kill stale sessions.
Vigilance & grievance system Public complaints · Case tracking
A separate governance surface for citizen grievances and formal vigilance investigations, with its own entry points, roles, and case log.
- Built two public streams — grievance (citizen complaints) and vigilance (formal investigations) — each with reference numbers, source taxonomy, assignee tracking, and a per-case action log (
complaint_logs). - Added dedicated staff shells for CVO, vigilance-author, and verification-officer roles, plus DPDP data-processing consent capture on public forms.
- Hardened public forms with CAPTCHA challenges, per-route rate limiting, and strict complaint-attachment validation (PDF/PNG/JPEG, size-capped, rejected loudly rather than dropped silently).
Outbound webhooks & change feed Signed events · At-least-once delivery
An integration layer that lets external systems stay in sync with published content — over real-time push webhooks or a pull-based change feed, both carrying the same event IDs.
- Emitted HMAC-SHA256 signed, timestamped events (Stripe-style
t=…,v1=…signatures with a 5-minute replay window and constant-time compare) on content publish / update / delete. - Generated events from the audit log inside the write transaction, so a rolled-back edit never fires an event — and only publicly-visible changes are emitted, never drafts, workflow steps, or complaints.
- Made delivery at-least-once: an async worker with exponential-backoff retries (7 attempts → dead-letter), a per-endpoint circuit breaker, and
SELECT … FOR UPDATE SKIP LOCKEDclaiming for safe multi-process delivery. - Guarded targets against SSRF — https-only, credentials/redirects rejected, and a DNS re-resolve on every send that blocks private, link-local, and cloud-metadata ranges.
- Added a cursor-paged pull change feed (API-key auth) sharing the same event IDs, plus secret rotation with overlap, so consumers can seed, catch up, or replay after an outage.
Platform hardening Validation · Security headers · Uploads
- Zod-validated request bodies on every write endpoint with structured
422field-error responses, plus SQL-injection-prevention middleware and request-size caps. - A media library (
media_assets) with sanitized filenames, per-asset metadata, and soft deletes so nothing referenced by live content is ever hard-lost. - Locked down the surface with CSP, HSTS, X-Frame-Options, Referrer-Policy, a non-wildcard CORS allowlist, and tiered rate limits (auth vs API vs public forms).
- Extended the CMS to RTI hubs, CSR initiatives, annual reports / investor KPIs, tenders, careers, and announcements — all sharing the same bilingual workflow and audit spine.