trying to rework database migrations
This commit is contained in:
245
migrations.old/20260120000001_add_webhook_support.sql
Normal file
245
migrations.old/20260120000001_add_webhook_support.sql
Normal file
@@ -0,0 +1,245 @@
|
||||
-- Migration: Add Webhook Support to Triggers
|
||||
-- Date: 2026-01-20
|
||||
-- Description: Adds webhook capabilities to the trigger system, allowing any trigger
|
||||
-- to be webhook-enabled with a unique webhook key for external integrations.
|
||||
|
||||
|
||||
-- Add webhook columns to trigger table
|
||||
ALTER TABLE trigger
|
||||
ADD COLUMN IF NOT EXISTS webhook_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN IF NOT EXISTS webhook_key VARCHAR(64) UNIQUE,
|
||||
ADD COLUMN IF NOT EXISTS webhook_secret VARCHAR(128);
|
||||
|
||||
-- Add comments for documentation
|
||||
COMMENT ON COLUMN trigger.webhook_enabled IS
|
||||
'Whether webhooks are enabled for this trigger. When enabled, external systems can POST to the webhook URL to create events.';
|
||||
|
||||
COMMENT ON COLUMN trigger.webhook_key IS
|
||||
'Unique webhook key used in the webhook URL. Format: wh_[32 alphanumeric chars]. Acts as a bearer token for webhook authentication.';
|
||||
|
||||
COMMENT ON COLUMN trigger.webhook_secret IS
|
||||
'Optional secret for HMAC signature verification. When set, webhook requests must include a valid X-Webhook-Signature header.';
|
||||
|
||||
-- Create index for fast webhook key lookup
|
||||
CREATE INDEX IF NOT EXISTS idx_trigger_webhook_key
|
||||
ON trigger(webhook_key)
|
||||
WHERE webhook_key IS NOT NULL;
|
||||
|
||||
-- Create index for querying webhook-enabled triggers
|
||||
CREATE INDEX IF NOT EXISTS idx_trigger_webhook_enabled
|
||||
ON trigger(webhook_enabled)
|
||||
WHERE webhook_enabled = TRUE;
|
||||
|
||||
-- Add webhook-related metadata tracking to events
|
||||
-- Events use the 'config' JSONB column for metadata
|
||||
-- We'll add indexes to efficiently query webhook-sourced events
|
||||
|
||||
-- Create index for webhook-sourced events (using config column)
|
||||
CREATE INDEX IF NOT EXISTS idx_event_webhook_source
|
||||
ON event((config->>'source'))
|
||||
WHERE (config->>'source') = 'webhook';
|
||||
|
||||
-- Create index for webhook key lookup in event config
|
||||
CREATE INDEX IF NOT EXISTS idx_event_webhook_key
|
||||
ON event((config->>'webhook_key'))
|
||||
WHERE config->>'webhook_key' IS NOT NULL;
|
||||
|
||||
-- Function to generate webhook key
|
||||
CREATE OR REPLACE FUNCTION generate_webhook_key()
|
||||
RETURNS VARCHAR(64) AS $$
|
||||
DECLARE
|
||||
key_prefix VARCHAR(3) := 'wh_';
|
||||
random_suffix VARCHAR(32);
|
||||
new_key VARCHAR(64);
|
||||
max_attempts INT := 10;
|
||||
attempt INT := 0;
|
||||
BEGIN
|
||||
LOOP
|
||||
-- Generate 32 random alphanumeric characters
|
||||
random_suffix := encode(gen_random_bytes(24), 'base64');
|
||||
random_suffix := REPLACE(random_suffix, '/', '');
|
||||
random_suffix := REPLACE(random_suffix, '+', '');
|
||||
random_suffix := REPLACE(random_suffix, '=', '');
|
||||
random_suffix := LOWER(LEFT(random_suffix, 32));
|
||||
|
||||
-- Construct full key
|
||||
new_key := key_prefix || random_suffix;
|
||||
|
||||
-- Check if key already exists
|
||||
IF NOT EXISTS (SELECT 1 FROM trigger WHERE webhook_key = new_key) THEN
|
||||
RETURN new_key;
|
||||
END IF;
|
||||
|
||||
-- Increment attempt counter
|
||||
attempt := attempt + 1;
|
||||
IF attempt >= max_attempts THEN
|
||||
RAISE EXCEPTION 'Failed to generate unique webhook key after % attempts', max_attempts;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
COMMENT ON FUNCTION generate_webhook_key() IS
|
||||
'Generates a unique webhook key with format wh_[32 alphanumeric chars]. Ensures uniqueness by checking existing keys.';
|
||||
|
||||
-- Function to enable webhooks for a trigger
|
||||
CREATE OR REPLACE FUNCTION enable_trigger_webhook(
|
||||
p_trigger_id BIGINT
|
||||
)
|
||||
RETURNS TABLE(
|
||||
webhook_enabled BOOLEAN,
|
||||
webhook_key VARCHAR(64),
|
||||
webhook_url TEXT
|
||||
) AS $$
|
||||
DECLARE
|
||||
v_new_key VARCHAR(64);
|
||||
v_existing_key VARCHAR(64);
|
||||
v_base_url TEXT;
|
||||
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;
|
||||
|
||||
-- Update trigger to enable webhooks
|
||||
UPDATE trigger
|
||||
SET
|
||||
webhook_enabled = TRUE,
|
||||
webhook_key = v_new_key,
|
||||
updated = NOW()
|
||||
WHERE id = p_trigger_id;
|
||||
|
||||
-- Construct webhook URL (base URL should be configured elsewhere)
|
||||
-- For now, return just the path
|
||||
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;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
COMMENT ON FUNCTION enable_trigger_webhook(BIGINT) IS
|
||||
'Enables webhooks for a trigger. Generates a new webhook key if one does not exist. Returns webhook details.';
|
||||
|
||||
-- Function to disable webhooks for a trigger
|
||||
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 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 is retained for audit purposes.';
|
||||
|
||||
-- Function to regenerate webhook key for a trigger
|
||||
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.';
|
||||
|
||||
-- Create a view for webhook statistics
|
||||
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.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.created;
|
||||
|
||||
COMMENT ON VIEW webhook_stats IS
|
||||
'Statistics for webhook-enabled triggers including event counts and timestamps.';
|
||||
|
||||
-- Grant permissions (adjust as needed for your RBAC setup)
|
||||
-- GRANT SELECT ON webhook_stats TO attune_api;
|
||||
-- GRANT EXECUTE ON FUNCTION generate_webhook_key() TO attune_api;
|
||||
-- GRANT EXECUTE ON FUNCTION enable_trigger_webhook(BIGINT) TO attune_api;
|
||||
-- GRANT EXECUTE ON FUNCTION disable_trigger_webhook(BIGINT) TO attune_api;
|
||||
-- GRANT EXECUTE ON FUNCTION regenerate_trigger_webhook_key(BIGINT) TO attune_api;
|
||||
|
||||
-- Trigger update timestamp is already handled by existing triggers
|
||||
-- No need to add it again
|
||||
|
||||
-- Migration complete messages
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Webhook support migration completed successfully';
|
||||
RAISE NOTICE 'Webhook-enabled triggers can now receive events via POST /api/v1/webhooks/:webhook_key';
|
||||
END $$;
|
||||
Reference in New Issue
Block a user