diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7d70631d7..45b35fc55 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,7 +20,7 @@ - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature / Module (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Documentation update +- [ ] Documentation update ## Checklist: - [ ] I have read the [Contribution Guidelines](CONTRIBUTING.md). diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..749fd8fac --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# add the gitgnore files here + +.venv/ + +# Sprint planning (local / team use) +documents/sprints/ \ No newline at end of file diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 000000000..dee964374 --- /dev/null +++ b/alembic.ini @@ -0,0 +1,149 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = postgresql://postgres:jgLiCYBjKkL7H8OG@db.pcqgvueodyxnvilfelmn.supabase.co:5432/postgres + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 000000000..f07c16cb7 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,207 @@ +# BookMyVenue — Backend + +FastAPI backend for BookMyVenue. This folder holds the API server that powers venue discovery, bookings, and admin workflows. + +See the full architecture reference: [`documents/FolderArchitecture.md`](../documents/FolderArchitecture.md) + +--- + +## Prerequisites + +- Python 3.11+ (recommended) +- PostgreSQL +- Git + +--- + +## 1. Create the backend project + +From the repository root: + +```bash +cd backend +``` + +Scaffold the **modular monolithic** layout below. One deployable app, organized by feature modules — not microservices. + +```bash +backend/ +├── app/ +│ ├── main.py # FastAPI entry point — register routes here +│ ├── core/ +│ │ ├── config.py # Settings loaded from .env +│ │ └── security.py # JWT, password hashing +│ ├── db/ +│ │ ├── session.py # Database session / engine +│ │ └── base.py # SQLAlchemy declarative base +│ ├── modules/ # Feature-based modules (one folder per domain) +│ │ ├── auth/ +│ │ │ ├── routes.py # HTTP handlers only +│ │ │ ├── schemas.py # Pydantic request/response models +│ │ │ ├── service.py # Business logic +│ │ │ └── models.py # SQLAlchemy tables +│ │ ├── users/ +│ │ ├── venues/ +│ │ ├── bookings/ +│ │ └── admin/ +│ └── utils/ +│ └── helpers.py +├── migrations/ # Alembic migrations +├── tests/ +├── .env # Local secrets (never commit) +├── .env.example # Template for contributors +├── requirements.txt +└── README.md +``` + +### Modular monolithic rules + +| Layer | Responsibility | +|-------|----------------| +| `routes.py` | Accept requests, return responses. No business logic. | +| `service.py` | All business rules and orchestration. | +| `models.py` | Database tables (SQLAlchemy). | +| `schemas.py` | Input/output validation (Pydantic). | +| `core/` | App-wide config, security, shared settings. | +| `db/` | Database connection setup only. | + +**Do not** put logic in `main.py` or route handlers. **Do not** access the database directly from routes — go through services. + +Request flow: + +```text +Client → routes.py → service.py → database → response +``` + +--- + +## 2. Create a virtual environment + +```bash +python3 -m venv .venv +``` + +Activate it: + +**macOS / Linux** + +```bash +source .venv/bin/activate +``` + +**Windows** + +```bash +.venv\Scripts\activate +``` + +Your shell prompt should show `(.venv)` when active. + +--- + +## 3. Install dependencies + +Create a `requirements.txt` with at least: + +```txt +fastapi +uvicorn[standard] +sqlalchemy +psycopg2-binary +python-dotenv +pydantic-settings +python-jose[cryptography] +passlib[bcrypt] +alembic +``` + +Install: + +```bash +pip install -r requirements.txt +``` + +To save your current environment after adding packages: + +```bash +pip freeze > requirements.txt +``` + +--- + +## 4. Environment variables (`.env`) + +Copy the example file and fill in your local values: + +```bash +cp .env.example .env +``` + +Example `.env.example`: + +```env +# App +APP_NAME=BookMyVenue +APP_ENV=development +DEBUG=true + +# Server +HOST=0.0.0.0 +PORT=8000 + +# Database +DATABASE_URL=postgresql://user:password@localhost:5432/bookmyvenue + +# Security +SECRET_KEY=change-me-to-a-long-random-string +ACCESS_TOKEN_EXPIRE_MINUTES=30 +ALGORITHM=HS256 + +# CORS (frontend URL) +CORS_ORIGINS=http://localhost:5173,http://localhost:3000 +``` + +Load these in `app/core/config.py` using `pydantic-settings` or `python-dotenv`. **Never commit `.env`** — it is listed in `.gitignore`. + +--- + +## 5. Run the development server + +```bash +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +- API: http://localhost:8000 +- Interactive docs: http://localhost:8000/docs +- OpenAPI schema: http://localhost:8000/openapi.json + +--- + +## 6. Database migrations (Alembic) + +From the `backend/` directory: + +```bash +alembic revision --autogenerate -m "describe your change" +alembic upgrade head +``` + +--- + +## Adding a new feature module + +1. Create a folder under `app/modules//`. +2. Add `routes.py`, `schemas.py`, `service.py`, and `models.py`. +3. Register the router in `app/main.py`. +4. Add a migration if the module introduces new tables. + +Keep each module self-contained. Shared code belongs in `core/`, `db/`, or `utils/` — not copied across modules. + +--- + +## Related docs + +- [System Design](../documents/SystemDesign.md) +- [Folder Architecture](../documents/FolderArchitecture.md) +- [Database Design](../documents/DBDesign.md) +- [Contributing](../CONTRIBUTING.md) diff --git a/documents/APIDocumentation.md b/documents/APIDocumentation.md new file mode 100644 index 000000000..137f5843d --- /dev/null +++ b/documents/APIDocumentation.md @@ -0,0 +1,1921 @@ +# BookMyVenue — API Documentation + +**Project:** BookMyVenue +**Version:** v1 +**Base URL:** `http://localhost:5000/api/v1` +**Format:** JSON +**Authentication:** JWT Bearer Token + +--- + +## Table of Contents + +1. [Overview](#1-overview) +2. [Conventions](#2-conventions) +3. [Authentication APIs](#3-authentication-apis) +4. [User Profile APIs](#4-user-profile-apis) +5. [Venue APIs](#5-venue-apis) +6. [Amenity APIs](#6-amenity-apis) +7. [Booking APIs](#7-booking-apis) +8. [Owner APIs](#8-owner-apis) +9. [Feedback & Rating APIs](#9-feedback--rating-apis) +10. [Issue APIs](#10-issue-apis) +11. [Admin APIs](#11-admin-apis) +12. [Payment APIs](#12-payment-apis) +13. [Error Reference](#13-error-reference) +14. [Role & Access Matrix](#14-role--access-matrix) + +--- + +## 1. Overview + +BookMyVenue is a venue discovery and booking platform with three roles: + +| Role | Description | +|--------|--------------------------------------------------| +| `user` | Browse venues, create bookings, pay, rate venues | +| `owner`| Manage venues and view bookings for their spaces | +| `admin`| Approve venues, manage users, oversee platform | + +All protected endpoints require: + +```http +Authorization: Bearer +``` + +--- + +## 2. Conventions + +### 2.1 Request headers + +| Header | Required | Description | +|-----------------|----------|--------------------------------------| +| `Content-Type` | Yes* | `application/json` for JSON bodies | +| `Authorization` | Conditional | `Bearer ` for protected routes | + +\* Not required for `GET` requests without a body. + +### 2.2 Date & time formats + +| Field | Format | Example | +|----------------|--------------|----------------| +| Date | `YYYY-MM-DD` | `2026-06-20` | +| Time | `HH:MM` | `18:00` | +| DateTime (ISO) | ISO 8601 | `2026-06-20T18:00:00Z` | + +### 2.3 Standard success response wrapper + +Most endpoints return data directly or use a simple message object: + +```json +{ + "message": "Operation completed successfully", + "data": {} +} +``` + +### 2.4 Standard error response + +```json +{ + "error": { + "code": "VALIDATION_ERROR", + "message": "Human-readable description", + "details": [ + { + "field": "email", + "message": "Invalid email format" + } + ] + } +} +``` + +### 2.5 Pagination (list endpoints) + +Query parameters: + +| Param | Type | Default | Description | +|---------|---------|---------|--------------------| +| `page` | integer | `1` | Page number | +| `limit` | integer | `20` | Items per page (max 100) | + +Paginated response: + +```json +{ + "data": [], + "pagination": { + "page": 1, + "limit": 20, + "total_items": 150, + "total_pages": 8 + } +} +``` + +### 2.6 Roles + +Valid role values: `user`, `owner`, `admin` + +During registration, only `user` or `owner` can be self-selected. `admin` accounts are created internally. + +### 2.7 API ↔ Database field mapping + +JSON request/response fields use the **same names** as database columns unless noted as derived below. + +| API field | DB table.column | Notes | +|-----------|-----------------|-------| +| `name`, `email`, `mobile`, `role`, `is_active` | `users.*` | `password` in requests → `users.password_hash` (hashed) | +| `business_name`, `phone` | `owner_profiles.*` | Owner profile endpoints only | +| `price_per_day` | `venues.price_per_day` | Venue price (INR per day) | +| `approval_status` | `venues.approval_status` | `pending`, `approved`, `rejected` | +| `rejection_reason` | `venues.rejection_reason` | Set on admin reject | +| `average_rating`, `total_reviews` | `venues.*` | Denormalized from `venue_ratings` | +| `is_active` | `venues.is_active` | `false` when admin blocks venue | +| `image_url` | `venue_images.image_url` | | +| `display_order` | `venue_images.display_order` | | +| `amenity_ids` | `venue_amenities.amenity_id` | Array in request; rows in join table | +| `booking_date`, `time_slot`, `notes`, `amount`, `status` | `bookings.*` | | +| `cancellation_reason` | `bookings.cancellation_reason` | Cancel booking request body | +| `cancelled_at` | `bookings.cancelled_at` | Set when status → `cancelled` | +| `payment_id` | `payments.payment_id` | External payment ID string | +| `currency`, `gateway`, `gateway_*` | `payments.*` | | +| `payment_status` | `payments.status` | **Derived** — latest payment for booking (not on `bookings`) | +| `refund_id`, `refund` status fields | `refunds.*` | `refund_status` in cancel response = `refunds.status` | +| `rating`, `comment` | `venue_ratings.*` | | +| `message` | `venue_feedback.message` | Feedback endpoint only | +| `subject`, `description`, `admin_note` | `issues.*` | Issue `status`: `open`, `in_progress`, `resolved`, `closed` | +| `thumbnail_url` | — | **Derived** — first `venue_images.image_url` by `display_order` | + +### 2.8 Shared enum values + +Must match [DBDesign.md](./DBDesign.md) constraints exactly. + +| Field | Allowed values | +|-------|----------------| +| `users.role` | `user`, `owner`, `admin` | +| `venues.approval_status` | `pending`, `approved`, `rejected` | +| `bookings.status` | `pending_payment`, `booked`, `cancelled` | +| `payments.status` | `created`, `paid`, `failed`, `refunded`, `refund_pending` | +| `refunds.status` | `refund_pending`, `refunded`, `failed` | +| `issues.status` | `open`, `in_progress`, `resolved`, `closed` | +| `payments.gateway` | `razorpay`, `stripe` | + +--- + +## 3. Authentication APIs + +### 3.1 Register + +Create a new account. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/auth/register` | +| **Auth** | None | + +**Request body** + +```json +{ + "name": "Alan", + "email": "alan@gmail.com", + "password": "123456", + "mobile": "9090900000", + "role": "user" +} +``` + +| Field | Type | Required | Rules | +|------------|--------|----------|------------------------------------| +| `name` | string | Yes | 2–100 characters | +| `email` | string | Yes | Valid email, unique | +| `password` | string | Yes | Min 6 characters | +| `mobile` | string | No | 10-digit phone number | +| `role` | string | Yes | `user` or `owner` | + +**Success response — `201 Created`** + +```json +{ + "message": "User registered successfully", + "data": { + "id": 1, + "name": "Alan", + "email": "alan@gmail.com", + "mobile": "9090900000", + "role": "user", + "is_active": true, + "created_at": "2026-06-01T10:00:00Z" + } +} +``` + +**Error responses** + +| Status | Code | When | +|--------|-------------------|-----------------------------| +| `400` | `VALIDATION_ERROR`| Invalid input | +| `409` | `EMAIL_EXISTS` | Email already registered | + +--- + +### 3.2 Login + +Authenticate and receive tokens. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/auth/login` | +| **Auth** | None | + +**Request body** + +```json +{ + "email": "alan@gmail.com", + "password": "123456" +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Login successful", + "data": { + "access_token": "eyJhbGciOiJIUzI1NiIs...", + "refresh_token": "eyJhbGciOiJIUzI1NiIs...", + "token_type": "bearer", + "expires_in": 1800 + } +} +``` + +| Field | Description | +|-----------------|--------------------------------------| +| `access_token` | JWT used for API requests (30 min) | +| `refresh_token` | Used to obtain a new access token | +| `expires_in` | Access token lifetime in seconds | + +**Error responses** + +| Status | Code | When | +|--------|-----------------|-------------------| +| `401` | `INVALID_CREDENTIALS` | Wrong email/password | +| `403` | `ACCOUNT_DISABLED` | User is inactive | + +--- + +### 3.3 Refresh Token + +Obtain a new access token without re-login. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/auth/refresh` | +| **Auth** | None | + +**Request body** + +```json +{ + "refresh_token": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +**Success response — `200 OK`** + +```json +{ + "data": { + "access_token": "eyJhbGciOiJIUzI1NiIs...", + "token_type": "bearer", + "expires_in": 1800 + } +} +``` + +--- + +### 3.4 Logout + +Invalidate the current refresh token. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/auth/logout` | +| **Auth** | Bearer token | + +**Request body** + +```json +{ + "refresh_token": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Logged out successfully" +} +``` + +--- + +### 3.5 Forgot Password + +Request a password reset link. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/auth/forgot-password` | +| **Auth** | None | + +**Request body** + +```json +{ + "email": "alan@gmail.com" +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "If the email exists, a reset link has been sent" +} +``` + +--- + +### 3.6 Reset Password + +Set a new password using a reset token. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/auth/reset-password` | +| **Auth** | None | + +**Request body** + +```json +{ + "token": "reset_token_from_email", + "new_password": "newSecurePassword123" +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Password reset successfully" +} +``` + +--- + +## 4. User Profile APIs + +### 4.1 Get Current User + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/users/me` | +| **Auth** | Bearer token | + +**Success response — `200 OK`** + +```json +{ + "data": { + "id": 1, + "name": "Alan", + "email": "alan@gmail.com", + "mobile": "9090900000", + "role": "user", + "is_active": true, + "created_at": "2026-06-01T10:00:00Z", + "updated_at": "2026-06-01T10:00:00Z" + } +} +``` + +--- + +### 4.2 Update Profile + +| | | +|---|---| +| **Method** | `PUT` | +| **URL** | `/users/me` | +| **Auth** | Bearer token | + +**Request body** + +```json +{ + "name": "Alan Updated", + "mobile": "9090900001" +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Profile updated successfully", + "data": { + "id": 1, + "name": "Alan Updated", + "email": "alan@gmail.com", + "mobile": "9090900001", + "role": "user", + "is_active": true, + "updated_at": "2026-06-10T12:00:00Z" + } +} +``` + +--- + +### 4.3 Change Password + +| | | +|---|---| +| **Method** | `PATCH` | +| **URL** | `/users/me/password` | +| **Auth** | Bearer token | + +**Request body** + +```json +{ + "current_password": "123456", + "new_password": "newSecurePassword123" +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Password changed successfully" +} +``` + +--- + +## 5. Venue APIs + +### 5.1 Get All Venues (Public) + +Browse approved venues. Supports search and filters. + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/venues` | +| **Auth** | None | + +**Query parameters** + +| Param | Type | Description | +|--------------|---------|--------------------------------------| +| `location` | string | Filter by city/area (partial match) | +| `min_price` | number | Minimum `price_per_day` | +| `max_price` | number | Maximum `price_per_day` | +| `amenity` | string | Filter by amenity name | +| `search` | string | Search name or description | +| `sort` | string | `price_asc`, `price_desc`, `rating` | +| `page` | integer | Page number | +| `limit` | integer | Items per page | + +**Example** + +```http +GET /venues?location=Kochi&min_price=5000&max_price=20000&page=1&limit=10 +``` + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "id": 1, + "name": "Grand Hall", + "location": "Kochi", + "price_per_day": 10000, + "approval_status": "approved", + "average_rating": 4.5, + "thumbnail_url": "https://cdn.example.com/venues/1/img1.jpg", + "amenities": ["Wi-Fi", "Parking", "AC"] + } + ], + "pagination": { + "page": 1, + "limit": 10, + "total_items": 45, + "total_pages": 5 + } +} +``` + +--- + +### 5.2 Get Venue Details (Public) + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/venues/:id` | +| **Auth** | None | + +**Example** + +```http +GET /venues/1 +``` + +**Success response — `200 OK`** + +```json +{ + "data": { + "id": 1, + "owner_id": 5, + "name": "Grand Hall", + "location": "Kochi, Kerala", + "price_per_day": 10000, + "description": "Large event venue with stage and seating for 500", + "approval_status": "approved", + "is_active": true, + "average_rating": 4.5, + "total_reviews": 28, + "amenities": [ + { "id": 1, "name": "Wi-Fi" }, + { "id": 2, "name": "Parking" }, + { "id": 3, "name": "AC" } + ], + "images": [ + { "id": 1, "image_url": "https://cdn.example.com/venues/1/img1.jpg", "display_order": 0 }, + { "id": 2, "image_url": "https://cdn.example.com/venues/1/img2.jpg", "display_order": 1 } + ], + "created_at": "2026-05-15T08:00:00Z", + "updated_at": "2026-05-15T08:00:00Z" + } +} +``` + +**Error responses** + +| Status | Code | When | +|--------|----------------|-------------------| +| `404` | `VENUE_NOT_FOUND`| Venue does not exist | + +--- + +### 5.3 Create Venue + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/venues` | +| **Auth** | Bearer token — **Owner only** | + +**Request body** + +```json +{ + "name": "Grand Hall", + "location": "Kochi", + "price_per_day": 10000, + "description": "Large event venue", + "amenity_ids": [1, 2, 3] +} +``` + +| Field | Type | Required | DB column | +|-----------------|----------|----------|------------------------| +| `name` | string | Yes | `venues.name` | +| `location` | string | Yes | `venues.location` | +| `price_per_day` | number | Yes | `venues.price_per_day` | +| `description` | string | No | `venues.description` | +| `amenity_ids` | integer[]| No | `venue_amenities` | + +**Success response — `201 Created`** + +```json +{ + "message": "Venue added successfully. Pending admin approval.", + "data": { + "id": 12, + "name": "Grand Hall", + "location": "Kochi", + "price_per_day": 10000, + "approval_status": "pending", + "is_active": true, + "created_at": "2026-06-09T16:00:00Z" + } +} +``` + +--- + +### 5.4 Update Venue + +| | | +|---|---| +| **Method** | `PUT` | +| **URL** | `/venues/:id` | +| **Auth** | Bearer token — **Owner (own venue) or Admin** | + +**Example** + +```http +PUT /venues/12 +``` + +**Request body** + +```json +{ + "name": "Grand Hall Premium", + "location": "Kochi, Edapally", + "price_per_day": 12000, + "description": "Renovated large event venue", + "amenity_ids": [1, 2, 3, 4] +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Venue updated successfully", + "data": { + "id": 12, + "name": "Grand Hall Premium", + "location": "Kochi, Edapally", + "price_per_day": 12000, + "approval_status": "pending", + "updated_at": "2026-06-10T10:00:00Z" + } +} +``` + +> **Note:** Major updates may reset `approval_status` to `pending`. + +--- + +### 5.5 Delete Venue + +| | | +|---|---| +| **Method** | `DELETE` | +| **URL** | `/venues/:id` | +| **Auth** | Bearer token — **Owner (own venue) or Admin** | + +**Example** + +```http +DELETE /venues/12 +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Venue deleted successfully" +} +``` + +**Error responses** + +| Status | Code | When | +|--------|-----------------------|-----------------------------------| +| `403` | `FORBIDDEN` | Not the venue owner | +| `409` | `ACTIVE_BOOKINGS_EXIST`| Venue has upcoming bookings | + +--- + +### 5.6 Get My Venues (Owner) + +List venues owned by the authenticated user. + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/venues/my-venues` | +| **Auth** | Bearer token — **Owner only** | + +**Query parameters:** `page`, `limit`, `approval_status` (`approved`, `pending`, `rejected`) + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "id": 12, + "name": "Grand Hall", + "location": "Kochi", + "price_per_day": 10000, + "approval_status": "pending", + "is_active": true, + "total_bookings": 5, + "created_at": "2026-06-09T16:00:00Z" + } + ], + "pagination": { + "page": 1, + "limit": 20, + "total_items": 3, + "total_pages": 1 + } +} +``` + +--- + +### 5.7 Upload Venue Images + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/venues/:id/images` | +| **Auth** | Bearer token — **Owner (own venue)** | +| **Content-Type** | `multipart/form-data` | + +**Form fields** + +| Field | Type | Required | Description | +|---------|------|----------|--------------------| +| `images`| file | Yes | One or more images (max 5 MB each, JPG/PNG/WebP) | + +**Success response — `201 Created`** + +```json +{ + "message": "Images uploaded successfully", + "data": { + "images": [ + { "id": 10, "image_url": "https://cdn.example.com/venues/12/img10.jpg", "display_order": 0 }, + { "id": 11, "image_url": "https://cdn.example.com/venues/12/img11.jpg", "display_order": 1 } + ] + } +} +``` + +--- + +### 5.8 Delete Venue Image + +| | | +|---|---| +| **Method** | `DELETE` | +| **URL** | `/venues/:id/images/:image_id` | +| **Auth** | Bearer token — **Owner (own venue)** | + +**Success response — `200 OK`** + +```json +{ + "message": "Image deleted successfully" +} +``` + +--- + +### 5.9 Check Venue Availability + +Check if a venue is available on a given date and time slot. + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/venues/:id/availability` | +| **Auth** | None | + +**Query parameters** + +| Param | Type | Required | Description | +|----------------|--------|----------|-----------------| +| `booking_date` | string | Yes | `YYYY-MM-DD` | +| `time_slot` | string | No | `HH:MM` | + +**Example** + +```http +GET /venues/1/availability?booking_date=2026-06-20&time_slot=18:00 +``` + +**Success response — `200 OK`** + +```json +{ + "data": { + "venue_id": 1, + "booking_date": "2026-06-20", + "time_slot": "18:00", + "is_available": true + } +} +``` + +--- + +## 6. Amenity APIs + +### 6.1 Get All Amenities + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/amenities` | +| **Auth** | None | + +**Success response — `200 OK`** + +```json +{ + "data": [ + { "id": 1, "name": "Wi-Fi" }, + { "id": 2, "name": "Parking" }, + { "id": 3, "name": "AC" }, + { "id": 4, "name": "Projector" } + ] +} +``` + +--- + +## 7. Booking APIs + +### 7.1 Create Booking + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/bookings` | +| **Auth** | Bearer token — **User only** | + +**Request body** + +```json +{ + "venue_id": 1, + "booking_date": "2026-06-20", + "time_slot": "18:00", + "notes": "Birthday party setup needed" +} +``` + +| Field | Type | Required | DB column | +|----------------|--------|----------|--------------------------| +| `venue_id` | integer| Yes | `bookings.venue_id` | +| `booking_date` | string | Yes | `bookings.booking_date` | +| `time_slot` | string | Yes | `bookings.time_slot` | +| `notes` | string | No | `bookings.notes` | + +**Success response — `201 Created`** + +```json +{ + "message": "Booking created", + "data": { + "id": 101, + "venue_id": 1, + "venue_name": "Grand Hall", + "booking_date": "2026-06-20", + "time_slot": "18:00", + "status": "pending_payment", + "amount": 10000, + "created_at": "2026-06-10T14:30:00Z" + } +} +``` + +> After creating a booking, proceed to [Payment APIs](#12-payment-apis) to complete payment. Booking status becomes `booked` only after successful payment. + +--- + +### 7.2 Get My Bookings + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/bookings/my-bookings` | +| **Auth** | Bearer token — **User only** | + +**Query parameters:** `status` (`booked`, `cancelled`, `pending_payment`), `page`, `limit` + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "id": 101, + "venue_id": 1, + "venue_name": "Grand Hall", + "venue_location": "Kochi", + "booking_date": "2026-06-20", + "time_slot": "18:00", + "status": "booked", + "amount": 10000, + "payment_status": "paid", + "created_at": "2026-06-10T14:30:00Z" + } + ], + "pagination": { + "page": 1, + "limit": 20, + "total_items": 5, + "total_pages": 1 + } +} +``` + +--- + +### 7.3 Get Booking Details + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/bookings/:id` | +| **Auth** | Bearer token — **User (own booking), Owner (their venue), or Admin** | + +**Success response — `200 OK`** + +```json +{ + "data": { + "id": 101, + "user_id": 1, + "user_name": "Alan", + "venue_id": 1, + "venue_name": "Grand Hall", + "booking_date": "2026-06-20", + "time_slot": "18:00", + "status": "booked", + "amount": 10000, + "notes": "Birthday party setup needed", + "cancellation_reason": null, + "cancelled_at": null, + "payment_status": "paid", + "payment": { + "payment_id": "pay_abc123", + "status": "paid", + "paid_at": "2026-06-10T14:35:00Z" + }, + "created_at": "2026-06-10T14:30:00Z" + } +} +``` + +--- + +### 7.4 Cancel Booking + +| | | +|---|---| +| **Method** | `PATCH` | +| **URL** | `/bookings/:id/cancel` | +| **Auth** | Bearer token — **User (own booking)** | + +**Request body (optional)** + +```json +{ + "cancellation_reason": "Schedule conflict" +} +``` + +| Field | Type | Required | DB column | +|-----------------------|--------|----------|--------------------------------| +| `cancellation_reason` | string | No | `bookings.cancellation_reason` | + +**Success response — `200 OK`** + +```json +{ + "message": "Booking cancelled successfully", + "data": { + "id": 101, + "status": "cancelled", + "cancellation_reason": "Schedule conflict", + "cancelled_at": "2026-06-11T09:00:00Z", + "refund_status": "refund_pending" + } +} +``` + +> `refund_status` maps to `refunds.status` when a refund row is created for the paid booking. + +> If payment was completed, a refund is initiated automatically. See [Refund Payment](#124-refund-payment). + +**Error responses** + +| Status | Code | When | +|--------|-------------------------|-----------------------------| +| `400` | `CANCELLATION_NOT_ALLOWED`| Booking date has passed | +| `404` | `BOOKING_NOT_FOUND` | Invalid booking ID | + +--- + +## 8. Owner APIs + +### 8.1 Get Bookings for My Venues + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/owner/bookings` | +| **Auth** | Bearer token — **Owner only** | + +**Query parameters:** `venue_id`, `status`, `booking_date`, `page`, `limit` + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "id": 101, + "venue_id": 12, + "venue_name": "Grand Hall", + "user_name": "Alan", + "user_email": "alan@gmail.com", + "booking_date": "2026-06-20", + "time_slot": "18:00", + "status": "booked", + "amount": 10000, + "payment_status": "paid" + } + ], + "pagination": { + "page": 1, + "limit": 20, + "total_items": 8, + "total_pages": 1 + } +} +``` + +--- + +### 8.2 Get Owner Dashboard Stats + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/owner/dashboard` | +| **Auth** | Bearer token — **Owner only** | + +**Success response — `200 OK`** + +```json +{ + "data": { + "total_venues": 3, + "approved_venues": 2, + "pending_venues": 1, + "total_bookings": 25, + "upcoming_bookings": 8, + "total_revenue": 250000, + "average_rating": 4.3 + } +} +``` + +--- + +### 8.3 Update Owner Profile + +| | | +|---|---| +| **Method** | `PUT` | +| **URL** | `/owner/profile` | +| **Auth** | Bearer token — **Owner only** | + +**Request body** + +```json +{ + "business_name": "Alan Events Pvt Ltd", + "phone": "9090900000" +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Owner profile updated successfully", + "data": { + "business_name": "Alan Events Pvt Ltd", + "phone": "9090900000" + } +} +``` + +--- + +## 9. Feedback & Rating APIs + +### 9.1 Submit Venue Rating + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/venues/:id/ratings` | +| **Auth** | Bearer token — **User only** | + +**Request body** + +```json +{ + "rating": 5, + "comment": "Excellent venue, great staff!" +} +``` + +| Field | Type | Required | DB column | +|-----------|---------|----------|------------------------| +| `rating` | integer | Yes | `venue_ratings.rating` (1–5) | +| `comment` | string | No | `venue_ratings.comment` (max 500) | + +**Success response — `201 Created`** + +```json +{ + "message": "Rating submitted successfully", + "data": { + "id": 50, + "venue_id": 1, + "rating": 5, + "comment": "Excellent venue, great staff!", + "created_at": "2026-06-21T10:00:00Z" + } +} +``` + +--- + +### 9.2 Get Venue Ratings + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/venues/:id/ratings` | +| **Auth** | None | + +**Query parameters:** `page`, `limit` + +**Success response — `200 OK`** + +```json +{ + "data": { + "average_rating": 4.5, + "total_reviews": 28, + "reviews": [ + { + "id": 50, + "user_name": "Alan", + "rating": 5, + "comment": "Excellent venue, great staff!", + "created_at": "2026-06-21T10:00:00Z" + } + ] + }, + "pagination": { + "page": 1, + "limit": 20, + "total_items": 28, + "total_pages": 2 + } +} +``` + +--- + +### 9.3 Submit Venue Feedback + +General feedback (not a star rating). + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/venues/:id/feedback` | +| **Auth** | Bearer token — **User only** | + +**Request body** + +```json +{ + "message": "Would be great to have more parking space." +} +``` + +**Success response — `201 Created`** + +```json +{ + "message": "Feedback submitted successfully" +} +``` + +--- + +## 10. Issue APIs + +### 10.1 Raise an Issue + +Report a problem with a venue or booking. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/issues` | +| **Auth** | Bearer token — **User only** | + +**Request body** + +```json +{ + "venue_id": 1, + "booking_id": 101, + "subject": "Venue not as described", + "description": "The venue did not have the promised AV equipment." +} +``` + +| Field | Type | Required | DB column | +|---------------|---------|----------|---------------------| +| `venue_id` | integer | Yes | `issues.venue_id` | +| `booking_id` | integer | No | `issues.booking_id` | +| `subject` | string | Yes | `issues.subject` | +| `description` | string | Yes | `issues.description`| + +**Success response — `201 Created`** + +```json +{ + "message": "Issue raised successfully", + "data": { + "id": 7, + "status": "open", + "created_at": "2026-06-21T12:00:00Z" + } +} +``` + +--- + +### 10.2 Get My Issues + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/issues/my-issues` | +| **Auth** | Bearer token — **User only** | + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "id": 7, + "venue_id": 1, + "venue_name": "Grand Hall", + "subject": "Venue not as described", + "status": "open", + "created_at": "2026-06-21T12:00:00Z" + } + ] +} +``` + +--- + +### 10.3 Get All Issues (Admin) + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/admin/issues` | +| **Auth** | Bearer token — **Admin only** | + +**Query parameters:** `status` (`open`, `in_progress`, `resolved`, `closed`), `page`, `limit` + +--- + +### 10.4 Update Issue Status (Admin) + +| | | +|---|---| +| **Method** | `PATCH` | +| **URL** | `/admin/issues/:id` | +| **Auth** | Bearer token — **Admin only** | + +**Request body** + +```json +{ + "status": "resolved", + "admin_note": "Contacted owner, issue addressed." +} +``` + +| Field | Type | Required | DB column | +|--------------|--------|----------|------------------------| +| `status` | string | Yes | `issues.status` | +| `admin_note` | string | No | `issues.admin_note` | + +When `status` is `resolved`, the API sets `issues.resolved_at` to the current timestamp. + +--- + +## 11. Admin APIs + +All admin endpoints require `Authorization: Bearer ` with role `admin`. + +--- + +### 11.1 Get Pending Venues + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/admin/pending-venues` | +| **Auth** | Admin | + +**Query parameters:** `page`, `limit` + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "id": 12, + "owner_id": 5, + "owner_name": "Venue Owner", + "name": "Grand Hall", + "location": "Kochi", + "price_per_day": 10000, + "approval_status": "pending", + "created_at": "2026-06-09T16:00:00Z" + } + ], + "pagination": { + "page": 1, + "limit": 20, + "total_items": 4, + "total_pages": 1 + } +} +``` + +--- + +### 11.2 Approve Venue + +| | | +|---|---| +| **Method** | `PATCH` | +| **URL** | `/admin/venues/:id/approve` | +| **Auth** | Admin | + +**Example** + +```http +PATCH /admin/venues/12/approve +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Venue approved", + "data": { + "id": 12, + "approval_status": "approved", + "updated_at": "2026-06-10T11:00:00Z" + } +} +``` + +Sets `venues.approval_status = 'approved'`. + +--- + +### 11.3 Reject Venue + +| | | +|---|---| +| **Method** | `PATCH` | +| **URL** | `/admin/venues/:id/reject` | +| **Auth** | Admin | + +**Request body (optional)** + +```json +{ + "rejection_reason": "Incomplete venue information or misleading photos" +} +``` + +| Field | Type | Required | DB column | +|--------------------|--------|----------|---------------------------| +| `rejection_reason` | string | No | `venues.rejection_reason` | + +**Success response — `200 OK`** + +```json +{ + "message": "Venue rejected", + "data": { + "id": 12, + "approval_status": "rejected", + "rejection_reason": "Incomplete venue information or misleading photos", + "updated_at": "2026-06-10T11:00:00Z" + } +} +``` + +--- + +### 11.4 Get All Users + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/admin/users` | +| **Auth** | Admin | + +**Query parameters:** `role`, `is_active`, `search`, `page`, `limit` + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "id": 1, + "name": "Alan", + "email": "alan@gmail.com", + "role": "user", + "is_active": true, + "created_at": "2026-06-01T10:00:00Z" + } + ], + "pagination": { + "page": 1, + "limit": 20, + "total_items": 120, + "total_pages": 6 + } +} +``` + +--- + +### 11.5 Get User by ID + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/admin/users/:id` | +| **Auth** | Admin | + +--- + +### 11.6 Deactivate User + +| | | +|---|---| +| **Method** | `PATCH` | +| **URL** | `/admin/users/:id/deactivate` | +| **Auth** | Admin | + +**Success response — `200 OK`** + +```json +{ + "message": "User deactivated successfully" +} +``` + +--- + +### 11.7 Activate User + +| | | +|---|---| +| **Method** | `PATCH` | +| **URL** | `/admin/users/:id/activate` | +| **Auth** | Admin | + +--- + +### 11.8 Get All Venues (Admin) + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/admin/venues` | +| **Auth** | Admin | + +**Query parameters:** `approval_status`, `owner_id`, `page`, `limit` + +--- + +### 11.9 Remove / Block Venue + +| | | +|---|---| +| **Method** | `DELETE` | +| **URL** | `/admin/venues/:id` | +| **Auth** | Admin | + +Sets `venues.is_active = false` (soft block). Venue row is retained for audit. + +**Success response — `200 OK`** + +```json +{ + "message": "Venue removed from platform", + "data": { + "id": 12, + "is_active": false, + "updated_at": "2026-06-10T12:00:00Z" + } +} +``` + +--- + +### 11.10 Admin Dashboard Stats + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/admin/dashboard` | +| **Auth** | Admin | + +**Success response — `200 OK`** + +```json +{ + "data": { + "total_users": 120, + "total_owners": 35, + "total_venues": 80, + "pending_venues": 4, + "total_bookings": 450, + "total_revenue": 4500000, + "open_issues": 3 + } +} +``` + +--- + +## 12. Payment APIs + +Payment integration supports online booking checkout. Bookings start as `pending_payment` and move to `booked` after successful payment. + +**Supported gateways (planned):** Razorpay / Stripe + +--- + +### 12.1 Create Payment Order + +Initiate payment for a booking. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/payments/create-order` | +| **Auth** | Bearer token — **User only** | + +**Request body** + +```json +{ + "booking_id": 101, + "currency": "INR" +} +``` + +| Field | Type | Required | Description | +|--------------|---------|----------|--------------------------| +| `booking_id` | integer | Yes | Booking to pay for | +| `currency` | string | No | Default: `INR` | + +**Success response — `201 Created`** + +```json +{ + "message": "Payment order created", + "data": { + "payment_id": "pay_abc123", + "booking_id": 101, + "amount": 10000, + "currency": "INR", + "gateway": "razorpay", + "gateway_order_id": "order_Mxyz123", + "status": "created", + "expires_at": "2026-06-10T15:00:00Z" + } +} +``` + +Use `gateway_order_id` on the frontend to open the payment gateway checkout. + +--- + +### 12.2 Verify Payment + +Confirm payment after the user completes checkout on the gateway. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/payments/verify` | +| **Auth** | Bearer token — **User only** | + +**Request body (Razorpay example)** + +```json +{ + "payment_id": "pay_abc123", + "gateway_order_id": "order_Mxyz123", + "gateway_payment_id": "pay_Rxyz456", + "gateway_signature": "signature_from_gateway" +} +``` + +**Success response — `200 OK`** + +```json +{ + "message": "Payment verified successfully", + "data": { + "payment_id": "pay_abc123", + "booking_id": 101, + "status": "paid", + "amount": 10000, + "paid_at": "2026-06-10T14:35:00Z", + "booking_status": "booked" + } +} +``` + +**Error responses** + +| Status | Code | When | +|--------|-------------------|-------------------------------| +| `400` | `PAYMENT_FAILED` | Gateway reported failure | +| `400` | `INVALID_SIGNATURE`| Signature verification failed| +| `409` | `ALREADY_PAID` | Booking already paid | + +--- + +### 12.3 Get Payment Details + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/payments/:payment_id` | +| **Auth** | Bearer token — **User (own payment), Owner, or Admin** | + +**Success response — `200 OK`** + +```json +{ + "data": { + "payment_id": "pay_abc123", + "booking_id": 101, + "user_id": 1, + "venue_name": "Grand Hall", + "amount": 10000, + "currency": "INR", + "status": "paid", + "gateway": "razorpay", + "gateway_payment_id": "pay_Rxyz456", + "paid_at": "2026-06-10T14:35:00Z", + "created_at": "2026-06-10T14:30:00Z" + } +} +``` + +Payment status values: `created`, `paid`, `failed`, `refunded`, `refund_pending` + +--- + +### 12.4 Get My Payments + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/payments/my-payments` | +| **Auth** | Bearer token — **User only** | + +**Query parameters:** `status`, `page`, `limit` + +**Success response — `200 OK`** + +```json +{ + "data": [ + { + "payment_id": "pay_abc123", + "booking_id": 101, + "venue_name": "Grand Hall", + "amount": 10000, + "status": "paid", + "paid_at": "2026-06-10T14:35:00Z" + } + ], + "pagination": { + "page": 1, + "limit": 20, + "total_items": 3, + "total_pages": 1 + } +} +``` + +--- + +### 12.5 Refund Payment + +Triggered automatically on booking cancellation, or manually by admin. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/payments/:payment_id/refund` | +| **Auth** | Bearer token — **Admin**, or **User** (own payment via cancelled booking) | + +**Request body** + +```json +{ + "reason": "Booking cancelled by user", + "amount": 10000 +} +``` + +| Field | Type | Required | DB column | +|----------|--------|----------|------------------| +| `reason` | string | Yes | `refunds.reason` | +| `amount` | number | No | `refunds.amount` (full payment amount if omitted) | + +**Success response — `200 OK`** + +```json +{ + "message": "Refund initiated successfully", + "data": { + "payment_id": "pay_abc123", + "refund_id": "rfnd_xyz789", + "amount": 10000, + "status": "refund_pending", + "created_at": "2026-06-11T09:05:00Z" + } +} +``` + +> On refund initiation, `payments.status` is updated to `refund_pending`. When complete, `refunds.status` → `refunded` and `payments.status` → `refunded`. + +--- + +### 12.6 Payment Webhook (Gateway → Server) + +Called by the payment gateway — not by the frontend. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/payments/webhook` | +| **Auth** | Gateway signature verification | + +**Headers** + +```http +X-Razorpay-Signature: +``` + +**Request body** + +Gateway-specific payload (e.g. `payment.captured`, `payment.failed`, `refund.processed`). + +**Success response — `200 OK`** + +```json +{ + "message": "Webhook processed" +} +``` + +--- + +### 12.7 Payment Flow (End-to-End) + +```text +1. User creates booking → POST /bookings (status: pending_payment) +2. User initiates payment → POST /payments/create-order +3. Frontend opens gateway checkout using gateway_order_id +4. User pays on gateway +5. Frontend verifies payment → POST /payments/verify +6. Booking confirmed → status: booked +7. (Optional) Gateway webhook → POST /payments/webhook (server-side confirmation) +``` + +--- + +## 13. Error Reference + +| HTTP Status | Error Code | Description | +|-------------|-------------------------|------------------------------------| +| `400` | `VALIDATION_ERROR` | Invalid request body or params | +| `401` | `UNAUTHORIZED` | Missing or invalid token | +| `401` | `INVALID_CREDENTIALS` | Wrong email or password | +| `403` | `FORBIDDEN` | Insufficient role/permission | +| `403` | `ACCOUNT_DISABLED` | User account is inactive | +| `404` | `NOT_FOUND` | Resource does not exist | +| `404` | `VENUE_NOT_FOUND` | Venue does not exist | +| `404` | `BOOKING_NOT_FOUND` | Booking does not exist | +| `409` | `EMAIL_EXISTS` | Email already registered | +| `409` | `ALREADY_BOOKED` | Venue/date slot already taken | +| `409` | `ALREADY_PAID` | Payment already completed | +| `409` | `ACTIVE_BOOKINGS_EXIST` | Cannot delete venue with bookings | +| `422` | `UNPROCESSABLE` | Business rule violation | +| `500` | `INTERNAL_ERROR` | Unexpected server error | + +--- + +## 14. Role & Access Matrix + +| Endpoint | Public | User | Owner | Admin | +|---------------------------------------|:------:|:----:|:-----:|:-----:| +| `POST /auth/register` | ✅ | — | — | — | +| `POST /auth/login` | ✅ | — | — | — | +| `GET /venues` | ✅ | ✅ | ✅ | ✅ | +| `GET /venues/:id` | ✅ | ✅ | ✅ | ✅ | +| `POST /venues` | — | — | ✅ | ✅ | +| `PUT /venues/:id` | — | — | ✅* | ✅ | +| `DELETE /venues/:id` | — | — | ✅* | ✅ | +| `GET /venues/my-venues` | — | — | ✅ | — | +| `POST /bookings` | — | ✅ | — | — | +| `GET /bookings/my-bookings` | — | ✅ | — | — | +| `PATCH /bookings/:id/cancel` | — | ✅* | — | — | +| `GET /owner/bookings` | — | — | ✅ | — | +| `POST /venues/:id/ratings` | — | ✅ | — | — | +| `POST /issues` | — | ✅ | — | — | +| `POST /payments/create-order` | — | ✅ | — | — | +| `POST /payments/verify` | — | ✅ | — | — | +| `GET /payments/my-payments` | — | ✅ | — | — | +| `GET /admin/pending-venues` | — | — | — | ✅ | +| `PATCH /admin/venues/:id/approve` | — | — | — | ✅ | +| `PATCH /admin/venues/:id/reject` | — | — | — | ✅ | +| `GET /admin/users` | — | — | — | ✅ | +| `DELETE /admin/venues/:id` | — | — | — | ✅ | + +\* Own resource only + +--- + +## Related Documents + +- [Product Requirements (PRD)](./PRD.md) +- [Database Design](./DBDesign.md) +- [System Design](./SystemDesign.md) +- [Folder Architecture](./FolderArchitecture.md) +- [Backend Setup](../backend/README.md) + +--- + +**Last updated:** June 2026 diff --git a/documents/DBDesign.md b/documents/DBDesign.md new file mode 100644 index 000000000..4552ca4e4 --- /dev/null +++ b/documents/DBDesign.md @@ -0,0 +1,855 @@ +# BookMyVenue — Database Design (PostgreSQL) + +**Project:** BookMyVenue +**Database:** PostgreSQL 15+ +**ORM:** SQLAlchemy (FastAPI backend) + +This document defines all tables, relationships, constraints, and indexes for the platform. It aligns with the [API Documentation](./APIDocumentation.md). + +--- + +## Table of Contents + +1. [Overview](#1-overview) +2. [Entity Relationship Diagram](#2-entity-relationship-diagram) +3. [Tables](#3-tables) + - [users](#31-users) + - [refresh_tokens](#32-refresh_tokens) + - [password_reset_tokens](#33-password_reset_tokens) + - [owner_profiles](#34-owner_profiles) + - [venues](#35-venues) + - [venue_images](#36-venue_images) + - [amenities](#37-amenities) + - [venue_amenities](#38-venue_amenities) + - [bookings](#39-bookings) + - [payments](#310-payments) + - [refunds](#311-refunds) + - [venue_ratings](#312-venue_ratings) + - [venue_feedback](#313-venue_feedback) + - [issues](#314-issues) +4. [API ↔ Database Field Mapping](#4-api--database-field-mapping) +5. [Relationships Summary](#5-relationships-summary) +6. [Indexes](#6-indexes) +7. [Enums & Status Values](#7-enums--status-values) +8. [Sample Seed Data](#8-sample-seed-data) +9. [Migration Notes](#9-migration-notes) + +--- + +## 1. Overview + +| Domain | Tables | +|---------------|-----------------------------------------------------| +| Auth & Users | `users`, `refresh_tokens`, `password_reset_tokens`, `owner_profiles` | +| Venues | `venues`, `venue_images`, `amenities`, `venue_amenities` | +| Bookings | `bookings` | +| Payments | `payments`, `refunds` | +| Feedback | `venue_ratings`, `venue_feedback`, `issues` | + +**Design principles** + +- Use `SERIAL` / `BIGSERIAL` primary keys for internal IDs. +- Use UUID or prefixed string IDs (`pay_abc123`) for external-facing payment IDs. +- Store passwords as bcrypt hashes — never plain text. +- Use `TIMESTAMP WITH TIME ZONE` for all datetime columns. +- Soft-delete is not used in MVP; use `is_active` on users and status fields elsewhere. + +--- + +## 2. Entity Relationship Diagram + +```mermaid +erDiagram + users ||--o{ refresh_tokens : has + users ||--o{ password_reset_tokens : has + users ||--o| owner_profiles : has + users ||--o{ venues : owns + users ||--o{ bookings : makes + users ||--o{ venue_ratings : writes + users ||--o{ venue_feedback : writes + users ||--o{ issues : raises + users ||--o{ payments : pays + + venues ||--o{ venue_images : has + venues ||--o{ venue_amenities : has + venues ||--o{ bookings : receives + venues ||--o{ venue_ratings : receives + venues ||--o{ venue_feedback : receives + venues ||--o{ issues : related_to + + amenities ||--o{ venue_amenities : tagged_on + + bookings ||--o{ payments : has + bookings ||--o{ issues : related_to + + payments ||--o{ refunds : has + + users { + serial id PK + varchar name + varchar email UK + varchar mobile + text password_hash + varchar role + boolean is_active + } + + venues { + serial id PK + int owner_id FK + varchar name + varchar location + numeric price_per_day + varchar approval_status + } + + bookings { + serial id PK + int user_id FK + int venue_id FK + date booking_date + time time_slot + varchar status + numeric amount + } + + payments { + serial id PK + varchar payment_id UK + int booking_id FK + int user_id FK + numeric amount + varchar status + } +``` + +--- + +## 3. Tables + +### 3.1 `users` + +Stores all platform users: normal users, venue owners, and admins. + +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(150) NOT NULL UNIQUE, + mobile VARCHAR(20), + password_hash TEXT NOT NULL, + role VARCHAR(20) NOT NULL DEFAULT 'user' + CHECK (role IN ('user', 'owner', 'admin')), + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|-----------------|----------------|----------|------------------------------------------| +| `id` | SERIAL | No | Primary key | +| `name` | VARCHAR(100) | No | Full name | +| `email` | VARCHAR(150) | No | Unique login email | +| `mobile` | VARCHAR(20) | Yes | Phone number | +| `password_hash` | TEXT | No | Bcrypt-hashed password | +| `role` | VARCHAR(20) | No | `user`, `owner`, or `admin` | +| `is_active` | BOOLEAN | No | `false` when admin deactivates account | +| `created_at` | TIMESTAMPTZ | No | Account creation time | +| `updated_at` | TIMESTAMPTZ | No | Last profile update | + +--- + +### 3.2 `refresh_tokens` + +Stores refresh tokens for JWT session management and logout invalidation. + +```sql +CREATE TABLE refresh_tokens ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token_hash TEXT NOT NULL UNIQUE, + expires_at TIMESTAMPTZ NOT NULL, + revoked_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|--------------|-------------|----------|---------------------------------------| +| `id` | SERIAL | No | Primary key | +| `user_id` | INTEGER | No | Token owner | +| `token_hash` | TEXT | No | Hashed refresh token (never store raw)| +| `expires_at` | TIMESTAMPTZ | No | Token expiry | +| `revoked_at` | TIMESTAMPTZ | Yes | Set on logout | +| `created_at` | TIMESTAMPTZ | No | Issue time | + +--- + +### 3.3 `password_reset_tokens` + +Temporary tokens for forgot-password flow. + +```sql +CREATE TABLE password_reset_tokens ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token_hash TEXT NOT NULL UNIQUE, + expires_at TIMESTAMPTZ NOT NULL, + used_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|--------------|-------------|----------|----------------------------| +| `id` | SERIAL | No | Primary key | +| `user_id` | INTEGER | No | User requesting reset | +| `token_hash` | TEXT | No | Hashed reset token | +| `expires_at` | TIMESTAMPTZ | No | Typically 1 hour validity | +| `used_at` | TIMESTAMPTZ | Yes | Set when password changed | +| `created_at` | TIMESTAMPTZ | No | Token creation time | + +--- + +### 3.4 `owner_profiles` + +Extended profile data for venue owners. + +```sql +CREATE TABLE owner_profiles ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE, + business_name VARCHAR(150), + phone VARCHAR(20), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|-----------------|--------------|----------|--------------------------| +| `id` | SERIAL | No | Primary key | +| `user_id` | INTEGER | No | One profile per owner | +| `business_name` | VARCHAR(150) | Yes | Registered business name | +| `phone` | VARCHAR(20) | Yes | Business contact number | +| `created_at` | TIMESTAMPTZ | No | Record creation | +| `updated_at` | TIMESTAMPTZ | No | Last update | + +--- + +### 3.5 `venues` + +Venue listings created by owners. Requires admin approval before going public. + +```sql +CREATE TABLE venues ( + id SERIAL PRIMARY KEY, + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(150) NOT NULL, + location VARCHAR(255) NOT NULL, + price_per_day NUMERIC(10, 2) NOT NULL CHECK (price_per_day >= 0), + description TEXT, + approval_status VARCHAR(20) NOT NULL DEFAULT 'pending' + CHECK (approval_status IN ('pending', 'approved', 'rejected')), + rejection_reason TEXT, + average_rating NUMERIC(3, 2) DEFAULT 0.00 CHECK (average_rating >= 0 AND average_rating <= 5), + total_reviews INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|---------------------|----------------|----------|------------------------------------------| +| `id` | SERIAL | No | Primary key | +| `owner_id` | INTEGER | No | FK → `users.id` (owner role) | +| `name` | VARCHAR(150) | No | Venue display name | +| `location` | VARCHAR(255) | No | City / address | +| `price_per_day` | NUMERIC(10,2) | No | Daily rental price (INR) | +| `description` | TEXT | Yes | Full venue description | +| `approval_status` | VARCHAR(20) | No | `pending`, `approved`, `rejected` | +| `rejection_reason` | TEXT | Yes | Admin reason when rejected | +| `average_rating` | NUMERIC(3,2) | Yes | Denormalized avg rating (updated on review) | +| `total_reviews` | INTEGER | No | Count of ratings | +| `is_active` | BOOLEAN | No | `false` when admin blocks/removes venue | +| `created_at` | TIMESTAMPTZ | No | Submission time | +| `updated_at` | TIMESTAMPTZ | No | Last edit time | + +> Public venue queries should filter: `approval_status = 'approved' AND is_active = TRUE`. + +--- + +### 3.6 `venue_images` + +Multiple images per venue. + +```sql +CREATE TABLE venue_images ( + id SERIAL PRIMARY KEY, + venue_id INTEGER NOT NULL REFERENCES venues(id) ON DELETE CASCADE, + image_url TEXT NOT NULL, + display_order INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|-----------------|-------------|----------|--------------------------------| +| `id` | SERIAL | No | Primary key | +| `venue_id` | INTEGER | No | FK → `venues.id` | +| `image_url` | TEXT | No | CDN / storage URL | +| `display_order` | INTEGER | No | Sort order in gallery (0 first)| +| `created_at` | TIMESTAMPTZ | No | Upload time | + +--- + +### 3.7 `amenities` + +Master list of venue amenities (Wi-Fi, Parking, AC, etc.). + +```sql +CREATE TABLE amenities ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|--------------|--------------|----------|-----------------------| +| `id` | SERIAL | No | Primary key | +| `name` | VARCHAR(100) | No | Unique amenity label | +| `created_at` | TIMESTAMPTZ | No | Record creation | + +--- + +### 3.8 `venue_amenities` + +Many-to-many join between venues and amenities. + +```sql +CREATE TABLE venue_amenities ( + id SERIAL PRIMARY KEY, + venue_id INTEGER NOT NULL REFERENCES venues(id) ON DELETE CASCADE, + amenity_id INTEGER NOT NULL REFERENCES amenities(id) ON DELETE CASCADE, + UNIQUE (venue_id, amenity_id) +); +``` + +| Column | Type | Nullable | Description | +|--------------|---------|----------|----------------------| +| `id` | SERIAL | No | Primary key | +| `venue_id` | INTEGER | No | FK → `venues.id` | +| `amenity_id` | INTEGER | No | FK → `amenities.id` | + +--- + +### 3.9 `bookings` + +Core booking records linking users to venues. + +```sql +CREATE TABLE bookings ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + venue_id INTEGER NOT NULL REFERENCES venues(id) ON DELETE CASCADE, + booking_date DATE NOT NULL, + time_slot TIME NOT NULL, + notes TEXT, + amount NUMERIC(10, 2) NOT NULL CHECK (amount >= 0), + status VARCHAR(20) NOT NULL DEFAULT 'pending_payment' + CHECK (status IN ('pending_payment', 'booked', 'cancelled')), + cancellation_reason TEXT, + cancelled_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + UNIQUE (venue_id, booking_date, time_slot) +); +``` + +| Column | Type | Nullable | Description | +|-----------------------|---------------|----------|------------------------------------------| +| `id` | SERIAL | No | Primary key | +| `user_id` | INTEGER | No | FK → `users.id` (booker) | +| `venue_id` | INTEGER | No | FK → `venues.id` | +| `booking_date` | DATE | No | Event date | +| `time_slot` | TIME | No | Start time (24-hour) | +| `notes` | TEXT | Yes | Special requests from user | +| `amount` | NUMERIC(10,2) | No | Booking price (copied from venue at creation) | +| `status` | VARCHAR(20) | No | `pending_payment`, `booked`, `cancelled` | +| `cancellation_reason` | TEXT | Yes | Reason provided on cancel | +| `cancelled_at` | TIMESTAMPTZ | Yes | When booking was cancelled | +| `created_at` | TIMESTAMPTZ | No | Booking creation time | +| `updated_at` | TIMESTAMPTZ | No | Last status change | + +> The unique constraint on `(venue_id, booking_date, time_slot)` prevents double-booking the same slot. + +--- + +### 3.10 `payments` + +Payment records for booking checkout (Razorpay / Stripe). + +```sql +CREATE TABLE payments ( + id SERIAL PRIMARY KEY, + payment_id VARCHAR(50) NOT NULL UNIQUE, + booking_id INTEGER NOT NULL REFERENCES bookings(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + amount NUMERIC(10, 2) NOT NULL CHECK (amount > 0), + currency VARCHAR(3) NOT NULL DEFAULT 'INR', + status VARCHAR(20) NOT NULL DEFAULT 'created' + CHECK (status IN ('created', 'paid', 'failed', 'refunded', 'refund_pending')), + gateway VARCHAR(30) NOT NULL DEFAULT 'razorpay' + CHECK (gateway IN ('razorpay', 'stripe')), + gateway_order_id VARCHAR(100), + gateway_payment_id VARCHAR(100), + gateway_signature TEXT, + failure_reason TEXT, + paid_at TIMESTAMPTZ, + expires_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|----------------------|---------------|----------|------------------------------------------| +| `id` | SERIAL | No | Internal primary key | +| `payment_id` | VARCHAR(50) | No | External ID (e.g. `pay_abc123`) | +| `booking_id` | INTEGER | No | FK → `bookings.id` | +| `user_id` | INTEGER | No | FK → `users.id` (payer) | +| `amount` | NUMERIC(10,2) | No | Payment amount | +| `currency` | VARCHAR(3) | No | ISO currency code (default `INR`) | +| `status` | VARCHAR(20) | No | Payment lifecycle status | +| `gateway` | VARCHAR(30) | No | `razorpay`, `stripe` | +| `gateway_order_id` | VARCHAR(100) | Yes | Order ID from payment gateway | +| `gateway_payment_id` | VARCHAR(100) | Yes | Payment ID from gateway after checkout | +| `gateway_signature` | TEXT | Yes | Signature for verification | +| `failure_reason` | TEXT | Yes | Reason if payment failed | +| `paid_at` | TIMESTAMPTZ | Yes | Successful payment timestamp | +| `expires_at` | TIMESTAMPTZ | Yes | Order expiry (unpaid orders) | +| `created_at` | TIMESTAMPTZ | No | Order creation time | +| `updated_at` | TIMESTAMPTZ | No | Last status update | + +--- + +### 3.11 `refunds` + +Refund records linked to payments (manual or auto on cancellation). + +```sql +CREATE TABLE refunds ( + id SERIAL PRIMARY KEY, + refund_id VARCHAR(50) NOT NULL UNIQUE, + payment_id INTEGER NOT NULL REFERENCES payments(id) ON DELETE CASCADE, + amount NUMERIC(10, 2) NOT NULL CHECK (amount > 0), + reason TEXT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'refund_pending' + CHECK (status IN ('refund_pending', 'refunded', 'failed')), + gateway_refund_id VARCHAR(100), + initiated_by INTEGER REFERENCES users(id) ON DELETE SET NULL, + completed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|---------------------|---------------|----------|--------------------------------| +| `id` | SERIAL | No | Primary key | +| `refund_id` | VARCHAR(50) | No | External ID (e.g. `rfnd_xyz789`) | +| `payment_id` | INTEGER | No | FK → `payments.id` | +| `amount` | NUMERIC(10,2) | No | Refund amount (full or partial)| +| `reason` | TEXT | No | Refund reason | +| `status` | VARCHAR(20) | No | Refund lifecycle status | +| `gateway_refund_id` | VARCHAR(100) | Yes | ID from payment gateway | +| `initiated_by` | INTEGER | Yes | FK → `users.id` (admin/user) | +| `completed_at` | TIMESTAMPTZ | Yes | When refund completed | +| `created_at` | TIMESTAMPTZ | No | Refund initiation time | + +--- + +### 3.12 `venue_ratings` + +Star ratings and review comments from users. + +```sql +CREATE TABLE venue_ratings ( + id SERIAL PRIMARY KEY, + venue_id INTEGER NOT NULL REFERENCES venues(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + rating SMALLINT NOT NULL CHECK (rating >= 1 AND rating <= 5), + comment VARCHAR(500), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + UNIQUE (venue_id, user_id) +); +``` + +| Column | Type | Nullable | Description | +|--------------|--------------|----------|--------------------------------| +| `id` | SERIAL | No | Primary key | +| `venue_id` | INTEGER | No | FK → `venues.id` | +| `user_id` | INTEGER | No | FK → `users.id` (reviewer) | +| `rating` | SMALLINT | No | 1–5 stars | +| `comment` | VARCHAR(500) | Yes | Optional review text | +| `created_at` | TIMESTAMPTZ | No | Review submission time | +| `updated_at` | TIMESTAMPTZ | No | Last edit | + +> One rating per user per venue. Update `venues.average_rating` and `venues.total_reviews` when a rating is added or changed. + +--- + +### 3.13 `venue_feedback` + +General text feedback (separate from star ratings). + +```sql +CREATE TABLE venue_feedback ( + id SERIAL PRIMARY KEY, + venue_id INTEGER NOT NULL REFERENCES venues(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + message TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|--------------|-------------|----------|--------------------------| +| `id` | SERIAL | No | Primary key | +| `venue_id` | INTEGER | No | FK → `venues.id` | +| `user_id` | INTEGER | No | FK → `users.id` | +| `message` | TEXT | No | Feedback message | +| `created_at` | TIMESTAMPTZ | No | Submission time | + +--- + +### 3.14 `issues` + +User-reported problems related to venues or bookings. + +```sql +CREATE TABLE issues ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + venue_id INTEGER NOT NULL REFERENCES venues(id) ON DELETE CASCADE, + booking_id INTEGER REFERENCES bookings(id) ON DELETE SET NULL, + subject VARCHAR(200) NOT NULL, + description TEXT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'open' + CHECK (status IN ('open', 'in_progress', 'resolved', 'closed')), + admin_note TEXT, + resolved_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +| Column | Type | Nullable | Description | +|---------------|--------------|----------|--------------------------------------| +| `id` | SERIAL | No | Primary key | +| `user_id` | INTEGER | No | FK → `users.id` (reporter) | +| `venue_id` | INTEGER | No | FK → `venues.id` | +| `booking_id` | INTEGER | Yes | FK → `bookings.id` (optional link) | +| `subject` | VARCHAR(200) | No | Short issue title | +| `description` | TEXT | No | Full issue details | +| `status` | VARCHAR(20) | No | `open`, `in_progress`, `resolved`, `closed` | +| `admin_note` | TEXT | Yes | Admin resolution notes | +| `resolved_at` | TIMESTAMPTZ | Yes | When issue was resolved | +| `created_at` | TIMESTAMPTZ | No | Report time | +| `updated_at` | TIMESTAMPTZ | No | Last status update | + +--- + +## 4. API ↔ Database Field Mapping + +Column names in the [API Documentation](./APIDocumentation.md) match database columns directly. Use this table when implementing serializers and request handlers. + +| DB table.column | API JSON field | Notes | +|-----------------|----------------|-------| +| `users.name` | `name` | | +| `users.email` | `email` | | +| `users.mobile` | `mobile` | | +| `users.password_hash` | `password` | Request only; never returned in responses | +| `users.role` | `role` | `user`, `owner`, `admin` | +| `users.is_active` | `is_active` | | +| `users.created_at` | `created_at` | ISO 8601 | +| `users.updated_at` | `updated_at` | ISO 8601 | +| `owner_profiles.business_name` | `business_name` | | +| `owner_profiles.phone` | `phone` | | +| `venues.price_per_day` | `price_per_day` | Not `price` | +| `venues.approval_status` | `approval_status` | `pending`, `approved`, `rejected` | +| `venues.rejection_reason` | `rejection_reason` | Admin reject body/response | +| `venues.is_active` | `is_active` | Admin block sets `false` | +| `venues.average_rating` | `average_rating` | | +| `venues.total_reviews` | `total_reviews` | | +| `venue_images.image_url` | `image_url` | Not `url` | +| `venue_images.display_order` | `display_order` | | +| `bookings.booking_date` | `booking_date` | `YYYY-MM-DD` | +| `bookings.time_slot` | `time_slot` | `HH:MM` in API; `TIME` in DB | +| `bookings.notes` | `notes` | | +| `bookings.amount` | `amount` | Copied from `venues.price_per_day` at creation | +| `bookings.status` | `status` | `pending_payment`, `booked`, `cancelled` | +| `bookings.cancellation_reason` | `cancellation_reason` | Cancel booking request | +| `bookings.cancelled_at` | `cancelled_at` | | +| `payments.payment_id` | `payment_id` | External string ID | +| `payments.status` | `status` / `payment_status` | `payment_status` on booking lists = joined `payments.status` | +| `payments.gateway` | `gateway` | `razorpay`, `stripe` | +| `payments.currency` | `currency` | Default `INR` | +| `payments.gateway_order_id` | `gateway_order_id` | | +| `payments.gateway_payment_id` | `gateway_payment_id` | | +| `payments.gateway_signature` | `gateway_signature` | Verify endpoint only | +| `payments.paid_at` | `paid_at` | | +| `payments.expires_at` | `expires_at` | | +| `refunds.refund_id` | `refund_id` | | +| `refunds.status` | `status` / `refund_status` | `refund_pending`, `refunded`, `failed` | +| `refunds.reason` | `reason` | Refund request body | +| `refunds.amount` | `amount` | | +| `venue_ratings.rating` | `rating` | 1–5 | +| `venue_ratings.comment` | `comment` | Max 500 chars | +| `venue_feedback.message` | `message` | | +| `issues.subject` | `subject` | | +| `issues.description` | `description` | | +| `issues.status` | `status` | `open`, `in_progress`, `resolved`, `closed` | +| `issues.admin_note` | `admin_note` | | +| `issues.resolved_at` | `resolved_at` | Set when status → `resolved` | + +### Derived API fields (not stored) + +| API field | Source | +|-----------|--------| +| `thumbnail_url` | First `venue_images.image_url` ordered by `display_order` | +| `venue_name`, `user_name`, `owner_name` | Joined from related tables | +| `total_bookings`, `total_revenue` | Aggregated counts/sums | +| `payment_status` on booking responses | Latest `payments.status` for `booking_id` | + +### Status transition rules + +| Table | Transition | +|-------|------------| +| `venues.approval_status` | `pending` → `approved` (admin approve) or `rejected` (admin reject) | +| `bookings.status` | `pending_payment` → `booked` (payment verified) → `cancelled` (user cancel) | +| `payments.status` | `created` → `paid` or `failed`; `paid` → `refund_pending` → `refunded` | +| `refunds.status` | `refund_pending` → `refunded` or `failed` | + +--- + +## 5. Relationships Summary + +| Parent | Child | Relationship | On Delete | +|-----------------|---------------------|--------------|-------------| +| `users` | `venues` | One → Many | CASCADE | +| `users` | `bookings` | One → Many | CASCADE | +| `users` | `payments` | One → Many | CASCADE | +| `users` | `venue_ratings` | One → Many | CASCADE | +| `users` | `venue_feedback` | One → Many | CASCADE | +| `users` | `issues` | One → Many | CASCADE | +| `users` | `owner_profiles` | One → One | CASCADE | +| `users` | `refresh_tokens` | One → Many | CASCADE | +| `venues` | `venue_images` | One → Many | CASCADE | +| `venues` | `bookings` | One → Many | CASCADE | +| `venues` | `venue_ratings` | One → Many | CASCADE | +| `venues` | `venue_feedback` | One → Many | CASCADE | +| `venues` | `issues` | One → Many | CASCADE | +| `venues` | `venue_amenities` | One → Many | CASCADE | +| `amenities` | `venue_amenities` | One → Many | CASCADE | +| `bookings` | `payments` | One → Many | CASCADE | +| `bookings` | `issues` | One → Many | SET NULL | +| `payments` | `refunds` | One → Many | CASCADE | + +--- + +## 6. Indexes + +Recommended indexes for query performance: + +```sql +-- Users +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_role ON users(role); + +-- Venues +CREATE INDEX idx_venues_owner_id ON venues(owner_id); +CREATE INDEX idx_venues_approval_status ON venues(approval_status); +CREATE INDEX idx_venues_location ON venues(location); +CREATE INDEX idx_venues_price ON venues(price_per_day); + +-- Bookings +CREATE INDEX idx_bookings_user_id ON bookings(user_id); +CREATE INDEX idx_bookings_venue_id ON bookings(venue_id); +CREATE INDEX idx_bookings_date ON bookings(booking_date); +CREATE INDEX idx_bookings_status ON bookings(status); + +-- Payments +CREATE INDEX idx_payments_booking_id ON payments(booking_id); +CREATE INDEX idx_payments_user_id ON payments(user_id); +CREATE INDEX idx_payments_status ON payments(status); +CREATE INDEX idx_payments_gateway_order_id ON payments(gateway_order_id); + +-- Ratings & Issues +CREATE INDEX idx_venue_ratings_venue_id ON venue_ratings(venue_id); +CREATE INDEX idx_issues_status ON issues(status); +CREATE INDEX idx_issues_user_id ON issues(user_id); + +-- Tokens +CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id); +CREATE INDEX idx_password_reset_tokens_user_id ON password_reset_tokens(user_id); +``` + +--- + +## 7. Enums & Status Values + +> These values are identical to [API section 2.8](./APIDocumentation.md#28-shared-enum-values). + +### User roles + +| Value | Description | +|---------|--------------------| +| `user` | Normal booker | +| `owner` | Venue owner | +| `admin` | Platform admin | + +### Venue approval status + +| Value | Description | +|------------|--------------------------------| +| `pending` | Awaiting admin review | +| `approved` | Live on platform | +| `rejected` | Rejected by admin | + +### Booking status + +| Value | Description | +|--------------------|--------------------------------------| +| `pending_payment` | Created, awaiting payment | +| `booked` | Payment confirmed, slot reserved | +| `cancelled` | Cancelled by user or system | + +### Payment status + +| Value | Description | +|------------------|----------------------------| +| `created` | Order created, not paid | +| `paid` | Payment successful | +| `failed` | Payment attempt failed | +| `refund_pending` | Refund in progress | +| `refunded` | Fully refunded | + +### Refund status (`refunds.status`) + +| Value | Description | +|------------------|--------------------------| +| `refund_pending` | Initiated, processing | +| `refunded` | Completed | +| `failed` | Refund failed | + +### Payment gateway (`payments.gateway`) + +| Value | Description | +|------------|--------------------| +| `razorpay` | Default gateway | +| `stripe` | Alternate gateway | + +### Issue status (`issues.status`) + +| Value | Description | +|---------------|--------------------------| +| `open` | Newly reported | +| `in_progress` | Admin is investigating | +| `resolved` | Issue fixed | +| `closed` | Closed without action | + +--- + +## 8. Sample Seed Data + +Use for local development and testing: + +```sql +-- Admin user (password: admin123 — hash in app) +INSERT INTO users (name, email, mobile, password_hash, role) +VALUES ('Admin User', 'admin@bookmyvenue.com', '9000000001', '$2b$12$placeholder', 'admin'); + +-- Owner +INSERT INTO users (name, email, mobile, password_hash, role) +VALUES ('Venue Owner', 'owner@bookmyvenue.com', '9000000002', '$2b$12$placeholder', 'owner'); + +-- Normal user +INSERT INTO users (name, email, mobile, password_hash, role) +VALUES ('Alan', 'alan@gmail.com', '9090900000', '$2b$12$placeholder', 'user'); + +-- Amenities +INSERT INTO amenities (name) VALUES + ('Wi-Fi'), ('Parking'), ('AC'), ('Projector'), ('Stage'), ('Catering'); + +-- Owner profile +INSERT INTO owner_profiles (user_id, business_name, phone) +VALUES (2, 'Alan Events Pvt Ltd', '9000000002'); + +-- Venue (approved) +INSERT INTO venues (owner_id, name, location, price_per_day, description, approval_status) +VALUES (2, 'Grand Hall', 'Kochi, Kerala', 10000.00, 'Large event venue with stage and seating for 500', 'approved'); + +-- Venue amenities +INSERT INTO venue_amenities (venue_id, amenity_id) VALUES (1, 1), (1, 2), (1, 3); + +-- Venue image +INSERT INTO venue_images (venue_id, image_url, display_order) +VALUES (1, 'https://cdn.example.com/venues/1/img1.jpg', 0); +``` + +> Replace `$2b$12$placeholder` with real bcrypt hashes generated by the application. + +--- + +## 9. Migration Notes + +### Development + +For quick local setup only: + +```python +Base.metadata.create_all(bind=engine) +``` + +### Production + +Always use Alembic migrations: + +```bash +cd backend +alembic init migrations +alembic revision --autogenerate -m "initial schema" +alembic upgrade head +``` + +See [database production guide](./database/database_production.md) for details. + +### Suggested migration order + +1. `users` +2. `refresh_tokens`, `password_reset_tokens` +3. `owner_profiles` +4. `venues`, `amenities`, `venue_amenities`, `venue_images` +5. `bookings` +6. `payments`, `refunds` +7. `venue_ratings`, `venue_feedback`, `issues` + +--- + +## Related Documents + +- [API Documentation](./APIDocumentation.md) +- [System Design](./SystemDesign.md) +- [Product Requirements (PRD)](./PRD.md) +- [Folder Architecture](./FolderArchitecture.md) +- [Backend Setup](../backend/README.md) + +--- + +**Last updated:** June 2026 diff --git a/documents/PRD.md b/documents/PRD.md new file mode 100644 index 000000000..ae0519370 --- /dev/null +++ b/documents/PRD.md @@ -0,0 +1,190 @@ + +# 📄 Product Requirement Document (PRD) + +## Project: BookMyVenue (MVP) + +## 1. Purpose + +BookMyVenue is a simple platform where users can: + +* Find venues +* View details +* Book them online. + +It also allows venue owners to: + +* List their spaces +* Manage bookings + +And a super admin to: + +* Control and approve everything +* Manage the venues if needed block them + + +## 2. 👥 Users (Roles) + +### 1. Normal User + +* Can register and login +* Can browse venues +* Can book a venue +* Provide the Rating +* Raise issues + +### 2. Venue Owner + +* Can add and manage venues +* Can see bookings for their venues +* get the user feedbacks + +### 3. Super Admin + +* Can approve or reject venues +* Can manage users and owners +* Has full control over the platform + +## 3. Core Features (MVP Only) + +### Authentication + +* User can register +* User can login +* Role-based access (user / owner / admin) + +### Venue Management + +* Owner can: + + * Add a venue + * Edit venue details + * Delete venue + * Get user feedback + +* Venue includes: + + * Name + * Location + * Price + * Description + * Amenities + * Images + * Rating + + +### Venue Browsing + +* Users can: + + * View all venues + * View venue details + * Search/filter (basic: location, price) + * Send Feedback + + + +### Booking System + +* User can: + + * Book a venue for a date +* System stores: + + * User + * Venue + * Date + * Status (booked/cancelled) + + +### Admin Control + +* Admin can: + + * Approve or reject venues before they go live + * View all users + * Remove bad listings + + +## 4. Out of Scope (NOT in MVP) + +Do NOT build these now: + +* Online payments +* Reviews and ratings +* Chat system +* Notifications +* AI recommendations +* Advanced search + + +## 5. ⚙️ Basic Flow + +### User Flow: + +1. User signs up / logs in +2. User browses venues +3. User selects a venue +4. User books a date + + +### Owner Flow: + +1. Owner logs in +2. Owner adds venue +3. Waits for admin approval +4. Manages bookings + + +### Admin Flow: + +1. Admin logs in +2. Reviews new venues +3. Approves or rejects them + + +## 6. 📦 Data (Simple Overview) + +### User + +* id +* name +* email +* password +* role + +### Venue + +* id +* owner_id +* name +* location +* price +* description +* approved (true/false) + +### Booking + +* id +* user_id +* venue_id +* date +* status + +--- + +## 7. Goal of MVP + +* Build a working backend API +* Keep it simple and clean +* Make it easy for contributors to understand +* No overengineering + +--- + +## 8. Key Rules + +* Keep logic simple +* Write clean APIs +* Avoid unnecessary features +* Focus on functionality, not perfection + diff --git a/documents/SystemDesign.md b/documents/SystemDesign.md new file mode 100644 index 000000000..93659297b --- /dev/null +++ b/documents/SystemDesign.md @@ -0,0 +1,141 @@ +# 📄 System Design / Architecture + +## Project: BookMyVenue (MVP) + +## 1. 🎯 Goal + +Build a simple backend system where: + +* Users can find and book venues +* Owners can list venues +* Admin can control everything + +System should be: + +* Easy to understand +* Easy to contribute +* Easy to scale later (not now) + + +## 2. 🧱 Tech Stack + +* Backend: FastAPI ( why because in future implementing an ai will be easy to configure ) +* Database: PostgreSQL ( structured way to store datas , in future nosql can be added) +* Authentication: JWT (token-based login) + + +### Flow: + +``` +Client (Frontend / Postman) + ↓ + FastAPI Server + ↓ + PostgreSQL Database +``` + +## 3. ⚙️ How It Works (Step by Step) + +### Example: Booking a Venue + +1. User sends request → `POST /bookings/` +2. FastAPI: + + * Checks user login (JWT) + * Validates data +3. Backend saves booking in database +4. Response sent back to user + +--- + +## 4. 🧩 Main Components + +### 1. API Layer (Routes) + +* Handles incoming requests +* Example: + + * `/auth` + * `/venues` + * `/bookings` + * `/admin` + +--- + +### 2. Service Layer (Logic) + +* Contains business logic +* Example: + + * Check venue availability + * Validate booking + * Handle permissions + +--- + +### 3. Database Layer + +* Stores all data +* Tables: + + * Users + * Venues + * Bookings + +--- + +### 4. Authentication System + +* Uses JWT tokens +* Flow: + + 1. User logs in + 2. Server returns token + 3. User sends token in future requests + +--- + +## 5. Role-Based Access + +System checks user role before actions: + +* User → can book +* Owner → can manage venues +* Admin → can approve/reject + +--- + + +## 6. Data Flow Example + +### Add Venue (Owner) + +1. Owner → sends `POST /venues` +2. API receives request +3. Service validates data +4. Save in DB (status = not approved) +5. Admin later approves + +--- + +## 7. 🚫 What We Are NOT Doing Now + +* Microservices +* Caching (Redis) +* Message queues +* Real-time systems +* Load balancing + +You don’t need them. Adding them now = wasted time. + +--- + +## 8. 🚀 Future Scalability (Later, Not Now) + +When system grows: + +* Add caching +* Split services +* Use cloud deployment + + diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 000000000..e294524f5 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,208 @@ +# BookMyVenue — Frontend + +Web client for BookMyVenue. This folder holds the user-facing app for browsing venues, making bookings, and managing listings.. + +During Phase 1 (MVP), contributors may use any frontend stack. Whatever you choose, follow the **modular monolithic** layout described below — one deployable app, organized by feature modules. + +--- + +## Prerequisites + +- Node.js 18+ (recommended: 20 LTS) +- npm, yarn, or pnpm +- Git +- Running backend API (see [`backend/README.md`](../backend/README.md)) + +--- + +## 1. Create the frontend project + +From the repository root: + +```bash +cd frontend +``` + +### Option A — React + Vite (recommended starting point) + +```bash +npm create vite@latest . -- --template react-ts +npm install +``` + +### Option B — Next.js + +```bash +npx create-next-app@latest . --typescript --eslint --app --src-dir +``` + +### Option C — Your preferred stack + +Use Vue, Svelte, Angular, or another framework if you prefer. Document your choice in your Pull Request. The folder structure rules below still apply. + +--- + +## 2. Modular monolithic folder structure + +After scaffolding, reorganize (or grow) the project into feature modules. One app, one build — not separate micro-frontends. + +```bash +frontend/ +├── src/ +│ ├── main.tsx # App entry point +│ ├── app/ # App shell: routing, layout, providers +│ │ ├── App.tsx +│ │ ├── router.tsx +│ │ └── providers.tsx +│ ├── core/ # Global config and shared infrastructure +│ │ ├── config.ts # Env-based settings (API URL, etc.) +│ │ ├── api/ +│ │ │ └── client.ts # HTTP client (fetch/axios) with auth headers +│ │ └── auth/ +│ │ └── AuthContext.tsx # Auth state shared across modules +│ ├── modules/ # Feature-based modules (mirror backend domains) +│ │ ├── auth/ +│ │ │ ├── pages/ # Login, register +│ │ │ ├── components/ # Module-specific UI +│ │ │ ├── hooks/ # Module-specific hooks +│ │ │ ├── services/ # API calls for this feature +│ │ │ └── types.ts # Module-specific types +│ │ ├── venues/ +│ │ ├── bookings/ +│ │ ├── admin/ +│ │ └── profile/ +│ ├── components/ # Shared, reusable UI (buttons, modals, etc.) +│ │ └── ui/ +│ ├── hooks/ # Shared hooks +│ ├── utils/ # Pure helpers (formatting, validation) +│ └── styles/ # Global styles / design tokens +├── public/ +├── .env # Local env (never commit) +├── .env.example # Template for contributors +├── package.json +└── README.md +``` + +### Modular monolithic rules + +| Location | Responsibility | +|----------|----------------| +| `modules//pages/` | Route-level screens for one domain. | +| `modules//services/` | API calls for that feature only. | +| `modules//components/` | UI used inside that module. | +| `core/` | App-wide config, API client, auth — no feature UI here. | +| `components/ui/` | Generic building blocks reused across modules. | +| `app/` | Routing, layout, and top-level providers. | + +**Do not** put feature logic in `App.tsx`. **Do not** call the API directly from page components — use the module's `services/`. **Do not** create random top-level folders; add a new folder under `modules/` instead. + +Data flow: + +```text +Page → module service → core API client → backend → response → UI +``` + +--- + +## 3. Install dependencies + +After scaffolding, install base dependencies: + +```bash +npm install +``` + +Common additions (install what your stack needs): + +```bash +# Routing +npm install react-router-dom + +# HTTP client +npm install axios + +# Env validation (optional) +npm install zod +``` + +Lock your versions by committing `package-lock.json` (or `pnpm-lock.yaml` / `yarn.lock`). + +--- + +## 4. Environment variables (`.env`) + +Copy the example file and point to your local backend: + +```bash +cp .env.example .env +``` + +Example `.env.example`: + +```env +# Backend API base URL +VITE_API_BASE_URL=http://localhost:8000 + +# App metadata +VITE_APP_NAME=BookMyVenue +``` + +For **Next.js**, use the `NEXT_PUBLIC_` prefix instead of `VITE_`: + +```env +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 +``` + +Read env values in `src/core/config.ts` — not scattered across components. **Never commit `.env`**. + +--- + +## 5. Run the development server + +**Vite (React)** + +```bash +npm run dev +``` + +Default URL: http://localhost:5173 + +**Next.js** + +```bash +npm run dev +``` + +Default URL: http://localhost:3000 + +Ensure `CORS_ORIGINS` on the backend includes your frontend URL. + +--- + +## 6. Build for production + +```bash +npm run build +npm run preview # Vite — preview production build locally +``` + +--- + +## Adding a new feature module + +1. Create `src/modules//` with `pages/`, `components/`, `services/`, and `types.ts`. +2. Add routes in `src/app/router.tsx`. +3. Keep API calls inside the module's `services/` layer using the shared client from `core/api/`. +4. Move reusable UI into `src/components/ui/` only when two or more modules need it. + +Each module should be understandable on its own, similar to the backend's `app/modules/` layout. + +--- + +## Related docs + +- [Backend setup](../backend/README.md) +- [System Design](../documents/SystemDesign.md) +- [Folder Architecture (backend reference)](../documents/FolderArchitecture.md) +- [Product Requirements](../documents/PRD.md) +- [Contributing](../CONTRIBUTING.md) diff --git a/frontend/hy.txt b/frontend/hy.txt new file mode 100644 index 000000000..e69de29bb