-- ============================================================ -- 008_profile_avatars_storage.sql -- -- Creates the `avatars` Supabase Storage bucket and the RLS policies -- that let each user manage only their own avatar file while letting -- everyone read (so rendering tags without signed URLs works). -- -- File path convention used by the app: -- avatars/{auth.uid()}/avatar-. -- The policies rely on the first path segment matching auth.uid()::text. -- -- Idempotent — safe to re-run. -- ============================================================ INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) VALUES ( 'avatars', 'avatars', TRUE, 2097152, -- 2 MB ARRAY['image/png', 'image/jpeg', 'image/webp', 'image/gif'] ) ON CONFLICT (id) DO UPDATE SET public = EXCLUDED.public, file_size_limit = EXCLUDED.file_size_limit, allowed_mime_types = EXCLUDED.allowed_mime_types; -- Policies live on storage.objects. Drop-if-exists because Postgres -- has no CREATE POLICY IF NOT EXISTS, and we want this migration to -- re-run cleanly. DROP POLICY IF EXISTS "Avatars are publicly readable" ON storage.objects; CREATE POLICY "Avatars are publicly readable" ON storage.objects FOR SELECT USING (bucket_id = 'avatars'); DROP POLICY IF EXISTS "Users can upload their own avatar" ON storage.objects; CREATE POLICY "Users can upload their own avatar" ON storage.objects FOR INSERT WITH CHECK ( bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1] ); DROP POLICY IF EXISTS "Users can update their own avatar" ON storage.objects; CREATE POLICY "Users can update their own avatar" ON storage.objects FOR UPDATE USING ( bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1] ); DROP POLICY IF EXISTS "Users can delete their own avatar" ON storage.objects; CREATE POLICY "Users can delete their own avatar" ON storage.objects FOR DELETE USING ( bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1] );