Implement call lifecycle, realtime fan-out and all bonus items#8
Open
crydafan wants to merge 42 commits into
Open
Implement call lifecycle, realtime fan-out and all bonus items#8crydafan wants to merge 42 commits into
crydafan wants to merge 42 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Core implementation
call-service— event processing, persistence, pub/subresolveNextStatus) enforces the allowed transition graph and throwsInvalidTransitionErroron illegal movescall_initiated→call_routed→call_answered→call_hold→call_ended) apply business rules (SLA flag at >30 s wait, hold limit at >60 s, short-call flag at <10 s, reroute flag at >15 s routing time) and attach them as metadata on the persisted eventrealtime-service— Redis subscriber + Socket.io fan-outcall-status-updateschannel; on each message it emits to a room named after thecallIdso only clients watching that call receive the updatefrontend— real data, real-timeuseCallsfetches via React Query and subscribes each visible call to its Socket.io room; status updates patch the cache in place without a refetchBonus items
GET /api/callsaccepts?page=&limit=(default 20, max 100); returns{ data, total, page, limit, totalPages };PaginatedResult<T>generic lives in contracts so frontend types stay in syncfindEventByTypeForCallbefore doing any work and return the existing event on duplicate deliveryinfra/docker/index.js+index.d.tsinto a singleindex.ts; types are derived from Zod schemas viaz.infer<>so they can never divergeTIMESTAMP METHOD path → STATUS (Xms)on every response);/healthenriched withuptimeandtimestamppnpm infra:devbuilds a shared image (installs all deps, pre-builds contracts), then mounts each service's source directory; nodemon + ts-node hot-reloads the Node services, Next.js runsnext dev;CHOKIDAR_USEPOLLING/WATCHPACK_POLLINGenabled for reliable watching on Mac/Windows Docker DesktopTesting
CallService.test.ts) cover all five event handlers, the state machine, business rules, idempotency, and error paths — everything isolated from infrastructure via mocksevents.test.ts) test the HTTP layer end-to-end via supertest withcallServicemocked at the service boundary: 201, 400 (Zod), 401, 404, 422, 500 — no DB or Redis neededTradeoffs and assumptions
call_routedis deduplicated by event type, meaning a retry returns the original routing rather than allowing re-assignment. For a real system we'd probably want "latest route wins" semantics with a different dedup key.failed_publishesoutbox table drained by a background job, skipped here in favour of simplicity.COUNT(*) OVER ()would be more efficient on large tables.packages/contracts/index.tsrequiresdocker compose buildto take effect; only servicesrc/directories hot-reload. Acceptable given how rarely the contract layer changes.What I'd do next
console.logwith pino, add a request correlation ID headerPOST /api/eventsto protect against replay floods