Skip to main content

NS Exam Portal

A production-grade multi-tenant online examination platform for educational institutions and corporate assessment providers. Built on a decoupled React + Express + Turso (libSQL) stack, deployed on Cloudflare Pages and GCP Cloud Run.

Developed and maintained by NS Software Solutions


Table of Contents


Quick Start

Prerequisites

  • Node.js 18+ and npm
  • Firebase project with Firebase Admin service account credentials
  • Turso database instance and auth token
  • Git

Installation

1. Clone the repository:

git clone <YOUR_GIT_URL>
cd exam-portal-ns

2. Configure the backend:

cd backend
cp .env.example .env
# Edit backend/.env — add Turso URL/token and Firebase service account credentials
npm install

3. Configure the frontend:

cd ../frontend
cp .env.example .env
# Edit frontend/.env — add Firebase public config and VITE_API_URL
npm install

4. Start development servers:

Backend (port 8082 by default):

cd backend
npm run dev

Frontend (Vite dev server):

cd frontend
npm run dev

Tech Stack

LayerTechnologyNotes
FrontendReact 18, TypeScript, ViteSingle Page Application with code splitting
UIshadcn/ui (Radix UI + Tailwind CSS)Dark/light theme support, accessible components
RoutingReact Router v6Role-based layout structures with protected routes
Auth (client)Firebase AuthenticationAnonymous (guest) + email/password + token refresh
BackendNode.js 22, Express, TypeScriptREST API + RPC endpoints, ESM modules
DatabaseTurso (libSQL / SQLite)Serverless, edge-distributed with global replication
Auth (server)Firebase Admin SDKJWT verification, ADC on Cloud Run
File StorageFirebase Storage (GCS)Proctoring evidence with signed URLs
Hosting — FrontendCloudflare PagesEdge CDN, custom domain: test.nssoftwaresolutions.in
Hosting — BackendGCP Cloud Run (Asia South 2)Containerized, auto-scaling, health monitoring
CI/CDGCP Cloud BuildAutomated build and deployment pipeline
MonitoringGCP Cloud MonitoringPerformance metrics, logs, alerts

Features

Multi-Role Platform

RoleAccess
Super AdminFull platform control — tenant management, subscription and billing administration, global settings, audit logs, security.
Client AdminOrganizational control — question banks, test creation, student roster, analytics, subscription and Pay Per Test management.
StudentTest-taking, history, post-submission scorecard and XLSX performance report download.
Guest StudentAnonymous exam access via share code, no account required.

Subscription & Billing System

  • Four subscription tiers: Free, Starter (₹1,999/mo), Growth (₹3,999/mo), Enterprise (custom).
  • Plan-driven limits: exams per month, candidates per exam, questions per exam — enforced server-side at every write operation.
  • Plan-driven feature flags: csv_import, xlsx_export, analytics, custom_branding, advanced_proctoring, camera_proctoring.
  • Subscription request workflow: Client Admins submit upgrade requests; Super Admins approve (auto-syncs limits, features, history) or reject.
  • Automatic expiry: subscription status flips to expired on every /api/superadmin/subscriptions fetch when expiry_date is past.
  • Pay Per Test (PPT) System: Transactional assessment packages (Base ₹99, Basic ₹199, Standard ₹399, Professional ₹499, Placement Drive ₹1,499). Features include question limits (50-200), candidate capacity (50-500), proctoring controls, branding, and analytics. Client requests → Super Admin approves → Inventory available → Consumed on test creation with hard limits locked in test_billing.

Test Management

  • Folder-based test organization with uncategorized fallback.
  • Draft / Published workflow with status and active field sync.
  • Scheduled activation and termination windows (scheduled_start / scheduled_end).
  • Configurable attempts limit per student (0 = unlimited).
  • Guest access toggle and public link with 8-character share code and QR code.
  • Test cloning (POST /api/rpc/clone-test) — copies questions, clears schedule, resets share code, forces draft status.
  • read_only lock: auto-applied when Pay Per Test candidate capacity is exhausted; blocks structural edits while allowing branding/visibility changes.

Assessment Builder

  • Visual question palette with drag-and-drop-style reordering.
  • Multi-section support with per-section configuration:
    • Custom countdown timer (duration_minutes)
    • Navigation lock (once candidate advances, prior section is locked)
    • Shuffle questions and/or options independently
    • Section-level negative marks override
  • Question types: mcq, true_false, multi_select, fill_blank, subjective, coding.
  • Bulk CSV question import with two-stage validation, batch rollback by import_batch_id, and question versioning.

Exam Engine (Student / Guest)

  • Fullscreen enforcement with violation counter (3 strikes → auto-submit).
  • Tab-switch / window-blur detection.
  • Copy, paste, cut, right-click blocking.
  • CSS text selection disabled on exam content.
  • Section countdown timers with auto-advance or auto-submit on expiry.
  • Section navigation lock: backend enforces via timer elapsed check and answer progression tracking — not just client-side.
  • Auto-save answers with 2-second debounce (POST /api/attempt-answers).
  • Auto-flush dirty answers before submission.
  • Score masked until show_results_after_submission = 1 AND result_status = published.

Grading

  • Server-side grading only (POST /api/rpc/submit-attempt).
  • Supports single correct (correct_answer) and multi-select (correct_answers JSON array).
  • Section-level negative marks override test-level negative marks.
  • Score is floored at 0 (cannot go negative).
  • Submission idempotent — duplicate submits return 400.

Proctoring

  • Event types: TAB_SWITCH, WINDOW_BLUR, FULLSCREEN_EXIT, NO_FACE, MULTIPLE_FACES, CAMERA_DISCONNECTED, CAMERA_PERMISSION_DENIED.
  • Severity scoring: LOW (1), MEDIUM (2–3), HIGH (5). Total risk score per attempt.
  • Evidence snapshots (base64 → Firebase Storage GCS) for NO_FACE, MULTIPLE_FACES, CAMERA_DISCONNECTED.
  • 30-second deduplication window: repeated events accumulate duration instead of inserting duplicates.
  • Signed URLs (15-minute expiry) for evidence image retrieval.
  • Feature-gated: advanced_proctoring (basic events) and camera_proctoring (camera events).

Analytics

  • Super Admin dashboard: total clients, students, questions, tests, attempts; subscription plan distribution; expiring-soon alerts; today's exam activity and proctoring events; top orgs by student count; live load metrics (concurrent users, RPS, CPU, memory, API latency).
  • Client Admin dashboard: student/question/test counts; average score and pass rate; top 5 performers; per-test performance breakdown.
  • XLSX performance report: 3-sheet workbook (Summary, Detailed Questions, Analytics) with signed download for guests via attempt_token.

Security

  • Firebase ID token verification on every authenticated request.
  • Tenant isolation: every query scoped to client_id resolved from the authenticated user's profile.
  • BOLA/IDOR protection: all attempt/answer/proctoring reads verify student_id === user.id or admin role.
  • Guest attempt token: attempt_token (UUID) required for all guest read/write/submit/report/proctoring operations.
  • Rate limiting: 1,000 req/15 min global; 100 req/min on auth-sensitive endpoints.
  • Maintenance mode: globally blocks new attempt creation when enabled; admin panels stay accessible.

Platform Controls (Super Admin)

  • Maintenance mode toggle.
  • Platform-wide announcement banner.
  • User registration enable/disable.
  • Platform branding logo (base64 or URL, reflected in all admin sidebars).
  • Password reset for any user (Firebase Admin SDK).
  • Paginated audit log with filters (user, action, entity type, date range).
  • Client suspension (sets active_status = 0, blocks all exam operations for that tenant).

Project Structure

exam-portal-ns/
├── backend/
│ ├── src/
│ │ ├── auth/
│ │ │ └── auth.ts # Firebase Admin SDK init + token verification
│ │ ├── db/
│ │ │ └── db.ts # Turso client init + all schema migrations
│ │ ├── middleware/
│ │ │ ├── auth.ts # Global auth context extractor
│ │ │ └── authz.ts # requireRole() middleware factory
│ │ ├── routes/
│ │ │ ├── rpc/
│ │ │ │ ├── clone-test.ts # Test duplication handler
│ │ │ │ └── submit-attempt.ts # Server-side grading engine
│ │ │ ├── attempt-answers.ts # Answer save/load with section lock enforcement
│ │ │ ├── attempts.ts # Attempt lifecycle management
│ │ │ ├── audit-logs.ts # Paginated audit log viewer (superadmin)
│ │ │ ├── clients.ts # Tenant CRUD + limits + features
│ │ │ ├── create-user.ts # Firebase + Turso user provisioning
│ │ │ ├── packages.ts # Pay Per Test package catalog + purchases
│ │ │ ├── proctoring.ts # Proctoring event logging + evidence upload
│ │ │ ├── profiles.ts # User profile management
│ │ │ ├── question-folders.ts # Question category folders
│ │ │ ├── questions.ts # Question bank CRUD + bulk import
│ │ │ ├── report.ts # XLSX performance report generation
│ │ │ ├── settings.ts # Global platform settings
│ │ │ ├── stats.ts # Dashboard analytics
│ │ │ ├── subscription-requests.ts # Upgrade request workflow
│ │ │ ├── subscriptions.ts # Subscription management (superadmin)
│ │ │ ├── test-folders.ts # Test folder management
│ │ │ ├── test-questions.ts # Test-question linking + quota enforcement
│ │ │ ├── test-sections.ts # Section CRUD
│ │ │ └── tests.ts # Test lifecycle + PPT integration
│ │ ├── services/
│ │ │ ├── audit.ts # createAuditLog() helper
│ │ │ ├── billing.ts # PPT package assignment + limit validation
│ │ │ ├── features.ts # isFeatureEnabled() — plan + override lookup
│ │ │ ├── limits.ts # getClientLimits() + usage tracking
│ │ │ └── roles.ts # hasRole(), getUserClientId(), isGuestStudent()
│ │ ├── validation/
│ │ │ └── schemas.ts # Zod schemas for test and question payloads
│ │ └── server.ts # Express entry point, routes, rate limiters
│ ├── Dockerfile
│ └── package.json
├── frontend/
│ ├── src/
│ │ ├── components/ # Reusable UI (Auth, Brand, ClientAdmin, SuperAdmin, TestEngine)
│ │ ├── contexts/
│ │ │ └── AuthContext.tsx # Firebase auth state + role resolution
│ │ ├── integrations/
│ │ │ └── firebase/client.ts # Firebase client SDK initialization
│ │ ├── pages/
│ │ │ ├── SuperAdmin/ # Dashboard, Clients, Packages, Subscriptions, AuditLogs, Settings
│ │ │ ├── ClientAdmin/ # Dashboard, Tests, Questions, Students, Subscription, Plans, PackageSelection
│ │ │ ├── Student/ # Dashboard, Engine, History, Review, SubmitSuccess
│ │ │ └── Test/ # Join (public share code entry)
│ │ ├── services/
│ │ │ └── api/client.ts # All API calls — typed apiFetch wrapper + domain APIs
│ │ └── hooks/ # useToast, useMobile
│ ├── wrangler.toml # Cloudflare Pages deployment config
│ └── package.json
├── docs/ # Technical documentation
└── cloudbuild.yaml # GCP Cloud Build pipeline

Environment Variables

Backend (backend/.env)

VariableRequiredDescription
PORTNoServer port. Default: 8080.
TURSO_DATABASE_URLYesTurso connection URL (libsql://...).
TURSO_AUTH_TOKENYesTurso authentication token.
FIREBASE_PROJECT_IDYes*Firebase project ID for token verification.
FIREBASE_CLIENT_EMAILYes*Firebase service account email.
FIREBASE_PRIVATE_KEYYes*Firebase service account private key (newlines as \n).
FIREBASE_STORAGE_BUCKETNoGCS bucket for proctoring evidence. Defaults to <PROJECT_ID>.appspot.com.
DISABLE_RATE_LIMITERNoSet true to disable rate limiting (k6 load tests only).
FRONTEND_URLNoAdditional CORS origin.
NODE_ENVNoproduction disables fallback JWT decode.

*Not required when running on Cloud Run with Application Default Credentials (K_SERVICE set).

Frontend (frontend/.env)

VariableRequiredDescription
VITE_API_URLYesBackend API base URL (e.g., https://api.example.com).
VITE_FIREBASE_API_KEYYesFirebase public API key.
VITE_FIREBASE_AUTH_DOMAINYesFirebase auth domain.
VITE_FIREBASE_PROJECT_IDYesFirebase project ID.
VITE_FIREBASE_APP_IDYesFirebase app ID.

Deployment

Frontend — Cloudflare Pages

cd frontend
npm run build
# Deploy via Cloudflare Pages dashboard or wrangler
npx wrangler pages deploy dist

The wrangler.toml file in the frontend directory contains the Cloudflare Pages project configuration.

Backend — GCP Cloud Run

The backend is containerized using a multi-stage Dockerfile:

# Build and deploy using Cloud Build
gcloud builds submit --config cloudbuild.yaml

# Or deploy directly
gcloud run deploy exam-portal-api \
--source . \
--region asia-south2 \
--allow-unauthenticated \
--set-env-vars TURSO_DATABASE_URL=...,TURSO_AUTH_TOKEN=...

The production deployment at https://exam-portal-ns-479112457276.asia-south2.run.app uses Application Default Credentials (no manual Firebase key files needed on Cloud Run).


Development Guidelines

Running Tests

# Backend unit tests (Vitest)
cd backend
npm test

# Load tests (k6)
k6 run backend/loadtest.js

Code Style

  • TypeScript strict mode throughout.
  • All API routes use a single handler function pattern (export default async function handler).
  • Zod validation on all incoming request bodies for create/update operations.
  • Boolean fields stored as SQLite INTEGER (0/1); rowBools() helper converts on read.

Adding a New Feature Flag

  1. Add the feature name to subscription_plan_features seeds in db.ts.
  2. Use isFeatureEnabled(clientId, 'feature_name') in the relevant route handler.
  3. Add the feature checkbox to the ClientSettings.tsx module licensing panel.

Database Migrations

All schema changes are applied automatically at startup via runMigrations() in db.ts. New columns use ALTER TABLE ... ADD COLUMN IF NOT EXISTS pattern with try/catch to skip already-applied changes. New tables use CREATE TABLE IF NOT EXISTS.


Known Limitations & Future Enhancements

Current Limitations

  • Email Notifications: No automated email system for test assignments or subscription updates (manual communication required)
  • Proctoring Authenticity: Evidence is client-reported without server-side camera validation (future: AI-based verification)
  • Subscription Expiry: Lazy checking on Super Admin page load rather than scheduled cron jobs
  • Payment Integration: Manual billing process without automated payment gateway (planned: Stripe/Razorpay)
  • Advanced Analytics: Limited to basic metrics, lacking predictive analytics and distractor analysis

Technical Constraints

  • Browser Security: Client-side proctoring can be circumvented by determined users with DevTools access
  • Network Dependency: Requires stable internet connection with auto-save grace periods
  • Camera Reliability: Dependent on candidate hardware and environment quality
  • Mobile Support: Responsive design but no native mobile application

Future Roadmap

  • Short Term (3 months): Email notifications, enhanced proctoring, payment integration
  • Medium Term (6-12 months): Live proctoring, coding sandbox, AI question generation
  • Long Term (12+ months): Blockchain certificates, AR/VR assessments, global deployment

Comprehensive Documentation

The platform includes detailed documentation for developers, administrators, and auditors:

Core Documentation

  • ARCHITECTURE.md: Complete system architecture and design decisions
  • DATABASE_SCHEMA.md: Detailed ERD, table documentation, and business rules
  • SECURITY_AND_EXAM_INTEGRITY.md: Security controls, proctoring, and compliance features
  • CHANGELOG.md: Version history and feature evolution
  • Features-Flow.md: Role-based workflows and user journeys

Technical Documentation

  • API Reference: REST API endpoints with authentication requirements
  • Database Migrations: Runtime schema evolution strategy
  • Deployment Guide: Production deployment on GCP Cloud Run + Cloudflare Pages
  • Development Guidelines: Code standards, testing, and contribution processes

Support & Contact

Developed by: NS Software Solutions Email: info.nssoftwaresolutions@gmail.com Internship Program: internships.nssoftwaresolutions.in Documentation: docs.nssoftwaresolutions.in/exam-portal


License

© 2024–2026 NS Software Solutions. All rights reserved.