Tech Stack

  • Backend: Hono (TypeScript)
  • Database: PostgreSQL with Prisma ORM
  • Authentication: WorkOS
  • Payments: Razorpay

API Specification

Authentication

  1. POST /auth/start
    • Initiates OAuth flow with WorkOS. Returns authorization URL
      • Consumer & Vendor access
      • Returns: { authUrl: string }
  2. POST /auth/callback
    • Exchanges OAuth code for session token.
      • Consumer & Vendor access
      • Returns: { token: string, user: User }
  3. GET /auth/me
    • Retrieves current authenticated user profile.
      • Consumer & Vendor access
      • Returns: { user: User }
  4. POST /auth/logout
    • Invalidates current session token

Adspace Service

  1. GET /adspace
    • List all available adspace listings with filters.
      • Consumer: Browse all active adspaces
      • Vendor: List own adspaces
      • Returns: { adspaces: Adspace[], total: number, page: number }
  2. POST /adspace
    • Create new adspace business.
      • Vendor only
      • Body: { name, description, address, latitude, longitude, size, location, footTraffic, lighting, basePrice }
      • Returns: { adspace: Adspace }
  3. GET /adspace/:id
    • Retrieve specific adspace details.
      • Consumer & Vendor access
      • Returns: { adspace: Adspace, availableServices: Service[] }
  4. PATCH /adspace/:id
    • Update adspace information
      • Vendor owner only
      • Body: { name?, description?, address?, basePrice?, metadata? }
      • Returns: { adspace: Adspace }
  5. DELETE /adspace/:id
    • Soft delete adspace (mark inactive)
      • Vendor owner only
      • Returns: { success: boolean }
  6. GET /adspace/:id/bookings
    • List all bookings for specific adspace
      • Vendor owner only
      • Returns: { bookings: Booking[], total: number }
  7. GET /adspace/:id/analytics
    • Get adspace performance metrics
      • Vendor owner only
      • Returns: { totalBookings, revenue, occupancyRate, topServices }

Parking Service

  1. GET /parking
    • List all available parking spaces
      • Consumer: Browse all active parking
      • Vendor: List own parking businesses
      • Returns: { parking: Parking[], total: number, page: number }
  2. POST /parking
    • Create new parking business
      • Vendor only
      • Body: { name, description, address, latitude, longitude, totalSpaces, covered, hasCharging, security24h, accessType, basePrice }
      • Returns: { parking: Parking }
  3. GET /parking/:id
    • Retrieve specific parking location details
      • Consumer & Vendor access
      • Returns: { parking: Parking, availableServices: Service[], availableSpaces: number }
  4. PATCH /parking/:id
    • Update parking information
      • Vendor owner only
      • Body: { name?, description?, address?, totalSpaces?, basePrice?, metadata? }
      • Returns: { parking: Parking }
  5. DELETE /parking/:id
    • Soft delete parking (mark inactive)
      • Vendor owner only
      • Returns: { success: boolean }
  6. GET /parking/:id/bookings
    • List all bookings for specific parking location
      • Vendor owner only
      • Returns: { bookings: Booking[], total: number }
  7. GET /parking/:id/analytics
    • Get parking utilization metrics
      • Vendor owner only
      • Returns: { totalBookings, revenue, occupancyRate, averageStayDuration }

DineIn Service

  1. GET /dinein
    • List all available restaurants/dining venues
      • Consumer: Browse all active dining venues
      • Vendor: List own dining businesses
      • Returns: { dinein: DineIn[], total: number, page: number }
  2. POST /dinein
    • Create new dining business
      • Vendor only
      • Body: { name, description, address, latitude, longitude, cuisineTypes, seatingCapacity, avgPricePerHead, vegOptions, outdoorSeating, basePrice }
      • Returns: { dinein: DineIn }
  3. GET /dinein/:id
    • Retrieve specific dining venue details
      • Consumer & Vendor access
      • Returns: { dinein: DineIn, availableServices: Service[], availableTables: number }
  4. PATCH /dinein/:id
    • Update dining venue information
      • Vendor owner only
      • Body: { name?, description?, cuisineTypes?, seatingCapacity?, basePrice?, metadata? }
      • Returns: { dinein: DineIn }
  5. DELETE /dinein/:id
    • Soft delete dining venue (mark inactive)
      • Vendor owner only
      • Returns: { success: boolean }
  6. GET /dinein/:id/bookings
    • List all table reservations for specific venue
      • Vendor owner only
      • Returns: { bookings: Booking[], total: number }
  7. GET /dinein/:id/analytics
    • Get dining venue performance metrics
      • Vendor owner only
      • Returns: { totalReservations, revenue, averagePartySize, peakHours }

Farmhouse Service

  1. GET /farmhouse
    • List all available farmhouse venues.
      • Consumer: Browse all active farmhouses
      • Vendor: List own farmhouse businesses
      • Returns: { farmhouses: Farmhouse[], total: number, page: number }
  2. POST /farmhouse
    • Create new farmhouse business
      • Vendor only
      • Body: { name, description, address, latitude, longitude, totalAreaSqft, guestCapacity, hasAc, hasKitchen, parkingCapacity, amenities, basePrice }
      • Returns: { farmhouse: Farmhouse }
  3. GET /farmhouse/:id
    • Retrieve specific farmhouse details
      • Consumer & Vendor access
      • Returns: { farmhouse: Farmhouse, availableServices: Service[], upcomingBookings: Booking[] }
  4. PATCH /farmhouse/:id
    • Update farmhouse information
      • Vendor owner only
      • Body: { name?, description?, guestCapacity?, amenities?, basePrice?, metadata? }
      • Returns: { farmhouse: Farmhouse }
  5. DELETE /farmhouse/:id
    • Soft delete farmhouse (mark inactive)
      • Vendor owner only
      • Returns: { success: boolean }
  6. GET /farmhouse/:id/bookings
    • List all bookings for specific farmhouse
      • Vendor owner only
      • Returns: { bookings: Booking[], total: number }
  7. GET /farmhouse/:id/analytics
    • Get farmhouse utilization and revenue metrics
      • Vendor owner only
      • Returns: { totalBookings, revenue, occupancyRate, averageEventSize }

Services (Generic CRUD for all types)

  1. GET /services
    • List services across all business types
      • Consumer & Vendor access
      • Query: ?businessType=adspace|parking|dinein|farmhouse&businessId=uuid&status=active|inactive
      • Returns: { services: Service[], total: number }
  2. POST /services
    • Create service for a business
      • Vendor owner only
      • Body: { businessId, serviceType, title, description, basePrice, availability, metadata }
      • Returns: { service: Service }
  3. GET /services/:id
    • Retrieve specific service details
      • Consumer & Vendor access
      • Returns: { service: Service, business: Business, pricing: PricingInfo }
  4. PATCH /services/:id
    • Update service details
      • Vendor owner only
      • Body: { title?, description?, basePrice?, availability?, metadata? }
      • Returns: { service: Service }
  5. DELETE /services/:id
    • Deactivate service
      • Vendor owner only
      • Returns: { success: boolean }

Bookings (Consumer & Vendor)

  1. POST /bookings
    • Create new booking for a service
      • Consumer only
      • Body: { serviceId, metadata (date, time, duration, guests, etc.), couponCode? }
      • Returns: { booking: Booking, razorpayOrder: { id, amount, currency } }
  2. GET /bookings
    • List user’s bookings (consumer) or vendor’s bookings (vendor)
      • Consumer & Vendor access
      • Query: ?status=created|pending_payment|paid|completed|cancelled&page=1&limit=10
      • Returns: { bookings: Booking[], total: number, page: number }
  3. GET /bookings/:id
    • Retrieve specific booking details
      • Consumer & Vendor access (authorization checked)
      • Returns: { booking: Booking, service: Service, business: Business, payment: Payment? }
  4. PATCH /bookings/:id
    • Update booking status (consumer can cancel, vendor can mark complete).
      • Consumer & Vendor access (different permissions)
      • Body: { status?, notes? }
      • Returns: { booking: Booking }
  5. DELETE /bookings/:id
    • Cancel booking (only if not completed/paid)
      • Consumer only
      • Returns: { success: boolean, refundStatus: string }
  6. POST /bookings/:id/confirm-payment
    • Confirm Razorpay payment for booking
      • Consumer only
      • Body: { razorpayPaymentId, razorpaySignature }
      • Returns: { booking: Booking, paymentStatus: "paid" }

Payments

  1. POST /payments/razorpay/webhook
    • Handle Razorpay payment webhooks
      • No authentication required (webhook verification via signature)
      • Returns: { acknowledged: boolean }
  2. GET /payments/:bookingId
    • Get payment status for a booking
      • Consumer & Vendor access (authorization checked)
      • Returns: { payment: Payment }
  3. POST /payments/refund/:bookingId
    • Initiate refund for completed/cancelled booking
      • Consumer & Vendor owner access
      • Body: { reason? }
      • Returns: { refund: RefundStatus }

Analytics (Vendor Dashboard)

  1. GET /analytics/summary
    • Get vendor dashboard summary tiles
      • Vendor owner only
      • Returns: { totalBookings, completedBookings, totalRevenue, pendingRevenue, activeListings }
  2. GET /analytics/revenue
    • Get detailed revenue breakdown
      • Vendor owner only
      • Query: ?period=day|week|month|year&startDate=date&endDate=date
      • Returns: { revenue: RevenueData[], totalAmount, averagePerBooking }
  3. GET /analytics/bookings
    • Get booking trends and statistics
      • Vendor owner only
      • Query: ?businessType=adspace|parking|dinein|farmhouse&period=day|week|month
      • Returns: { bookingTrends: BookingTrend[], conversionRate, cancellationRate }

Database Schema(Proposed)

model User {
  id         String    @id @default(cuid())
  workosId   String    @unique
  email      String    @unique
  name       String
  role       Role      @default(CONSUMER)
  createdAt  DateTime  @default(now())
  updatedAt  DateTime  @updatedAt
 
  vendor     Vendor?
  bookings   Booking[]
 
  @@map("users")
}
 
enum Role {
  CONSUMER
  VENDOR_OWNER
  VENDOR_STAFF
}
 
model Vendor {
  id         String    @id @default(cuid())
  ownerId    String    @unique
  owner      User      @relation(fields: [ownerId], references: [id], onDelete: Cascade)
  name       String
  status     String    @default("active")
  createdAt  DateTime  @default(now())
  updatedAt  DateTime  @updatedAt
 
  businesses Business[]
 
  @@index([ownerId])
  @@map("vendors")
}
 
model Business {
  id          String      @id @default(cuid())
  vendorId    String
  vendor      Vendor      @relation(fields: [vendorId], references: [id], onDelete: Cascade)
  type        BusinessType
  name        String
  description String?
  address     String?
  latitude    Float?
  longitude   Float?
  basePrice   Float?
  status      String      @default("active")
  metadata    Json?
  createdAt   DateTime    @default(now())
  updatedAt   DateTime    @updatedAt
 
  services    Service[]
 
  @@unique([vendorId, name])
  @@index([type])
  @@index([vendorId])
  @@map("businesses")
}
 
enum BusinessType {
  ADSPACE
  PARKING
  DINEIN
  FARMHOUSE
}
 
model Service {
  id           String     @id @default(cuid())
  businessId   String
  business     Business   @relation(fields: [businessId], references: [id], onDelete: Cascade)
  serviceType  String
  title        String
  description  String?
  basePrice    Float
  availability Int        @default(1)
  status       String     @default("active")
  metadata     Json?
  createdAt    DateTime   @default(now())
  updatedAt    DateTime   @updatedAt
 
  bookings     Booking[]
 
  @@index([businessId])
  @@map("services")
}
 
model Booking {
  id              String          @id @default(cuid())
  userId          String
  user            User            @relation(fields: [userId], references: [id], onDelete: Cascade)
  serviceId       String
  service         Service         @relation(fields: [serviceId], references: [id])
  status          BookingStatus   @default(CREATED)
  baseAmount      Float
  discountAmount  Float           @default(0)
  finalAmount     Float
  razorpayOrderId String?
  metadata        Json?
  createdAt       DateTime        @default(now())
  updatedAt       DateTime        @updatedAt
  completedAt     DateTime?
 
  payment Payment?
 
  @@index([userId])
  @@index([serviceId])
  @@index([status])
  @@map("bookings")
}
 
enum BookingStatus {
  CREATED
  PENDING_PAYMENT
  PAID
  COMPLETED
  CANCELLED
}
 
model Payment {
  id                 String         @id @default(cuid())
  bookingId          String         @unique
  booking            Booking        @relation(fields: [bookingId], references: [id], onDelete: Cascade)
  razorpayPaymentId  String?        @unique
  razorpaySignature  String?
  status             PaymentStatus  @default(PENDING)
  amount             Float
  signatureVerified  Boolean        @default(false)
  createdAt          DateTime       @default(now())
  updatedAt          DateTime       @updatedAt
 
  @@index([bookingId])
  @@map("payments")
}
 
enum PaymentStatus {
  PENDING
  SUCCESS
  FAILED
  CANCELLED
}