250 lines
8.5 KiB
PL/PgSQL
250 lines
8.5 KiB
PL/PgSQL
-- Migration: Consolidate Webhook Configuration
|
|
-- Date: 2026-01-27
|
|
-- Description: Consolidates multiple webhook_* columns into a single webhook_config JSONB column
|
|
-- for cleaner schema and better flexibility. Keeps webhook_enabled and webhook_key
|
|
-- as separate columns for indexing and quick filtering.
|
|
|
|
|
|
-- Step 1: Add new webhook_config column
|
|
ALTER TABLE trigger
|
|
ADD COLUMN IF NOT EXISTS webhook_config JSONB DEFAULT '{}'::jsonb;
|
|
|
|
COMMENT ON COLUMN trigger.webhook_config IS
|
|
'Webhook configuration as JSON. Contains settings like secret, HMAC config, rate limits, IP whitelist, etc.';
|
|
|
|
-- Step 2: Migrate existing data to webhook_config
|
|
-- Build JSON object from existing columns
|
|
UPDATE trigger
|
|
SET webhook_config = jsonb_build_object(
|
|
'secret', COALESCE(webhook_secret, NULL),
|
|
'hmac', jsonb_build_object(
|
|
'enabled', COALESCE(webhook_hmac_enabled, false),
|
|
'secret', COALESCE(webhook_hmac_secret, NULL),
|
|
'algorithm', COALESCE(webhook_hmac_algorithm, 'sha256')
|
|
),
|
|
'rate_limit', jsonb_build_object(
|
|
'enabled', COALESCE(webhook_rate_limit_enabled, false),
|
|
'requests', COALESCE(webhook_rate_limit_requests, NULL),
|
|
'window_seconds', COALESCE(webhook_rate_limit_window_seconds, NULL)
|
|
),
|
|
'ip_whitelist', jsonb_build_object(
|
|
'enabled', COALESCE(webhook_ip_whitelist_enabled, false),
|
|
'ips', COALESCE(
|
|
(SELECT jsonb_agg(ip) FROM unnest(webhook_ip_whitelist) AS ip),
|
|
'[]'::jsonb
|
|
)
|
|
),
|
|
'payload_size_limit_kb', COALESCE(webhook_payload_size_limit_kb, NULL)
|
|
)
|
|
WHERE webhook_enabled = true OR webhook_key IS NOT NULL;
|
|
|
|
-- Step 3: Drop dependent views that reference the columns we're about to drop
|
|
DROP VIEW IF EXISTS webhook_stats;
|
|
DROP VIEW IF EXISTS webhook_stats_detailed;
|
|
|
|
-- Step 4: Drop NOT NULL constraints on columns we're about to drop
|
|
ALTER TABLE trigger
|
|
DROP CONSTRAINT IF EXISTS trigger_webhook_hmac_enabled_not_null,
|
|
DROP CONSTRAINT IF EXISTS trigger_webhook_rate_limit_enabled_not_null,
|
|
DROP CONSTRAINT IF EXISTS trigger_webhook_ip_whitelist_enabled_not_null;
|
|
|
|
-- Step 5: Drop old webhook columns (keeping webhook_enabled and webhook_key)
|
|
ALTER TABLE trigger
|
|
DROP COLUMN IF EXISTS webhook_secret,
|
|
DROP COLUMN IF EXISTS webhook_hmac_enabled,
|
|
DROP COLUMN IF EXISTS webhook_hmac_secret,
|
|
DROP COLUMN IF EXISTS webhook_hmac_algorithm,
|
|
DROP COLUMN IF EXISTS webhook_rate_limit_enabled,
|
|
DROP COLUMN IF EXISTS webhook_rate_limit_requests,
|
|
DROP COLUMN IF EXISTS webhook_rate_limit_window_seconds,
|
|
DROP COLUMN IF EXISTS webhook_ip_whitelist_enabled,
|
|
DROP COLUMN IF EXISTS webhook_ip_whitelist,
|
|
DROP COLUMN IF EXISTS webhook_payload_size_limit_kb;
|
|
|
|
-- Step 6: Drop old indexes that referenced removed columns
|
|
DROP INDEX IF EXISTS idx_trigger_webhook_enabled;
|
|
|
|
-- Step 7: Recreate index for webhook_enabled with better name
|
|
CREATE INDEX IF NOT EXISTS idx_trigger_webhook_enabled
|
|
ON trigger(webhook_enabled)
|
|
WHERE webhook_enabled = TRUE;
|
|
|
|
-- Index on webhook_key already exists from previous migration
|
|
-- CREATE INDEX IF NOT EXISTS idx_trigger_webhook_key ON trigger(webhook_key) WHERE webhook_key IS NOT NULL;
|
|
|
|
-- Step 8: Add GIN index for webhook_config JSONB queries
|
|
CREATE INDEX IF NOT EXISTS idx_trigger_webhook_config
|
|
ON trigger USING gin(webhook_config)
|
|
WHERE webhook_config IS NOT NULL AND webhook_config != '{}'::jsonb;
|
|
|
|
-- Step 9: Recreate webhook stats view with new schema
|
|
CREATE OR REPLACE VIEW webhook_stats AS
|
|
SELECT
|
|
t.id as trigger_id,
|
|
t.ref as trigger_ref,
|
|
t.webhook_enabled,
|
|
t.webhook_key,
|
|
t.webhook_config,
|
|
t.created as webhook_created_at,
|
|
COUNT(e.id) as total_events,
|
|
MAX(e.created) as last_event_at,
|
|
MIN(e.created) as first_event_at
|
|
FROM trigger t
|
|
LEFT JOIN event e ON
|
|
e.trigger = t.id
|
|
AND (e.config->>'source') = 'webhook'
|
|
WHERE t.webhook_enabled = TRUE
|
|
GROUP BY t.id, t.ref, t.webhook_enabled, t.webhook_key, t.webhook_config, t.created;
|
|
|
|
COMMENT ON VIEW webhook_stats IS
|
|
'Statistics for webhook-enabled triggers including event counts and timestamps.';
|
|
|
|
-- Step 10: Update helper functions to work with webhook_config
|
|
|
|
-- Update enable_trigger_webhook to work with new schema
|
|
CREATE OR REPLACE FUNCTION enable_trigger_webhook(
|
|
p_trigger_id BIGINT,
|
|
p_config JSONB DEFAULT '{}'::jsonb
|
|
)
|
|
RETURNS TABLE(
|
|
webhook_enabled BOOLEAN,
|
|
webhook_key VARCHAR(64),
|
|
webhook_url TEXT,
|
|
webhook_config JSONB
|
|
) AS $$
|
|
DECLARE
|
|
v_new_key VARCHAR(64);
|
|
v_existing_key VARCHAR(64);
|
|
v_base_url TEXT;
|
|
v_config JSONB;
|
|
BEGIN
|
|
-- Check if trigger exists
|
|
IF NOT EXISTS (SELECT 1 FROM trigger WHERE id = p_trigger_id) THEN
|
|
RAISE EXCEPTION 'Trigger with id % does not exist', p_trigger_id;
|
|
END IF;
|
|
|
|
-- Get existing webhook key if any
|
|
SELECT t.webhook_key INTO v_existing_key
|
|
FROM trigger t
|
|
WHERE t.id = p_trigger_id;
|
|
|
|
-- Generate new key if one doesn't exist
|
|
IF v_existing_key IS NULL THEN
|
|
v_new_key := generate_webhook_key();
|
|
ELSE
|
|
v_new_key := v_existing_key;
|
|
END IF;
|
|
|
|
-- Merge provided config with defaults
|
|
v_config := p_config || jsonb_build_object(
|
|
'hmac', COALESCE(p_config->'hmac', jsonb_build_object('enabled', false, 'algorithm', 'sha256')),
|
|
'rate_limit', COALESCE(p_config->'rate_limit', jsonb_build_object('enabled', false)),
|
|
'ip_whitelist', COALESCE(p_config->'ip_whitelist', jsonb_build_object('enabled', false, 'ips', '[]'::jsonb))
|
|
);
|
|
|
|
-- Update trigger to enable webhooks
|
|
UPDATE trigger
|
|
SET
|
|
webhook_enabled = TRUE,
|
|
webhook_key = v_new_key,
|
|
webhook_config = v_config,
|
|
updated = NOW()
|
|
WHERE id = p_trigger_id;
|
|
|
|
-- Construct webhook URL
|
|
v_base_url := '/api/v1/webhooks/' || v_new_key;
|
|
|
|
-- Return result
|
|
RETURN QUERY
|
|
SELECT
|
|
TRUE::BOOLEAN as webhook_enabled,
|
|
v_new_key as webhook_key,
|
|
v_base_url as webhook_url,
|
|
v_config as webhook_config;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON FUNCTION enable_trigger_webhook(BIGINT, JSONB) IS
|
|
'Enables webhooks for a trigger with optional configuration. Generates a new webhook key if one does not exist. Returns webhook details.';
|
|
|
|
-- Update disable_trigger_webhook (no changes needed, but recreate for consistency)
|
|
CREATE OR REPLACE FUNCTION disable_trigger_webhook(
|
|
p_trigger_id BIGINT
|
|
)
|
|
RETURNS BOOLEAN AS $$
|
|
BEGIN
|
|
-- Check if trigger exists
|
|
IF NOT EXISTS (SELECT 1 FROM trigger WHERE id = p_trigger_id) THEN
|
|
RAISE EXCEPTION 'Trigger with id % does not exist', p_trigger_id;
|
|
END IF;
|
|
|
|
-- Update trigger to disable webhooks
|
|
-- Note: We keep the webhook_key and webhook_config for audit purposes
|
|
UPDATE trigger
|
|
SET
|
|
webhook_enabled = FALSE,
|
|
updated = NOW()
|
|
WHERE id = p_trigger_id;
|
|
|
|
RETURN TRUE;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON FUNCTION disable_trigger_webhook(BIGINT) IS
|
|
'Disables webhooks for a trigger. Webhook key and config are retained for audit purposes.';
|
|
|
|
-- Update regenerate_trigger_webhook_key (no changes to logic)
|
|
CREATE OR REPLACE FUNCTION regenerate_trigger_webhook_key(
|
|
p_trigger_id BIGINT
|
|
)
|
|
RETURNS TABLE(
|
|
webhook_key VARCHAR(64),
|
|
previous_key_revoked BOOLEAN
|
|
) AS $$
|
|
DECLARE
|
|
v_old_key VARCHAR(64);
|
|
v_new_key VARCHAR(64);
|
|
BEGIN
|
|
-- Check if trigger exists
|
|
IF NOT EXISTS (SELECT 1 FROM trigger WHERE id = p_trigger_id) THEN
|
|
RAISE EXCEPTION 'Trigger with id % does not exist', p_trigger_id;
|
|
END IF;
|
|
|
|
-- Get existing key
|
|
SELECT t.webhook_key INTO v_old_key
|
|
FROM trigger t
|
|
WHERE t.id = p_trigger_id;
|
|
|
|
-- Generate new key
|
|
v_new_key := generate_webhook_key();
|
|
|
|
-- Update trigger with new key
|
|
UPDATE trigger
|
|
SET
|
|
webhook_key = v_new_key,
|
|
updated = NOW()
|
|
WHERE id = p_trigger_id;
|
|
|
|
-- Return result
|
|
RETURN QUERY
|
|
SELECT
|
|
v_new_key as webhook_key,
|
|
(v_old_key IS NOT NULL)::BOOLEAN as previous_key_revoked;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON FUNCTION regenerate_trigger_webhook_key(BIGINT) IS
|
|
'Regenerates the webhook key for a trigger. The old key is immediately revoked.';
|
|
|
|
-- Drop old webhook-specific functions that are no longer needed
|
|
DROP FUNCTION IF EXISTS enable_trigger_webhook_hmac(BIGINT, VARCHAR);
|
|
DROP FUNCTION IF EXISTS disable_trigger_webhook_hmac(BIGINT);
|
|
|
|
-- Migration complete messages
|
|
DO $$
|
|
BEGIN
|
|
RAISE NOTICE 'Webhook configuration consolidation completed successfully';
|
|
RAISE NOTICE 'Webhook settings now stored in webhook_config JSONB column';
|
|
RAISE NOTICE 'Kept separate columns: webhook_enabled (indexed), webhook_key (indexed)';
|
|
END $$;
|