Skip to content

Akhilesh29/onelot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Car Reservation Service

A backend service that allows users to reserve cars for a limited time (hold/lock), then confirm or cancel bookings. The system ensures only one active reservation can exist for a car at a time.

Features

  • Create a reservation (place a hold on a car)
  • Confirm a reservation
  • Cancel a reservation
  • Automatic expiration of stale holds
  • Get reservation details by reservation ID
  • Get current reservation for a car
  • Concurrency handling (prevents double reservations)
  • Idempotency support via requestId
  • State machine validation (only valid transitions allowed)

Tech Stack

  • Runtime: Node.js with TypeScript
  • Framework: Express.js
  • Database: PostgreSQL
  • Validation: Zod

Prerequisites

  • Node.js (v18 or higher)
  • PostgreSQL (v12 or higher)
  • npm or yarn

Setup

  1. Clone the repository and install dependencies:

    npm install
  2. Set up PostgreSQL database:

    createdb onelot
  3. Configure environment variables: Create a .env file in the root directory:

    DB_HOST=localhost
    DB_PORT=5432
    DB_NAME=onelot
    DB_USER=postgres
    DB_PASSWORD=postgres
    PORT=3000
    NODE_ENV=development
    
  4. Run database migrations:

    psql -d onelot -f migrations/001_initial_schema.sql
  5. Start the server:

    npm run dev

    Or build and run:

    npm run build
    npm start

API Endpoints

1. Create Reservation (Place a Hold)

POST /reservations

Places a hold on a car for a specified duration.

Request Body:

{
  "carId": 1,
  "buyerId": 123,
  "requestId": "unique-request-id-123",  // Optional: for idempotency
  "holdDurationMinutes": 15  // Optional: defaults to 15 minutes
}

Response (201 Created):

{
  "id": 1,
  "carId": 1,
  "buyerId": 123,
  "state": "HELD",
  "expiresAt": "2024-01-15T10:30:00.000Z",
  "requestId": "unique-request-id-123",
  "createdAt": "2024-01-15T10:15:00.000Z",
  "updatedAt": "2024-01-15T10:15:00.000Z"
}

Error Responses:

  • 400: Validation error
  • 404: Car not found
  • 409: Car already has an active reservation

2. Confirm Reservation

POST /reservations/:id/confirm

Confirms a HELD reservation.

Request Body:

{
  "requestId": "confirm-request-id-456"  // Optional: for idempotency
}

Response (200 OK):

{
  "id": 1,
  "carId": 1,
  "buyerId": 123,
  "state": "CONFIRMED",
  "expiresAt": "2024-01-15T10:30:00.000Z",
  "requestId": "unique-request-id-123",
  "createdAt": "2024-01-15T10:15:00.000Z",
  "updatedAt": "2024-01-15T10:20:00.000Z"
}

Error Responses:

  • 400: Invalid state transition or validation error
  • 404: Reservation not found

3. Cancel Reservation

POST /reservations/:id/cancel

Cancels a HELD reservation.

Request Body:

{
  "requestId": "cancel-request-id-789"  // Optional: for idempotency
}

Response (200 OK):

{
  "id": 1,
  "carId": 1,
  "buyerId": 123,
  "state": "CANCELLED",
  "expiresAt": "2024-01-15T10:30:00.000Z",
  "requestId": "unique-request-id-123",
  "createdAt": "2024-01-15T10:15:00.000Z",
  "updatedAt": "2024-01-15T10:25:00.000Z"
}

Error Responses:

  • 400: Invalid state transition or validation error
  • 404: Reservation not found

4. Get Reservation by ID

GET /reservations/:id

Retrieves reservation details by ID.

Response (200 OK):

{
  "id": 1,
  "carId": 1,
  "buyerId": 123,
  "state": "HELD",
  "expiresAt": "2024-01-15T10:30:00.000Z",
  "requestId": "unique-request-id-123",
  "createdAt": "2024-01-15T10:15:00.000Z",
  "updatedAt": "2024-01-15T10:15:00.000Z"
}

Error Responses:

  • 404: Reservation not found

5. Get Current Reservation for Car

GET /reservations/car/:carId

Retrieves the current active reservation (HELD or CONFIRMED) for a car.

Response (200 OK):

{
  "id": 1,
  "carId": 1,
  "buyerId": 123,
  "state": "HELD",
  "expiresAt": "2024-01-15T10:30:00.000Z",
  "requestId": "unique-request-id-123",
  "createdAt": "2024-01-15T10:15:00.000Z",
  "updatedAt": "2024-01-15T10:15:00.000Z"
}

Error Responses:

  • 404: No active reservation found for this car

Database Schema

Tables

cars

  • id (SERIAL PRIMARY KEY)
  • dealer_id (INTEGER)
  • status (VARCHAR)
  • created_at (TIMESTAMP)
  • updated_at (TIMESTAMP)

reservations

  • id (SERIAL PRIMARY KEY)
  • car_id (INTEGER, FOREIGN KEY)
  • buyer_id (INTEGER)
  • state (VARCHAR): HELD, CONFIRMED, CANCELLED, or EXPIRED
  • expires_at (TIMESTAMP)
  • request_id (VARCHAR, UNIQUE): For idempotency
  • created_at (TIMESTAMP)
  • updated_at (TIMESTAMP)

reservation_events

  • id (SERIAL PRIMARY KEY)
  • reservation_id (INTEGER, FOREIGN KEY)
  • event_type (VARCHAR): CREATED, CONFIRMED, CANCELLED, EXPIRED
  • from_state (VARCHAR, nullable)
  • to_state (VARCHAR)
  • metadata (JSONB)
  • created_at (TIMESTAMP)

State Machine

Reservation states and valid transitions:

HELD → CONFIRMED
HELD → CANCELLED
HELD → EXPIRED (automatic)
CONFIRMED → (terminal state)
CANCELLED → (terminal state)
EXPIRED → (terminal state)

Key Features

Concurrency Handling

The system uses PostgreSQL FOR UPDATE row-level locking to prevent concurrent reservation attempts. When creating a reservation, the car and any active reservations are locked, ensuring only one reservation can be created at a time.

Idempotency

All mutating operations (create, confirm, cancel) support an optional requestId parameter. When provided, repeated calls with the same requestId will return the same result without creating duplicate actions.

Automatic Expiration

A background worker runs every minute to automatically expire holds that have passed their expiresAt timestamp. The worker updates the state from HELD to EXPIRED and creates an audit event.

Testing Examples

Example: Create and Confirm a Reservation

# 1. Create a reservation
curl -X POST http://localhost:3000/reservations \
  -H "Content-Type: application/json" \
  -d '{
    "carId": 1,
    "buyerId": 123,
    "requestId": "req-123",
    "holdDurationMinutes": 15
  }'

# 2. Confirm the reservation
curl -X POST http://localhost:3000/reservations/1/confirm \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "confirm-req-123"
  }'

Example: Test Idempotency

# First call
curl -X POST http://localhost:3000/reservations \
  -H "Content-Type: application/json" \
  -d '{
    "carId": 1,
    "buyerId": 123,
    "requestId": "unique-id-123"
  }'

# Second call with same requestId - returns same reservation
curl -X POST http://localhost:3000/reservations \
  -H "Content-Type: application/json" \
  -d '{
    "carId": 1,
    "buyerId": 123,
    "requestId": "unique-id-123"
  }'

About

a backend service that allows users to reserve cars for a limited time (hold/lock), then confirm or cancel bookings.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors