Skip to content

feat(m4): Driver Matching & Ride Lifecycle#16

Merged
codeRisshi25 merged 1 commit intomainfrom
feature/m4-matching-lifecycle
Mar 6, 2026
Merged

feat(m4): Driver Matching & Ride Lifecycle#16
codeRisshi25 merged 1 commit intomainfrom
feature/m4-matching-lifecycle

Conversation

@codeRisshi25
Copy link
Copy Markdown
Owner

@codeRisshi25 codeRisshi25 commented Mar 6, 2026

Overview

Implements Issue #9 — nearest-first cascade driver matching, ride state machine, OTP pickup verification, and full ride lifecycle processing.

What Changed

Schema Migration

  • New RideOffer model + OfferStatus enum (PENDING|ACCEPTED|REJECTED|EXPIRED)
  • Added otp, fare, distance to Trip
  • Migration SQL at packages/common/prisma/migrations/20260306_add_ride_offers_and_trip_fields/

packages/common

  • constants/matching.ts — MATCHING_RADIUS_KM (5), EXPANDED (10), OFFER_TIMEOUT (30s), MAX_CASCADE (10), Redis key prefixes
  • schemas/otp.schema.ts — Zod schemas for OTP verification + ride cancel with tripId

ride-worker — Core Engine

State Machine (state-machine.ts)

REQUESTED → ACCEPTED → STARTED → COMPLETED (M5)
    │           │
    └→ CANCELLED ←┘

Cascade Matching (matching/cascade.ts)

  1. GEOSEARCH Redis GEO for drivers within radius, sorted by distance
  2. Filter drivers:busy Redis set
  3. Send offer → create RideOffer → schedule 30s timeout
  4. Timeout while PENDING → EXPIRED → cascade to next driver
  5. All drivers exhausted → expand radius once → cancel if still empty

ride-matching.worker.ts — dispatches cascade/timeout jobs

ride-lifecycle.worker.ts — full implementations:

  • ACCEPT: SETNX lock → offer ACCEPTED → set Trip.driverId → add to busy set → generate OTP → Redis TTL + DB audit → notify rider (ride:accepted + ride:otp)
  • VERIFY_OTP: compare OTP, max 3 attempts (Redis counter), match → STARTED, exceeded → auto-cancel
  • CANCEL: validate state → CANCELLED → cleanup Redis (OTP, lock, busy set, attempts) → expire pending offers → notify rooms

api-gateway — API Layer

Socket Events:

  • driver:accept-ride { tripId, offerId } — SETNX lock → lifecycle ACCEPT job
  • driver:reject-ride { tripId, offerId } — mark REJECTED → immediate cascade

REST Endpoints:

Method Path Auth Purpose
POST /rides/:tripId/accept driver REST fallback for accept
POST /rides/:tripId/reject driver REST fallback for reject
POST /rides/:tripId/verify-otp driver OTP verification
POST /rides/:tripId/driver-cancel driver Driver cancels ride
PATCH /rides/cancel rider Enhanced with { tripId } body

Race Condition Protection:

SETNX ride:lock:{tripId} → first driver wins, second gets "already accepted"

Cross-Process Notification

ride-worker emits socket events via Redis pub/sub → api-gateway's Redis adapter delivers to clients

Tests

71 tests total (all passing):

Suite Tests
api-gateway socket auth 5
api-gateway driver handler 7 (+ accept/reject)
api-gateway rider handler 5
api-gateway auth service 9
api-gateway driver service 10
api-gateway ride service 11 (+ accept/reject/OTP/lock)
api-gateway schemas 11
ride-worker state machine 13
TypeScript: api-gateway OK | ride-worker OK | common OK

Depends On

  • M1 (Redis GEO), M2 (BullMQ infrastructure), M3 (Socket.io layer)

Blocks

  • M5 (STARTED → COMPLETED + fare calculation)

Closes #9

Summary by CodeRabbit

  • New Features
    • Drivers can now accept or reject ride offers with real-time acknowledgments and automatic room joining.
    • Added one-time password (OTP) verification for pickup confirmation with attempt tracking.
    • Riders can cancel rides by specifying trip ID; drivers can cancel accepted rides.
    • Implemented intelligent driver cascade matching using location-based search with automatic fallback radius expansion.
    • Enhanced real-time notifications for ride lifecycle status updates via socket events.

- Nearest-first cascade matching algorithm (Redis GEOSEARCH + busy filter)
  - 5km initial radius, 10km fallback, max 10 cascade attempts
  - 30s offer timeout with delayed BullMQ jobs
  - Cross-process Socket.io emission via Redis pub/sub

- Ride state machine (REQUESTED→ACCEPTED→STARTED→COMPLETED)
  - validateTransition() throws on invalid transitions
  - Terminal states (COMPLETED, CANCELLED) block all transitions

- OTP system for pickup verification
  - 4-digit numeric OTP, Redis TTL 15min, max 3 attempts
  - Auto-cancel on max attempts exceeded

- Full ride-lifecycle worker (ACCEPT, VERIFY_OTP, CANCEL)
  - ACCEPT: lock (SETNX), offer update, busy set, OTP gen, notify rider
  - VERIFY_OTP: compare OTP, track attempts, auto-transition to STARTED
  - CANCEL: cleanup Redis (OTP, lock, busy), expire pending offers, notify rooms

- Full ride-matching worker with cascade dispatch

- Socket events: driver:accept-ride, driver:reject-ride (with SETNX lock)

- REST endpoints: POST /rides/:tripId/accept|reject|verify-otp|driver-cancel
  Enhanced PATCH /rides/cancel with { tripId } body

- Schema migration: RideOffer model + OfferStatus enum + Trip.otp/fare/distance
- Matching constants + OTP Zod schemas in packages/common
- Vitest setup for ride-worker (state machine: 13 tests)
- Updated ride.service tests (11 tests covering accept/reject/OTP/cancel)

Tests: 71 passed (58 api-gateway + 13 ride-worker)
TypeScript: api-gateway OK | ride-worker OK | common OK

Closes #9
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f42386bc-58a1-43c1-8493-17195bcf9d05

📥 Commits

Reviewing files that changed from the base of the PR and between 0e2d6a4 and aa0913d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • apps/api-gateway/src/routes/ride.routes.ts
  • apps/api-gateway/src/services/__tests__/ride.service.test.ts
  • apps/api-gateway/src/services/ride.service.ts
  • apps/api-gateway/src/sockets/__tests__/driver-handler.test.ts
  • apps/api-gateway/src/sockets/handlers/driver.ts
  • apps/ride-worker/package.json
  • apps/ride-worker/src/__tests__/state-machine.test.ts
  • apps/ride-worker/src/matching/cascade.ts
  • apps/ride-worker/src/matching/index.ts
  • apps/ride-worker/src/state-machine.ts
  • apps/ride-worker/src/workers/ride-lifecycle.worker.ts
  • apps/ride-worker/src/workers/ride-matching.worker.ts
  • apps/ride-worker/vitest.config.ts
  • learn/m3-socketio-notifications.md
  • learn/m4-matching-lifecycle.md
  • packages/common/constants/matching.ts
  • packages/common/index.ts
  • packages/common/prisma/migrations/20260306_add_ride_offers_and_trip_fields/migration.sql
  • packages/common/prisma/schema.prisma
  • packages/common/schemas/index.ts
  • packages/common/schemas/otp.schema.ts

📝 Walkthrough

Walkthrough

Implements the complete M4 Driver Matching & Ride Lifecycle milestone, introducing a Redis-based cascade driver matching algorithm, OTP-based pickup verification, ride state machine with enforced transitions, and lifecycle-based processing for ride acceptance, rejection, and cancellation. Includes API routes, socket handlers, database schema changes, and comprehensive worker implementations.

Changes

Cohort / File(s) Summary
Driver Matching & Cascade Algorithm
apps/ride-worker/src/matching/cascade.ts, apps/ride-worker/src/matching/index.ts
Implements nearest-first cascade matching using Redis GEOSEARCH to find nearby drivers, filters busy drivers, creates RideOffers with expiry timestamps, handles timeouts with fallback radius expansion, and cascades to next driver on timeout or rejection via Redis pub/sub.
Ride State Machine
apps/ride-worker/src/state-machine.ts, apps/ride-worker/src/__tests__/state-machine.test.ts
Defines valid ride status transitions (REQUESTED→ACCEPTED/CANCELLED, ACCEPTED→STARTED/CANCELLED, STARTED→COMPLETED) with enforced validation; includes test suite validating allowed/disallowed transitions.
Ride Lifecycle Worker
apps/ride-worker/src/workers/ride-lifecycle.worker.ts
Fully implements lifecycle processor handling ACCEPT (with distributed locking via Redis SETNX), VERIFY_OTP (with attempt tracking), and CANCEL actions; generates and stores OTP with 15-minute TTL; manages driver busy state and cleanup on cancellation.
Ride Matching Worker
apps/ride-worker/src/workers/ride-matching.worker.ts
Extends to handle offer-timeout vs cascade branching using new CascadeContext interface; delegates to cascade matching or timeout handlers based on job type.
API Gateway Routes & Validation
apps/api-gateway/src/routes/ride.routes.ts, packages/common/schemas/otp.schema.ts
Adds REST endpoints for driver accept/reject/verify-otp; rider cancel with tripId; applies rideCancelSchema and otpVerifySchema validation; imports new service functions.
API Gateway Ride Service
apps/api-gateway/src/services/ride.service.ts
Adds four new public functions: acceptRide (validates driver/offer, acquires Redis lock, publishes ACCEPT job), rejectRide (marks offer REJECTED, cascades with coordinates), verifyOtp (publishes VERIFY_OTP job), driverCancelRide (validates state, publishes CANCEL job); modifies cancelRide to accept optional tripId; integrates Redis and lifecycle/matching queues.
Driver Socket Handlers
apps/api-gateway/src/sockets/handlers/driver.ts, apps/api-gateway/src/sockets/__tests__/driver-handler.test.ts
Adds driver:accept-ride and driver:reject-ride socket events; accept joins ride:{tripId} room on success; includes test mocks for acceptRide/rejectRide and Promise-based _trigger method.
Database Schema & Migration
packages/common/prisma/schema.prisma, packages/common/prisma/migrations/.../migration.sql
Adds RideOffer model with PENDING/ACCEPTED/REJECTED/EXPIRED status enum; adds otp, fare, distance fields to Trip; creates foreign keys and indexes; updates Trip and Driver relations.
Matching Constants & Exports
packages/common/constants/matching.ts, packages/common/schemas/index.ts, packages/common/index.ts
Centralizes cascade and OTP configuration (MATCHING_RADIUS_KM, OFFER_TIMEOUT_SECONDS, MAX_ATTEMPTS, OTP_TTL_SECONDS, Redis key prefixes); exports via barrel files.
Testing & Configuration
apps/ride-worker/vitest.config.ts, apps/ride-worker/package.json
Adds Vitest configuration and test scripts (test, test:watch, test:coverage) to ride-worker; enables coverage reporting.
Design Documentation
learn/m3-socketio-notifications.md, learn/m4-matching-lifecycle.md
Documents Socket.io architecture, real-time notification service abstraction, and comprehensive M4 workflow including cascade, OTP, state machine, and cross-process socket emission patterns.

Sequence Diagrams

sequenceDiagram
    participant Rider
    participant API Gateway
    participant Redis
    participant Ride Worker
    participant DB as Database
    participant Driver
    
    Rider->>API Gateway: POST /rides (create ride request)
    API Gateway->>Ride Worker: Publish ride-requests job
    Ride Worker->>Redis: GEOSEARCH nearby drivers (5km)
    Redis-->>Ride Worker: List of nearby driver IDs
    Ride Worker->>DB: Filter out busy drivers
    Ride Worker->>DB: Create RideOffer (PENDING, expireAt: now+30s)
    Ride Worker->>Redis: Publish ride:offer via Socket.io
    Redis-->>Driver: ride:offer (tripId, offerId)
    Ride Worker->>Ride Worker: Schedule offer-timeout job (30s delay)
    
    alt Driver Accepts
        Driver->>API Gateway: Socket: driver:accept-ride
        API Gateway->>Redis: SETNX ride:lock:{tripId}
        Redis-->>API Gateway: Lock acquired
        API Gateway->>Ride Worker: Publish ride-lifecycle ACCEPT job
        Ride Worker->>DB: Update RideOffer to ACCEPTED
        Ride Worker->>DB: Update Trip: driverId, status=ACCEPTED
        Ride Worker->>Redis: Add driver to drivers:busy set
        Ride Worker->>Redis: Store OTP (otp:{tripId}, TTL=15m)
        Ride Worker->>DB: Save OTP to Trip record
        Ride Worker->>Redis: Publish ride:accepted event
        Ride Worker->>Redis: Publish ride:otp event with OTP
        Ride Worker-->>Driver: ride:accepted + ride:otp notifications
        Ride Worker-->>Rider: ride:accepted + ride:otp notifications
    else Offer Timeout (30s)
        Ride Worker->>DB: Check if RideOffer still PENDING
        Ride Worker->>DB: Mark RideOffer as EXPIRED
        Ride Worker->>Redis: Publish ride:offer-expired
        Redis-->>Driver: ride:offer-expired
        Ride Worker->>Redis: Find next nearest driver (exclude offered drivers)
        alt Next Driver Found
            Ride Worker->>DB: Create new RideOffer
            Ride Worker->>Ride Worker: Schedule new offer-timeout job
        else No Drivers Within Radius
            Ride Worker->>Redis: Expand radius to 10km, retry
            alt Still No Drivers
                Ride Worker->>DB: Update Trip status=CANCELLED
                Ride Worker->>Redis: Publish ride:cancelled (no drivers available)
                Ride Worker-->>Rider: Cancellation notification
            end
        end
    end
Loading
sequenceDiagram
    participant Driver
    participant API Gateway
    participant Redis
    participant Ride Worker
    participant DB as Database
    participant Rider
    
    Driver->>API Gateway: POST /rides/{tripId}/verify-otp {otp: "1234"}
    API Gateway->>DB: Validate driver assigned to trip
    API Gateway->>Ride Worker: Publish ride-lifecycle VERIFY_OTP job
    Ride Worker->>Redis: Get OTP from otp:{tripId}
    Redis-->>Ride Worker: Stored OTP
    Ride Worker->>Redis: Check otp:attempts:{tripId}
    
    alt OTP Match
        Ride Worker->>DB: Update Trip status=STARTED
        Ride Worker->>Redis: Delete OTP key
        Ride Worker->>Redis: Delete attempts counter
        Ride Worker->>Redis: Delete ride:lock:{tripId}
        Ride Worker->>Redis: Publish ride:started event
        Ride Worker-->>Driver: ride:started notification
        Ride Worker-->>Rider: ride:started notification
    else OTP Mismatch & Attempts < 3
        Ride Worker->>Redis: Increment otp:attempts:{tripId}
        Ride Worker-->>Driver: OTP error with remaining attempts
    else Max Attempts Exceeded
        Ride Worker->>DB: Update Trip status=CANCELLED
        Ride Worker->>Redis: Publish ride:cancelled (max OTP attempts)
        Ride Worker->>Redis: Remove driver from drivers:busy
        Ride Worker->>Redis: Clean up OTP and lock keys
        Ride Worker-->>Driver: Cancellation notification
        Ride Worker-->>Rider: Cancellation notification
    end
Loading
sequenceDiagram
    participant Driver
    participant API Gateway
    participant Redis
    participant Ride Worker
    participant DB as Database
    participant Rider
    
    Driver->>API Gateway: Socket: driver:reject-ride {tripId, offerId}
    API Gateway->>DB: Validate driver & offer ownership
    API Gateway->>Ride Worker: Publish ride-matching job (no delay)
    Ride Worker->>DB: Mark RideOffer as REJECTED
    Ride Worker->>Redis: GEOSEARCH remaining nearby drivers
    Ride Worker->>DB: Filter out busy drivers and already-offered drivers
    alt Next Driver Available
        Ride Worker->>DB: Create new RideOffer
        Ride Worker->>Redis: Publish ride:offer to next driver
        Ride Worker->>Ride Worker: Schedule offer-timeout job (30s)
    else No More Drivers
        Ride Worker->>DB: Update Trip status=CANCELLED
        Ride Worker->>Redis: Publish ride:cancelled
        Ride Worker-->>Rider: Cancellation notification
    end
    API Gateway-->>Driver: driver:reject-ride:ack
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • [M5] Live Location Tracking & Ride Completion #10: [M5] Ride Completion & Fare Calculation — This PR implements the ride-lifecycle.worker.ts foundation (ACCEPT, VERIFY_OTP, CANCEL actions) that M5 will extend with the COMPLETE action, fare calculation logic, and history endpoints; direct code-level overlap in the lifecycle worker and state-machine validation.

Possibly related PRs

Poem

🐰 A cascade of nearby drivers now hops with glee,
Redis GEO finds the nearest, fair and free,
OTP locked in place, four digits to share,
State machines dance through transitions with care,
From REQUESTED to STARTED, the ride takes flight,
M4 complete—the matching shines bright! 🚗✨

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/m4-matching-lifecycle

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codeRisshi25 codeRisshi25 merged commit c56d312 into main Mar 6, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[M4] Driver Matching & Ride Lifecycle

1 participant