e85b319c93
Co-authored-by: Copilot <copilot@github.com>
177 lines
7.7 KiB
PL/PgSQL
177 lines
7.7 KiB
PL/PgSQL
-- =============================================================================
|
|
-- 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;
|