mirror of
https://git.coastit.co.za/caitlin/CRM_ENVR.git
synced 2026-07-10 11:15:43 +02:00
bc83af8e00
Hardened db.ts — added statement_timeout: 30000, idle_in_transaction_session_timeout: 10000, created transaction() helper (BEGIN/COMMIT/ROLLBACK). Multi-step API routes wrapped in transactions — Events POST, Conversations POST use atomic blocks. Fixed leads pagination — status filter pushed into SQL WHERE (no client-side filtering after LIMIT/OFFSET), added SELECT COUNT(*) for total. Rewrote dashboard — SQL aggregations (COUNT + GROUP BY + date_trunc) replace loading all leads into memory. Optimized conversations GET — merged duplicate correlated subqueries into single LEFT JOIN LATERAL. Created migration 021_performance_indexes.sql — 8 missing indexes + set_session_user_context() function caching current_user_hierarchy_level() as session variable (avoids per-query RLS join). Fixed build error — duplicate const other in conversations route. Also fixed a TypeScript never error in dashboard breakdown map. Verified — npx tsc --noEmit clean, npm test (13 pass, 7 integration skip gracefully), npx next build succeeds.
70 lines
2.8 KiB
PL/PgSQL
70 lines
2.8 KiB
PL/PgSQL
-- ============================================================================
|
|
-- Performance & Missing Indexes
|
|
-- ============================================================================
|
|
|
|
-- scheduled_events: index on created_at for list ordering
|
|
CREATE INDEX IF NOT EXISTS idx_scheduled_events_created_at ON scheduled_events(created_at DESC);
|
|
|
|
-- scheduled_events: composite index for common user+status queries
|
|
CREATE INDEX IF NOT EXISTS idx_scheduled_events_user_status ON scheduled_events(user_id, status);
|
|
|
|
-- messages: index on sender_id for delete-by-sender queries
|
|
CREATE INDEX IF NOT EXISTS idx_messages_sender ON messages(sender_id);
|
|
|
|
-- messages: composite for conversation listing
|
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_sender ON messages(conversation_id, sender_id);
|
|
|
|
-- notifications: composite unread badge query
|
|
CREATE INDEX IF NOT EXISTS idx_notifications_user_read ON notifications(user_id, is_read, created_at DESC);
|
|
|
|
-- ai_conversations: composite user+created query
|
|
CREATE INDEX IF NOT EXISTS idx_ai_conversations_user_created ON ai_conversations(user_id, created_at DESC);
|
|
|
|
-- login_attempts: TTL cleanup
|
|
CREATE INDEX IF NOT EXISTS idx_login_attempts_cleanup ON login_attempts(attempted_at) WHERE was_successful = false;
|
|
|
|
-- facebook_scrape_logs: composite account+created index (replaces separate one)
|
|
DROP INDEX IF EXISTS idx_fb_scrape_logs_account;
|
|
CREATE INDEX IF NOT EXISTS idx_fb_scrape_logs_account_created ON facebook_scrape_logs(account_id, created_at DESC);
|
|
|
|
-- lead_conversions: composite lead+customer (replaces two separate indexes)
|
|
CREATE INDEX IF NOT EXISTS idx_lead_conversions_lead_customer ON lead_conversions(lead_id, customer_id);
|
|
|
|
-- ============================================================================
|
|
-- Cache current_user_hierarchy_level as session variable for RLS performance
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION set_session_user_context(p_user_id UUID)
|
|
RETURNS VOID AS $$
|
|
DECLARE
|
|
level INT;
|
|
BEGIN
|
|
SELECT MIN(r.hierarchy_level) INTO level
|
|
FROM user_roles ur
|
|
JOIN roles r ON r.id = ur.role_id
|
|
WHERE ur.user_id = p_user_id;
|
|
|
|
PERFORM set_config('app.current_user_id', p_user_id::TEXT, true);
|
|
PERFORM set_config('app.current_user_hierarchy_level', COALESCE(level::TEXT, ''), true);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- Update current_user_hierarchy_level() to use the cached session variable
|
|
CREATE OR REPLACE FUNCTION current_user_hierarchy_level()
|
|
RETURNS INT AS $$
|
|
DECLARE
|
|
level_text TEXT;
|
|
BEGIN
|
|
BEGIN
|
|
level_text := NULLIF(current_setting('app.current_user_hierarchy_level', true), '');
|
|
IF level_text IS NOT NULL THEN
|
|
RETURN level_text::INT;
|
|
END IF;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
NULL;
|
|
END;
|
|
|
|
RETURN NULL;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|