-- Extensions CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Enums DO $$ BEGIN CREATE TYPE routestatus AS ENUM ('ACTIVE', 'INACTIVE', 'MAINTENANCE'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE stoptype AS ENUM ('TERMINAL', 'REGULAR', 'EXPRESS_ONLY'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE busscheduletype AS ENUM ('WEEKDAY', 'WEEKEND', 'HOLIDAY'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE userrole AS ENUM ('ADMIN', 'PASSENGER', 'DRIVER', 'PROMOTER'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE vehicletype AS ENUM ('taxi', 'bus'); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE usercouponstatus AS ENUM ('claimed', 'redeemed', 'expired'); EXCEPTION WHEN duplicate_object THEN null; END $$; -- 1. Users CREATE TABLE IF NOT EXISTS public.users ( id UUID PRIMARY KEY, -- Will link to auth.users.id email TEXT UNIQUE NOT NULL, full_name TEXT NOT NULL, role userrole DEFAULT 'PASSENGER'::userrole, is_active BOOLEAN DEFAULT true, is_verified BOOLEAN DEFAULT false, profile_photo_url TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 2. Driver Profiles CREATE TABLE IF NOT EXISTS public.driver_profiles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES public.users(id) ON DELETE CASCADE, cedula TEXT NOT NULL, vehicle_type vehicletype NOT NULL, license_plate TEXT NOT NULL, photo_url TEXT, vehicle_photo_url TEXT, cooperative_name TEXT, shift TEXT, payment_methods TEXT, speaks_english BOOLEAN DEFAULT false ); -- 3. Bus Stops CREATE TABLE IF NOT EXISTS public.bus_stops ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, latitude FLOAT NOT NULL, longitude FLOAT NOT NULL, city TEXT NOT NULL, address TEXT, stop_type stoptype NOT NULL, has_shelter BOOLEAN DEFAULT false, has_seating BOOLEAN DEFAULT false, is_accessible BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 4. Routes CREATE TABLE IF NOT EXISTS public.routes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT UNIQUE NOT NULL, description TEXT, origin_city TEXT NOT NULL, destination_city TEXT NOT NULL, distance_km FLOAT, estimated_duration_minutes INTEGER, average_speed_kmh FLOAT, color TEXT DEFAULT '#FEE715', direction TEXT DEFAULT 'outbound', status routestatus DEFAULT 'ACTIVE'::routestatus, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 5. Route Stops CREATE TABLE IF NOT EXISTS public.route_stops ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), route_id UUID REFERENCES public.routes(id) ON DELETE CASCADE, stop_id UUID REFERENCES public.bus_stops(id) ON DELETE CASCADE, stop_order INTEGER NOT NULL, travel_time_minutes INTEGER, stop_delay_minutes INTEGER DEFAULT 0, is_pickup_point BOOLEAN DEFAULT false, is_dropoff_point BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 6. Bus Schedules CREATE TABLE IF NOT EXISTS public.bus_schedules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), route_id UUID REFERENCES public.routes(id) ON DELETE CASCADE, departure_time TIME NOT NULL, frequency_minutes INTEGER, schedule_type busscheduletype NOT NULL, is_active BOOLEAN DEFAULT true, is_published BOOLEAN DEFAULT true, notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 7. Businesses CREATE TABLE IF NOT EXISTS public.businesses ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, address TEXT, phone TEXT, image_url TEXT, social_media TEXT, category TEXT, latitude FLOAT, longitude FLOAT, area TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 8. Coupons CREATE TABLE IF NOT EXISTS public.coupons ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), business_id UUID REFERENCES public.businesses(id) ON DELETE CASCADE, title TEXT NOT NULL, description TEXT, image_url TEXT, social_media TEXT, terms TEXT, discount_percentage FLOAT, discount_amount FLOAT, category TEXT, valid_from TIMESTAMPTZ, valid_until TIMESTAMPTZ, is_active BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 9. Shuttles CREATE TABLE IF NOT EXISTS public.shuttles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), route_name TEXT NOT NULL, description TEXT, origin TEXT NOT NULL, destination TEXT NOT NULL, vehicle_type TEXT NOT NULL, company_name TEXT, trip_type TEXT DEFAULT 'one_way', price_per_person FLOAT, price_private_trip FLOAT, estimated_duration TEXT, departure_times TEXT, contact_whatsapp TEXT NOT NULL, phone_number TEXT, english_speaking BOOLEAN DEFAULT false, image_url TEXT, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 10. Taxis CREATE TABLE IF NOT EXISTS public.taxis ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), owner_name TEXT NOT NULL, phone_number TEXT NOT NULL, license_plate TEXT NOT NULL, cooperative TEXT, corregimiento TEXT NOT NULL, shift TEXT NOT NULL, rating FLOAT, english_speaking BOOLEAN DEFAULT false, image_url TEXT, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- 11. Favorites CREATE TABLE IF NOT EXISTS public.favorites ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES public.users(id) ON DELETE CASCADE, item_type TEXT NOT NULL, item_id UUID NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 12. User Coupons CREATE TABLE IF NOT EXISTS public.user_coupons ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES public.users(id) ON DELETE CASCADE, coupon_id UUID REFERENCES public.coupons(id) ON DELETE CASCADE, status usercouponstatus DEFAULT 'claimed'::usercouponstatus, redemption_code TEXT NOT NULL, claimed_at TIMESTAMPTZ DEFAULT NOW(), redeemed_at TIMESTAMPTZ, UNIQUE(user_id, coupon_id) ); -- TRIGGER FOR AUTH.USERS -> PUBLIC.USERS CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS TRIGGER AS $$ BEGIN INSERT INTO public.users (id, email, full_name, role) VALUES ( new.id, new.email, COALESCE(new.raw_user_meta_data->>'full_name', new.email), COALESCE((new.raw_user_meta_data->>'role')::userrole, 'PASSENGER'::userrole) ); RETURN new; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Trigger firing when a new user signs up DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user(); -- SET DEFAULT RLS (Row Level Security) TO EVERY TABLE ALTER TABLE public.users ENABLE ROW LEVEL SECURITY; ALTER TABLE public.routes ENABLE ROW LEVEL SECURITY; ALTER TABLE public.bus_stops ENABLE ROW LEVEL SECURITY; ALTER TABLE public.route_stops ENABLE ROW LEVEL SECURITY; ALTER TABLE public.bus_schedules ENABLE ROW LEVEL SECURITY; ALTER TABLE public.shuttles ENABLE ROW LEVEL SECURITY; ALTER TABLE public.businesses ENABLE ROW LEVEL SECURITY; ALTER TABLE public.coupons ENABLE ROW LEVEL SECURITY; ALTER TABLE public.user_coupons ENABLE ROW LEVEL SECURITY; ALTER TABLE public.taxis ENABLE ROW LEVEL SECURITY; ALTER TABLE public.favorites ENABLE ROW LEVEL SECURITY; ALTER TABLE public.driver_profiles ENABLE ROW LEVEL SECURITY; -- SIMPLE POLICIES (Read for all, Insert/Update/Delete requires auth) -- You can tighten these up later, this gets you going securely. -- Routes, Stops, Shuttles, Taxis, Businesses, Coupons: public read CREATE POLICY "Public Read" ON public.routes FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.bus_stops FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.route_stops FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.bus_schedules FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.shuttles FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.businesses FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.coupons FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.taxis FOR SELECT USING (true); CREATE POLICY "Public Read" ON public.driver_profiles FOR SELECT USING (true); -- Users can read their own data, Admins can read everything CREATE POLICY "Users can view own data" ON public.users FOR SELECT USING (auth.uid() = id); CREATE POLICY "Users can update own data" ON public.users FOR UPDATE USING (auth.uid() = id); -- Allow admins to manage (This policy assumes admins are users with role = 'ADMIN') CREATE OR REPLACE FUNCTION is_admin() RETURNS BOOLEAN AS $$ BEGIN RETURN EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'ADMIN'); END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE POLICY "Admins full access" ON public.routes FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.bus_stops FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.route_stops FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.bus_schedules FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.shuttles FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.businesses FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.coupons FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.taxis FOR ALL USING (is_admin()); CREATE POLICY "Admins full access" ON public.users FOR ALL USING (is_admin()); -- Favorites and User Coupons CREATE POLICY "Users can view own favorites" ON public.favorites FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "Users can manage own favorites" ON public.favorites FOR ALL USING (auth.uid() = user_id); CREATE POLICY "Users can view own coupons" ON public.user_coupons FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "Users can manage own coupons" ON public.user_coupons FOR ALL USING (auth.uid() = user_id);