Initial commit: SIBU 2.0 MISSION
This commit is contained in:
@ -0,0 +1,445 @@
|
||||
-- Location: supabase/migrations/20241019215951_sibu_transportation_system.sql
|
||||
-- Schema Analysis: Fresh project with no existing database objects
|
||||
-- Integration Type: Complete new schema for SIBU transportation system
|
||||
-- Dependencies: New transportation management module
|
||||
|
||||
-- 1. Extensions & Custom Types
|
||||
CREATE TYPE public.route_status AS ENUM ('active', 'inactive', 'maintenance');
|
||||
CREATE TYPE public.stop_type AS ENUM ('terminal', 'regular', 'express_only');
|
||||
CREATE TYPE public.bus_schedule_type AS ENUM ('weekday', 'weekend', 'holiday');
|
||||
|
||||
-- 2. Core Tables (no foreign keys)
|
||||
|
||||
-- Routes table - stores bus route information
|
||||
CREATE TABLE public.routes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL UNIQUE, -- e.g., "Boquete>David", "David>Boquete"
|
||||
description TEXT,
|
||||
origin_city TEXT NOT NULL,
|
||||
destination_city TEXT NOT NULL,
|
||||
distance_km DECIMAL(6,2),
|
||||
estimated_duration_minutes INTEGER,
|
||||
status public.route_status DEFAULT 'active'::public.route_status,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Bus stops table
|
||||
CREATE TABLE public.bus_stops (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
latitude DECIMAL(10,8) NOT NULL,
|
||||
longitude DECIMAL(11,8) NOT NULL,
|
||||
city TEXT NOT NULL,
|
||||
address TEXT,
|
||||
stop_type public.stop_type DEFAULT 'regular'::public.stop_type,
|
||||
has_shelter BOOLEAN DEFAULT false,
|
||||
has_seating BOOLEAN DEFAULT false,
|
||||
is_accessible BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 3. Dependent Tables (with foreign keys)
|
||||
|
||||
-- Route stops - junction table connecting routes to their stops
|
||||
CREATE TABLE public.route_stops (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
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, -- Order of stop in the route (1, 2, 3...)
|
||||
travel_time_minutes INTEGER, -- Time from previous stop
|
||||
is_pickup_point BOOLEAN DEFAULT true,
|
||||
is_dropoff_point BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Bus schedules table
|
||||
CREATE TABLE public.bus_schedules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
route_id UUID REFERENCES public.routes(id) ON DELETE CASCADE,
|
||||
departure_time TIME NOT NULL,
|
||||
frequency_minutes INTEGER DEFAULT 30, -- How often bus runs (every 30 minutes)
|
||||
schedule_type public.bus_schedule_type DEFAULT 'weekday'::public.bus_schedule_type,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Real-time bus tracking (for future implementation)
|
||||
CREATE TABLE public.bus_tracking (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
route_id UUID REFERENCES public.routes(id) ON DELETE CASCADE,
|
||||
current_stop_id UUID REFERENCES public.bus_stops(id) ON DELETE SET NULL,
|
||||
next_stop_id UUID REFERENCES public.bus_stops(id) ON DELETE SET NULL,
|
||||
estimated_arrival_time TIMESTAMPTZ,
|
||||
delay_minutes INTEGER DEFAULT 0,
|
||||
bus_number TEXT,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
last_updated TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 4. Indexes for Performance
|
||||
CREATE INDEX idx_routes_origin_destination ON public.routes(origin_city, destination_city);
|
||||
CREATE INDEX idx_routes_status ON public.routes(status);
|
||||
CREATE INDEX idx_bus_stops_city ON public.bus_stops(city);
|
||||
CREATE INDEX idx_bus_stops_location ON public.bus_stops(latitude, longitude);
|
||||
CREATE INDEX idx_route_stops_route_id ON public.route_stops(route_id);
|
||||
CREATE INDEX idx_route_stops_stop_id ON public.route_stops(stop_id);
|
||||
CREATE INDEX idx_route_stops_order ON public.route_stops(route_id, stop_order);
|
||||
CREATE INDEX idx_bus_schedules_route_id ON public.bus_schedules(route_id);
|
||||
CREATE INDEX idx_bus_schedules_departure_time ON public.bus_schedules(departure_time);
|
||||
CREATE INDEX idx_bus_tracking_route_id ON public.bus_tracking(route_id);
|
||||
CREATE INDEX idx_bus_tracking_active ON public.bus_tracking(is_active);
|
||||
|
||||
-- 5. Helper Functions (MUST BE BEFORE RLS POLICIES)
|
||||
CREATE OR REPLACE FUNCTION public.calculate_next_bus_arrival(
|
||||
p_route_id UUID,
|
||||
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
|
||||
avg_speed_kmh DECIMAL := 65.0; -- Average bus speed
|
||||
stop_dwell_time INTEGER := 2; -- Minutes per stop
|
||||
route_distance DECIMAL;
|
||||
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 := (stop_position - 1) * stop_dwell_time + (stop_position * 3); -- ~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 UUID)
|
||||
RETURNS TABLE(
|
||||
stop_id UUID,
|
||||
stop_name TEXT,
|
||||
latitude DECIMAL,
|
||||
longitude DECIMAL,
|
||||
stop_order INTEGER,
|
||||
travel_time_minutes INTEGER
|
||||
)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT
|
||||
bs.id,
|
||||
bs.name,
|
||||
bs.latitude,
|
||||
bs.longitude,
|
||||
rs.stop_order,
|
||||
rs.travel_time_minutes
|
||||
FROM public.route_stops rs
|
||||
JOIN public.bus_stops bs ON rs.stop_id = bs.id
|
||||
WHERE rs.route_id = p_route_id
|
||||
ORDER BY rs.stop_order;
|
||||
$$;
|
||||
|
||||
-- 6. Enable RLS
|
||||
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.bus_tracking ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- 7. RLS Policies (Pattern 4: Public Read for Transportation Data)
|
||||
-- All transportation data is publicly readable
|
||||
CREATE POLICY "public_can_read_routes" ON public.routes
|
||||
FOR SELECT TO public USING (true);
|
||||
|
||||
CREATE POLICY "public_can_read_bus_stops" ON public.bus_stops
|
||||
FOR SELECT TO public USING (true);
|
||||
|
||||
CREATE POLICY "public_can_read_route_stops" ON public.route_stops
|
||||
FOR SELECT TO public USING (true);
|
||||
|
||||
CREATE POLICY "public_can_read_bus_schedules" ON public.bus_schedules
|
||||
FOR SELECT TO public USING (true);
|
||||
|
||||
CREATE POLICY "public_can_read_bus_tracking" ON public.bus_tracking
|
||||
FOR SELECT TO public USING (true);
|
||||
|
||||
-- Admin policies for data management (for future admin features)
|
||||
CREATE POLICY "admin_manage_routes" ON public.routes
|
||||
FOR ALL TO authenticated USING (false) WITH CHECK (false); -- Disabled for now
|
||||
|
||||
CREATE POLICY "admin_manage_bus_stops" ON public.bus_stops
|
||||
FOR ALL TO authenticated USING (false) WITH CHECK (false); -- Disabled for now
|
||||
|
||||
-- 8. Triggers for updated_at timestamps
|
||||
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER update_routes_updated_at
|
||||
BEFORE UPDATE ON public.routes
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_bus_stops_updated_at
|
||||
BEFORE UPDATE ON public.bus_stops
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
-- 9. Mock Data for SIBU Transportation System
|
||||
DO $$
|
||||
DECLARE
|
||||
-- Route IDs
|
||||
boquete_david_id UUID := gen_random_uuid();
|
||||
david_boquete_id UUID := gen_random_uuid();
|
||||
palmira_david_id UUID := gen_random_uuid();
|
||||
david_palmira_id UUID := gen_random_uuid();
|
||||
caldera_david_id UUID := gen_random_uuid();
|
||||
david_caldera_id UUID := gen_random_uuid();
|
||||
|
||||
-- Stop IDs for Boquete area
|
||||
terminal_boquete_id UUID := gen_random_uuid();
|
||||
centro_boquete_id UUID := gen_random_uuid();
|
||||
parque_boquete_id UUID := gen_random_uuid();
|
||||
hospital_boquete_id UUID := gen_random_uuid();
|
||||
escuela_boquete_id UUID := gen_random_uuid();
|
||||
mercado_boquete_id UUID := gen_random_uuid();
|
||||
|
||||
-- Stop IDs for David area
|
||||
terminal_david_id UUID := gen_random_uuid();
|
||||
centro_david_id UUID := gen_random_uuid();
|
||||
hospital_david_id UUID := gen_random_uuid();
|
||||
mall_david_id UUID := gen_random_uuid();
|
||||
|
||||
-- Stop IDs for Palmira
|
||||
centro_palmira_id UUID := gen_random_uuid();
|
||||
escuela_palmira_id UUID := gen_random_uuid();
|
||||
|
||||
-- Stop IDs for Caldera
|
||||
puerto_caldera_id UUID := gen_random_uuid();
|
||||
centro_caldera_id UUID := gen_random_uuid();
|
||||
BEGIN
|
||||
-- Insert Routes
|
||||
INSERT INTO public.routes (id, name, description, origin_city, destination_city, distance_km, estimated_duration_minutes, status) VALUES
|
||||
(boquete_david_id, 'Boquete>David', 'Ruta desde Boquete hacia David con paradas principales', 'Boquete', 'David', 38.5, 45, 'active'),
|
||||
(david_boquete_id, 'David>Boquete', 'Ruta desde David hacia Boquete con paradas principales', 'David', 'Boquete', 38.5, 45, 'active'),
|
||||
(palmira_david_id, 'Palmira>David', 'Ruta desde Palmira hacia David', 'Palmira', 'David', 25.2, 35, 'active'),
|
||||
(david_palmira_id, 'David>Palmira', 'Ruta desde David hacia Palmira', 'David', 'Palmira', 25.2, 35, 'active'),
|
||||
(caldera_david_id, 'Caldera>David', 'Ruta desde Caldera hacia David', 'Caldera', 'David', 42.8, 50, 'active'),
|
||||
(david_caldera_id, 'David>Caldera', 'Ruta desde David hacia Caldera', 'David', 'Caldera', 42.8, 50, 'active');
|
||||
|
||||
-- Insert Bus Stops
|
||||
INSERT INTO public.bus_stops (id, name, latitude, longitude, city, address, stop_type, has_shelter, has_seating) VALUES
|
||||
-- Boquete stops
|
||||
(terminal_boquete_id, 'Terminal de Boquete', 8.7697, -82.4328, 'Boquete', 'Centro de Boquete', 'terminal', true, true),
|
||||
(centro_boquete_id, 'Centro de Boquete', 8.7720, -82.4315, 'Boquete', 'Calle Central', 'regular', true, true),
|
||||
(parque_boquete_id, 'Parque Central Boquete', 8.7705, -82.4340, 'Boquete', 'Junto al Parque Central', 'regular', false, true),
|
||||
(hospital_boquete_id, 'Hospital de Boquete', 8.7680, -82.4350, 'Boquete', 'Hospital Regional', 'regular', true, true),
|
||||
(escuela_boquete_id, 'Escuela Primaria Boquete', 8.7740, -82.4300, 'Boquete', 'Zona Educativa', 'regular', false, false),
|
||||
(mercado_boquete_id, 'Mercado Municipal Boquete', 8.7715, -82.4365, 'Boquete', 'Mercado Central', 'regular', true, true),
|
||||
|
||||
-- David stops
|
||||
(terminal_david_id, 'Terminal de David', 8.4177, -82.4270, 'David', 'Terminal de Transporte', 'terminal', true, true),
|
||||
(centro_david_id, 'Centro de David', 8.4194, -82.4255, 'David', 'Parque Cervantes', 'regular', true, true),
|
||||
(hospital_david_id, 'Hospital Chiriquí', 8.4156, -82.4289, 'David', 'Complejo Hospitalario', 'regular', true, true),
|
||||
(mall_david_id, 'Chiriquí Mall', 8.4089, -82.4178, 'David', 'Centro Comercial', 'regular', true, true),
|
||||
|
||||
-- Palmira stops
|
||||
(centro_palmira_id, 'Centro de Palmira', 8.3544, -82.3611, 'Palmira', 'Plaza Central', 'regular', true, true),
|
||||
(escuela_palmira_id, 'Escuela de Palmira', 8.3567, -82.3598, 'Palmira', 'Zona Escolar', 'regular', false, true),
|
||||
|
||||
-- Caldera stops
|
||||
(puerto_caldera_id, 'Puerto de Caldera', 8.2456, -81.7234, 'Caldera', 'Zona Portuaria', 'terminal', true, true),
|
||||
(centro_caldera_id, 'Centro de Caldera', 8.2478, -81.7198, 'Caldera', 'Centro del Pueblo', 'regular', true, false);
|
||||
|
||||
-- Insert Route Stops (Boquete > David)
|
||||
INSERT INTO public.route_stops (route_id, stop_id, stop_order, travel_time_minutes, is_pickup_point, is_dropoff_point) VALUES
|
||||
(boquete_david_id, terminal_boquete_id, 1, 0, true, false),
|
||||
(boquete_david_id, centro_boquete_id, 2, 3, true, true),
|
||||
(boquete_david_id, parque_boquete_id, 3, 2, true, true),
|
||||
(boquete_david_id, hospital_boquete_id, 4, 3, true, true),
|
||||
(boquete_david_id, mall_david_id, 5, 35, true, true),
|
||||
(boquete_david_id, centro_david_id, 6, 5, true, true),
|
||||
(boquete_david_id, terminal_david_id, 7, 3, false, true);
|
||||
|
||||
-- Insert Route Stops (David > Boquete)
|
||||
INSERT INTO public.route_stops (route_id, stop_id, stop_order, travel_time_minutes, is_pickup_point, is_dropoff_point) VALUES
|
||||
(david_boquete_id, terminal_david_id, 1, 0, true, false),
|
||||
(david_boquete_id, centro_david_id, 2, 3, true, true),
|
||||
(david_boquete_id, mall_david_id, 3, 5, true, true),
|
||||
(david_boquete_id, hospital_boquete_id, 4, 35, true, true),
|
||||
(david_boquete_id, parque_boquete_id, 5, 3, true, true),
|
||||
(david_boquete_id, centro_boquete_id, 6, 2, true, true),
|
||||
(david_boquete_id, terminal_boquete_id, 7, 3, false, true);
|
||||
|
||||
-- Insert Route Stops (Palmira > David)
|
||||
INSERT INTO public.route_stops (route_id, stop_id, stop_order, travel_time_minutes, is_pickup_point, is_dropoff_point) VALUES
|
||||
(palmira_david_id, centro_palmira_id, 1, 0, true, false),
|
||||
(palmira_david_id, escuela_palmira_id, 2, 2, true, true),
|
||||
(palmira_david_id, hospital_david_id, 3, 25, true, true),
|
||||
(palmira_david_id, centro_david_id, 4, 5, true, true),
|
||||
(palmira_david_id, terminal_david_id, 5, 3, false, true);
|
||||
|
||||
-- Insert Route Stops (David > Palmira)
|
||||
INSERT INTO public.route_stops (route_id, stop_id, stop_order, travel_time_minutes, is_pickup_point, is_dropoff_point) VALUES
|
||||
(david_palmira_id, terminal_david_id, 1, 0, true, false),
|
||||
(david_palmira_id, centro_david_id, 2, 3, true, true),
|
||||
(david_palmira_id, hospital_david_id, 3, 5, true, true),
|
||||
(david_palmira_id, escuela_palmira_id, 4, 25, true, true),
|
||||
(david_palmira_id, centro_palmira_id, 5, 2, false, true);
|
||||
|
||||
-- Insert Route Stops (Caldera > David)
|
||||
INSERT INTO public.route_stops (route_id, stop_id, stop_order, travel_time_minutes, is_pickup_point, is_dropoff_point) VALUES
|
||||
(caldera_david_id, puerto_caldera_id, 1, 0, true, false),
|
||||
(caldera_david_id, centro_caldera_id, 2, 3, true, true),
|
||||
(caldera_david_id, hospital_david_id, 3, 40, true, true),
|
||||
(caldera_david_id, centro_david_id, 4, 5, true, true),
|
||||
(caldera_david_id, terminal_david_id, 5, 3, false, true);
|
||||
|
||||
-- Insert Route Stops (David > Caldera)
|
||||
INSERT INTO public.route_stops (route_id, stop_id, stop_order, travel_time_minutes, is_pickup_point, is_dropoff_point) VALUES
|
||||
(david_caldera_id, terminal_david_id, 1, 0, true, false),
|
||||
(david_caldera_id, centro_david_id, 2, 3, true, true),
|
||||
(david_caldera_id, hospital_david_id, 3, 5, true, true),
|
||||
(david_caldera_id, centro_caldera_id, 4, 40, true, true),
|
||||
(david_caldera_id, puerto_caldera_id, 5, 3, false, true);
|
||||
|
||||
-- Insert Bus Schedules
|
||||
INSERT INTO public.bus_schedules (route_id, departure_time, frequency_minutes, schedule_type, is_active) VALUES
|
||||
-- Boquete > David (every 30 minutes from 5:00 to 20:00)
|
||||
(boquete_david_id, '05:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '05:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '06:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '06:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '07:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '07:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '08:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '08:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '09:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '09:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '10:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '10:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '11:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '11:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '12:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '12:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '13:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '13:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '14:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '14:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '15:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '15:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '16:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '16:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '17:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '17:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '18:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '18:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '19:00:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '19:30:00', 30, 'weekday', true),
|
||||
(boquete_david_id, '20:00:00', 30, 'weekday', true),
|
||||
|
||||
-- David > Boquete (every 30 minutes from 5:30 to 20:30)
|
||||
(david_boquete_id, '05:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '06:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '06:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '07:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '07:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '08:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '08:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '09:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '09:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '10:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '10:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '11:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '11:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '12:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '12:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '13:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '13:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '14:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '14:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '15:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '15:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '16:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '16:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '17:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '17:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '18:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '18:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '19:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '19:30:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '20:00:00', 30, 'weekday', true),
|
||||
(david_boquete_id, '20:30:00', 30, 'weekday', true),
|
||||
|
||||
-- Palmira routes (every 60 minutes)
|
||||
(palmira_david_id, '06:00:00', 60, 'weekday', true),
|
||||
(palmira_david_id, '07:00:00', 60, 'weekday', true),
|
||||
(palmira_david_id, '08:00:00', 60, 'weekday', true),
|
||||
(palmira_david_id, '12:00:00', 60, 'weekday', true),
|
||||
(palmira_david_id, '17:00:00', 60, 'weekday', true),
|
||||
(palmira_david_id, '18:00:00', 60, 'weekday', true),
|
||||
|
||||
(david_palmira_id, '08:00:00', 60, 'weekday', true),
|
||||
(david_palmira_id, '14:00:00', 60, 'weekday', true),
|
||||
(david_palmira_id, '18:00:00', 60, 'weekday', true),
|
||||
|
||||
-- Caldera routes (every 45 minutes)
|
||||
(caldera_david_id, '05:30:00', 45, 'weekday', true),
|
||||
(caldera_david_id, '06:15:00', 45, 'weekday', true),
|
||||
(caldera_david_id, '07:00:00', 45, 'weekday', true),
|
||||
(caldera_david_id, '07:45:00', 45, 'weekday', true),
|
||||
(caldera_david_id, '16:00:00', 45, 'weekday', true),
|
||||
(caldera_david_id, '17:00:00', 45, 'weekday', true),
|
||||
|
||||
(david_caldera_id, '09:00:00', 45, 'weekday', true),
|
||||
(david_caldera_id, '15:00:00', 45, 'weekday', true),
|
||||
(david_caldera_id, '18:30:00', 45, 'weekday', true);
|
||||
|
||||
RAISE NOTICE 'SIBU Transportation System mock data created successfully!';
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE 'Error creating mock data: %', SQLERRM;
|
||||
END $$;
|
||||
362
old/supabase/migrations/20241019220000_fix_existing_schema.sql
Normal file
362
old/supabase/migrations/20241019220000_fix_existing_schema.sql
Normal file
@ -0,0 +1,362 @@
|
||||
-- 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 $$;
|
||||
77
old/supabase/migrations/20250126225029_taxi_module.sql
Normal file
77
old/supabase/migrations/20250126225029_taxi_module.sql
Normal file
@ -0,0 +1,77 @@
|
||||
-- Location: supabase/migrations/20250126225029_taxi_module.sql
|
||||
-- Schema Analysis: Existing transportation system with routes, stops, timetable
|
||||
-- Integration Type: NEW_MODULE - Adding taxi directory functionality
|
||||
-- Dependencies: No direct references to existing tables (standalone module)
|
||||
|
||||
-- Create tables for taxi directory functionality
|
||||
CREATE TABLE public.taxis (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
phone TEXT NOT NULL,
|
||||
district TEXT NOT NULL,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create favorites table (conditional user relationship)
|
||||
CREATE TABLE public.favorite_taxis (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL,
|
||||
taxi_id UUID REFERENCES public.taxis(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Essential indexes for performance
|
||||
CREATE INDEX idx_taxis_district ON public.taxis(district);
|
||||
CREATE INDEX idx_taxis_is_active ON public.taxis(is_active);
|
||||
CREATE INDEX idx_taxis_name ON public.taxis(name);
|
||||
CREATE INDEX idx_favorite_taxis_user_id ON public.favorite_taxis(user_id);
|
||||
CREATE INDEX idx_favorite_taxis_taxi_id ON public.favorite_taxis(taxi_id);
|
||||
|
||||
-- Unique constraint for user-taxi favorites
|
||||
CREATE UNIQUE INDEX idx_favorite_taxis_unique ON public.favorite_taxis(user_id, taxi_id);
|
||||
|
||||
-- Enable RLS for security
|
||||
ALTER TABLE public.taxis ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.favorite_taxis ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS Policies using Pattern 4: Public Read, Private Write
|
||||
CREATE POLICY "public_can_read_taxis"
|
||||
ON public.taxis
|
||||
FOR SELECT
|
||||
TO public
|
||||
USING (true);
|
||||
|
||||
-- Pattern 2: Simple User Ownership for favorites
|
||||
CREATE POLICY "users_manage_own_favorite_taxis"
|
||||
ON public.favorite_taxis
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid())
|
||||
WITH CHECK (user_id = auth.uid());
|
||||
|
||||
-- Allow anonymous users to read favorites (for local storage fallback)
|
||||
CREATE POLICY "public_can_read_favorite_taxis"
|
||||
ON public.favorite_taxis
|
||||
FOR SELECT
|
||||
TO public
|
||||
USING (true);
|
||||
|
||||
-- Sample taxi data for different districts
|
||||
DO $$
|
||||
BEGIN
|
||||
INSERT INTO public.taxis (name, phone, district, is_active) VALUES
|
||||
('Taxi Central Boquete', '+507 720-1234', 'Boquete', true),
|
||||
('Taxi Flores David', '+507 775-5678', 'David', true),
|
||||
('Taxi Montaña Verde', '+507 720-9101', 'Boquete', true),
|
||||
('Taxi Ciudad David', '+507 775-1122', 'David', true),
|
||||
('Taxi Volcán Express', '+507 771-3344', 'Volcán', true),
|
||||
('Taxi Dolega Rápido', '+507 721-5566', 'Dolega', true),
|
||||
('Taxi Bugaba Seguro', '+507 772-7788', 'Bugaba', true),
|
||||
('Taxi Renacimiento', '+507 773-9900', 'Renacimiento', true),
|
||||
('Taxi Alanje Centro', '+507 774-2233', 'Alanje', true),
|
||||
('Taxi Boquerón', '+507 775-4455', 'Boquerón', true),
|
||||
('Taxi Los Naranjos', '+507 720-6677', 'Boquete', true),
|
||||
('Taxi San Lorenzo', '+507 775-8899', 'David', true);
|
||||
END $$;
|
||||
@ -0,0 +1,97 @@
|
||||
-- Location: supabase/migrations/20251031165808_enhance_taxi_directory.sql
|
||||
-- Schema Analysis: Existing taxis table with district column, needs shift column and terminology update
|
||||
-- Integration Type: PARTIAL_EXISTS - Extending existing taxi module
|
||||
-- Dependencies: Existing taxis and favorite_taxis tables
|
||||
|
||||
-- 1. Create shift enum type for taxi shifts
|
||||
CREATE TYPE public.taxi_shift AS ENUM ('day', 'evening', 'night');
|
||||
|
||||
-- 2. Add shift column to existing taxis table
|
||||
ALTER TABLE public.taxis
|
||||
ADD COLUMN shift public.taxi_shift DEFAULT 'day'::public.taxi_shift;
|
||||
|
||||
-- 3. Rename district column to corregimiento for terminology consistency
|
||||
ALTER TABLE public.taxis
|
||||
RENAME COLUMN district TO corregimiento;
|
||||
|
||||
-- 4. Update existing indexes to match new column names
|
||||
DROP INDEX IF EXISTS idx_taxis_district;
|
||||
CREATE INDEX idx_taxis_corregimiento ON public.taxis(corregimiento);
|
||||
CREATE INDEX idx_taxis_shift ON public.taxis(shift);
|
||||
|
||||
-- 5. Add updated sample data with new structure
|
||||
DO $$
|
||||
DECLARE
|
||||
taxi1_id UUID := gen_random_uuid();
|
||||
taxi2_id UUID := gen_random_uuid();
|
||||
taxi3_id UUID := gen_random_uuid();
|
||||
taxi4_id UUID := gen_random_uuid();
|
||||
taxi5_id UUID := gen_random_uuid();
|
||||
taxi6_id UUID := gen_random_uuid();
|
||||
BEGIN
|
||||
-- Remove existing sample data first
|
||||
DELETE FROM public.taxis WHERE phone IN ('+507 720-1234', '+507 775-5678');
|
||||
|
||||
-- Insert comprehensive sample data with corregimiento and shift
|
||||
INSERT INTO public.taxis (id, name, phone, corregimiento, shift, is_active, created_at, updated_at) VALUES
|
||||
(taxi1_id, 'Taxi Central Boquete', '+507 720-1234', 'Boquete', 'day'::public.taxi_shift, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(taxi2_id, 'Taxi Flores David', '+507 775-5678', 'David', 'evening'::public.taxi_shift, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(taxi3_id, 'Taxi Noctuno David', '+507 776-9999', 'David', 'night'::public.taxi_shift, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(taxi4_id, 'Taxi Diurno Chiriquí', '+507 721-4567', 'Chiriquí', 'day'::public.taxi_shift, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(taxi5_id, 'Taxi Tarde Boquete', '+507 722-8890', 'Boquete', 'evening'::public.taxi_shift, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(taxi6_id, 'Taxi Madrugada Chiriquí', '+507 723-1122', 'Chiriquí', 'night'::public.taxi_shift, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
|
||||
|
||||
EXCEPTION
|
||||
WHEN unique_violation THEN
|
||||
RAISE NOTICE 'Some taxi data already exists, skipping duplicates';
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE 'Error inserting taxi data: %', SQLERRM;
|
||||
END $$;
|
||||
|
||||
-- 6. Create helper function for getting distinct corregimientos
|
||||
CREATE OR REPLACE FUNCTION public.get_distinct_corregimientos()
|
||||
RETURNS TABLE(corregimiento TEXT)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT DISTINCT t.corregimiento::TEXT
|
||||
FROM public.taxis t
|
||||
WHERE t.is_active = true
|
||||
ORDER BY t.corregimiento::TEXT;
|
||||
$$;
|
||||
|
||||
-- 7. Create helper function for filtering taxis by corregimiento and shift
|
||||
CREATE OR REPLACE FUNCTION public.filter_taxis_by_criteria(
|
||||
selected_corregimiento TEXT DEFAULT NULL,
|
||||
selected_shift TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS TABLE(
|
||||
id UUID,
|
||||
name TEXT,
|
||||
phone TEXT,
|
||||
corregimiento TEXT,
|
||||
shift TEXT,
|
||||
is_active BOOLEAN,
|
||||
created_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ
|
||||
)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT
|
||||
t.id,
|
||||
t.name,
|
||||
t.phone,
|
||||
t.corregimiento::TEXT,
|
||||
t.shift::TEXT,
|
||||
t.is_active,
|
||||
t.created_at,
|
||||
t.updated_at
|
||||
FROM public.taxis t
|
||||
WHERE t.is_active = true
|
||||
AND (selected_corregimiento IS NULL OR t.corregimiento = selected_corregimiento)
|
||||
AND (selected_shift IS NULL OR t.shift::TEXT = selected_shift)
|
||||
ORDER BY t.name ASC;
|
||||
$$;
|
||||
@ -0,0 +1,119 @@
|
||||
-- Schema Analysis: Existing coupons table with basic structure
|
||||
-- Integration Type: extension - adding missing columns for enhanced functionality
|
||||
-- Dependencies: existing coupons table
|
||||
|
||||
-- Add missing columns to existing coupons table
|
||||
ALTER TABLE public.coupons
|
||||
ADD COLUMN image_url TEXT,
|
||||
ADD COLUMN category TEXT,
|
||||
ADD COLUMN is_active BOOLEAN DEFAULT true,
|
||||
ADD COLUMN created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- Create enum for category validation
|
||||
CREATE TYPE public.coupon_category AS ENUM (
|
||||
'restaurantes',
|
||||
'tiendas',
|
||||
'servicios',
|
||||
'entretenimiento',
|
||||
'salud',
|
||||
'belleza'
|
||||
);
|
||||
|
||||
-- Update category column to use enum with proper constraint
|
||||
ALTER TABLE public.coupons
|
||||
ALTER COLUMN category TYPE public.coupon_category USING category::public.coupon_category;
|
||||
|
||||
-- Add indexes for performance
|
||||
CREATE INDEX idx_coupons_category ON public.coupons(category);
|
||||
CREATE INDEX idx_coupons_is_active ON public.coupons(is_active);
|
||||
CREATE INDEX idx_coupons_created_at ON public.coupons(created_at);
|
||||
CREATE INDEX idx_coupons_valid_until ON public.coupons(valid_until);
|
||||
|
||||
-- Enable RLS if not already enabled
|
||||
ALTER TABLE public.coupons ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS Policy: Public read access for active and valid coupons
|
||||
CREATE POLICY "public_can_read_active_coupons"
|
||||
ON public.coupons
|
||||
FOR SELECT
|
||||
TO public
|
||||
USING (
|
||||
is_active = true
|
||||
AND (valid_until IS NULL OR valid_until >= CURRENT_DATE)
|
||||
);
|
||||
|
||||
-- Mock data with proper Spanish categories
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Insert sample coupons with all required fields
|
||||
INSERT INTO public.coupons (
|
||||
business_name, title, description, valid_until,
|
||||
image_url, category, is_active, created_at
|
||||
) VALUES
|
||||
(
|
||||
'Restaurante El Buen Sabor',
|
||||
'Descuento del 20%',
|
||||
'Descuento válido en toda la carta. No aplica con otras promociones.',
|
||||
'2025-01-15'::date,
|
||||
'https://images.unsplash.com/photo-1647695822638-a40e238ddc39',
|
||||
'restaurantes'::public.coupon_category,
|
||||
true,
|
||||
'2024-10-15 10:00:00'::timestamptz
|
||||
),
|
||||
(
|
||||
'Tienda La Moderna',
|
||||
'Descuento de $10',
|
||||
'Descuento en compras mayores a $50. Válido en toda la tienda.',
|
||||
'2024-12-31'::date,
|
||||
'https://images.unsplash.com/photo-1599190118801-8e4463c6a993',
|
||||
'tiendas'::public.coupon_category,
|
||||
true,
|
||||
'2024-10-10 14:30:00'::timestamptz
|
||||
),
|
||||
(
|
||||
'Spa Relajación Total',
|
||||
'Descuento del 30% en masajes',
|
||||
'Descuento en todos los tipos de masajes. Incluye aromaterapia.',
|
||||
'2024-11-30'::date,
|
||||
'https://images.unsplash.com/photo-1706795033849-7ca391f007c5',
|
||||
'belleza'::public.coupon_category,
|
||||
true,
|
||||
'2024-10-05 09:15:00'::timestamptz
|
||||
),
|
||||
(
|
||||
'Café Montaña',
|
||||
'Café gratis con postre',
|
||||
'Café americano gratis con la compra de cualquier postre.',
|
||||
'2024-10-25'::date,
|
||||
'https://images.unsplash.com/photo-1587299103717-ca5b2db07f47',
|
||||
'restaurantes'::public.coupon_category,
|
||||
true,
|
||||
'2024-10-12 11:20:00'::timestamptz
|
||||
),
|
||||
(
|
||||
'Farmacia San José',
|
||||
'Descuento del 15% en medicamentos',
|
||||
'Descuento en medicamentos de venta libre y productos de cuidado personal.',
|
||||
'2024-12-15'::date,
|
||||
'https://images.unsplash.com/photo-1701117553039-d39c1ee3f3cd',
|
||||
'salud'::public.coupon_category,
|
||||
true,
|
||||
'2024-10-08 13:45:00'::timestamptz
|
||||
),
|
||||
(
|
||||
'Cine Boquete',
|
||||
'2x1 en entradas',
|
||||
'Dos entradas por el precio de una. Válido de lunes a miércoles.',
|
||||
'2024-11-20'::date,
|
||||
'https://images.unsplash.com/photo-1669697243629-8c8a5d6dad9e',
|
||||
'entretenimiento'::public.coupon_category,
|
||||
true,
|
||||
'2024-10-01 08:30:00'::timestamptz
|
||||
);
|
||||
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN
|
||||
RAISE NOTICE 'Objects already exist, skipping creation';
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE 'Migration error: %', SQLERRM;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user