Architecture

System Architecture

A complete technical breakdown of how AEGIS ExamLab is structured — from its Next.js routing and MongoDB data model to the security chain, heartbeat telemetry, and middleware firewall.

Repository Directory Structure

aegis-examlab/
├── src/
│   ├── app/ # Next.js App Router root
│   │   ├── (dashboard)/ # Route group — all admin pages
│   │   │   ├── dashboard/pcs/
│   │   │   ├── dashboard/students/
│   │   │   ├── dashboard/questions/
│   │   │   ├── dashboard/exams/
│   │   │   ├── dashboard/live-status/
│   │   │   ├── dashboard/results/
│   │   │   └── dashboard/logs/
│   │   ├── exam/[examId]/ # Student exam interface (dynamic route)
│   │   ├── login/ # Admin login page
│   │   ├── api/ # Utility API routes (debug, migrations)
│   │   └── page.tsx # Root: PC Registration portal
│   ├── components/ # Radix UI + shadcn/ui component library
│   ├── hooks/ # Custom React hooks (use-toast)
│   ├── lib/
│   │   ├── actions.ts # ALL Server Actions — business logic
│   │   ├── mongodb.ts # DB connection + collection helpers
│   │   ├── types.ts # TypeScript type definitions
│   │   └── utils.ts # Tailwind class utilities
│   └── middleware.ts # Edge routing firewall
├── docs/ # GitHub Pages documentation (this site)
├── scripts/seed-db.js # Admin credential bootstrapper
├── database-fix.js # MongoDB index repair utility
├── Dockerfile # Multi-stage production image
└── .env.example # Environment variable template

MongoDB Collections

pcs

Approved and pending client machines

_id, name, ipAddress, macAddress?
uniqueIdentifier, status (Pending|Approved|Rejected)
assignedStudentId?, liveStatus?, lastSeen?

pc_requests

Unapproved registration requests

name, uniqueIdentifier, requestedAt

students

Student roster with exam assignments

name, rollNumber (unique), classBatch, assignedExamId?

questions

Question bank with AI metadata

text, options[], correctOptions[], category, tags[], weight, negativeMarking

exams

Exam configurations and state

title, description, startTime, duration, status (Scheduled|In Progress|Completed), questionIds[]

exam_results

Immutable submission records

studentId, examId, answers[], score, totalQuestions, completedAt

admins

Admin credentials

username, password, role (admin|superadmin)

admin_logs

Full audit trail

adminUsername, action, details{"{}"}, timestamp

Server-Side Validation Chain

Before any exam data is served or any submission is accepted, the request must pass through a strict sequential trust chain. Each step references the previous, making it impossible to bypass one level by knowing another's identifiers.

1. PC Exists & Approved

Lookup by pcIdentifierstatus === 'Approved'

2. Student Mapping Verified

PC's assignedStudentId === provided studentId

3. Exam Assignment Verified

Student's assignedExamId === provided examId

4. Exam In Progress

Exam status === 'In Progress'

5. No Prior Submission

No document in exam_results for this student+exam

✓ Access Granted

Middleware Routing Firewall

The src/middleware.ts Edge function intercepts all matching requests before Next.js renders a single component. This ensures the auth check has zero overhead from React rendering.

Route PatternAuth CheckRationale
/ExemptPC registration is public by design — any machine must be able to self-register.
/loginExemptRequired to be publicly accessible for login flow.
/exam/*Exempt (token-based)Exam pages use localStorage device tokens verified by Server Actions, not HTTP cookies.
/dashboard/*RequiredChecks for auth cookie. Redirects to /login if absent.

Heartbeat Telemetry System

AEGIS uses HTTP long-polling over WebSockets for telemetry. This is a deliberate architectural choice.

Why HTTP Polling over WebSockets?

Institutional networks (universities, examination halls) frequently operate behind strict Layer-7 firewalls that block WebSocket upgrade requests. HTTP polling (standard GET/POST) is virtually never blocked. A 15-second interval provides near-real-time visibility without significant server overhead for typical exam sizes (10–100 concurrent PCs).

The client's useEffect in src/app/exam/[examId]/page.tsx sets up a setInterval that fires updatePcLiveStatus(pcIdentifier, 'Attempting') every 15,000ms. The cleanup function (return () => clearInterval(heartbeatInterval)) prevents memory leaks when the component unmounts.

Absolute Epoch Timer Synchronization

A common attack vector in web-based exams is manipulating the local system clock to extend the countdown timer. AEGIS defends against this architecturally:

// src/app/exam/[examId]/page.tsx
const examEndTime = new Date(data.exam.startTime).getTime()
               + data.exam.duration * 60 * 1000;
const now = new Date().getTime();
const remainingTime = Math.max(0, Math.floor((examEndTime - now) / 1000));

The countdown is computed from the server-provided absolute epoch timestamps (startTime + duration in minutes). Even if a student manipulates their clock, the server's own status field governs submission eligibility — once the server marks the exam Completed, getExamDetails rejects all requests.

MongoDB Connection Pooling

Next.js in production spawns multiple Node.js worker processes. Each cold-started serverless function would naively open a new MongoDB connection, rapidly exhausting the cluster's connection limit (typically 500 on free-tier Atlas). AEGIS solves this with a module-level cache:

// src/lib/mongodb.ts
let cachedClient: MongoClient | null = null;

export async function getDb() {"{"}
  if (cachedClient) return cachedClient.db(); // ← reuse existing
  await client.connect();
  cachedClient = client;
  return client.db();
{"}"}

The cachedClient is stored in module scope. In Next.js, module scope persists across multiple requests within the same Node.js process, effectively creating a connection pool per worker without needing an external pooling library.