-- ============================================================ -- 016_flow_media.sql -- -- Adds support for media nodes in conversational flows: -- -- 1. New 'send_media' value on `flow_nodes.node_type` CHECK -- constraint. Mirrors the same drop-and-recreate pattern migration -- 010 used to land the original list. The node config lives in -- JSONB and is shape-checked by the validator + TS types, not the -- DB. -- -- 2. `flow-media` Supabase Storage bucket where the builder uploads -- the file the customer will receive. Public bucket so Meta can -- pull the URL without auth — same trade-off as the avatars -- bucket (see migration 008). Per-user RLS on writes scopes the -- bucket so one tenant can't read/overwrite another's media. -- -- Path convention: -- flow-media/{auth.uid()}/-. -- First path segment must equal auth.uid()::text — same shape -- migration 008 uses for avatars so the policy code reads the -- same. -- -- Size limit 16 MB — Meta's WhatsApp Cloud API caps documents at -- 100 MB but videos at 16 MB and images at 5 MB; we pick the -- tightest universal cap that still works for the document case -- that prompted this feature (PDF invoices / receipts). -- -- Idempotent — safe to re-run. -- ============================================================ -- ============================================================ -- 1. flow_nodes.node_type — add 'send_media' -- ============================================================ ALTER TABLE flow_nodes DROP CONSTRAINT IF EXISTS flow_nodes_node_type_check; ALTER TABLE flow_nodes ADD CONSTRAINT flow_nodes_node_type_check CHECK (node_type IN ( 'start', 'send_buttons', 'send_list', 'send_message', 'send_media', 'collect_input', 'condition', 'set_tag', 'handoff', 'http_fetch', 'end' )); -- ============================================================ -- 2. flow-media storage bucket -- ============================================================ INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) VALUES ( 'flow-media', 'flow-media', TRUE, 16777216, -- 16 MB (Meta video cap; documents/images fit under this) ARRAY[ -- Images 'image/png', 'image/jpeg', 'image/webp', -- Videos 'video/mp4', 'video/3gpp', -- Documents 'application/pdf', 'application/vnd.ms-powerpoint', 'application/msword', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/plain' ] ) 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. Same drop-then-create pattern as -- migration 008 (no CREATE POLICY IF NOT EXISTS in Postgres). DROP POLICY IF EXISTS "Flow media is publicly readable" ON storage.objects; CREATE POLICY "Flow media is publicly readable" ON storage.objects FOR SELECT USING (bucket_id = 'flow-media'); DROP POLICY IF EXISTS "Users can upload their own flow media" ON storage.objects; CREATE POLICY "Users can upload their own flow media" ON storage.objects FOR INSERT WITH CHECK ( bucket_id = 'flow-media' AND auth.uid()::text = (storage.foldername(name))[1] ); DROP POLICY IF EXISTS "Users can update their own flow media" ON storage.objects; CREATE POLICY "Users can update their own flow media" ON storage.objects FOR UPDATE USING ( bucket_id = 'flow-media' AND auth.uid()::text = (storage.foldername(name))[1] ); DROP POLICY IF EXISTS "Users can delete their own flow media" ON storage.objects; CREATE POLICY "Users can delete their own flow media" ON storage.objects FOR DELETE USING ( bucket_id = 'flow-media' AND auth.uid()::text = (storage.foldername(name))[1] );