Skip to content

Latest commit

Β 

History

97 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

HI Tech Examination

A self-hosted, secure MCQ examination platform built with Next.js 16, PostgreSQL, and Prisma ORM.

License: MIT Next.js TypeScript Prisma Tailwind CSS

HI Tech Examination enables administrators to create, manage, and conduct secure MCQ examinations with access control, time limits, full-screen enforcement, and email notifications β€” all self-hosted. Unlike Google Forms, it is purpose-built for examinations.

![HI Tech Examination Hero](https://placehold.co/1200x400/1a1a2e/ffffff?text=HI Tech Examination+Banner)


✨ Features

For Administrators

Feature Description
JWT Authentication Secure login with access + refresh token rotation (HTTP-only cookies)
Email Verification SMTP-based verification required before admin access is granted
Password Recovery Forgot/reset password flow with token-based reset links
Exam Builder Create MCQ exams with 4-option questions, drag-and-drop reordering
Exam Settings Time limits, shuffle questions/options, passing score, scheduled start/end dates
Access Control Public link (anyone with link) or specific Gmail whitelist per exam
Publish Management Draft β†’ Published β†’ Closed lifecycle control
Submissions Viewer View all student responses, per-student detailed results
Analytics Dashboard Total attempts, average score, pass rate visualized with Recharts
Audit Logging Track admin actions for accountability
Email Notifications Receive submission notifications via SMTP (Nodemailer)

For Students

Feature Description
No Registration Required Students identified by Gmail only, auto-created on first exam access
One-Session Enforcement Each student can attempt an exam only once (DB-level unique constraint)
Full-Screen Mode Enforced on exam start with exit prevention and logging
Tab Switch Detection Detected, logged, and configurable warning thresholds
Time Limits + Auto-Submit Countdown timer; exam auto-submits on expiry
Exit Prevention beforeunload warning; auto-submit on close attempt
Immediate Results Score displayed on submission (if enabled by admin)
Email Confirmation Students receive acknowledgment/result emails via SMTP

πŸ— Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Client                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Admin Panel β”‚         β”‚   Exam Taking (Public)  β”‚   β”‚
β”‚  β”‚  /admin/**   β”‚         β”‚   /exam/[slug]          β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚                            β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         β–Ό        Next.js App Router  β–Ό                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Admin API   β”‚         β”‚     Exam API            β”‚   β”‚
β”‚  β”‚  /api/admin/ β”‚         β”‚     /api/exam/          β”‚   β”‚
β”‚  β”‚  (JWT guard) β”‚         β”‚     (session token)     β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚         β”‚                            β”‚                   β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                        β–Ό                                 β”‚
β”‚               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”‚
β”‚               β”‚   Prisma ORM    β”‚                        β”‚
β”‚               β”‚   (PostgreSQL)  β”‚                        β”‚
β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Nodemailer SMTP β”‚    β”‚  Rate Limiting / Audit   β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ›  Tech Stack

Layer Technology
Framework Next.js 16.2.3 (App Router)
Language TypeScript 5 (strict mode)
React React 19.2.4
Styling Tailwind CSS v4, shadcn/ui
Database PostgreSQL
ORM Prisma 7.7.0
Auth JWT (jsonwebtoken) + bcryptjs
Validation Zod v4
Email Nodemailer
State Zustand
Drag & Drop dnd-kit
Charts Recharts
Icons Lucide React
Notifications Sonner

πŸ“‚ Project Structure

examforge/
β”œβ”€β”€ app/                          # Next.js App Router
β”‚   β”œβ”€β”€ admin/                    # Admin panel pages
β”‚   β”‚   β”œβ”€β”€ dashboard/            # Dashboard (exams, settings)
β”‚   β”‚   β”œβ”€β”€ login/                # Admin login
β”‚   β”‚   β”œβ”€β”€ register/             # Admin registration (invite-only)
β”‚   β”‚   β”œβ”€β”€ forgot-password/      # Password recovery
β”‚   β”‚   β”œβ”€β”€ reset-password/       # Password reset
β”‚   β”‚   └── verify-email/         # Email verification
β”‚   β”œβ”€β”€ api/                      # API route handlers
β”‚   β”‚   β”œβ”€β”€ admin/                # Admin API (JWT-protected via withAdminAuth)
β”‚   β”‚   └── exam/                 # Public exam API (session-validated via withExamSession)
β”‚   β”œβ”€β”€ exam/[slug]/              # Public exam entry, taking, and result
β”‚   β”œβ”€β”€ layout.tsx                # Root layout (Geist fonts)
β”‚   β”œβ”€β”€ page.tsx                  # Landing page
β”‚   └── globals.css               # Tailwind v4 + CSS variable theme
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ admin/                    # Admin-specific components
β”‚   β”‚   β”œβ”€β”€ AccessControl.tsx     # Exam access rule configuration
β”‚   β”‚   β”œβ”€β”€ AuthProvider.tsx      # Admin auth context provider
β”‚   β”‚   β”œβ”€β”€ DashboardNav.tsx      # Dashboard navigation sidebar
β”‚   β”‚   β”œβ”€β”€ ExamForm.tsx          # Exam creation/editing form
β”‚   β”‚   β”œβ”€β”€ QuestionEditor.tsx    # Question & option editor (dnd-kit)
β”‚   β”‚   └── SubmissionsTable.tsx  # Submissions list viewer
β”‚   β”œβ”€β”€ exam/                     # Exam-taking components
β”‚   β”‚   β”œβ”€β”€ ExamEntry.tsx         # Gmail verification entry
β”‚   β”‚   β”œβ”€β”€ ExamTimer.tsx         # Countdown timer display
β”‚   β”‚   β”œβ”€β”€ FullscreenGuard.tsx   # Full-screen enforcement
β”‚   β”‚   β”œβ”€β”€ QuestionCard.tsx      # Question display & option selection
β”‚   β”‚   └── ResultCard.tsx        # Score result display
β”‚   └── ui/                       # Shadcn UI primitives (~60 components)
β”œβ”€β”€ hooks/
β”‚   └── use-mobile.ts             # Responsive breakpoint hook
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ generated/prisma/         # Auto-generated Prisma client
β”‚   β”œβ”€β”€ validators/               # Zod schemas (access, auth, exam)
β”‚   β”œβ”€β”€ apiClient.ts              # Frontend API client with auto-refresh
β”‚   β”œβ”€β”€ auth.ts                   # JWT sign/verify, token hashing
β”‚   β”œβ”€β”€ withAdminAuth.ts          # Admin route handler wrapper
β”‚   β”œβ”€β”€ withExamSession.ts        # Exam session handler wrapper
β”‚   β”œβ”€β”€ prisma.ts                 # Prisma singleton (dev hot-reload safe)
β”‚   β”œβ”€β”€ mailer.ts                 # Nodemailer transporter configuration
β”‚   β”œβ”€β”€ email-templates/          # HTML email template definitions
β”‚   β”œβ”€β”€ audit.ts                  # Audit logging helpers
β”‚   β”œβ”€β”€ rateLimit.ts              # In-memory rate limiting middleware
β”‚   β”œβ”€β”€ slugify.ts                # URL slug generation utility
β”‚   └── utils.ts                  # cn() utility (clsx + tailwind-merge)
β”œβ”€β”€ prisma/
β”‚   β”œβ”€β”€ schema.prisma             # Database schema (15+ models)
β”‚   β”œβ”€β”€ seed.ts                   # Seed script for initial super admin
β”‚   └── migrations/               # Prisma SQL migrations
└── public/                       # Static assets

πŸš€ Getting Started

Prerequisites

  • Node.js 20+
  • PostgreSQL 14+ (running and accessible)
  • SMTP credentials (Gmail App Password, SendGrid, etc. β€” for email features)

1. Clone & Install

git clone https://github.com/your-username/examforge.git
cd examforge
npm install

2. Configure Environment

Copy the example environment file and fill in your values:

cp .env.example .env.local
Variable Description Example
DATABASE_URL PostgreSQL connection string postgresql://user:pass@localhost:5432/examforge
JWT_ACCESS_SECRET JWT access token signing key (β‰₯32 chars) your_super_secret_key_32_chars_min
JWT_REFRESH_SECRET JWT refresh token signing key (β‰₯32 chars) another_super_secret_key_32_chars
JWT_ACCESS_EXPIRES_IN Access token expiry 15m
JWT_REFRESH_EXPIRES_IN Refresh token expiry 7d
SMTP_HOST SMTP server host smtp.gmail.com
SMTP_PORT SMTP server port 587
SMTP_USER SMTP username you@gmail.com
SMTP_PASS SMTP password (App Password for Gmail) abcd efgh ijkl mnop
NEXT_PUBLIC_APP_URL Public app URL http://localhost:3000
SEED_ADMIN_EMAIL Initial super admin email admin@yourdomain.com
SEED_ADMIN_PASSWORD Initial super admin password ChangeMe@123
SEED_ADMIN_NAME Initial super admin display name Super Admin

3. Set Up Database

# Generate Prisma client (output: lib/generated/prisma/)
npm run db:generate

# Run pending migrations
npm run db:migrate

# Seed the initial super admin account
npm run db:seed

4. Start Development Server

npm run dev

Open http://localhost:3000 in your browser.

5. Production Build

npm run build     # Compile for production
npm run start     # Start production server

πŸ“‹ Available Scripts

Command Description
npm run dev Start development server with hot reload
npm run build Production build (optimized)
npm run start Start production server
npm run lint Run ESLint
npm run db:generate Regenerate Prisma client after schema changes
npm run db:migrate Run database migrations (development)
npm run db:seed Seed database (creates super admin via prisma/seed.ts)

πŸ” Security Model

Authentication

  • Admin β€” JWT access tokens (short-lived, default 15 min) passed via Authorization: Bearer header. Refresh tokens (long-lived, default 7 days) stored as SHA-256 hashes in HTTP-only, SameSite=Strict, Secure cookies. Token rotation on every refresh β€” old tokens are revoked.
  • Student β€” Session tokens (UUID) stored in sessionStorage (never localStorage). Validated server-side on every protected API call.

Exam Session Security

Mechanism Implementation
One session per student DB unique constraint (exam_form_id, student_id)
Full-screen enforcement requestFullscreen() API; exit events logged + warnings triggered
Tab/visibility detection visibilitychange listener logs switches via heartbeat API
Exit prevention beforeunload dialog triggers auto-save + warning
Time limit auto-submit Client countdown + server-side heartbeat force-submits on expiry
Rate limiting In-memory rate limiter on student-facing endpoints
Token hashing All tokens (refresh, email verify, password reset) stored as SHA-256 hashes

Data Validation

  • All API inputs validated with Zod v4 schemas in lib/validators/
  • Prisma parameterized queries prevent SQL injection
  • Sensitive fields (passwordHash, tokenHash) never exposed in API responses
  • Email addresses normalized to lowercase before storage

πŸ—„ Database

Models (15+)

Model Purpose
Admin Admin accounts (invite-only, email-verified)
Student Auto-created student records (Gmail-based, no password)
ExamForm Exam definition, settings, scheduling, publishing
ExamQuestion Questions belonging to an exam
ExamQuestionOption Answer options per question
ExamSession One session per student per exam with security tracking
ExamResponse Student's response per question
ExamResponseOption Selected options per response (multi-choice support)
ExamAccessRule Access type per exam (public_link or specific_emails)
ExamAllowedEmail Whitelisted emails for restricted-access exams
RefreshToken JWT refresh token store (rotation-based, revocable)
EmailNotification Outbound email queue log (pending β†’ sent/failed)
AuditLog Admin action audit trail

Schema Location

  • Source: prisma/schema.prisma
  • Generated client: lib/generated/prisma/ (custom output path)
  • Client import: Always import from @/lib/prisma (singleton, dev hot-reload safe)

πŸ“§ Email System

Powered by Nodemailer over SMTP. Emails are queued in the EmailNotification table before dispatch with automatic retry (up to 3 attempts).

Event Recipient Purpose
Admin registered New admin Email verification link
Admin forgot password Admin Password reset link
Exam published (specific emails) Allowed students Exam invitation with link
Student submits exam Student Score / result confirmation
Student submits exam Admin (exam owner) New submission notification

Email templates are defined in lib/email-templates/.


🌐 API Routes

Admin API (/api/admin/*) β€” JWT-Protected

Method Route Description
POST /auth/register Create admin (by existing admin)
POST /auth/login Login β†’ access token + refresh cookie
POST /auth/logout Revoke refresh token
POST /auth/refresh Rotate refresh token
POST /auth/verify-email Verify email token
POST /auth/forgot-password Send password reset email
POST /auth/reset-password Reset password with token
GET /me Current admin profile
GET/POST /exams List / create exams
GET/PATCH/DELETE /exams/[id] Read / update / archive exam
PATCH /exams/[id]/publish Toggle published state
CRUD /exams/[id]/questions Manage questions & options
PATCH /exams/[id]/questions/reorder Reorder questions
GET/PATCH /exams/[id]/access Manage access rules
POST/DELETE /exams/[id]/access/emails Manage allowed emails
GET /exams/[id]/submissions List all submissions
GET /exams/[id]/analytics Pass rate, average score stats

Exam API (/api/exam/*) β€” Session-Token Based

Method Route Description
GET [slug] Get exam info (if published & within schedule)
POST [slug]/verify-access Validate Gmail β†’ create/return session
GET [slug]/questions Get questions (requires session token)
POST [slug]/response Save answer for one question
POST [slug]/submit Finalize exam, calculate score, send emails
POST [slug]/heartbeat Update security counters, check expiry
GET [slug]/result/[sessionId] Fetch result after submission

🎨 UI Components

Built with shadcn/ui (Radix primitives) and Tailwind CSS v4.

Category Components
Form Button, Input, Label, Textarea, Select, Checkbox, RadioGroup, Switch, Calendar, Form
Overlay Dialog, Alert, Toast (Sonner), Tooltip, Popover, Dropdown Menu, Context Menu, Sheet, Drawer
Navigation Tabs, Breadcrumb, Pagination, Command Palette (cmdk)
Layout Card, Separator, Scroll Area, Resizable Panels, Carousel
Data Table, Badge, Progress, Chart (Recharts)
Feedback Skeleton, Spinner, Avatar

Dark mode supported via next-themes. The cn() utility from @/lib/utils handles conditional class merging (clsx + tailwind-merge).


πŸ§‘β€πŸ’» Development Conventions

TypeScript

  • Strict mode enabled (strict: true)
  • Path alias: @/* β†’ project root
  • Target: ES2017, module: ESNext

API Patterns

  • Admin routes wrapped with withAdminAuth() β€” provides adminId in context
  • Exam routes wrapped with withExamSession() β€” validates active session
  • Request bodies validated with Zod schemas in lib/validators/

Auth Flow

  1. Login β†’ server issues access token (15 min) + sets refresh token cookie (7 days)
  2. Access token passed via Authorization: Bearer header
  3. On 401: apiFetch() in @/lib/apiClient.ts automatically refreshes (singleton in-flight β€” concurrent 401s share one refresh call)
  4. On refresh: old token revoked, new pair issued (rotation)

Frontend API Client

import { apiFetch } from "@/lib/apiClient";

// Example: create an exam
const response = await apiFetch("/api/admin/exams", {
  method: "POST",
  body: JSON.stringify({ title: "Midterm 2026", timeLimitMinutes: 60 }),
});

πŸ—Ί Roadmap

  • Multiple-choice question scoring (partial credit)
  • Bulk student import via CSV
  • Exam result export (CSV/PDF)
  • Admin role-based permissions (editor vs viewer)
  • IP-based access restrictions
  • Browser lockdown integration (Respondus, Safe Exam Browser)
  • Multi-language support (i18n)
  • Docker deployment configuration
  • Automated email queue processor (cron / waitUntil)

πŸ“„ License

This project is licensed under the MIT License.


🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“¬ Support

For issues, questions, or suggestions, please open an issue on GitHub.


Built with ❀️ using Next.js, Prisma, and PostgreSQL.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages