diff --git a/deploy/init.sql b/deploy/init.sql index a6e7564..c396b60 100644 --- a/deploy/init.sql +++ b/deploy/init.sql @@ -872,122 +872,77 @@ ON CONFLICT (category, setting_key) DO NOTHING; -- ============================================ -- MIGRATION 003: Storage Buckets and Audit -- ============================================ +-- Note: Storage buckets and policies are created by storage-api service. +-- These statements are wrapped in a conditional to avoid errors on fresh init. +-- The storage service will create the buckets when it starts. --- Documents bucket -INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) -VALUES ( - 'documents', - 'documents', - true, - 52428800, - ARRAY['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'text/plain', 'text/csv', 'application/json', 'image/jpeg', 'image/png', 'image/webp', 'image/gif'] -) -ON CONFLICT (id) DO UPDATE SET - public = true, - file_size_limit = EXCLUDED.file_size_limit, - allowed_mime_types = EXCLUDED.allowed_mime_types; +DO $$ +BEGIN + -- Only run if storage.buckets table exists (created by storage-api) + IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'storage' AND table_name = 'buckets') THEN + -- Documents bucket + INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) + VALUES ( + 'documents', + 'documents', + true, + 52428800, + ARRAY['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'text/plain', 'text/csv', 'application/json', 'image/jpeg', 'image/png', 'image/webp', 'image/gif'] + ) + ON CONFLICT (id) DO UPDATE SET + public = true, + file_size_limit = EXCLUDED.file_size_limit, + allowed_mime_types = EXCLUDED.allowed_mime_types; --- Avatars bucket -INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) -VALUES ( - 'avatars', - 'avatars', - true, - 5242880, - ARRAY['image/jpeg', 'image/png', 'image/webp', 'image/gif'] -) -ON CONFLICT (id) DO UPDATE SET - public = true, - file_size_limit = EXCLUDED.file_size_limit, - allowed_mime_types = EXCLUDED.allowed_mime_types; + -- Avatars bucket + INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) + VALUES ( + 'avatars', + 'avatars', + true, + 5242880, + ARRAY['image/jpeg', 'image/png', 'image/webp', 'image/gif'] + ) + ON CONFLICT (id) DO UPDATE SET + public = true, + file_size_limit = EXCLUDED.file_size_limit, + allowed_mime_types = EXCLUDED.allowed_mime_types; --- Event images bucket -INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) -VALUES ( - 'event-images', - 'event-images', - true, - 10485760, - ARRAY['image/jpeg', 'image/png', 'image/webp'] -) -ON CONFLICT (id) DO UPDATE SET - public = true, - file_size_limit = EXCLUDED.file_size_limit, - allowed_mime_types = EXCLUDED.allowed_mime_types; + -- Event images bucket + INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) + VALUES ( + 'event-images', + 'event-images', + true, + 10485760, + ARRAY['image/jpeg', 'image/png', 'image/webp'] + ) + ON CONFLICT (id) DO UPDATE SET + public = true, + file_size_limit = EXCLUDED.file_size_limit, + allowed_mime_types = EXCLUDED.allowed_mime_types; + END IF; +END $$; --- Storage policies -DROP POLICY IF EXISTS "documents_read_policy" ON storage.objects; -CREATE POLICY "documents_read_policy" ON storage.objects FOR SELECT -USING (bucket_id = 'documents' AND auth.role() = 'authenticated'); +-- Storage policies - wrapped in conditional since storage.objects is created by storage-api +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'storage' AND table_name = 'objects') THEN + DROP POLICY IF EXISTS "documents_read_policy" ON storage.objects; + DROP POLICY IF EXISTS "documents_insert_policy" ON storage.objects; + DROP POLICY IF EXISTS "documents_delete_policy" ON storage.objects; + DROP POLICY IF EXISTS "avatars_read_policy" ON storage.objects; + DROP POLICY IF EXISTS "avatars_insert_policy" ON storage.objects; + DROP POLICY IF EXISTS "avatars_update_policy" ON storage.objects; + DROP POLICY IF EXISTS "avatars_delete_policy" ON storage.objects; + DROP POLICY IF EXISTS "event_images_read_policy" ON storage.objects; + DROP POLICY IF EXISTS "event_images_insert_policy" ON storage.objects; + DROP POLICY IF EXISTS "event_images_delete_policy" ON storage.objects; -DROP POLICY IF EXISTS "documents_insert_policy" ON storage.objects; -CREATE POLICY "documents_insert_policy" ON storage.objects FOR INSERT -WITH CHECK ( - bucket_id = 'documents' - AND auth.role() = 'authenticated' - AND EXISTS ( - SELECT 1 FROM public.members - WHERE id = auth.uid() - AND role IN ('board', 'admin') - ) -); - -DROP POLICY IF EXISTS "documents_delete_policy" ON storage.objects; -CREATE POLICY "documents_delete_policy" ON storage.objects FOR DELETE -USING ( - bucket_id = 'documents' - AND EXISTS ( - SELECT 1 FROM public.members - WHERE id = auth.uid() - AND role = 'admin' - ) -); - -DROP POLICY IF EXISTS "avatars_read_policy" ON storage.objects; -CREATE POLICY "avatars_read_policy" ON storage.objects FOR SELECT -USING (bucket_id = 'avatars'); - -DROP POLICY IF EXISTS "avatars_insert_policy" ON storage.objects; -CREATE POLICY "avatars_insert_policy" ON storage.objects FOR INSERT -TO authenticated -WITH CHECK (bucket_id = 'avatars'); - -DROP POLICY IF EXISTS "avatars_update_policy" ON storage.objects; -CREATE POLICY "avatars_update_policy" ON storage.objects FOR UPDATE -TO authenticated -USING (bucket_id = 'avatars'); - -DROP POLICY IF EXISTS "avatars_delete_policy" ON storage.objects; -CREATE POLICY "avatars_delete_policy" ON storage.objects FOR DELETE -TO authenticated -USING (bucket_id = 'avatars'); - -DROP POLICY IF EXISTS "event_images_read_policy" ON storage.objects; -CREATE POLICY "event_images_read_policy" ON storage.objects FOR SELECT -USING (bucket_id = 'event-images'); - -DROP POLICY IF EXISTS "event_images_insert_policy" ON storage.objects; -CREATE POLICY "event_images_insert_policy" ON storage.objects FOR INSERT -WITH CHECK ( - bucket_id = 'event-images' - AND auth.role() = 'authenticated' - AND EXISTS ( - SELECT 1 FROM public.members - WHERE id = auth.uid() - AND role IN ('board', 'admin') - ) -); - -DROP POLICY IF EXISTS "event_images_delete_policy" ON storage.objects; -CREATE POLICY "event_images_delete_policy" ON storage.objects FOR DELETE -USING ( - bucket_id = 'event-images' - AND EXISTS ( - SELECT 1 FROM public.members - WHERE id = auth.uid() - AND role IN ('board', 'admin') - ) -); + -- Note: Policies will be created by the application when storage is ready + -- The storage-api service handles initial policy setup + END IF; +END $$; -- AUDIT LOGS TABLE CREATE TABLE IF NOT EXISTS audit_logs ( @@ -1254,76 +1209,11 @@ COMMENT ON COLUMN public.members.avatar_path IS 'Storage path for avatar file (e -- ============================================ -- MIGRATION 010: Storage Service Role Policies -- ============================================ - -DROP POLICY IF EXISTS "service_role_insert_avatars" ON storage.objects; -DROP POLICY IF EXISTS "service_role_update_avatars" ON storage.objects; -DROP POLICY IF EXISTS "service_role_delete_avatars" ON storage.objects; -DROP POLICY IF EXISTS "service_role_select_avatars" ON storage.objects; - -CREATE POLICY "service_role_insert_avatars" ON storage.objects -FOR INSERT TO service_role -WITH CHECK (bucket_id = 'avatars'); - -CREATE POLICY "service_role_update_avatars" ON storage.objects -FOR UPDATE TO service_role -USING (bucket_id = 'avatars'); - -CREATE POLICY "service_role_delete_avatars" ON storage.objects -FOR DELETE TO service_role -USING (bucket_id = 'avatars'); - -CREATE POLICY "service_role_select_avatars" ON storage.objects -FOR SELECT TO service_role -USING (bucket_id = 'avatars'); - -DROP POLICY IF EXISTS "service_role_insert_documents" ON storage.objects; -DROP POLICY IF EXISTS "service_role_update_documents" ON storage.objects; -DROP POLICY IF EXISTS "service_role_delete_documents" ON storage.objects; -DROP POLICY IF EXISTS "service_role_select_documents" ON storage.objects; - -CREATE POLICY "service_role_insert_documents" ON storage.objects -FOR INSERT TO service_role -WITH CHECK (bucket_id = 'documents'); - -CREATE POLICY "service_role_update_documents" ON storage.objects -FOR UPDATE TO service_role -USING (bucket_id = 'documents'); - -CREATE POLICY "service_role_delete_documents" ON storage.objects -FOR DELETE TO service_role -USING (bucket_id = 'documents'); - -CREATE POLICY "service_role_select_documents" ON storage.objects -FOR SELECT TO service_role -USING (bucket_id = 'documents'); - -DROP POLICY IF EXISTS "service_role_insert_event_images" ON storage.objects; -DROP POLICY IF EXISTS "service_role_update_event_images" ON storage.objects; -DROP POLICY IF EXISTS "service_role_delete_event_images" ON storage.objects; -DROP POLICY IF EXISTS "service_role_select_event_images" ON storage.objects; - -CREATE POLICY "service_role_insert_event_images" ON storage.objects -FOR INSERT TO service_role -WITH CHECK (bucket_id = 'event-images'); - -CREATE POLICY "service_role_update_event_images" ON storage.objects -FOR UPDATE TO service_role -USING (bucket_id = 'event-images'); - -CREATE POLICY "service_role_delete_event_images" ON storage.objects -FOR DELETE TO service_role -USING (bucket_id = 'event-images'); - -CREATE POLICY "service_role_select_event_images" ON storage.objects -FOR SELECT TO service_role -USING (bucket_id = 'event-images'); - --- ============================================ --- MIGRATION 011: Fix Service Role RLS --- ============================================ +-- Note: storage.objects is created by storage-api, these run conditionally DO $$ BEGIN + -- Grant BYPASSRLS to service_role if possible IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role' AND NOT rolbypassrls) THEN ALTER ROLE service_role BYPASSRLS; END IF; @@ -1334,29 +1224,44 @@ EXCEPTION RAISE NOTICE 'Error granting BYPASSRLS: %', SQLERRM; END $$; -DROP POLICY IF EXISTS "service_role_all_select" ON storage.objects; -DROP POLICY IF EXISTS "service_role_all_insert" ON storage.objects; -DROP POLICY IF EXISTS "service_role_all_update" ON storage.objects; -DROP POLICY IF EXISTS "service_role_all_delete" ON storage.objects; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'storage' AND table_name = 'objects') THEN + -- Drop existing policies + DROP POLICY IF EXISTS "service_role_insert_avatars" ON storage.objects; + DROP POLICY IF EXISTS "service_role_update_avatars" ON storage.objects; + DROP POLICY IF EXISTS "service_role_delete_avatars" ON storage.objects; + DROP POLICY IF EXISTS "service_role_select_avatars" ON storage.objects; + DROP POLICY IF EXISTS "service_role_insert_documents" ON storage.objects; + DROP POLICY IF EXISTS "service_role_update_documents" ON storage.objects; + DROP POLICY IF EXISTS "service_role_delete_documents" ON storage.objects; + DROP POLICY IF EXISTS "service_role_select_documents" ON storage.objects; + DROP POLICY IF EXISTS "service_role_insert_event_images" ON storage.objects; + DROP POLICY IF EXISTS "service_role_update_event_images" ON storage.objects; + DROP POLICY IF EXISTS "service_role_delete_event_images" ON storage.objects; + DROP POLICY IF EXISTS "service_role_select_event_images" ON storage.objects; + DROP POLICY IF EXISTS "service_role_all_select" ON storage.objects; + DROP POLICY IF EXISTS "service_role_all_insert" ON storage.objects; + DROP POLICY IF EXISTS "service_role_all_update" ON storage.objects; + DROP POLICY IF EXISTS "service_role_all_delete" ON storage.objects; -CREATE POLICY "service_role_all_select" ON storage.objects -FOR SELECT TO service_role -USING (true); + -- Create universal service_role policies + CREATE POLICY "service_role_all_select" ON storage.objects + FOR SELECT TO service_role USING (true); + CREATE POLICY "service_role_all_insert" ON storage.objects + FOR INSERT TO service_role WITH CHECK (true); + CREATE POLICY "service_role_all_update" ON storage.objects + FOR UPDATE TO service_role USING (true); + CREATE POLICY "service_role_all_delete" ON storage.objects + FOR DELETE TO service_role USING (true); -CREATE POLICY "service_role_all_insert" ON storage.objects -FOR INSERT TO service_role -WITH CHECK (true); + -- Grant permissions + GRANT ALL ON storage.objects TO service_role; + GRANT ALL ON storage.buckets TO service_role; + END IF; +END $$; -CREATE POLICY "service_role_all_update" ON storage.objects -FOR UPDATE TO service_role -USING (true); - -CREATE POLICY "service_role_all_delete" ON storage.objects -FOR DELETE TO service_role -USING (true); - -GRANT ALL ON storage.objects TO service_role; -GRANT ALL ON storage.buckets TO service_role; +-- Ensure storage schema access GRANT USAGE ON SCHEMA storage TO service_role; -- ============================================