Architecture Overview
Last updated: 2026-02-27
TalentSync is a two-repo monorepo:
| Repo | Stack | Role |
|---|---|---|
API-honeyhimself/ | Express + TypeScript, MongoDB/Mongoose, Firebase Admin, Zod | REST API backend |
PeerZoom/ | React + TypeScript, Vite, Axios, shadcn/ui | Recruiter + candidate frontend |
All routes are mounted under /v1/. The frontend reads VITE_SERVER_URL + '/v1' as the API base (set via VITE_SERVER_URL env var, consumed by auth.service.ts axios instance).
High-Level Entity Relationship
erDiagram
Plan ||--o{ Organization : "subscribed_to"
Organization ||--o{ User : "has_members"
Organization ||--o{ JobOpening : "owns"
User ||--o{ JobOpening : "creates"
StageTypeConfig ||--o{ JobOpening : "defines_stage_shapes"
ScreeningQuestion ||--o{ JobOpening : "in_screeningConfig"
DSAProblem ||--o{ JobOpening : "in_dsaConfig"
ScenarioQuestion ||--o{ JobOpening : "in_aiTechnical_aiConversational"
InterviewQuestion ||--o{ JobOpening : "in_live1on1_cultureFit"
JobOpening ||--o{ CandidatePipeline : "tracks_candidates"
Participant ||--o{ CandidatePipeline : "enrolled_in"
CandidatePipeline ||--o{ Interview : "has_stage_sessions"
JobOpening ||--o{ Interview : "conducted_for"
Participant ||--o{ Interview : "attends"
User ||--o{ Interview : "hosts"
ScreeningQuestion ||--o{ Interview : "in_screeningResponses"
DSAProblem ||--o{ Interview : "in_dsaSubmissions"
ScenarioQuestion ||--o{ Interview : "in_aiResponses"Core Design Principles
1. Backend Drives the UI
StageTypeConfig is a seeded MongoDB collection that tells the frontend what stage types exist, what tools they enable, and what fields they require. The frontend renders forms dynamically — there is no STAGE_TYPES array, no STAGE_FIELD_CONFIG object, and no hardcoded per-stage conditional logic in frontend code.
2. Per-Stage Polymorphic Configuration
Each stage in a JobOpening.stages[] array carries only the config relevant to its type:
automated_screening→screeningConfigtechnical_dsa→dsaConfigtechnical_ai_assisted→aiTechnicalConfigai_conversational→aiConversationalConfiglive_1on1→live1on1Configculture_fit_hr→cultureFitConfig
Only one config block is populated per stage. All other blocks are absent (not null, not empty).
3. Unified Candidate Pipeline State
CandidatePipeline is the single source of truth for where a candidate stands in a job. One document per (jobOpeningId, participantId) pair. It contains two parallel status views:
- Internal view — frank evaluation terms (
pass,fail,shortlisted,rejected) - Candidate-facing view — deliberate, humane language (
in_progress,advanced,not_selected)
The projection layer enforces the boundary at every API response.
4. Polymorphic Question Bank
Four separate collections, one per question domain:
| Collection | Stage Types | Distinctive fields |
|---|---|---|
ScreeningQuestion | automated_screening | category, chipId, contentHash (dedup) |
DSAProblem | technical_dsa | testCases[{isHidden}], starterCode, timeLimitMs |
ScenarioQuestion | technical_ai_assisted, ai_conversational | evaluationRubric, followUpGuide, scenarioType |
InterviewQuestion | live_1on1, culture_fit_hr | interviewerHints, expectedAnswerGuide, questionsVisibleTo: interviewer_only |
5. Org-Level Usage Gating
All 6 stage types are available on every plan. Monetization is volume-based:
| Limit | Enforced at |
|---|---|
maxActiveJobs | POST /v1/jobs |
maxCandidatesPerJob | POST /v1/interviews (first invite for a candidate) |
maxInterviewsPerMonth | POST /v1/interviews |
Limits are stored in Organization.featureSnapshot (snapshotted from the Plan doc) for fast reads without joins.
6. Privacy by Design
candidateProjection() strips all recruiter-internal fields before any candidate API response. Candidates see:
- Their own submitted answers
- A single
candidateAggregateScore(0–100) per completed stage - Coarse-grained stage status (
candidateStatus) - Their own global status (
candidateFacingStatus)
Candidates never see: AI scores, AI analysis, recruiter notes, feedback, criteria scores, internal results, or audio recordings.
Twelve Collections at a Glance
| Collection | Purpose |
|---|---|
Plan | System-seeded pricing tiers (free / starter / pro / enterprise) |
Organization | Recruiter team, billing unit, featureSnapshot |
User | Recruiter accounts (Firebase auth) |
Participant | Candidate identity — recruiter-created, candidate-claimed |
StageTypeConfig | System-seeded — defines all 6 stage types and their UI shapes |
ScreeningQuestion | Question bank for automated screening rounds |
DSAProblem | Coding problems with hidden test cases for DSA rounds |
ScenarioQuestion | Open-ended scenarios for AI-assisted rounds |
InterviewQuestion | Private question bank for live interview rounds |
JobOpening | Job listings with typed per-stage configuration |
CandidatePipeline | Single source of truth for candidate pipeline state per job |
Interview | One session per stage — holds all runtime data (responses, AI reports, feedback) |
Deployment Shape
┌──────────────────────────────────────────────────────────┐
│ PeerZoom (React/Vite — Vercel or similar) │
│ Recruiter dashboard + candidate-facing public pages │
│ Public routes: │
│ /candidate/screening (no auth — ?token= query param) │
│ /candidate/decline/:token (no auth — decline page) │
│ /dsa/:token (no auth — token-gated) │
│ /ai-session/:token (no auth — token-gated) │
│ /lobby/:roomId (no auth — meeting link) │
│ /room/:roomId (no auth — meeting link) │
└──────────────────────────────────────────────────────────┘
│ HTTPS / WebSocket
┌──────────────────────────────────────────────────────────┐
│ API-honeyhimself (Express/TS — Railway or similar) │
│ All routes under /v1/ │
│ Auth: Firebase Admin SDK JWT verification │
│ Validation: Zod schemas (at controller level) │
│ ORM: Mongoose │
└──────────────────────────────────────────────────────────┘
│ Mongoose │ Admin SDK
┌────────────────────┐ ┌───────────────────────────┐
│ MongoDB Atlas │ │ Firebase Auth │
│ 12 collections │ │ Shared by recruiters │
│ │ │ and candidates │
└────────────────────┘ └───────────────────────────┘File Conventions
- Backend models:
API-honeyhimself/src/models/ - Backend controllers:
API-honeyhimself/src/controllers/ - Backend routes:
API-honeyhimself/src/routes/+routes/v1/index.ts(mount point) - Frontend services:
PeerZoom/src/services/ - Frontend job components:
PeerZoom/src/components/jobs/ - Path alias
@/maps tosrc/in both repos - Backend scripts: run with
npx tsx src/scripts/foo.ts(usestsx, notts-node)