# 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.*