49a9900d15
* New 'locations' table for reusable venues (Tractor Supply, Boot Barn, etc.) — each stop now links via stops.location_id while keeping the denormalized location text for backwards compat. * 6 SECURITY DEFINER RPCs: admin_create_location, admin_create_locations_batch, admin_update_location, admin_delete_location, admin_attach_location_to_stop, get_locations_for_brand. Plus admin_list_locations for the admin tab. * Backfill: 26 unique venues extracted from the 269 Tuxedo stops, all linked. * Stops & Routes page now has tabs (Stops | Locations) via ?tab= query param. Locations tab has full CRUD UI: search, status filter, pagination, edit, delete, add-venue modal. * StopsHeaderActions now tab-aware — Upload Schedule / Add Stop only show on Stops tab; Locations tab has its own Add Venue button inline.
20 lines
920 B
SQL
20 lines
920 B
SQL
-- Migration 204: Add permissive read policy for locations
|
|
--
|
|
-- Why: The admin Stops & Routes page reads stops via the Supabase JS client
|
|
-- (anon key + RLS). The `stops` table has a `Public read stops` policy with
|
|
-- qual=true, which lets the anon role see all non-deleted stops — that's how
|
|
-- the page works in dev mode (no Supabase auth). The admin_locations tab needs
|
|
-- the same behavior so the server component can use the same pattern:
|
|
-- supabase.from("locations").select(...)
|
|
--
|
|
-- Brand scoping is enforced at the application layer in the server component
|
|
-- (eq("brand_id", adminUser.brand_id)), matching how stops are scoped.
|
|
--
|
|
-- Brand-scoped public reads (storefront pages) go through get_locations_for_brand
|
|
-- RPC, which filters active=true.
|
|
|
|
DROP POLICY IF EXISTS "Public read locations" ON public.locations;
|
|
CREATE POLICY "Public read locations" ON public.locations
|
|
FOR SELECT
|
|
USING (true);
|