trying to rework database migrations

This commit is contained in:
2026-02-05 11:42:04 -06:00
parent 3b14c65998
commit 343488b3eb
83 changed files with 5793 additions and 876 deletions

View 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 $$;