diff --git a/supabase/migrations/208_stops_date_to_date.sql b/supabase/migrations/208_stops_date_to_date.sql new file mode 100644 index 0000000..415bf1b --- /dev/null +++ b/supabase/migrations/208_stops_date_to_date.sql @@ -0,0 +1,73 @@ +-- Migration 208: Change stops.date from TIMESTAMPTZ to DATE +-- +-- Why +-- --- +-- The `stops.date` column was created as TIMESTAMPTZ but is used throughout +-- the app as a calendar date (no time-of-day component). The mismatch +-- forces every client consumer to do `new Date(s.date + "T00:00:00")` to +-- coerce the timestamp into a local-midnight Date — and that hack silently +-- produces an Invalid Date when Supabase returns a `2026-06-15 00:00:00+00` +-- style string, leaving the Stops & Routes calendar empty. +-- +-- Storing the column as DATE makes Supabase return a clean `YYYY-MM-DD` +-- string, which the existing `+ "T00:00:00"` parsing handles correctly. +-- +-- The `time` (TEXT, "HH:MM") and `cutoff_time` (TIMESTAMPTZ) columns are +-- unchanged — they legitimately carry time-of-day information. +-- +-- Affected RPC +-- ------------- +-- `get_public_stops_for_brand` declares `date TIMESTAMPTZ` in its +-- RETURNS TABLE. Recreate it with `date DATE` so the return type matches +-- the underlying column. + +-- 1. Drop the existing RPC so we can recreate it with a new return type. +-- PostgreSQL refuses CREATE OR REPLACE when the OUT-parameter row type +-- changes (error 42P13). +DROP FUNCTION IF EXISTS get_public_stops_for_brand(TEXT); + +-- 2. Convert the column. USING date::DATE truncates the time component, +-- which is what we want — all existing rows are stored at 00:00 anyway. +ALTER TABLE public.stops + ALTER COLUMN date TYPE DATE USING date::DATE; + +-- 3. Recreate the public RPC with the corrected return type. +CREATE OR REPLACE FUNCTION get_public_stops_for_brand(p_brand_slug TEXT) +RETURNS TABLE ( + id UUID, + city TEXT, + state TEXT, + date DATE, + "time" TEXT, + location TEXT, + address TEXT, + slug TEXT, + cutoff_time TIMESTAMPTZ +) +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +BEGIN + RETURN QUERY + SELECT + s.id, + s.city, + s.state, + s.date, + s."time", + s.location, + s.address, + s.slug, + s.cutoff_time + FROM stops s + INNER JOIN brands b ON s.brand_id = b.id + WHERE s.active = true + AND b.slug = p_brand_slug + ORDER BY s.date ASC; +END; +$$; + +GRANT EXECUTE ON FUNCTION get_public_stops_for_brand(TEXT) TO anon; +GRANT EXECUTE ON FUNCTION get_public_stops_for_brand(TEXT) TO authenticated; +GRANT EXECUTE ON FUNCTION get_public_stops_for_brand(TEXT) TO service_role;