-- TravelHub: multi-tenant flight booking schema -- Strategy: shared database, shared schema, tenant_id discriminator on every -- tenant-owned table. Reference data (airports, airlines) is global. CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ---------------------------------------------------------------- tenants -- CREATE TABLE IF NOT EXISTS tenants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended')), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ------------------------------------------------------------------ users -- CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, email TEXT NOT NULL, password_hash TEXT NOT NULL, full_name TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'agent' CHECK (role IN ('admin', 'agent')), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- The same email may exist in different tenants; unique only within one. CONSTRAINT users_tenant_email_key UNIQUE (tenant_id, email) ); CREATE INDEX IF NOT EXISTS idx_users_tenant ON users(tenant_id); -- ------------------------------------------------- reference data (global) -- CREATE TABLE IF NOT EXISTS airports ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), iata_code CHAR(3) NOT NULL UNIQUE, name TEXT NOT NULL, city TEXT NOT NULL, country TEXT NOT NULL, timezone TEXT NOT NULL DEFAULT 'UTC' ); CREATE TABLE IF NOT EXISTS airlines ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), iata_code CHAR(2) NOT NULL UNIQUE, name TEXT NOT NULL, logo_url TEXT ); -- ---------------------------------------------------------------- flights -- -- Inventory is per-tenant: each agency loads its own negotiated fares. CREATE TABLE IF NOT EXISTS flights ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, airline_id UUID NOT NULL REFERENCES airlines(id), flight_number TEXT NOT NULL, origin_airport_id UUID NOT NULL REFERENCES airports(id), destination_airport_id UUID NOT NULL REFERENCES airports(id), departure_time TIMESTAMPTZ NOT NULL, arrival_time TIMESTAMPTZ NOT NULL, aircraft TEXT NOT NULL DEFAULT '', cabin_class TEXT NOT NULL DEFAULT 'economy' CHECK (cabin_class IN ('economy', 'premium_economy', 'business', 'first')), base_price NUMERIC(10,2) NOT NULL CHECK (base_price >= 0), currency CHAR(3) NOT NULL DEFAULT 'USD', seats_total INTEGER NOT NULL CHECK (seats_total > 0), seats_available INTEGER NOT NULL CHECK (seats_available >= 0), status TEXT NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'delayed', 'cancelled')), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT flights_seats_check CHECK (seats_available <= seats_total), CONSTRAINT flights_times_check CHECK (arrival_time > departure_time) ); CREATE INDEX IF NOT EXISTS idx_flights_search ON flights(tenant_id, origin_airport_id, destination_airport_id, departure_time); -- --------------------------------------------------------------- bookings -- CREATE TABLE IF NOT EXISTS bookings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id), flight_id UUID NOT NULL REFERENCES flights(id), booking_reference TEXT NOT NULL, passenger_count INTEGER NOT NULL CHECK (passenger_count > 0), total_amount NUMERIC(10,2) NOT NULL CHECK (total_amount >= 0), currency CHAR(3) NOT NULL DEFAULT 'USD', status TEXT NOT NULL DEFAULT 'confirmed' CHECK (status IN ('confirmed', 'cancelled')), contact_email TEXT NOT NULL, contact_phone TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- PNR is unique per tenant, not globally. CONSTRAINT bookings_tenant_ref_key UNIQUE (tenant_id, booking_reference) ); CREATE INDEX IF NOT EXISTS idx_bookings_tenant_created ON bookings(tenant_id, created_at DESC); -- ------------------------------------------------------------- passengers -- CREATE TABLE IF NOT EXISTS passengers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, booking_id UUID NOT NULL REFERENCES bookings(id) ON DELETE CASCADE, first_name TEXT NOT NULL, last_name TEXT NOT NULL, date_of_birth DATE, passport_number TEXT NOT NULL DEFAULT '', seat_number TEXT NOT NULL DEFAULT '' ); CREATE INDEX IF NOT EXISTS idx_passengers_booking ON passengers(booking_id);