suna/backend/supabase/migrations/20250728193819_fix_template...

245 lines
8.7 KiB
PL/PgSQL

-- Safe Templates Config Structure Migration for Production
-- This migration safely updates agent_templates to use the unified config structure
-- with proper existence checks for production environments
BEGIN;
-- Function to check if column exists
CREATE OR REPLACE FUNCTION column_exists(p_table_name text, p_column_name text)
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = p_table_name
AND column_name = p_column_name
);
END;
$$ LANGUAGE plpgsql;
-- Function to check if constraint exists
CREATE OR REPLACE FUNCTION constraint_exists(p_table_name text, p_constraint_name text)
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM information_schema.table_constraints
WHERE table_schema = 'public'
AND table_name = p_table_name
AND constraint_name = p_constraint_name
);
END;
$$ LANGUAGE plpgsql;
-- Backup existing templates if not already done
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'agent_templates_backup') THEN
CREATE TABLE agent_templates_backup AS SELECT * FROM agent_templates;
END IF;
END
$$;
-- Add config column if it doesn't exist
DO $$
BEGIN
IF NOT column_exists('agent_templates', 'config') THEN
ALTER TABLE agent_templates ADD COLUMN config JSONB DEFAULT '{}'::jsonb;
END IF;
END
$$;
-- Migrate data from old structure to new config structure (only if old columns exist)
DO $$
BEGIN
-- Only migrate if we have old columns and config is empty
IF column_exists('agent_templates', 'system_prompt') AND
column_exists('agent_templates', 'agentpress_tools') THEN
UPDATE agent_templates
SET config = jsonb_build_object(
'system_prompt', COALESCE(system_prompt, ''),
'tools', jsonb_build_object(
'agentpress', COALESCE(agentpress_tools, '{}'::jsonb),
'mcp', '[]'::jsonb,
'custom_mcp', COALESCE(
CASE
WHEN column_exists('agent_templates', 'mcp_requirements')
THEN mcp_requirements
ELSE '[]'::jsonb
END,
'[]'::jsonb
)
),
'metadata', jsonb_build_object(
'avatar', avatar,
'avatar_color', avatar_color,
'template_metadata', COALESCE(metadata, '{}'::jsonb)
)
)
WHERE config = '{}'::jsonb OR config IS NULL;
END IF;
END
$$;
-- Drop old columns if they exist
DO $$
BEGIN
IF column_exists('agent_templates', 'system_prompt') THEN
ALTER TABLE agent_templates DROP COLUMN system_prompt;
END IF;
IF column_exists('agent_templates', 'mcp_requirements') THEN
ALTER TABLE agent_templates DROP COLUMN mcp_requirements;
END IF;
IF column_exists('agent_templates', 'agentpress_tools') THEN
ALTER TABLE agent_templates DROP COLUMN agentpress_tools;
END IF;
END
$$;
-- Add constraints if they don't exist
DO $$
BEGIN
IF NOT constraint_exists('agent_templates', 'agent_templates_config_structure_check') THEN
ALTER TABLE agent_templates ADD CONSTRAINT agent_templates_config_structure_check
CHECK (
config ? 'system_prompt' AND
config ? 'tools' AND
config ? 'metadata'
);
END IF;
IF NOT constraint_exists('agent_templates', 'agent_templates_tools_structure_check') THEN
ALTER TABLE agent_templates ADD CONSTRAINT agent_templates_tools_structure_check
CHECK (
config->'tools' ? 'agentpress' AND
config->'tools' ? 'mcp' AND
config->'tools' ? 'custom_mcp'
);
END IF;
END
$$;
-- Create indexes if they don't exist
CREATE INDEX IF NOT EXISTS idx_agent_templates_creator_id ON agent_templates(creator_id);
CREATE INDEX IF NOT EXISTS idx_agent_templates_is_public ON agent_templates(is_public);
CREATE INDEX IF NOT EXISTS idx_agent_templates_marketplace_published_at ON agent_templates(marketplace_published_at);
CREATE INDEX IF NOT EXISTS idx_agent_templates_download_count ON agent_templates(download_count);
CREATE INDEX IF NOT EXISTS idx_agent_templates_tags ON agent_templates USING gin(tags);
CREATE INDEX IF NOT EXISTS idx_agent_templates_created_at ON agent_templates(created_at);
-- Add config-specific indexes
CREATE INDEX IF NOT EXISTS idx_agent_templates_config_tools ON agent_templates USING gin((config->'tools'));
CREATE INDEX IF NOT EXISTS idx_agent_templates_config_agentpress ON agent_templates USING gin((config->'tools'->'agentpress'));
-- Add sanitization function for template creation
CREATE OR REPLACE FUNCTION sanitize_config_for_template(input_config JSONB)
RETURNS JSONB AS $$
DECLARE
sanitized_config JSONB;
custom_mcp_array JSONB;
custom_mcp_item JSONB;
sanitized_mcp JSONB;
result_array JSONB := '[]'::jsonb;
BEGIN
-- Start with the basic structure
sanitized_config := jsonb_build_object(
'system_prompt', COALESCE(input_config->>'system_prompt', ''),
'tools', jsonb_build_object(
'agentpress', COALESCE(input_config->'tools'->'agentpress', '{}'::jsonb),
'mcp', COALESCE(input_config->'tools'->'mcp', '[]'::jsonb),
'custom_mcp', '[]'::jsonb
),
'metadata', jsonb_build_object(
'avatar', input_config->'metadata'->>'avatar',
'avatar_color', input_config->'metadata'->>'avatar_color'
)
);
-- Get custom_mcp array safely
custom_mcp_array := COALESCE(input_config->'tools'->'custom_mcp', '[]'::jsonb);
-- Process each custom MCP item
FOR custom_mcp_item IN SELECT jsonb_array_elements(custom_mcp_array)
LOOP
-- Create sanitized MCP item
sanitized_mcp := jsonb_build_object(
'name', custom_mcp_item->>'name',
'type', custom_mcp_item->>'type',
'display_name', COALESCE(custom_mcp_item->>'display_name', custom_mcp_item->>'name'),
'enabledTools', COALESCE(custom_mcp_item->'enabledTools', '[]'::jsonb)
);
-- Add config based on type
IF custom_mcp_item->>'type' = 'pipedream' THEN
-- For pipedream, keep URL but remove profile_id from headers
sanitized_mcp := jsonb_set(
sanitized_mcp,
'{config}',
jsonb_build_object(
'url', custom_mcp_item->'config'->>'url',
'headers', COALESCE(custom_mcp_item->'config'->'headers', '{}'::jsonb) - 'profile_id'
)
);
ELSE
-- For other types (like http with secure URLs), remove all config
sanitized_mcp := jsonb_set(sanitized_mcp, '{config}', '{}'::jsonb);
END IF;
-- Add to result array
result_array := result_array || sanitized_mcp;
END LOOP;
-- Update sanitized config with cleaned custom_mcps
sanitized_config := jsonb_set(
sanitized_config,
'{tools,custom_mcp}',
result_array
);
RETURN sanitized_config;
END;
$$ LANGUAGE plpgsql;
-- Create function to increment download count
CREATE OR REPLACE FUNCTION increment_template_download_count(template_id_param UUID)
RETURNS void AS $$
BEGIN
UPDATE agent_templates
SET download_count = download_count + 1,
updated_at = NOW()
WHERE template_id = template_id_param;
END;
$$ LANGUAGE plpgsql;
-- Enable RLS if not already enabled
ALTER TABLE agent_templates ENABLE ROW LEVEL SECURITY;
-- Drop and recreate RLS policies to ensure they're current
DROP POLICY IF EXISTS "Users can view public templates or their own templates" ON agent_templates;
CREATE POLICY "Users can view public templates or their own templates" ON agent_templates
FOR SELECT USING (
is_public = true OR
creator_id = (auth.jwt() ->> 'sub')::uuid
);
DROP POLICY IF EXISTS "Users can create their own templates" ON agent_templates;
CREATE POLICY "Users can create their own templates" ON agent_templates
FOR INSERT WITH CHECK (creator_id = (auth.jwt() ->> 'sub')::uuid);
DROP POLICY IF EXISTS "Users can update their own templates" ON agent_templates;
CREATE POLICY "Users can update their own templates" ON agent_templates
FOR UPDATE USING (creator_id = (auth.jwt() ->> 'sub')::uuid);
DROP POLICY IF EXISTS "Users can delete their own templates" ON agent_templates;
CREATE POLICY "Users can delete their own templates" ON agent_templates
FOR DELETE USING (creator_id = (auth.jwt() ->> 'sub')::uuid);
-- Clean up helper functions
DROP FUNCTION IF EXISTS column_exists(text, text);
DROP FUNCTION IF EXISTS constraint_exists(text, text);
COMMIT;