wacrm/supabase/migrations/016_flow_media.sql
Arnas Donauskas 62073d9915
feat(flows): add send_media node for image / video / document sends
Lets flows attach an image, video, or document (e.g. PDF invoice /
receipt) to a customer message — the most-requested gap in v1 was that
nodes were text-only. Single node with a `media_type` discriminator
rather than three node types: Meta's wire payload differs only in the
top-level key, so three types would triple the surface area (forms,
engine cases, menu entries) for no behavioural difference.

Files are uploaded directly from the builder to a new public
`flow-media` Supabase Storage bucket (per-user RLS, 16 MB cap, MIME
allowlist) and the resulting public URL is what Meta fetches at send
time. Same trade-off as the existing avatars bucket.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 19:11:29 +02:00

115 lines
4.0 KiB
SQL

-- ============================================================
-- 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()}/<timestamp>-<basename>.<ext>
-- 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]
);