-- 004_trips_for_range_rpc.sql -- Multi-day variant of trips_for_day. The 14-day cap is enforced here in SQL -- so PostgREST can't be tricked into asking for an absurd range via direct RPC. CREATE OR REPLACE FUNCTION public.trips_for_range( p_start date, p_end date, p_cost_centres text[] DEFAULT NULL ) RETURNS TABLE ( trip_id bigint, imei text, vehicle_name text, vehicle_number text, cost_centre text, start_time timestamptz, end_time timestamptz, distance_km numeric, path_geojson json, timestamps_rel int[] ) LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public, tracksolid AS $$ BEGIN IF p_start IS NULL OR p_end IS NULL OR p_end < p_start THEN RAISE EXCEPTION 'Invalid date range: p_start=%, p_end=%', p_start, p_end; END IF; IF (p_end - p_start) > 13 THEN RAISE EXCEPTION 'Range too large: max 14 days, got %', (p_end - p_start + 1); END IF; RETURN QUERY WITH range_trips AS ( SELECT v.* FROM public.trips_viz_v1 v WHERE v.start_time::date BETWEEN p_start AND p_end AND (p_cost_centres IS NULL OR v.cost_centre = ANY(p_cost_centres)) ) SELECT rt.trip_id, rt.imei, rt.vehicle_name, rt.vehicle_number, rt.cost_centre, rt.start_time, rt.end_time, rt.distance_km, ST_AsGeoJSON(ST_MakeLine(ph.geom ORDER BY ph.gps_time))::json AS path_geojson, array_agg( EXTRACT(EPOCH FROM ph.gps_time - rt.start_time)::int ORDER BY ph.gps_time ) AS timestamps_rel FROM range_trips rt JOIN tracksolid.position_history ph ON ph.imei = rt.imei AND ph.gps_time BETWEEN rt.start_time AND rt.end_time AND ph.geom IS NOT NULL GROUP BY rt.trip_id, rt.imei, rt.vehicle_name, rt.vehicle_number, rt.cost_centre, rt.start_time, rt.end_time, rt.distance_km HAVING count(ph.geom) >= 2; END; $$; GRANT EXECUTE ON FUNCTION public.trips_for_range(date, date, text[]) TO viz_anon;