Neeg06Code is a microservice-based interview practice platform where users:
- Sign in with Supabase auth
- Choose difficulty + topic + language
- Enter a realtime queue
- Get matched with another user
- Collaborate in a live pair-programming session with shared editor sync
The repository is a monorepo containing a Vite frontend, four backend services, a gateway (Nginx), shared contracts, and CI/CD workflows.
- System Overview
- Repository Structure
- Architecture and Runtime Flow
- Services
- API Contracts
- Socket Events
- Environment Configuration
- Local Deployment
- Build, Test, and Typecheck
- CI/CD
- Production Deployment
- Troubleshooting
- Auth and profile management via Supabase
- Role-based features (
user,admin,developer) - Question bank management (admin/developer)
- Matchmaking queue with timeout and reconnection support
- Match handoff over RabbitMQ
- Collaboration session bootstrap with per-user join tokens
- Realtime collaborative coding using Socket.IO + Yjs
- History page for past attempts
- Supabase: authentication + persistent data (
profiles,questions,admin_requests,history) - Redis: transient queue/session/presence state
- RabbitMQ: async event transport between matching and collaboration services
| Path | Purpose |
|---|---|
frontend/ |
React + Vite + TypeScript UI |
services/user-service/ |
Profile + admin/demote request APIs |
services/question-service/ |
Question CRUD + random question selection APIs |
services/matching-service/ |
Queueing + matching + websocket matchmaking |
services/collaboration-service/ |
Match event consumer + session lifecycle + collaborative editor sync |
nginx/ |
Gateway routing for HTTP and websocket traffic |
shared/ |
Shared cross-service/frontend contracts and constants |
.github/workflows/ |
CI and CD pipelines |
docker-compose.yml |
Local backend stack orchestration |
- Frontend opens matching socket and emits
join_queue. - Matching service stores queue state in Redis.
- On compatible pair found, matching service fetches a random question from question-service.
- Matching service publishes
match.foundevent to RabbitMQ exchangepeerprep. - Collaboration service consumes event and creates a session seed in Redis.
- Collaboration service generates per-user join tokens and
session-readypayloads. - If user notification socket is connected, payload is pushed immediately; otherwise queued in Redis for replay.
- Frontend connects to
/sessionnamespace with access token +sessionId+joinToken. - Collaboration service authenticates, restores document snapshot, emits
session:joined+doc:sync. - Participants exchange incremental Yjs updates through
doc:update.
- React 18 + TypeScript
- Vite
- Zustand (client store)
- Supabase JS client
- Socket.IO client
- Monaco Editor + Yjs integration
- Public:
/,/login,/signup - Protected:
/match,/queue,/account,/session/:sessionId,/history - Developer-only:
/dev-panel - Admin/Developer feature entry:
/questions
- Auth state managed through
AuthContextand synced withprofilesrole data. - Match preferences are selected in
MatchingSetup. - Queue flow handled by
useMatchmaking. - Session-ready notifications handled by
useCollabNotifications. - Live session state + reconnect/grace handling handled by
useCollabSession. - Pending collaboration session is persisted in local/session storage (
peerprep.pendingSession).
- Health check
- Profile retrieval
- Display-name lookup by user ID
- Admin promotion and demotion request lifecycle
- Supabase auth validation
- Supabase tables:
profiles,admin_requests
- List all questions
- Get question by ID
- Get random question by difficulty + topic
- Add/update/delete questions (role-gated)
- Supabase tables:
questions,profiles(for role checks via middleware)
- Accept matchmaking queue joins/cancels over websocket
- Maintain queue and request state in Redis
- Handle reconnects (
queue_rejoined) using request TTL - Periodically scan queues and attempt matches
- Create match records, mark matched users, emit
match_found - Publish match events to RabbitMQ for collaboration handoff
- Queue keys are partitioned by difficulty + language:
queue:<difficulty>:<language> - Timeout path emits
timeoutafter request expiry events from Redis keyspace notifications - Uses shared contracts from
shared/types.ts
- Consume
match.foundevents from RabbitMQ - Create idempotent session seeds in Redis
- Create and verify join tokens
- Deliver/queue
session-readynotifications - Authenticate notification and session sockets via Supabase token lookup
- Coordinate participant status (
connected,disconnected,left) - Handle reconnect grace periods and cleanup when both users leave
- Synchronize editor state via Yjs document updates and Redis snapshots
- Match-level lock prevents duplicate session creation.
- Pending notifications are indexed per user for replay after reconnect.
- Join tokens are stored hashed for verification.
- Document snapshots migrate from initial plain-text seed to
yjs-update-base64.
Base URL (through gateway): http://localhost:8080
| Service | Endpoint | Response |
|---|---|---|
| Gateway | GET /gateway/health |
plain text ok |
| User | GET /users/health |
{ "status": "User service is running" } |
| Question | GET /questions/health |
{ "status": "ok" } |
| Matching | GET /matching/health |
{ "message": "Matching service is running" } |
| Collaboration | GET /collaboration/health |
{ "service": "collaboration-service", "status": "ok", "port": 3004 } |
| Method | Route | Purpose |
|---|---|---|
| GET | /users/profile |
Get current user profile |
| GET | /users/profile/:userId |
Get display name by user ID |
| POST | /users/:id/admin-request |
Create promotion request |
| GET | /users/admin-requests |
List pending promotion requests (developer) |
| PATCH | /users/admin-requests/:id/approve |
Approve promotion request (developer) |
| PATCH | /users/admin-requests/:id/reject |
Reject promotion request (developer) |
| POST | /users/:id/demote-request |
Create demotion request (admin) |
| GET | /users/demote-requests |
List pending demotion requests (developer) |
| PATCH | /users/demote-requests/:id/approve |
Approve demotion request (developer) |
| PATCH | /users/demote-requests/:id/reject |
Reject demotion request (developer) |
| Method | Route | Purpose |
|---|---|---|
| GET | /questions |
Get all questions |
| GET | /questions/:id |
Get one question |
| GET | /questions/random/:difficulty/:topic |
Get random filtered question |
| POST | /questions |
Add question (admin/developer) |
| PUT | /questions/:id |
Update question (admin/developer) |
| DELETE | /questions/:id |
Delete question (admin/developer) |
Socket traffic is proxied through Nginx paths:
- Matching:
path=/matching/socket.io - Collaboration notification + session namespace:
path=/collaboration/socket.io
join_queuepayload:{ userId, difficulty, topics, language }cancel_queuepayload:{ userId }
match_foundpayload:{ matchId, question, peerId, difficulty, topic, language }queue_rejoinedpayload:{ timeLeft }queue_errorpayload:{ message }timeoutpayload:{ message }
notification:registerpayload:{ userId }
session-readypayload:{ sessionId, userId, joinToken, gracePeriodMs, language, question, websocketUrl }notification:errorpayload:{ message }
doc:updatepayload:{ update }session:leave(no payload)
session:joineddoc:syncdoc:updateparticipant:statussession:endedsession:error
Root .env drives Docker Compose and shared local stack values.
Start from:
.env.example(repository root)frontend/.env.local.exampleservices/*/.env.example(for standalone service runs)
- Ports:
NGINX_PORT,USER_SERVICE_PORT,QUESTION_SERVICE_PORT,MATCHING_SERVICE_PORT,COLLAB_SERVICE_PORT - Shared backends:
SUPABASE_URL,SUPABASE_SERVICE_KEY,REDIS_USERNAME,REDIS_PASSWORD,REDIS_HOST,REDIS_PORT,RABBITMQ_URL - Collaboration overrides:
FRONTEND_ORIGIN,PUBLIC_WS_URL,GRACE_PERIOD_MS,JOIN_TOKEN_TTL_MS,RABBITMQ_MATCH_FOUND_EXCHANGE,RABBITMQ_MATCH_FOUND_QUEUE,RABBITMQ_MATCH_FOUND_ROUTING_KEY - Logging:
LOG_LEVEL
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYVITE_GATEWAY_URLVITE_MATCHING_WS_PATHVITE_COLLAB_WS_PATH
PeerPrep runs locally as:
- frontend: Vite dev server from
frontend/ - backend gateway and services: Docker Compose from the repo root
- Supabase: external managed dependency
- Redis: external managed dependency
- RabbitMQ: local container via Docker Compose
- Docker and Docker Compose
- Node.js and npm
- accessible Supabase project
- accessible Redis instance
Use these files for local development:
- root
.env.local.exampleThis drives Docker Compose for the gateway, backend services, and RabbitMQ. - frontend
frontend/.env.local.exampleThis drives the frontend development build. - service examples under
services/These are useful when running an individual service directly outside Docker Compose.
Create the real env files you need before startup. Do not commit real secrets.
Suggested setup:
cp .env.local.example .env
cp frontend/.env.local.example frontend/.env.local- Fill in the root
.envwith local ports and shared dependency values. - Fill in
frontend/.env.localwith local frontend values. - Start the backend stack from the repository root:
docker compose up --build- In a separate shell, start the frontend:
npm run dev --prefix frontendThe frontend should talk to the local Nginx gateway, not directly to individual backend service ports.
Expected local gateway routes include:
/users/.../questions/.../history/.../matching/socket.io/collaboration/socket.io
The gateway configuration lives in nginx/nginx.conf.
npm run format:check
npm run build
npm run typecheck# Frontend
npm run dev --prefix frontend
npm run build --prefix frontend
# Services
npm run dev --prefix services/user-service
npm run dev --prefix services/question-service
npm run dev --prefix services/matching-service
npm run dev --prefix services/collaboration-serviceCI runs:
- Root formatting check (
npm run format:check) - Matrix package validation (install, typecheck, tests/coverage where configured, build)
- Docker image build validation for backend services
Current Docker image validation covers:
user-servicequestion-servicematching-servicecollaboration-service
It does not currently build nginx in CI.
CD runs on successful CI on main (or manual dispatch) and currently handles the frontend only.
It does:
- Build the frontend with production
VITE_*values - Upload the built
frontend/distartifact - Deploy the frontend to Firebase Hosting
Current GitHub Actions secrets required for frontend CD:
GCP_SA_KEYVITE_SUPABASE_URLVITE_SUPABASE_ANON_KEY
Current Firebase deploy target:
- Firebase project ID:
neeg06code-prod
Backend deployment is manual and is performed from a developer machine using scripts/deploy-backend.sh.
For infrastructure details, refer to this README’s CI/CD and Production Deployment sections and the workflow files in .github/workflows/.
PeerPrep is currently deployed in the following shape:
- frontend: Firebase Hosting on
https://neeg06code.com - backend: AWS ECS Fargate services behind Nginx
- backend public entrypoint:
https://api.neeg06code.com - backend ingress: ALB -> Nginx -> internal services
- external managed dependencies: Supabase, Redis, RabbitMQ
The deployable backend services are:
nginxuser-servicequestion-servicematching-servicecollaboration-service
The frontend is the Vite app in frontend/.
Frontend production build values:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYVITE_GATEWAY_URL=https://api.neeg06code.comVITE_MATCHING_WS_PATH=/matching/socket.ioVITE_COLLAB_WS_PATH=/collaboration/socket.io
Backend runtime requirements:
user-servicePORT=3001NODE_ENV=productionSUPABASE_URLSUPABASE_SERVICE_KEY
question-servicePORT=3002NODE_ENV=productionSUPABASE_URLSUPABASE_SERVICE_KEY
matching-servicePORT=3003NODE_ENV=productionQUESTION_SERVICE_URL=http://question-service:3002REDIS_USERNAMEREDIS_PASSWORDREDIS_HOSTREDIS_PORTRABBITMQ_URL
collaboration-servicePORT=3004NODE_ENV=productionFRONTEND_ORIGIN=https://neeg06code.comPUBLIC_WS_URL=https://api.neeg06code.comSUPABASE_URLSUPABASE_SERVICE_KEYREDIS_USERNAMEREDIS_PASSWORDREDIS_HOSTREDIS_PORTRABBITMQ_URLRABBITMQ_MATCH_FOUND_EXCHANGERABBITMQ_MATCH_FOUND_QUEUERABBITMQ_MATCH_FOUND_ROUTING_KEY
Do not store production secrets in the repository.
The backend is currently deployed manually from a developer machine using:
./scripts/deploy-backend.shThe script:
- prompts for temporary AWS credentials, including the session token when required
- builds production images for the backend services
- pushes those images to ECR
- registers new ECS task definition revisions
- updates ECS services and waits for stability
The script currently assumes these fixed infrastructure values:
- AWS region:
ap-southeast-1 - AWS account ID:
894064921761 - ECS cluster:
neeg06code-prod - ECS task definition family prefix:
neeg06code - ECR repository prefix:
neeg06code - Docker target platform:
linux/amd64
It prompts only for:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKENIMAGE_TAG
Recommended backend rollout order:
user-servicequestion-servicematching-servicecollaboration-servicenginx
Backend infrastructure requirements:
- Ensure the ALB forwards traffic to the Nginx service.
- Ensure the ALB health check points at
/gateway/health. - Ensure Nginx listens internally on port
80.
The frontend is deployed automatically through GitHub Actions CD to Firebase Hosting.
The workflow:
- builds the frontend with production
VITE_*values - uploads the build artifact
- deploys the artifact to Firebase Hosting
Manual frontend deployment is still available when needed:
npm run build --prefix frontend
firebase deploy --only hostingThe Firebase Hosting configuration lives in firebase.json.
Validate backend deployment first:
https://api.neeg06code.com/gateway/healthhttps://api.neeg06code.com/users/healthhttps://api.neeg06code.com/questions/healthhttps://api.neeg06code.com/matching/healthhttps://api.neeg06code.com/collaboration/health
Then validate the frontend deployment:
https://neeg06code.com- authenticated frontend flow
- matchmaking websocket flow
- collaboration websocket flow
- Frontend cannot reach backend
- verify
VITE_GATEWAY_URLpoints to gateway (defaulthttp://localhost:8080)
- verify
- Matching queue never matches
- verify Redis credentials/connectivity and
QUESTION_SERVICE_URL
- verify Redis credentials/connectivity and
- No collaboration session after match
- verify RabbitMQ connection and exchange/queue/routing key alignment between matching and collaboration services
- Session reconnect fails quickly
- verify
GRACE_PERIOD_MSand join token TTL settings
- verify
- Role-based pages inaccessible
- verify
profiles.rolein Supabase (user/admin/developer)
- verify
- Matching service notes:
services/matching-service/README.md,services/matching-service/docs/ - Collaboration service notes:
services/collaboration-service/README.md
