-- 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');