Tech Stack
- Backend: Hono (TypeScript)
- Database: PostgreSQL with Prisma ORM
- Authentication: WorkOS
- Payments: Razorpay
API Specification
Authentication
- POST
/auth/start
- Initiates OAuth flow with WorkOS. Returns authorization URL
- Consumer & Vendor access
- Returns:
{ authUrl: string }
- POST
/auth/callback
- Exchanges OAuth code for session token.
- Consumer & Vendor access
- Returns:
{ token: string, user: User }
- GET
/auth/me
- Retrieves current authenticated user profile.
- Consumer & Vendor access
- Returns:
{ user: User }
- POST
/auth/logout
- Invalidates current session token
Adspace Service
- 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 }
- POST
/adspace
- Create new adspace business.
- Vendor only
- Body:
{ name, description, address, latitude, longitude, size, location, footTraffic, lighting, basePrice }
- Returns:
{ adspace: Adspace }
- GET
/adspace/:id
- Retrieve specific adspace details.
- Consumer & Vendor access
- Returns:
{ adspace: Adspace, availableServices: Service[] }
- PATCH
/adspace/:id
- Update adspace information
- Vendor owner only
- Body:
{ name?, description?, address?, basePrice?, metadata? }
- Returns:
{ adspace: Adspace }
- DELETE
/adspace/:id
- Soft delete adspace (mark inactive)
- Vendor owner only
- Returns:
{ success: boolean }
- GET
/adspace/:id/bookings
- List all bookings for specific adspace
- Vendor owner only
- Returns:
{ bookings: Booking[], total: number }
- GET
/adspace/:id/analytics
- Get adspace performance metrics
- Vendor owner only
- Returns:
{ totalBookings, revenue, occupancyRate, topServices }
Parking Service
- GET
/parking
- List all available parking spaces
- Consumer: Browse all active parking
- Vendor: List own parking businesses
- Returns:
{ parking: Parking[], total: number, page: number }
- POST
/parking
- Create new parking business
- Vendor only
- Body:
{ name, description, address, latitude, longitude, totalSpaces, covered, hasCharging, security24h, accessType, basePrice }
- Returns:
{ parking: Parking }
- GET
/parking/:id
- Retrieve specific parking location details
- Consumer & Vendor access
- Returns:
{ parking: Parking, availableServices: Service[], availableSpaces: number }
- PATCH
/parking/:id
- Update parking information
- Vendor owner only
- Body:
{ name?, description?, address?, totalSpaces?, basePrice?, metadata? }
- Returns:
{ parking: Parking }
- DELETE
/parking/:id
- Soft delete parking (mark inactive)
- Vendor owner only
- Returns:
{ success: boolean }
- GET
/parking/:id/bookings
- List all bookings for specific parking location
- Vendor owner only
- Returns:
{ bookings: Booking[], total: number }
- GET
/parking/:id/analytics
- Get parking utilization metrics
- Vendor owner only
- Returns:
{ totalBookings, revenue, occupancyRate, averageStayDuration }
DineIn Service
- 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 }
- POST
/dinein
- Create new dining business
- Vendor only
- Body:
{ name, description, address, latitude, longitude, cuisineTypes, seatingCapacity, avgPricePerHead, vegOptions, outdoorSeating, basePrice }
- Returns:
{ dinein: DineIn }
- GET
/dinein/:id
- Retrieve specific dining venue details
- Consumer & Vendor access
- Returns:
{ dinein: DineIn, availableServices: Service[], availableTables: number }
- PATCH
/dinein/:id
- Update dining venue information
- Vendor owner only
- Body:
{ name?, description?, cuisineTypes?, seatingCapacity?, basePrice?, metadata? }
- Returns:
{ dinein: DineIn }
- DELETE
/dinein/:id
- Soft delete dining venue (mark inactive)
- Vendor owner only
- Returns:
{ success: boolean }
- GET
/dinein/:id/bookings
- List all table reservations for specific venue
- Vendor owner only
- Returns:
{ bookings: Booking[], total: number }
- GET
/dinein/:id/analytics
- Get dining venue performance metrics
- Vendor owner only
- Returns:
{ totalReservations, revenue, averagePartySize, peakHours }
Farmhouse Service
- GET
/farmhouse
- List all available farmhouse venues.
- Consumer: Browse all active farmhouses
- Vendor: List own farmhouse businesses
- Returns:
{ farmhouses: Farmhouse[], total: number, page: number }
- POST
/farmhouse
- Create new farmhouse business
- Vendor only
- Body:
{ name, description, address, latitude, longitude, totalAreaSqft, guestCapacity, hasAc, hasKitchen, parkingCapacity, amenities, basePrice }
- Returns:
{ farmhouse: Farmhouse }
- GET
/farmhouse/:id
- Retrieve specific farmhouse details
- Consumer & Vendor access
- Returns:
{ farmhouse: Farmhouse, availableServices: Service[], upcomingBookings: Booking[] }
- PATCH
/farmhouse/:id
- Update farmhouse information
- Vendor owner only
- Body:
{ name?, description?, guestCapacity?, amenities?, basePrice?, metadata? }
- Returns:
{ farmhouse: Farmhouse }
- DELETE
/farmhouse/:id
- Soft delete farmhouse (mark inactive)
- Vendor owner only
- Returns:
{ success: boolean }
- GET
/farmhouse/:id/bookings
- List all bookings for specific farmhouse
- Vendor owner only
- Returns:
{ bookings: Booking[], total: number }
- GET
/farmhouse/:id/analytics
- Get farmhouse utilization and revenue metrics
- Vendor owner only
- Returns:
{ totalBookings, revenue, occupancyRate, averageEventSize }
Services (Generic CRUD for all types)
- 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 }
- POST
/services
- Create service for a business
- Vendor owner only
- Body:
{ businessId, serviceType, title, description, basePrice, availability, metadata }
- Returns:
{ service: Service }
- GET
/services/:id
- Retrieve specific service details
- Consumer & Vendor access
- Returns:
{ service: Service, business: Business, pricing: PricingInfo }
- PATCH
/services/:id
- Update service details
- Vendor owner only
- Body:
{ title?, description?, basePrice?, availability?, metadata? }
- Returns:
{ service: Service }
- DELETE
/services/:id
- Deactivate service
- Vendor owner only
- Returns:
{ success: boolean }
Bookings (Consumer & Vendor)
- 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 } }
- 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 }
- GET
/bookings/:id
- Retrieve specific booking details
- Consumer & Vendor access (authorization checked)
- Returns:
{ booking: Booking, service: Service, business: Business, payment: Payment? }
- PATCH
/bookings/:id
- Update booking status (consumer can cancel, vendor can mark complete).
- Consumer & Vendor access (different permissions)
- Body:
{ status?, notes? }
- Returns:
{ booking: Booking }
- DELETE
/bookings/:id
- Cancel booking (only if not completed/paid)
- Consumer only
- Returns:
{ success: boolean, refundStatus: string }
- POST
/bookings/:id/confirm-payment
- Confirm Razorpay payment for booking
- Consumer only
- Body:
{ razorpayPaymentId, razorpaySignature }
- Returns:
{ booking: Booking, paymentStatus: "paid" }
Payments
- POST
/payments/razorpay/webhook
- Handle Razorpay payment webhooks
- No authentication required (webhook verification via signature)
- Returns:
{ acknowledged: boolean }
- GET
/payments/:bookingId
- Get payment status for a booking
- Consumer & Vendor access (authorization checked)
- Returns:
{ payment: Payment }
- POST
/payments/refund/:bookingId
- Initiate refund for completed/cancelled booking
- Consumer & Vendor owner access
- Body:
{ reason? }
- Returns:
{ refund: RefundStatus }
Analytics (Vendor Dashboard)
- GET
/analytics/summary
- Get vendor dashboard summary tiles
- Vendor owner only
- Returns:
{ totalBookings, completedBookings, totalRevenue, pendingRevenue, activeListings }
- 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 }
- 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
}