bharath/docs/architecture/DATABASE_SCHEMA.md
Vivek 394117dd74 BharathTrucks MVP - 6 sprints complete
- Govt-app styled freight marketplace
- Role-based auth (driver/shipper/broker/admin)
- Load board with bidding system
- Trip tracking with status flow
- In-app messaging
- Admin panel
- Mobile bottom nav + PWA
- Docker + Coolify ready
2026-05-31 06:21:13 +00:00

365 lines
12 KiB
Markdown

# BharathTrucks — Database Schema Design
**Version:** 1.0
**Date:** 2026-05-31
**Database:** Supabase PostgreSQL
---
## 1. Schema Overview
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Users │────▶│ Profiles │────▶│ Loads │
│(Supabase)│ │ │ │ │
└──────────┘ └──────────┘ └────┬─────┘
│ │
│ ┌────▼─────┐
│ │ Bids │
│ └────┬─────┘
│ │
┌────▼─────┐ ┌────▼─────┐
│ Trucks │ │ Trips │
└──────────┘ └────┬─────┘
┌────▼─────┐
│ Payments │
└──────────┘
```
---
## 2. Tables
### 2.1 profiles
Extends Supabase auth.users with app-specific data.
```sql
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('driver', 'shipper', 'broker', 'admin')),
full_name TEXT NOT NULL,
phone TEXT UNIQUE NOT NULL,
email TEXT,
avatar_url TEXT,
language TEXT DEFAULT 'hi' CHECK (language IN ('en', 'hi')),
city TEXT,
state TEXT,
is_verified BOOLEAN DEFAULT FALSE,
is_premium BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
onboarding_complete BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_profiles_role ON profiles(role);
CREATE INDEX idx_profiles_city ON profiles(city);
```
### 2.2 driver_profiles
Additional driver-specific info.
```sql
CREATE TABLE driver_profiles (
id UUID PRIMARY KEY REFERENCES profiles(id) ON DELETE CASCADE,
license_number TEXT,
license_expiry DATE,
experience_years INTEGER DEFAULT 0,
routes_preferred TEXT[], -- ['Mumbai-Delhi', 'Chennai-Bangalore']
truck_id UUID REFERENCES trucks(id),
availability_status TEXT DEFAULT 'available' CHECK (availability_status IN ('available', 'on_trip', 'offline')),
total_trips INTEGER DEFAULT 0,
rating NUMERIC(2,1) DEFAULT 0.0,
bids_today INTEGER DEFAULT 0,
bids_today_date DATE DEFAULT CURRENT_DATE
);
```
### 2.3 shipper_profiles
```sql
CREATE TABLE shipper_profiles (
id UUID PRIMARY KEY REFERENCES profiles(id) ON DELETE CASCADE,
business_name TEXT,
gst_number TEXT,
business_type TEXT,
shipping_frequency TEXT CHECK (shipping_frequency IN ('daily', 'weekly', 'monthly', 'occasional')),
total_loads_posted INTEGER DEFAULT 0,
rating NUMERIC(2,1) DEFAULT 0.0
);
```
### 2.4 broker_profiles
```sql
CREATE TABLE broker_profiles (
id UUID PRIMARY KEY REFERENCES profiles(id) ON DELETE CASCADE,
agency_name TEXT,
experience_years INTEGER DEFAULT 0,
network_size INTEGER DEFAULT 0,
operating_regions TEXT[],
total_deals INTEGER DEFAULT 0,
total_commission NUMERIC(12,2) DEFAULT 0,
rating NUMERIC(2,1) DEFAULT 0.0
);
```
### 2.5 trucks
```sql
CREATE TABLE trucks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
registration_number TEXT UNIQUE NOT NULL,
truck_type TEXT NOT NULL CHECK (truck_type IN ('open', 'closed', 'container', 'flatbed', 'tanker', 'refrigerated', 'mini')),
capacity_tons NUMERIC(5,1) NOT NULL,
make TEXT, -- Tata, Ashok Leyland, etc.
model TEXT,
year INTEGER,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_trucks_owner ON trucks(owner_id);
CREATE INDEX idx_trucks_type ON trucks(truck_type);
```
### 2.6 loads
```sql
CREATE TABLE loads (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
posted_by UUID NOT NULL REFERENCES profiles(id),
shipper_id UUID REFERENCES profiles(id), -- if broker posts on behalf
origin_city TEXT NOT NULL,
origin_state TEXT NOT NULL,
destination_city TEXT NOT NULL,
destination_state TEXT NOT NULL,
weight_tons NUMERIC(5,1) NOT NULL,
truck_type_required TEXT NOT NULL,
material_type TEXT,
budget NUMERIC(10,2),
pickup_date DATE NOT NULL,
description TEXT,
is_urgent BOOLEAN DEFAULT FALSE,
status TEXT DEFAULT 'open' CHECK (status IN ('open', 'booked', 'in_transit', 'delivered', 'cancelled')),
bid_count INTEGER DEFAULT 0,
accepted_bid_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_loads_status ON loads(status);
CREATE INDEX idx_loads_origin ON loads(origin_city);
CREATE INDEX idx_loads_destination ON loads(destination_city);
CREATE INDEX idx_loads_posted_by ON loads(posted_by);
CREATE INDEX idx_loads_pickup_date ON loads(pickup_date);
CREATE INDEX idx_loads_truck_type ON loads(truck_type_required);
```
### 2.7 bids
```sql
CREATE TABLE bids (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
load_id UUID NOT NULL REFERENCES loads(id) ON DELETE CASCADE,
driver_id UUID NOT NULL REFERENCES profiles(id),
amount NUMERIC(10,2) NOT NULL,
estimated_delivery DATE,
note TEXT,
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'rejected', 'withdrawn')),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(load_id, driver_id) -- one bid per driver per load
);
CREATE INDEX idx_bids_load ON bids(load_id);
CREATE INDEX idx_bids_driver ON bids(driver_id);
CREATE INDEX idx_bids_status ON bids(status);
```
### 2.8 trips
```sql
CREATE TABLE trips (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
load_id UUID NOT NULL REFERENCES loads(id),
driver_id UUID NOT NULL REFERENCES profiles(id),
shipper_id UUID NOT NULL REFERENCES profiles(id),
bid_id UUID NOT NULL REFERENCES bids(id),
status TEXT DEFAULT 'confirmed' CHECK (status IN ('confirmed', 'picked_up', 'in_transit', 'delivered', 'cancelled')),
picked_up_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
driver_rating INTEGER CHECK (driver_rating BETWEEN 1 AND 5),
shipper_rating INTEGER CHECK (shipper_rating BETWEEN 1 AND 5),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_trips_driver ON trips(driver_id);
CREATE INDEX idx_trips_shipper ON trips(shipper_id);
CREATE INDEX idx_trips_status ON trips(status);
```
### 2.9 messages
```sql
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
load_id UUID REFERENCES loads(id),
sender_id UUID NOT NULL REFERENCES profiles(id),
receiver_id UUID NOT NULL REFERENCES profiles(id),
content TEXT NOT NULL,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_messages_receiver ON messages(receiver_id, is_read);
CREATE INDEX idx_messages_load ON messages(load_id);
```
### 2.10 payments
```sql
CREATE TABLE payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
trip_id UUID NOT NULL REFERENCES trips(id),
payer_id UUID NOT NULL REFERENCES profiles(id),
payee_id UUID NOT NULL REFERENCES profiles(id),
amount NUMERIC(10,2) NOT NULL,
method TEXT DEFAULT 'upi' CHECK (method IN ('upi', 'cash', 'bank_transfer')),
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'disputed')),
upi_reference TEXT,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_payments_trip ON payments(trip_id);
CREATE INDEX idx_payments_payee ON payments(payee_id);
```
### 2.11 broker_commissions
```sql
CREATE TABLE broker_commissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
broker_id UUID NOT NULL REFERENCES profiles(id),
trip_id UUID NOT NULL REFERENCES trips(id),
load_id UUID NOT NULL REFERENCES loads(id),
amount NUMERIC(10,2) NOT NULL,
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'received')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_commissions_broker ON broker_commissions(broker_id);
```
### 2.12 notifications
```sql
CREATE TABLE notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id),
type TEXT NOT NULL CHECK (type IN ('bid_received', 'bid_accepted', 'bid_rejected', 'trip_update', 'payment', 'system')),
title TEXT NOT NULL,
body TEXT,
reference_id UUID, -- load_id, bid_id, trip_id
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_notifications_user ON notifications(user_id, is_read);
```
---
## 3. Row Level Security (RLS) Policies
```sql
-- Profiles: users can read all, update own
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public profiles readable" ON profiles FOR SELECT USING (true);
CREATE POLICY "Users update own profile" ON profiles FOR UPDATE USING (auth.uid() = id);
-- Loads: all can read open loads, owners can update
ALTER TABLE loads ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Open loads readable" ON loads FOR SELECT USING (true);
CREATE POLICY "Owners manage loads" ON loads FOR ALL USING (auth.uid() = posted_by);
-- Bids: load owner + bidder can see
ALTER TABLE bids ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Bid participants can view" ON bids FOR SELECT
USING (auth.uid() = driver_id OR auth.uid() IN (SELECT posted_by FROM loads WHERE id = load_id));
CREATE POLICY "Drivers create bids" ON bids FOR INSERT WITH CHECK (auth.uid() = driver_id);
-- Messages: sender and receiver only
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Message participants" ON messages FOR SELECT
USING (auth.uid() = sender_id OR auth.uid() = receiver_id);
CREATE POLICY "Users send messages" ON messages FOR INSERT WITH CHECK (auth.uid() = sender_id);
```
---
## 4. Database Functions
```sql
-- Auto-update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER profiles_updated_at BEFORE UPDATE ON profiles
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER loads_updated_at BEFORE UPDATE ON loads
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER bids_updated_at BEFORE UPDATE ON bids
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER trips_updated_at BEFORE UPDATE ON trips
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- Increment bid count on load when new bid placed
CREATE OR REPLACE FUNCTION increment_bid_count()
RETURNS TRIGGER AS $$
BEGIN
UPDATE loads SET bid_count = bid_count + 1 WHERE id = NEW.load_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER bid_count_trigger AFTER INSERT ON bids
FOR EACH ROW EXECUTE FUNCTION increment_bid_count();
-- Reset daily bid count
CREATE OR REPLACE FUNCTION reset_daily_bids()
RETURNS void AS $$
BEGIN
UPDATE driver_profiles SET bids_today = 0, bids_today_date = CURRENT_DATE
WHERE bids_today_date < CURRENT_DATE;
END;
$$ LANGUAGE plpgsql;
```
---
## 5. Seed Data (Development)
```sql
-- Truck types reference
INSERT INTO trucks (owner_id, registration_number, truck_type, capacity_tons, make)
VALUES
-- Will be populated during testing
;
-- Sample cities for load testing
-- Mumbai, Delhi, Bangalore, Chennai, Hyderabad, Ahmedabad, Pune, Kolkata, Jaipur, Nagpur
```
---
*Schema designed for simplicity in Phase 1. Normalized where needed, denormalized (bid_count, total_trips) for read performance.*