Files
SIB/old/supabase/migrations/20241019220000_fix_existing_schema.sql

362 lines
16 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Fix existing SIBU transportation schema to match required structure
-- This migration updates the existing schema rather than recreating it
-- 1. First, let's add missing columns to routes table if they don't exist
DO $$
BEGIN
-- Add missing columns to routes table
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'description') THEN
ALTER TABLE public.routes ADD COLUMN description TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'origin_city') THEN
ALTER TABLE public.routes ADD COLUMN origin_city TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'destination_city') THEN
ALTER TABLE public.routes ADD COLUMN destination_city TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'distance_km') THEN
ALTER TABLE public.routes ADD COLUMN distance_km DECIMAL(6,2);
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'estimated_duration_minutes') THEN
ALTER TABLE public.routes ADD COLUMN estimated_duration_minutes INTEGER;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'status') THEN
-- Create enum type if it doesn't exist
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'route_status') THEN
CREATE TYPE public.route_status AS ENUM ('active', 'inactive', 'maintenance');
END IF;
ALTER TABLE public.routes ADD COLUMN status public.route_status DEFAULT 'active'::public.route_status;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'created_at') THEN
ALTER TABLE public.routes ADD COLUMN created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'routes' AND column_name = 'updated_at') THEN
ALTER TABLE public.routes ADD COLUMN updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
END IF;
END $$;
-- 2. Add missing columns to stops table
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'city') THEN
ALTER TABLE public.stops ADD COLUMN city TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'address') THEN
ALTER TABLE public.stops ADD COLUMN address TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'stop_type') THEN
-- Create enum type if it doesn't exist
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'stop_type') THEN
CREATE TYPE public.stop_type AS ENUM ('terminal', 'regular', 'express_only');
END IF;
ALTER TABLE public.stops ADD COLUMN stop_type public.stop_type DEFAULT 'regular'::public.stop_type;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'has_shelter') THEN
ALTER TABLE public.stops ADD COLUMN has_shelter BOOLEAN DEFAULT false;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'has_seating') THEN
ALTER TABLE public.stops ADD COLUMN has_seating BOOLEAN DEFAULT false;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'is_accessible') THEN
ALTER TABLE public.stops ADD COLUMN is_accessible BOOLEAN DEFAULT false;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'created_at') THEN
ALTER TABLE public.stops ADD COLUMN created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'stops' AND column_name = 'updated_at') THEN
ALTER TABLE public.stops ADD COLUMN updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
END IF;
END $$;
-- 3. Add missing columns to route_stops table
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'route_stops' AND column_name = 'id') THEN
ALTER TABLE public.route_stops ADD COLUMN id UUID DEFAULT gen_random_uuid();
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'route_stops' AND column_name = 'stop_order') THEN
ALTER TABLE public.route_stops ADD COLUMN stop_order INTEGER;
-- Update stop_order from existing seq column
UPDATE public.route_stops SET stop_order = seq WHERE stop_order IS NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'route_stops' AND column_name = 'travel_time_minutes') THEN
ALTER TABLE public.route_stops ADD COLUMN travel_time_minutes INTEGER;
-- Calculate travel time from dwell_sec (convert seconds to minutes)
UPDATE public.route_stops SET travel_time_minutes = COALESCE(dwell_sec / 60, 0) WHERE travel_time_minutes IS NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'route_stops' AND column_name = 'is_pickup_point') THEN
ALTER TABLE public.route_stops ADD COLUMN is_pickup_point BOOLEAN DEFAULT true;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'route_stops' AND column_name = 'is_dropoff_point') THEN
ALTER TABLE public.route_stops ADD COLUMN is_dropoff_point BOOLEAN DEFAULT true;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'route_stops' AND column_name = 'created_at') THEN
ALTER TABLE public.route_stops ADD COLUMN created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
END IF;
END $$;
-- 4. Create bus_schedules table based on existing timetable
CREATE TABLE IF NOT EXISTS public.bus_schedules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
route_id TEXT REFERENCES public.routes(id) ON DELETE CASCADE,
departure_time TIME NOT NULL,
frequency_minutes INTEGER DEFAULT 30,
schedule_type TEXT DEFAULT 'weekday',
is_active BOOLEAN DEFAULT true,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- 5. Migrate data from timetable to bus_schedules
INSERT INTO public.bus_schedules (route_id, departure_time, frequency_minutes, schedule_type, is_active)
SELECT
route_id,
departure_time,
30 as frequency_minutes,
'weekday' as schedule_type,
true as is_active
FROM public.timetable
WHERE NOT EXISTS (
SELECT 1 FROM public.bus_schedules bs
WHERE bs.route_id = timetable.route_id
AND bs.departure_time = timetable.departure_time
);
-- 6. Update existing route data with proper values
UPDATE public.routes SET
description = CASE
WHEN name = 'Boquete David' THEN 'Ruta desde Boquete hacia David con paradas principales'
WHEN name = 'David Boquete' THEN 'Ruta desde David hacia Boquete con paradas principales'
ELSE 'Ruta de transporte público'
END,
origin_city = CASE
WHEN direction = 'outbound' AND name LIKE 'Boquete%' THEN 'Boquete'
WHEN direction = 'inbound' AND name LIKE 'David%' THEN 'David'
ELSE SPLIT_PART(name, ' ', 1)
END,
destination_city = CASE
WHEN direction = 'outbound' AND name LIKE '%David' THEN 'David'
WHEN direction = 'inbound' AND name LIKE '%Boquete' THEN 'Boquete'
ELSE SPLIT_PART(name, ' ', 2)
END,
distance_km = 38.5,
estimated_duration_minutes = 45,
status = 'active'::public.route_status
WHERE description IS NULL;
-- 7. Add additional routes for Panama SIBU system
INSERT INTO public.routes (id, name, description, color, direction, origin_city, destination_city, distance_km, estimated_duration_minutes, status) VALUES
('palmira-david-out', 'Palmira>David', 'Ruta desde Palmira hacia David', '#FEE715', 'outbound', 'Palmira', 'David', 25.2, 35, 'active'),
('david-palmira-in', 'David>Palmira', 'Ruta desde David hacia Palmira', '#FEE715', 'inbound', 'David', 'Palmira', 25.2, 35, 'active'),
('caldera-david-out', 'Caldera>David', 'Ruta desde Caldera hacia David', '#FEE715', 'outbound', 'Caldera', 'David', 42.8, 50, 'active'),
('david-caldera-in', 'David>Caldera', 'Ruta desde David hacia Caldera', '#FEE715', 'inbound', 'David', 'Caldera', 42.8, 50, 'active')
ON CONFLICT (id) DO NOTHING;
-- 8. Add missing bus stops for complete Panama routes
INSERT INTO public.stops (name, lat, lng, city, address, stop_type, has_shelter, has_seating) VALUES
-- Palmira stops
('Centro de Palmira', 8.3544, -82.3611, 'Palmira', 'Plaza Central', 'regular', true, true),
('Escuela de Palmira', 8.3567, -82.3598, 'Palmira', 'Zona Escolar', 'regular', false, true),
-- Caldera stops
('Puerto de Caldera', 8.2456, -81.7234, 'Caldera', 'Zona Portuaria', 'terminal', true, true),
('Centro de Caldera', 8.2478, -81.7198, 'Caldera', 'Centro del Pueblo', 'regular', true, false),
-- Additional David stops
('Terminal de David', 8.4177, -82.4270, 'David', 'Terminal de Transporte', 'terminal', true, true),
('Centro de David', 8.4194, -82.4255, 'David', 'Parque Cervantes', 'regular', true, true),
('Hospital Chiriquí', 8.4156, -82.4289, 'David', 'Complejo Hospitalario', 'regular', true, true),
('Chiriquí Mall', 8.4089, -82.4178, 'David', 'Centro Comercial', 'regular', true, true),
-- Additional Boquete stops
('Terminal de Boquete', 8.7697, -82.4328, 'Boquete', 'Centro de Boquete', 'terminal', true, true),
('Centro de Boquete', 8.7720, -82.4315, 'Boquete', 'Calle Central', 'regular', true, true),
('Parque Central Boquete', 8.7705, -82.4340, 'Boquete', 'Junto al Parque Central', 'regular', false, true),
('Hospital de Boquete', 8.7680, -82.4350, 'Boquete', 'Hospital Regional', 'regular', true, true),
('Escuela Primaria Boquete', 8.7740, -82.4300, 'Boquete', 'Zona Educativa', 'regular', false, false),
('Mercado Municipal Boquete', 8.7715, -82.4365, 'Boquete', 'Mercado Central', 'regular', true, true)
ON CONFLICT (name, lat, lng) DO NOTHING;
-- 9. Helper Functions
CREATE OR REPLACE FUNCTION public.calculate_next_bus_arrival(
p_route_id TEXT,
p_stop_id UUID,
p_current_time TIME DEFAULT CURRENT_TIME
)
RETURNS TABLE(
next_departure TIME,
estimated_arrival_time TIMESTAMPTZ,
minutes_until_arrival INTEGER
)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
AS $$
DECLARE
stop_position INTEGER;
time_to_stop INTEGER;
next_schedule_time TIME;
BEGIN
-- Get the next scheduled departure
SELECT bs.departure_time INTO next_schedule_time
FROM public.bus_schedules bs
WHERE bs.route_id = p_route_id
AND bs.is_active = true
AND bs.departure_time > p_current_time
ORDER BY bs.departure_time ASC
LIMIT 1;
-- If no more schedules today, get first schedule tomorrow
IF next_schedule_time IS NULL THEN
SELECT bs.departure_time INTO next_schedule_time
FROM public.bus_schedules bs
WHERE bs.route_id = p_route_id
AND bs.is_active = true
ORDER BY bs.departure_time ASC
LIMIT 1;
END IF;
-- Get stop position in route and calculate travel time
SELECT rs.stop_order INTO stop_position
FROM public.route_stops rs
WHERE rs.route_id = p_route_id AND rs.stop_id = p_stop_id;
-- Calculate estimated travel time to this stop (simplified calculation)
time_to_stop := COALESCE((stop_position - 1) * 2 + (stop_position * 3), 5); -- ~3 min between stops
RETURN QUERY SELECT
next_schedule_time,
(CURRENT_DATE + next_schedule_time + (time_to_stop || ' minutes')::INTERVAL)::TIMESTAMPTZ,
EXTRACT(EPOCH FROM (
(CURRENT_DATE + next_schedule_time + (time_to_stop || ' minutes')::INTERVAL) - NOW()
))::INTEGER / 60;
END;
$$;
CREATE OR REPLACE FUNCTION public.get_route_stops_ordered(p_route_id TEXT)
RETURNS TABLE(
stop_id UUID,
stop_name TEXT,
latitude DOUBLE PRECISION,
longitude DOUBLE PRECISION,
stop_order INTEGER,
travel_time_minutes INTEGER
)
LANGUAGE sql
STABLE
SECURITY DEFINER
AS $$
SELECT
s.id,
s.name,
s.lat,
s.lng,
rs.stop_order,
rs.travel_time_minutes
FROM public.route_stops rs
JOIN public.stops s ON rs.stop_id = s.id
WHERE rs.route_id = p_route_id
ORDER BY rs.stop_order;
$$;
-- 10. Enable RLS if not already enabled
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'routes' AND rowsecurity = true) THEN
ALTER TABLE public.routes ENABLE ROW LEVEL SECURITY;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stops' AND rowsecurity = true) THEN
ALTER TABLE public.stops ENABLE ROW LEVEL SECURITY;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'route_stops' AND rowsecurity = true) THEN
ALTER TABLE public.route_stops ENABLE ROW LEVEL SECURITY;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'bus_schedules' AND rowsecurity = true) THEN
ALTER TABLE public.bus_schedules ENABLE ROW LEVEL SECURITY;
END IF;
END $$;
-- 11. Create RLS Policies (only if they don't exist)
DO $$
BEGIN
-- Routes policies
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'routes' AND policyname = 'public_can_read_routes') THEN
CREATE POLICY "public_can_read_routes" ON public.routes FOR SELECT TO public USING (true);
END IF;
-- Stops policies
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'stops' AND policyname = 'public_can_read_stops') THEN
CREATE POLICY "public_can_read_stops" ON public.stops FOR SELECT TO public USING (true);
END IF;
-- Route stops policies
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'route_stops' AND policyname = 'public_can_read_route_stops') THEN
CREATE POLICY "public_can_read_route_stops" ON public.route_stops FOR SELECT TO public USING (true);
END IF;
-- Bus schedules policies
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'bus_schedules' AND policyname = 'public_can_read_bus_schedules') THEN
CREATE POLICY "public_can_read_bus_schedules" ON public.bus_schedules FOR SELECT TO public USING (true);
END IF;
END $$;
-- 12. Create updated_at triggers
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_routes_updated_at') THEN
CREATE TRIGGER update_routes_updated_at
BEFORE UPDATE ON public.routes
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_stops_updated_at') THEN
CREATE TRIGGER update_stops_updated_at
BEFORE UPDATE ON public.stops
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
END $$;
-- 13. Create missing indexes for performance
CREATE INDEX IF NOT EXISTS idx_routes_origin_destination ON public.routes(origin_city, destination_city);
CREATE INDEX IF NOT EXISTS idx_routes_status ON public.routes(status);
CREATE INDEX IF NOT EXISTS idx_stops_city ON public.stops(city);
CREATE INDEX IF NOT EXISTS idx_bus_schedules_route_id ON public.bus_schedules(route_id);
CREATE INDEX IF NOT EXISTS idx_bus_schedules_departure_time ON public.bus_schedules(departure_time);
-- 14. Final success message (wrapped in DO block to avoid syntax error)
DO $$
BEGIN
RAISE NOTICE 'SIBU Transportation System schema updated successfully!';
END $$;