Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fa82151
feat: add Drizzle ORM to project
crydafan May 12, 2026
93e5aa1
chore: remove old schema file and update script
crydafan May 12, 2026
d7e4a39
fix: update package lockfile
crydafan May 12, 2026
23004f2
chore: generate database migration files
crydafan May 12, 2026
5e4ea33
refactor: separate express server setup
crydafan May 13, 2026
33085b0
fix: export database client
crydafan May 13, 2026
be62406
feat: add repository to manage call events
crydafan May 13, 2026
da60ba0
feat: add custom error classes
crydafan May 13, 2026
5f61797
feat: add repository to manage calls
crydafan May 13, 2026
684217c
fix: add room-based subscription for call events
crydafan May 13, 2026
1cb1100
feat: add Redis subscription for call status updates
crydafan May 13, 2026
18e380e
feat: implement call service for call lifecycle management
crydafan May 13, 2026
32b1075
feat: use react-query in frontend hooks
crydafan May 13, 2026
a619265
fix: forgot to add realtime service configuration
crydafan May 13, 2026
d33ea9a
feat: add rules and state machine for call lifecycle management
crydafan May 13, 2026
bf36a2b
refactor: replace direct ZodError handling with type guard function
crydafan May 13, 2026
5106649
fix: handle NotFoundError in call events route
crydafan May 13, 2026
c56b8bf
test: implement integration tests for event handling in API
crydafan May 13, 2026
cdbc241
test: add CallService tests with event processing and state transitions
crydafan May 13, 2026
6a241f4
fix: update CallService instance with dependency injection
crydafan May 13, 2026
021ed0e
fix: wrap children in Providers component in RootLayout
crydafan May 13, 2026
77c6b8a
feat: add socket status and error handling in dashboard
crydafan May 13, 2026
1f971a4
refactor: simplify event processing by extracting handlers for call e…
crydafan May 13, 2026
b7f08c6
feat: migrate contract definitions to TypeScript
crydafan May 13, 2026
f5053fd
feat: move docker-compose configuration to infra
crydafan May 13, 2026
afa9b85
chore: migrate to pnpm workspace configuration
crydafan May 13, 2026
5c05eb2
fix: run prettier formatter
crydafan May 13, 2026
78dd758
fix: correct table formatting in readme
crydafan May 13, 2026
43597e5
infra: containerize app services and load via compose
crydafan May 13, 2026
e47617e
test: replace events integration test with mocked service layer
crydafan May 13, 2026
b74b1e8
feat: add local development configuration for docker-compose
crydafan May 13, 2026
d65b868
feat: add request logging middleware
crydafan May 13, 2026
ab2c8de
feat: include uptime and timestamp in health check
crydafan May 13, 2026
1d1dbab
feat: add retry logic for publishing call status updates
crydafan May 13, 2026
1d8902c
feat: remove unused mappers for call and call event
crydafan May 13, 2026
ed8cbb2
feat: add findEventByTypeForCall for full idempotency
crydafan May 13, 2026
581980c
feat: implement pagination for call listing
crydafan May 13, 2026
42b685e
test: update listCalls and event retrieval methods
crydafan May 13, 2026
6f3f713
infra: update docker-compose project name
crydafan May 13, 2026
2faee51
infra: fix configuration files for call and realtime services
crydafan May 13, 2026
8c502ce
feat: add real-time call duration updates
crydafan May 13, 2026
0d0c33c
feat: add new call broadcasting and update subscription logic
crydafan May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
**/node_modules
.git
.gitignore
dist
**/dist
.next
**/.next
.env
**/.env
**/.env.*
59 changes: 44 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The repository includes a dashboard shell plus scaffolding for the backend and r
```

| Package | Purpose |
|---|---|
| --- | --- |
| `packages/frontend` | Dashboard UI for live calls and event history |
| `packages/call-service` | REST API, business rules, persistence, event publishing |
| `packages/realtime-service` | Redis subscriber and Socket.io fan-out layer |
Expand All @@ -33,6 +33,7 @@ Infrastructure for PostgreSQL and Redis is provided through Docker Compose.
## Scaffolding

The repo ships with:

- Express skeletons for `call-service` and `realtime-service` — routes, middleware, DB pool, and Redis client wired up but service logic unimplemented
- Next.js dashboard shell — components, hooks, and API/socket client stubs in place, currently rendering mock data
- Shared types and Zod validation in `packages/contracts`
Expand All @@ -49,7 +50,7 @@ call_initiated -> call_routed -> call_answered -> [call_hold] -> call_ended
```

| Event | Rules |
|---|---|
| --- | --- |
| `call_initiated` | Validate `queueId` exists. Start SLA timer with 30 second max wait. |
| `call_routed` | Assign an agent. Re-route if unanswered after 15 seconds. |
| `call_answered` | Flag or notify when `waitTime > 30`. Update agent-facing metadata as needed. |
Expand All @@ -62,19 +63,47 @@ call_initiated -> call_routed -> call_answered -> [call_hold] -> call_ended
4. Write unit tests around the business logic and at least one integration test for event ingestion.

Constraints:

- TypeScript throughout.
- Keep the multi-service architecture and PostgreSQL + Redis in the flow.
- No full auth system needed.
- You may refactor existing scaffolding if it improves clarity or correctness.

## Setup

### Option A: Docker-first (recommended)

Run the full stack (frontend, call-service, realtime-service, postgres, redis):

```bash
# Copy env files
cp packages/call-service/.env.example packages/call-service/.env
cp packages/realtime-service/.env.example packages/realtime-service/.env
cp packages/frontend/.env.local.example packages/frontend/.env.local

# Build and start everything
pnpm run infra:up

# Stop everything
pnpm run infra:down
```

Then open:

- Dashboard: <http://localhost:3000>
- call-service API: <http://localhost:3001>
- realtime-service WS: <http://localhost:3002>

### Option B: Local app processes + Docker infra

If you prefer to run app processes directly on your machine:

```bash
# 1. Install dependencies
npm install
pnpm install

# 2. Start infrastructure
npm run infra:up
# 2. Start only infra services
docker compose -f infra/docker/docker-compose.yml up -d postgres redis

# 3. Copy env files
cp packages/call-service/.env.example packages/call-service/.env
Expand All @@ -83,18 +112,18 @@ cp packages/frontend/.env.local.example packages/frontend/.env.local

# 4. Initialize the database schema
cd packages/call-service
npm run db:init
pnpm run db:init
cd ../..

# 5. Start the app
npm run dev
pnpm run dev
```

| Service | URL |
|---|---|
| Dashboard | http://localhost:3000 |
| call-service API | http://localhost:3001 |
| realtime-service WS | http://localhost:3002 |
| --- | --- |
| Dashboard | <http://localhost:3000> |
| call-service API | <http://localhost:3001> |
| realtime-service WS | <http://localhost:3002> |

## API

Expand All @@ -108,10 +137,10 @@ npm run dev
Vitest and Supertest are already installed in `call-service`. Run tests with:

```bash
npm test --workspace=packages/call-service
pnpm --filter call-service test
# or from inside the package
npm test
npm run test:watch
pnpm test
pnpm run test:watch
```

Placeholder test files are in `src/services/CallService.test.ts` and `src/routes/events.test.ts`.
Expand All @@ -126,7 +155,7 @@ Placeholder test files are in `src/services/CallService.test.ts` and `src/routes
## Evaluation

| Area | What strong signals look like |
|---|---|
| --- | --- |
| Ownership | Improves the system intentionally, not just the happy path |
| Domain modeling | Clean event handling, sensible state transitions, clear contracts |
| Distributed systems | Correct pub/sub flow, targeted realtime fan-out, reasonable failure thinking |
Expand Down
29 changes: 0 additions & 29 deletions docker-compose.yml

This file was deleted.

20 changes: 20 additions & 0 deletions infra/docker/Dockerfile.call-service
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:20-alpine

WORKDIR /app

RUN corepack enable

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/contracts/package.json packages/contracts/package.json
COPY packages/call-service/package.json packages/call-service/package.json

RUN pnpm install --frozen-lockfile

COPY packages/contracts ./packages/contracts
COPY packages/call-service ./packages/call-service

RUN pnpm --filter @voycelink/contracts build && pnpm --filter call-service build

EXPOSE 3001

CMD ["sh", "-c", "pnpm --filter call-service db:init && pnpm --filter call-service start"]
18 changes: 18 additions & 0 deletions infra/docker/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:20-alpine

WORKDIR /app

RUN corepack enable

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/contracts/package.json packages/contracts/package.json
COPY packages/call-service/package.json packages/call-service/package.json
COPY packages/realtime-service/package.json packages/realtime-service/package.json
COPY packages/frontend/package.json packages/frontend/package.json

RUN pnpm install --frozen-lockfile

# Build contracts so ts-node can resolve @voycelink/contracts at runtime.
# Source mounts (below) only cover each service's src/ — contracts dist stays in the image.
COPY packages/contracts ./packages/contracts
RUN pnpm --filter @voycelink/contracts build
20 changes: 20 additions & 0 deletions infra/docker/Dockerfile.frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:20-alpine

WORKDIR /app

RUN corepack enable

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/contracts/package.json packages/contracts/package.json
COPY packages/frontend/package.json packages/frontend/package.json

RUN pnpm install --frozen-lockfile

COPY packages/contracts ./packages/contracts
COPY packages/frontend ./packages/frontend

RUN pnpm --filter @voycelink/contracts build && pnpm --filter frontend build

EXPOSE 3000

CMD ["pnpm", "--filter", "frontend", "exec", "next", "start", "-H", "0.0.0.0", "-p", "3000"]
20 changes: 20 additions & 0 deletions infra/docker/Dockerfile.realtime-service
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:20-alpine

WORKDIR /app

RUN corepack enable

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/contracts/package.json packages/contracts/package.json
COPY packages/realtime-service/package.json packages/realtime-service/package.json

RUN pnpm install --frozen-lockfile

COPY packages/contracts ./packages/contracts
COPY packages/realtime-service ./packages/realtime-service

RUN pnpm --filter @voycelink/contracts build && pnpm --filter realtime-service build

EXPOSE 3002

CMD ["pnpm", "--filter", "realtime-service", "start"]
95 changes: 95 additions & 0 deletions infra/docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: voycelink-code-challenge-fs-dev

services:
call-service:
build:
context: ../..
dockerfile: infra/docker/Dockerfile.dev
volumes:
- ../../packages/call-service/src:/app/packages/call-service/src
- ../../packages/call-service/drizzle.config.ts:/app/packages/call-service/drizzle.config.ts
- ../../packages/call-service/drizzle:/app/packages/call-service/drizzle
- ../../packages/call-service/nodemon.json:/app/packages/call-service/nodemon.json
- ../../packages/call-service/tsconfig.json:/app/packages/call-service/tsconfig.json
env_file:
- ../../packages/call-service/.env
environment:
DATABASE_URL: postgresql://voycelink:secret@postgres:5432/calls_db
REDIS_URL: redis://redis:6379
CHOKIDAR_USEPOLLING: "true"
command: sh -c "pnpm --filter call-service db:init && pnpm --filter call-service dev"
ports:
- "3001:3001"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped

realtime-service:
build:
context: ../..
dockerfile: infra/docker/Dockerfile.dev
volumes:
- ../../packages/realtime-service/src:/app/packages/realtime-service/src
- ../../packages/realtime-service/nodemon.json:/app/packages/realtime-service/nodemon.json
- ../../packages/realtime-service/tsconfig.json:/app/packages/realtime-service/tsconfig.json
env_file:
- ../../packages/realtime-service/.env
environment:
REDIS_URL: redis://redis:6379
CHOKIDAR_USEPOLLING: "true"
command: pnpm --filter realtime-service dev
ports:
- "3002:3002"
depends_on:
redis:
condition: service_healthy
restart: unless-stopped

frontend:
build:
context: ../..
dockerfile: infra/docker/Dockerfile.dev
volumes:
- ../../packages/frontend:/app/packages/frontend
# Preserve the image's node_modules and Next.js cache inside the container
- /app/packages/frontend/node_modules
- /app/packages/frontend/.next
env_file:
- ../../packages/frontend/.env.local
environment:
WATCHPACK_POLLING: "true"
command: pnpm --filter frontend dev
ports:
- "3000:3000"
depends_on:
- call-service
- realtime-service
restart: unless-stopped

postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: voycelink
POSTGRES_PASSWORD: secret
POSTGRES_DB: calls_db
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U voycelink"]
interval: 5s
timeout: 5s
retries: 5

redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5

volumes:
postgres_data:
Loading