Skip to content

Repository files navigation

Interview Hub

AI-powered interview preparation platform. Practice DSA, system design, and coding questions with AI feedback, track your progress, and chat with AI assistants (Groq + Ollama).


Table of Contents

  1. Architecture Overview
  2. Application Flow
  3. Project Structure
  4. Tech Stack
  5. Prerequisites
  6. Local Setup
  7. Environment Variables
  8. API Reference
  9. Frontend Pages & Components
  10. State Management
  11. AI Services
  12. Security Model
  13. Database Schema
  14. Testing
  15. CI/CD
  16. Design Principles

Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                      Browser                            │
│   React 18 + Zustand + Vite (port 5173 dev)             │
│   ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐  │
│   │  Pages  │ │  Hooks   │ │  Stores  │ │ api.js    │  │
│   └────┬────┘ └────┬─────┘ └────┬─────┘ └─────┬─────┘  │
│        └───────────┴────────────┴──────────────┘        │
│                        │ HTTP + JWT Bearer               │
└────────────────────────┼────────────────────────────────┘
                         │ /api/v1/* (proxied in dev)
┌────────────────────────┼────────────────────────────────┐
│              Spring Boot 3.2 (port 8080)                │
│                         │                               │
│   ┌─────────────────────▼──────────────────────────┐    │
│   │              Security Filter Chain              │    │
│   │   JwtAuthenticationFilter → SecurityConfig     │    │
│   └─────────────────────┬──────────────────────────┘    │
│                         │                               │
│   ┌──────────┐  ┌───────▼──────┐  ┌──────────────────┐ │
│   │Controllers│→ │   Services   │→ │   Repositories   │ │
│   └──────────┘  └───────┬──────┘  └────────┬─────────┘ │
│                         │                  │            │
│              ┌──────────▼──────────┐       │            │
│              │  CompositeAiService │       │            │
│              │  (Groq → Ollama)    │       │            │
│              └──────────┬──────────┘       │            │
└─────────────────────────┼──────────────────┼────────────┘
                          │                  │
              ┌───────────▼──────┐  ┌────────▼──────────┐
              │  Groq API        │  │  PostgreSQL + pgv  │
              │  Ollama (local)  │  │  (pgvector ext.)   │
              └──────────────────┘  └───────────────────┘

Application Flow

Authentication Flow

User → LoginPage
  → POST /api/v1/auth/login { email, password }
  ← { token, refreshToken, user: { id, email, name, ... } }
  → token saved to localStorage + authStore
  → Navigate to /dashboard
  → All subsequent requests: Authorization: Bearer <token>

Chat Flow

ChatPage loads → GET /api/v1/chat/sessions  (load history)
User types message → useChatSession.ensureSession()
  → if no session: POST /api/v1/chat/sessions?topic=general
User sends → POST /api/v1/chat/messages { sessionId, message, model }
  → ChatService saves user message
  → CompositeAiService.chat(message) [Groq first, Ollama fallback]
  ← { role:"assistant", content:"AI reply", ... }
  → Message rendered in ChatPage

Topic Study Flow

Sidebar → TopicPage (/topics/:topic)
  → useTopicData(topic) fetches in parallel:
      GET /api/v1/questions/category/:topic  (question list)
      GET /api/v1/progress                   (completed steps)
  → User reads TheoryCard / CodingCard questions
  → "Mark done" → PUT /api/v1/progress/:topic/:stepName { status:"done" }
  → Progress % updates in DashboardPage

Interview Flow

InterviewPage → POST /api/v1/interviews/start?topic=dsa&difficulty=easy
  ← InterviewSession { id, topic, status:"active" }
  → Questions displayed, user answers
  → PUT /api/v1/interviews/:sessionId/progress?questionsAttempted=10&correctAnswers=7
  → POST /api/v1/interviews/:sessionId/complete
  → TopicProgress updated (proficiency level computed)

Document RAG Flow

AdminPage / DocumentsPage
  → POST /api/v1/documents/upload (multipart/form-data)
  → DocumentService: parse file → chunk text → embed via Ollama
  → Chunks stored in DocumentChunk with embeddings
  → RagService.generateAnswerFromDocuments(query, documentId)
      → retrieve chunks → build context → AiService.chat(prompt)

Playground Flow

PlaygroundPage → Monaco editor (write code)
  → POST /api/v1/playground/hint?topic=dsa&question=...
  → POST /api/v1/playground/review?code=...&language=java
  → POST /api/v1/playground/explain?topic=dsa&concept=...
  ← AI-generated hint / review / explanation
  Note: code execution is not available (no Docker runner)

Project Structure

interview-hub/
├── client/                          # React frontend
│   ├── src/
│   │   ├── pages/                   # Route-level page components
│   │   │   ├── LoginPage.jsx
│   │   │   ├── DashboardPage.jsx
│   │   │   ├── TopicPage.jsx
│   │   │   ├── ChatPage.jsx
│   │   │   ├── PlaygroundPage.jsx
│   │   │   ├── ProfilePage.jsx
│   │   │   └── AdminPage.jsx
│   │   ├── components/
│   │   │   ├── ui/                  # Navbar, Sidebar
│   │   │   ├── chat/                # ChatSidebar, ChatInput, MessageBubble
│   │   │   └── topics/              # TheoryCard, CodingCard
│   │   ├── hooks/                   # Focused custom hooks
│   │   │   ├── useChat.js
│   │   │   ├── useBookmark.js
│   │   │   ├── useProgress.js
│   │   │   ├── useDocuments.js
│   │   │   ├── useDashboardData.js
│   │   │   ├── useTopicData.js
│   │   │   ├── useChatSession.js
│   │   │   └── useStatusPoller.js
│   │   ├── store/                   # Zustand stores
│   │   │   ├── authStore.js
│   │   │   ├── chatStore.js
│   │   │   └── uiStore.js
│   │   ├── services/
│   │   │   └── api.js               # Axios instance + all API calls
│   │   ├── constants/
│   │   │   ├── topics.js            # TOPICS array (single source)
│   │   │   └── ui.js                # DIFFICULTY_COLORS
│   │   ├── utils/
│   │   │   ├── progress.js          # topicProgress()
│   │   │   ├── groupByDate.js       # groupByDate()
│   │   │   ├── clipboard.js         # copyToClipboard()
│   │   │   └── parseMessageContent.jsx
│   │   └── test/                    # Vitest test files
│   ├── vite.config.js
│   └── package.json
│
├── backend/                         # Spring Boot backend
│   └── src/main/java/com/interviewhub/
│       ├── controller/              # REST controllers
│       ├── service/                 # Business logic
│       │   ├── AiService.java       # Interface
│       │   ├── CompositeAiService.java  # @Primary: Groq→Ollama
│       │   ├── GroqService.java
│       │   ├── OllamaService.java
│       │   ├── AuthService.java
│       │   ├── PasswordService.java
│       │   ├── ChatService.java
│       │   ├── AdminService.java
│       │   ├── InterviewService.java
│       │   ├── QuestionService.java
│       │   ├── BookmarkService.java
│       │   ├── DocumentService.java
│       │   ├── RagService.java
│       │   └── EmbeddingService.java
│       ├── entity/                  # JPA entities
│       ├── dto/                     # Request/response DTOs
│       │   ├── request/
│       │   └── response/
│       ├── repository/              # Spring Data JPA repos
│       ├── security/                # JWT + filters
│       ├── config/                  # AppProperties, SecurityConfig, WebConfig
│       ├── util/                    # SecurityUtil, UserMapper, PageResponseMapper
│       └── exception/               # Custom exceptions + GlobalExceptionHandler
│   └── src/test/java/com/interviewhub/
│       ├── service/                 # 8 service unit tests
│       ├── entity/                  # Entity logic tests
│       ├── util/                    # Utility + JwtProvider tests
│       └── controller/              # 8 @WebMvcTest controller tests
│
├── docker-compose.yml               # PostgreSQL (pgvector) container
├── netlify.toml                     # Frontend deploy config
└── README.md                        # This file

Tech Stack

Layer Technology
Frontend React 18, Vite, Tailwind CSS, Zustand, Axios, React Router v6
Backend Spring Boot 3.2, Java 17, Spring Security, Spring Data JPA
Auth JWT (JJWT 0.12.3), BCrypt (strength 12)
Database PostgreSQL 16 with pgvector extension
AI — Cloud Groq API (llama-3.1-8b-instant)
AI — Local Ollama (llama2 model, nomic-embed-text for embeddings)
Document parsing Apache PDFBox, Apache POI (DOCX), CommonMark (MD)
Frontend tests Vitest, React Testing Library, jsdom
Backend tests JUnit 5, Mockito, Spring Security Test, MockMvc
Infra Docker Compose (Postgres), Netlify (frontend), self-hosted backend

Prerequisites

  • Java 17+ (java -version)
  • Maven 3.8+ (mvn -version)
  • Node.js 20+ (node -version)
  • Docker + Docker Compose (for PostgreSQL)
  • Ollama (optional, for local AI) — ollama.ai
  • A Groq API key (optional, for cloud AI) — console.groq.com

Local Setup

1. Clone and navigate

git clone https://github.com/OmNaphade/interview-hub.git
cd interview-hub

2. Start PostgreSQL

docker-compose up -d

This starts PostgreSQL on port 5433 (mapped from container 5432) with database interviewdb.

3. Start Ollama (optional — for local AI)

ollama pull llama2
ollama pull nomic-embed-text
ollama serve

4. Configure backend environment

Create backend/.env or export these environment variables (see Environment Variables):

export DB_USER=om_2026
export DB_PASSWORD=postgres
export JWT_SECRET=your-minimum-32-character-secret-key-here
export GROQ_API_KEY=gsk_...          # optional

5. Run the backend

cd backend
mvn spring-boot:run
# Starts on http://localhost:8080

6. Run the frontend

cd client
npm install
npm run dev
# Starts on http://localhost:5173
# API calls are proxied to http://localhost:8080

7. Open the app

Visit http://localhost:5173 — sign up for a new account.


Environment Variables

Backend (backend/src/main/resources/application.yml)

Variable Default Description
DB_USER postgres PostgreSQL username
DB_PASSWORD postgres PostgreSQL password
JWT_SECRET (required) HS512 signing key — min 32 chars
GROQ_API_KEY (empty) Groq cloud API key
OLLAMA_URL http://localhost:11434 Ollama service URL
FILE_UPLOAD_PATH ./uploads Directory for uploaded documents
GITHUB_CLIENT_ID (empty) GitHub OAuth app client ID
GITHUB_CLIENT_SECRET (empty) GitHub OAuth app secret
GOOGLE_CLIENT_ID (empty) Google OAuth client ID
GOOGLE_CLIENT_SECRET (empty) Google OAuth secret

Never commit real values for JWT_SECRET, API keys, or OAuth secrets.

Docker Compose

Variable Default
POSTGRES_USER om_2026
POSTGRES_PASSWORD postgres
POSTGRES_DB interviewdb
POSTGRES_PORT 5433

API Reference

All endpoints are under /api/v1. Protected routes require Authorization: Bearer <token>.

Auth — /api/v1/auth

Method Path Auth Body / Params Response
POST /auth/signup { email, password, name } { token, refreshToken, user }
POST /auth/login { email, password } { token, refreshToken, user }
POST /auth/change-password { currentPassword, newPassword } 200
POST /auth/forgot-password ?email= 200
POST /auth/reset-password ?token=&newPassword= 200

Users — /api/v1/users

Method Path Auth Body Response
GET /users/me UserResponse
PUT /users/me { name, bio } UserResponse
GET /users/:userId UserResponse

Chat — /api/v1/chat

Method Path Auth Body / Params Response
POST /chat/sessions ?title=&topic= ChatSession
GET /chat/sessions PageResponse<ChatSession>
GET /chat/sessions/:id ChatSession
DELETE /chat/sessions/:id 200
POST /chat/messages { sessionId, message, model } ChatResponse (AI reply)
GET /chat/sessions/:id/messages List<ChatResponse>
GET /chat/sessions/:id/messages/paginated Pageable PageResponse<ChatResponse>

Questions — /api/v1/questions

Method Path Auth Notes
GET /questions/category/:category Returns PageResponse<Question>
GET /questions/category/:category/difficulty/:difficulty Filtered
GET /questions/my-questions Current user's questions
GET /questions/:id Single question
PUT /questions/:id Update own question
DELETE /questions/:id Delete own question

Interviews — /api/v1/interviews

Method Path Auth Params / Body Response
POST /interviews/start ?topic=&difficulty= InterviewSession
GET /interviews Pageable PageResponse<InterviewSession>
GET /interviews/:id InterviewSession
PUT /interviews/:id/progress ?questionsAttempted=&correctAnswers= 200
POST /interviews/:id/complete 200

Progress — /api/v1/progress

Method Path Auth Body Response
GET /progress List<TopicProgress>
PUT /progress/:topic/:stepName { status: "done"|"pending" } TopicProgress

TopicProgress shape: { topic, completedSteps, questionsAttempted, questionsCorrect, proficiencyLevel } completedSteps is a comma-separated string: "theory,coding,interview"

Bookmarks — /api/v1/bookmarks

Method Path Auth Params Response
GET /bookmarks Pageable PageResponse<Bookmark>
POST /bookmarks ?questionId= Bookmark (201)
DELETE /bookmarks ?questionId= 200
GET /bookmarks/check ?questionId= true|false

Documents — /api/v1/documents

Method Path Auth Notes
POST /documents/upload multipart/form-data with file field
GET /documents PageResponse<Document>
GET /documents/:id Single document
DELETE /documents/:id Deletes file from disk too
GET /documents/:id/chunks PageResponse<DocumentChunk>

Supported file types: PDF, DOCX, TXT, MD. Max size: 100 MB.

Playground — /api/v1/playground

Method Path Auth Params Response
POST /playground/hint ?topic=&question= { hint }
POST /playground/review ?code=&language= { review }
POST /playground/explain ?topic=&concept= { explanation }
GET /playground/status { ai_available: bool }

Code execution is not available (no Docker runner). Only AI-powered analysis.

Admin — /api/v1/admin (requires ROLE_ADMIN)

Method Path Response
GET /admin/users PageResponse<AdminUserResponse>
GET /admin/statistics { totalUsers, timestamp }
DELETE /admin/users/:userId 200

AdminUserResponse deliberately excludes passwordHash.

Status — /api/v1/status

Method Path Auth Response
GET /status/health "Application is running"
GET /status/ready "Application is ready"

Paginated Response Shape

{
  "content": [
    {
      "id": "example-id",
      "field": "value"
    }
  ],
  "pageNumber": 0,
  "pageSize": 20,
  "totalElements": 100,
  "totalPages": 5,
  "first": true,
  "last": false
}

Frontend Pages & Components

Page Route Description
LoginPage /login Signup / login / forgot-password
DashboardPage /dashboard Topic grid with progress % per topic
TopicPage /topics/:topic Questions (theory + coding), step completion
ChatPage /chat Multi-session AI chat with mode selector
PlaygroundPage /playground Monaco editor + AI hint/review panel
ProfilePage /profile Edit name/bio, change password
AdminPage /admin User list, stats (ADMIN only)

Custom Hooks

Hook Purpose
useDashboardData Fetches topic stats + progress for dashboard
useTopicData(topic) Fetches questions, roadmap, progress for one topic
useChatSession Ensures a chat session exists before sending messages
useStatusPoller(ms) Polls /status/health on an interval
useBookmark Wraps bookmark add/remove API calls
useProgress Wraps progress update API calls
useDocuments Wraps document upload API calls
useChat Thin wrapper around chatStore

State Management

Three Zustand stores:

authStore

{ user, token }
// Actions: setUser, clearUser, setToken, clearToken, logout
// token is persisted to localStorage as 'auth_token'

chatStore

{ sessions, messages, activeSessionId, chatMode }
// Actions: setSessions, setMessages, addMessage,
//          appendAssistantMessage, updateLastMessage, finishStreamingMessage,
//          setActiveSession, setChatMode

uiStore

{ darkMode, sidebarOpen, toasts }
// Actions: toggleDarkMode, toggleSidebar, addToast, removeToast
// DOM class toggling (dark mode) handled in App.jsx useEffect — NOT in the store

AI Services

The backend uses the Strategy + Composite pattern for AI:

AiService (interface)
  ├── GroqService    — implements AiService, calls Groq REST API
  ├── OllamaService  — implements AiService, calls local Ollama
  └── CompositeAiService (@Primary)
        → tries Groq first
        → falls back to Ollama if Groq throws
        → throws if both fail

All services (ChatService, PlaygroundController, RagService) inject AiService — they never reference Groq or Ollama directly.

AI Methods

Method Description
chat(message) General conversational response
reviewCode(code, language) Code quality review
getHint(topic, question) Hint without revealing full answer
isAvailable() Health check

Model Configuration (application.yml)

app:
  external:
    ollama-model: llama2              # overridable via OLLAMA_MODEL env var
    groq-model: llama-3.1-8b-instant  # overridable via GROQ_MODEL env var
    ollama-url: http://localhost:11434

Security Model

JWT Authentication

  • Tokens signed with HS512, 24-hour expiry (refresh: 7 days)
  • Filter chain: JwtAuthenticationFilter → validates token → loads UserDetails via loadUserById → populates SecurityContextHolder
  • Frontend stores token in localStorage under key auth_token; attaches it as Authorization: Bearer <token> on every request

Password Security

  • Passwords hashed with BCrypt strength 12 via Spring's PasswordEncoder bean
  • PasswordUtils.java removed — all callers inject the bean directly
  • Password reset uses time-limited tokens (24-hour expiry) with hashed token storage

Role-Based Access

  • Default role: USER
  • Admin access: ROLE_ADMIN — granted when User.role = "ADMIN" in DB
  • @PreAuthorize("hasRole('ADMIN')") on all /admin/** endpoints
  • Passwords and sensitive fields: @JsonIgnore on passwordHash; AdminUserResponse DTO used instead of raw entity

CORS

Allowed origins configured in AppProperties (not hardcoded):

  • http://localhost:3000
  • http://localhost:5173

Database Schema

users
  id (UUID PK), email (unique), password_hash, name, bio,
  profile_picture_url, role (default "USER"), created_at, updated_at

chat_sessions
  id, user_id (FK), title, topic, created_at, updated_at

chat_messages
  id, session_id (FK), role (user|assistant), content, model, created_at

questions
  id, user_id (FK), question_text, answer_text, category, difficulty, created_at

interview_sessions
  id, user_id (FK), topic, difficulty, status (active|completed),
  total_questions, correct_answers, score, started_at, ended_at

topic_progress
  id, user_id (FK), topic, questions_attempted, questions_correct,
  proficiency_level (beginner|intermediate|advanced),
  completed_steps (CSV: "theory,coding,interview"), created_at, updated_at
  UNIQUE(user_id, topic)

bookmarks
  id, user_id (FK), question_id, resource_type (default "question"), created_at

documents
  id, user_id (FK), filename, file_type, file_hash, file_path,
  status (processing|completed|failed), chunk_count, ingested_at, created_at

document_chunks
  id, document_id (FK), chunk_text, source, chunk_index, embedding (bytea)

password_reset_tokens
  id, user_id (FK), token_hash, expires_at, used_at, created_at

Proficiency Level Logic (computed in entity)

score = (questionsCorrect / questionsAttempted) * 100
score >= 80  -> "advanced"
score >= 50  -> "intermediate"
score <  50  -> "beginner"

Testing

Backend — 88 tests

cd backend
mvn test
Category Files Tests
Service unit tests (Mockito) AuthService, ChatService, InterviewService, BookmarkService, QuestionService, PasswordService, AdminService, CompositeAiService ~50
Entity logic tests TopicProgress 10
Utility tests JwtProvider, PageResponseMapper, UserMapper 14
Controller slice tests (@WebMvcTest) All 8 controllers 24

Frontend — 58 tests

cd client
npm test             # run once
npm run test:watch   # watch mode
npm run test:coverage
Category Files Tests
Stores authStore, chatStore, uiStore 18
Utilities progress, groupByDate, clipboard 15
Hooks useStatusPoller, useChatSession, useBookmark 10
API contracts services/api 8
Constants topics, ui 7

CI/CD

Continuous Integration (CI)

Runs on GitHub Actions (.github/workflows/ci-test.yml) on every push and pull request to main / develop.

Job Runner Steps
backend-test ubuntu-latest JDK 21 (Temurin) · mvn clean package · mvn test against PostgreSQL 15 + Redis 7 service containers · uploads surefire reports
frontend-build ubuntu-latest (Node 20.x & 22.x matrix) npm install · npm run lint · npm run build
docker-build ubuntu-latest Builds the backend image via Buildx with GHA layer caching (runs after tests pass, push events only)
status-check ubuntu-latest Aggregates job results and fails the pipeline if any job failed

Continuous Deployment (CD)

Triggered after successful CI completion (.github/workflows/cd-deploy.yml). Automatically triggered on successful CI completion to main branch, or manually via workflow dispatch.

Job Purpose Deployment
deploy-decision Evaluates CI status and determines if deployment should proceed Decision gate
push-docker-image Authenticates to Docker Hub and builds/pushes backend image to registry with tags (latest, branch, commit SHA) Docker Hub registry
deploy-staging SSH-based deployment to staging environment with health checks and Slack notifications Staging server
deploy-production Blue-green deployment to production with smoke tests, Slack notifications, and automatic rollback on failure Production server
rollback-production Automatic rollback to previous image if production deployment fails Production server

Required Secrets for CD Pipeline

Configure these under Settings → Secrets and variables → Actions:

Secret Purpose
DOCKER_USERNAME Docker Hub username for image push
DOCKER_PASSWORD Docker Hub Personal Access Token (with Read & Write permissions)
STAGING_SERVER_HOST Staging server hostname/IP
STAGING_SERVER_USER SSH username for staging server
STAGING_SERVER_SSH_KEY SSH private key for staging server
PROD_SERVER_HOST Production server hostname/IP
PROD_SERVER_USER SSH username for production server
PROD_SERVER_SSH_KEY SSH private key for production server
PROD_DOMAIN Production domain for health checks
SLACK_WEBHOOK Slack webhook URL for deployment notifications

Design Principles

This codebase follows SOLID and Separation of Concerns:

Principle Implementation
SRP AuthService (auth only) · PasswordService (password flows) · AdminService (admin ops) · Each hook has a single responsibility
OCP AiService interface — add a new AI provider without touching ChatService or PlaygroundController
LSP GroqService and OllamaService are interchangeable via AiService
ISP AiService is a focused interface (chat, reviewCode, getHint, isAvailable)
DIP Controllers and services depend on AiService interface, not concrete GroqService/OllamaService
Mappers UserMapper, PageResponseMapper — no duplicated mapping logic across services
DTOs Request DTOs with validation; response DTOs that never expose entity internals
Frontend Data fetching isolated in custom hooks; stores contain only state + actions; utilities are pure functions

About

End-to-end owner of an AI interview platform: full-stack app, RAG pipeline, vector search, Dockerized infrastructure, and CI/CD deployment. (Active Development)

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages