feat: Add Supabase configuration and migrations for MC Cars application
- Create Kong declarative configuration for routing and authentication. - Implement initialization script to set up the database. - Add SQL migration for initializing roles, schemas, and seeding vehicle data. - Create leads and customers tables with appropriate policies and functions for CRM. - Seed admin user and configure storage bucket with RLS policies.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
psql -v ON_ERROR_STOP=1 -v pg_password="$POSTGRES_PASSWORD" --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f /sql/01-init.sql
|
||||
@@ -0,0 +1,198 @@
|
||||
-- =============================================================================
|
||||
-- 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';
|
||||
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
|
||||
('Porsche','911 GT3', 510, 318, '3.4s', 2, 890, 'Steiermark (TBD)',
|
||||
'Puristischer Hochdrehzahl-Saugmotor und kompromissloser Motorsport-Charakter.',
|
||||
'Pure high-revving naturally aspirated engine with uncompromising motorsport character.',
|
||||
'https://images.unsplash.com/photo-1611821064430-0d40291d0f0b?auto=format&fit=crop&w=1400&q=80',
|
||||
10),
|
||||
('Lamborghini','Huracan EVO', 640, 325, '2.9s', 2, 990, 'Steiermark (TBD)',
|
||||
'V10 mit 640 PS, scharfes Design und kompromisslose Performance auf Strasse und Rennstrecke.',
|
||||
'V10 with 640 hp, sharp design and uncompromising performance on road and track.',
|
||||
'https://images.unsplash.com/photo-1544636331-e26879cd4d9b?auto=format&fit=crop&w=1400&q=80',
|
||||
20),
|
||||
('Audi','RS6 Performance', 630, 305, '3.4s', 5, 540, 'Steiermark (TBD)',
|
||||
'Alltagstauglicher Kombi mit brutaler V8-Biturbo-Power und Allradantrieb.',
|
||||
'Everyday-ready estate with brutal twin-turbo V8 power and quattro AWD.',
|
||||
'https://images.unsplash.com/photo-1606664515524-ed2f786a0bd6?auto=format&fit=crop&w=1400&q=80',
|
||||
30),
|
||||
('BMW','M4 Competition', 530, 290, '3.5s', 4, 430, 'Steiermark (TBD)',
|
||||
'Reihensechszylinder-Biturbo mit praeziser Lenkung und sportlichem Fahrwerk.',
|
||||
'Twin-turbo inline-six with precise steering and sporty chassis.',
|
||||
'https://images.unsplash.com/photo-1555215695-3004980ad54e?auto=format&fit=crop&w=1400&q=80',
|
||||
40),
|
||||
('Nissan','GT-R R35', 570, 315, '2.8s', 4, 510, 'Steiermark (TBD)',
|
||||
'Ikonischer Allrad-Supersportler mit Twin-Turbo V6 und brutalem Antritt.',
|
||||
'Iconic AWD supercar with twin-turbo V6 and ferocious launch.',
|
||||
'https://images.unsplash.com/photo-1626668893632-6f3a4466d22f?auto=format&fit=crop&w=1400&q=80',
|
||||
50),
|
||||
('Mercedes-AMG','G63', 585, 220, '4.5s', 5, 620, 'Steiermark (TBD)',
|
||||
'Legendaere G-Klasse mit V8-Biturbo-Performance und unverkennbarem Design.',
|
||||
'Legendary G-Class with V8 biturbo performance and unmistakable design.',
|
||||
'https://images.unsplash.com/photo-1606611013016-969c19ba27bb?auto=format&fit=crop&w=1400&q=80',
|
||||
60)
|
||||
on conflict do nothing;
|
||||
@@ -0,0 +1,252 @@
|
||||
-- =============================================================================
|
||||
-- MC Cars - CRM schema: leads + customers + realtime publication.
|
||||
-- Runs from post-init AFTER auth/storage schemas exist.
|
||||
-- Idempotent.
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- LEADS: landing-page form submissions (anon may INSERT, only authenticated
|
||||
-- may SELECT/UPDATE).
|
||||
-- -----------------------------------------------------------------------------
|
||||
create table if not exists public.leads (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
name text not null,
|
||||
email text not null,
|
||||
phone text not null default '',
|
||||
vehicle_id uuid references public.vehicles(id) on delete set null,
|
||||
vehicle_label text not null default '', -- denormalized (brand + model) at submit time
|
||||
date_from date,
|
||||
date_to date,
|
||||
message text not null default '',
|
||||
status text not null default 'new'
|
||||
check (status in ('new','qualified','disqualified')),
|
||||
is_active boolean not null default true, -- false once qualified/disqualified
|
||||
admin_notes text not null default '',
|
||||
source text not null default 'website',
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
qualified_at timestamptz,
|
||||
qualified_by uuid references auth.users(id) on delete set null
|
||||
);
|
||||
|
||||
create index if not exists leads_active_created_idx on public.leads (is_active, created_at desc);
|
||||
create index if not exists leads_status_idx on public.leads (status);
|
||||
create index if not exists leads_vehicle_idx on public.leads (vehicle_id);
|
||||
|
||||
drop trigger if exists leads_touch on public.leads;
|
||||
create trigger leads_touch
|
||||
before update on public.leads
|
||||
for each row execute function public.tg_touch_updated_at();
|
||||
|
||||
alter table public.leads enable row level security;
|
||||
|
||||
drop policy if exists "leads_anon_insert" on public.leads;
|
||||
drop policy if exists "leads_admin_select_all" on public.leads;
|
||||
drop policy if exists "leads_admin_update" on public.leads;
|
||||
drop policy if exists "leads_admin_delete" on public.leads;
|
||||
|
||||
-- Anonymous visitors may submit the booking form, but NOT read anything back.
|
||||
create policy "leads_anon_insert"
|
||||
on public.leads for insert to anon
|
||||
with check (true);
|
||||
|
||||
create policy "leads_admin_select_all"
|
||||
on public.leads for select to authenticated
|
||||
using (true);
|
||||
|
||||
create policy "leads_admin_update"
|
||||
on public.leads for update to authenticated
|
||||
using (true) with check (true);
|
||||
|
||||
create policy "leads_admin_delete"
|
||||
on public.leads for delete to authenticated
|
||||
using (true);
|
||||
|
||||
grant insert on public.leads to anon;
|
||||
grant select, insert, update, delete on public.leads to authenticated;
|
||||
grant all on public.leads to service_role;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- CUSTOMERS: created ONLY by qualifying a lead from the admin panel.
|
||||
-- Keeps a hard FK back to the originating lead for audit.
|
||||
-- -----------------------------------------------------------------------------
|
||||
create table if not exists public.customers (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
lead_id uuid not null references public.leads(id) on delete restrict,
|
||||
name text not null,
|
||||
email text not null,
|
||||
phone text not null default '',
|
||||
first_contacted_at timestamptz not null default now(),
|
||||
notes text not null default '',
|
||||
status text not null default 'active'
|
||||
check (status in ('active','inactive')),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
created_by uuid references auth.users(id) on delete set null
|
||||
);
|
||||
|
||||
create unique index if not exists customers_lead_unique on public.customers (lead_id);
|
||||
create index if not exists customers_email_idx on public.customers (email);
|
||||
|
||||
drop trigger if exists customers_touch on public.customers;
|
||||
create trigger customers_touch
|
||||
before update on public.customers
|
||||
for each row execute function public.tg_touch_updated_at();
|
||||
|
||||
alter table public.customers enable row level security;
|
||||
|
||||
drop policy if exists "customers_admin_select" on public.customers;
|
||||
drop policy if exists "customers_admin_insert" on public.customers;
|
||||
drop policy if exists "customers_admin_update" on public.customers;
|
||||
drop policy if exists "customers_admin_delete" on public.customers;
|
||||
|
||||
create policy "customers_admin_select"
|
||||
on public.customers for select to authenticated using (true);
|
||||
|
||||
create policy "customers_admin_insert"
|
||||
on public.customers for insert to authenticated with check (true);
|
||||
|
||||
create policy "customers_admin_update"
|
||||
on public.customers for update to authenticated using (true) with check (true);
|
||||
|
||||
create policy "customers_admin_delete"
|
||||
on public.customers for delete to authenticated using (true);
|
||||
|
||||
grant select, insert, update, delete on public.customers to authenticated;
|
||||
grant all on public.customers to service_role;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- qualify_lead(lead_id, notes)
|
||||
-- Transactional: marks the lead qualified+inactive and creates the matching
|
||||
-- customer row. Called via PostgREST RPC by the admin UI.
|
||||
-- -----------------------------------------------------------------------------
|
||||
create or replace function public.qualify_lead(p_lead_id uuid, p_notes text default '')
|
||||
returns public.customers
|
||||
language plpgsql
|
||||
security invoker
|
||||
as $$
|
||||
declare
|
||||
v_lead public.leads;
|
||||
v_customer public.customers;
|
||||
v_user uuid := auth.uid();
|
||||
begin
|
||||
select * into v_lead from public.leads where id = p_lead_id for update;
|
||||
if not found then
|
||||
raise exception 'lead % not found', p_lead_id;
|
||||
end if;
|
||||
|
||||
if v_lead.status = 'qualified' then
|
||||
select * into v_customer from public.customers where lead_id = v_lead.id;
|
||||
return v_customer;
|
||||
end if;
|
||||
|
||||
update public.leads
|
||||
set status = 'qualified',
|
||||
is_active = false,
|
||||
qualified_at = now(),
|
||||
qualified_by = v_user,
|
||||
admin_notes = coalesce(nullif(p_notes, ''), admin_notes)
|
||||
where id = v_lead.id;
|
||||
|
||||
insert into public.customers (lead_id, name, email, phone, notes, created_by)
|
||||
values (v_lead.id, v_lead.name, v_lead.email, v_lead.phone, coalesce(p_notes,''), v_user)
|
||||
returning * into v_customer;
|
||||
|
||||
return v_customer;
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public.qualify_lead(uuid, text) from public;
|
||||
grant execute on function public.qualify_lead(uuid, text) to authenticated;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- disqualify_lead(lead_id, notes)
|
||||
-- -----------------------------------------------------------------------------
|
||||
create or replace function public.disqualify_lead(p_lead_id uuid, p_notes text default '')
|
||||
returns public.leads
|
||||
language plpgsql
|
||||
security invoker
|
||||
as $$
|
||||
declare
|
||||
v_lead public.leads;
|
||||
begin
|
||||
update public.leads
|
||||
set status = 'disqualified',
|
||||
is_active = false,
|
||||
admin_notes = coalesce(nullif(p_notes, ''), admin_notes)
|
||||
where id = p_lead_id
|
||||
returning * into v_lead;
|
||||
|
||||
if not found then
|
||||
raise exception 'lead % not found', p_lead_id;
|
||||
end if;
|
||||
|
||||
return v_lead;
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public.disqualify_lead(uuid, text) from public;
|
||||
grant execute on function public.disqualify_lead(uuid, text) to authenticated;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- reopen_lead(lead_id): moves a lead back to active/new (admin correction).
|
||||
-- -----------------------------------------------------------------------------
|
||||
create or replace function public.reopen_lead(p_lead_id uuid)
|
||||
returns public.leads
|
||||
language plpgsql
|
||||
security invoker
|
||||
as $$
|
||||
declare
|
||||
v_lead public.leads;
|
||||
begin
|
||||
update public.leads
|
||||
set status = 'new',
|
||||
is_active = true,
|
||||
qualified_at = null,
|
||||
qualified_by = null
|
||||
where id = p_lead_id
|
||||
returning * into v_lead;
|
||||
|
||||
-- If a customer was spawned from this lead, remove it.
|
||||
delete from public.customers where lead_id = p_lead_id;
|
||||
|
||||
return v_lead;
|
||||
end;
|
||||
$$;
|
||||
|
||||
revoke all on function public.reopen_lead(uuid) from public;
|
||||
grant execute on function public.reopen_lead(uuid) to authenticated;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Realtime: publish leads + customers so the admin UI sees live inserts/updates.
|
||||
-- -----------------------------------------------------------------------------
|
||||
do $$
|
||||
declare t text;
|
||||
begin
|
||||
if not exists (select 1 from pg_publication where pubname = 'supabase_realtime') then
|
||||
create publication supabase_realtime;
|
||||
end if;
|
||||
|
||||
foreach t in array array['leads','customers','vehicles'] loop
|
||||
if not exists (
|
||||
select 1 from pg_publication_tables
|
||||
where pubname='supabase_realtime' and schemaname='public' and tablename=t
|
||||
) then
|
||||
execute format('alter publication supabase_realtime add table public.%I', t);
|
||||
end if;
|
||||
end loop;
|
||||
end
|
||||
$$;
|
||||
|
||||
alter table public.leads replica identity full;
|
||||
alter table public.customers replica identity full;
|
||||
alter table public.vehicles replica identity full;
|
||||
|
||||
-- Tell PostgREST to reload its schema cache after new tables/functions appear.
|
||||
notify pgrst, 'reload schema';
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
-- Runs AFTER GoTrue and Storage auto-migrate their schemas.
|
||||
-- Seeds the admin user (from psql vars :admin_email / :admin_password),
|
||||
-- the vehicle-photos storage bucket, and storage RLS policies. Idempotent.
|
||||
--
|
||||
-- IMPORTANT: the seeded password is a BOOTSTRAP value only. The admin UI
|
||||
-- enforces a password rotation on first login via
|
||||
-- auth.users.raw_user_meta_data.must_change_password=true, so the real
|
||||
-- operational password is NEVER equal to the .env seed.
|
||||
|
||||
-- Publish psql vars as GUCs so the DO block can read them reliably.
|
||||
select set_config('mccars.admin_email', :'admin_email', false);
|
||||
select set_config('mccars.admin_password', :'admin_password', false);
|
||||
|
||||
do $$
|
||||
declare
|
||||
v_user_id uuid;
|
||||
v_email text := coalesce(nullif(current_setting('mccars.admin_email', true), ''), 'admin@mccars.local');
|
||||
v_pass text := coalesce(nullif(current_setting('mccars.admin_password', true), ''), 'mc-cars-admin');
|
||||
begin
|
||||
if not exists (select 1 from auth.users where email = v_email) then
|
||||
v_user_id := gen_random_uuid();
|
||||
insert into auth.users (
|
||||
id, instance_id, aud, role, email, encrypted_password,
|
||||
email_confirmed_at, raw_app_meta_data, raw_user_meta_data,
|
||||
created_at, updated_at, is_super_admin,
|
||||
confirmation_token, email_change, email_change_token_new, recovery_token
|
||||
) values (
|
||||
v_user_id,
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'authenticated', 'authenticated',
|
||||
v_email,
|
||||
crypt(v_pass, gen_salt('bf')),
|
||||
now(),
|
||||
jsonb_build_object('provider','email','providers',jsonb_build_array('email')),
|
||||
jsonb_build_object('must_change_password', true),
|
||||
now(), now(), false, '', '', '', ''
|
||||
);
|
||||
|
||||
insert into auth.identities (
|
||||
id, user_id, identity_data, provider, provider_id,
|
||||
last_sign_in_at, created_at, updated_at
|
||||
) values (
|
||||
gen_random_uuid(), v_user_id,
|
||||
jsonb_build_object('sub', v_user_id::text, 'email', v_email),
|
||||
'email', v_email,
|
||||
now(), now(), now()
|
||||
);
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Storage bucket + RLS
|
||||
-- -----------------------------------------------------------------------------
|
||||
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
||||
values ('vehicle-photos','vehicle-photos', true, 52428800,
|
||||
array['image/jpeg','image/png','image/webp','image/avif'])
|
||||
on conflict (id) do update
|
||||
set public = excluded.public,
|
||||
file_size_limit = excluded.file_size_limit,
|
||||
allowed_mime_types = excluded.allowed_mime_types;
|
||||
|
||||
drop policy if exists "vehicle_photos_public_read" on storage.objects;
|
||||
drop policy if exists "vehicle_photos_admin_insert" on storage.objects;
|
||||
drop policy if exists "vehicle_photos_admin_update" on storage.objects;
|
||||
drop policy if exists "vehicle_photos_admin_delete" on storage.objects;
|
||||
|
||||
create policy "vehicle_photos_public_read"
|
||||
on storage.objects for select using (bucket_id = 'vehicle-photos');
|
||||
|
||||
create policy "vehicle_photos_admin_insert"
|
||||
on storage.objects for insert to authenticated
|
||||
with check (bucket_id = 'vehicle-photos');
|
||||
|
||||
create policy "vehicle_photos_admin_update"
|
||||
on storage.objects for update to authenticated
|
||||
using (bucket_id = 'vehicle-photos') with check (bucket_id = 'vehicle-photos');
|
||||
|
||||
create policy "vehicle_photos_admin_delete"
|
||||
on storage.objects for delete to authenticated
|
||||
using (bucket_id = 'vehicle-photos');
|
||||
Reference in New Issue
Block a user