-- ============================================================================= -- MC Cars — Postgres bootstrap. -- Creates the Supabase service roles that GoTrue / PostgREST / Storage expect, -- installs required extensions, and sets up app schema + RLS + storage policies -- + admin seed + sample fleet. -- Runs once on first `docker compose up`. -- ============================================================================= -- The Postgres connection is authenticated as role `supabase_admin` (POSTGRES_USER), -- so we need it to be a superuser for the rest of this script to work. That is -- handled by POSTGRES_USER=supabase_admin in compose. create extension if not exists pgcrypto; create extension if not exists "uuid-ossp"; -- Make the password from the shell wrapper available to the DO block below. select set_config('mccars.pg_password', :'pg_password', false); -- ----------------------------------------------------------------------------- -- Supabase service roles -- ----------------------------------------------------------------------------- do $roles$ declare pw text := current_setting('mccars.pg_password', true); begin if pw is null or pw = '' then raise exception 'mccars.pg_password is not set'; end if; -- anon (used by PostgREST for unauthenticated requests) if not exists (select 1 from pg_roles where rolname = 'anon') then execute 'create role anon nologin noinherit'; end if; -- authenticated (role the JWT "authenticated" maps to) if not exists (select 1 from pg_roles where rolname = 'authenticated') then execute 'create role authenticated nologin noinherit'; end if; -- service_role (full access; never exposed to the browser) if not exists (select 1 from pg_roles where rolname = 'service_role') then execute 'create role service_role nologin noinherit bypassrls'; end if; -- authenticator (PostgREST logs in as this and switches role per JWT) if not exists (select 1 from pg_roles where rolname = 'authenticator') then execute format('create role authenticator login noinherit password %L', pw); else execute format('alter role authenticator with login noinherit password %L', pw); end if; -- supabase_auth_admin (GoTrue logs in with this, needs schema auth) if not exists (select 1 from pg_roles where rolname = 'supabase_auth_admin') then execute format('create role supabase_auth_admin login createrole password %L', pw); else execute format('alter role supabase_auth_admin with login createrole password %L', pw); end if; -- supabase_storage_admin (Storage service logs in with this) if not exists (select 1 from pg_roles where rolname = 'supabase_storage_admin') then execute format('create role supabase_storage_admin login createrole password %L', pw); else execute format('alter role supabase_storage_admin with login createrole password %L', pw); end if; -- Let authenticator impersonate app roles. execute 'grant anon, authenticated, service_role to authenticator'; -- Let storage_admin impersonate app roles (needed for RLS on storage.objects). execute 'grant anon, authenticated, service_role to supabase_storage_admin'; end $roles$; -- ----------------------------------------------------------------------------- -- Schemas for GoTrue / Storage (they create their own objects, but own schema) -- ----------------------------------------------------------------------------- create schema if not exists auth authorization supabase_auth_admin; create schema if not exists storage authorization supabase_storage_admin; create schema if not exists _realtime authorization postgres; grant usage on schema auth to service_role, authenticated, anon; grant usage on schema storage to service_role, authenticated, anon; grant all on schema _realtime to postgres; -- Allow service admins to create/alter objects for their own migrations. grant create, connect on database postgres to supabase_auth_admin, supabase_storage_admin; grant all on schema auth to supabase_auth_admin; grant all on schema storage to supabase_storage_admin; -- Storage-api's migration process expects to see public schema too. grant usage, create on schema public to supabase_storage_admin, supabase_auth_admin; -- ----------------------------------------------------------------------------- -- Application schema: public.vehicles -- ----------------------------------------------------------------------------- create table if not exists public.vehicles ( id uuid primary key default gen_random_uuid(), brand text not null, model text not null, power_hp integer not null default 0, top_speed_kmh integer not null default 0, acceleration text not null default '', seats integer not null default 2, daily_price_eur integer not null default 0, location text not null default 'Steiermark (TBD)', description_de text not null default '', description_en text not null default '', photo_url text not null default '', photo_path text, sort_order integer not null default 100, is_active boolean not null default true, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create index if not exists vehicles_active_sort_idx on public.vehicles (is_active, sort_order); create or replace function public.tg_touch_updated_at() returns trigger language plpgsql as $$ begin new.updated_at := now(); return new; end; $$; drop trigger if exists vehicles_touch on public.vehicles; create trigger vehicles_touch before update on public.vehicles for each row execute function public.tg_touch_updated_at(); alter table public.vehicles enable row level security; drop policy if exists "vehicles_public_read" on public.vehicles; drop policy if exists "vehicles_admin_read_all" on public.vehicles; drop policy if exists "vehicles_admin_insert" on public.vehicles; drop policy if exists "vehicles_admin_update" on public.vehicles; drop policy if exists "vehicles_admin_delete" on public.vehicles; create policy "vehicles_public_read" on public.vehicles for select using (is_active = true); create policy "vehicles_admin_read_all" on public.vehicles for select to authenticated using (true); create policy "vehicles_admin_insert" on public.vehicles for insert to authenticated with check (true); create policy "vehicles_admin_update" on public.vehicles for update to authenticated using (true) with check (true); create policy "vehicles_admin_delete" on public.vehicles for delete to authenticated using (true); grant select on public.vehicles to anon, authenticated; grant insert, update, delete on public.vehicles to authenticated; grant all on public.vehicles to service_role; -- ----------------------------------------------------------------------------- -- Seed sample fleet (admin can replace any row / photo via the UI) -- ----------------------------------------------------------------------------- insert into public.vehicles (brand, model, power_hp, top_speed_kmh, acceleration, seats, daily_price_eur, location, description_de, description_en, photo_url, sort_order) values ('Ferrari','296 GTB', 830, 330, '2.9s', 2, 850, 'Steiermark (TBD)', 'V6-Hybrid mit 830 PS, atemberaubendes Design und kompromisslose Performance auf Strasse und Rennstrecke.', 'V6 hybrid with 830 hp, breathtaking design and uncompromising performance on road and track.', '/images/ferrari-main-car.png', 10) on conflict do nothing;