SET statement_timeout = 0; SET lock_timeout = 0; SET idle_in_transaction_session_timeout = 0; SET client_encoding = 'UTF8'; SET standard_conforming_strings = on; SELECT pg_catalog.set_config('search_path', '', false); SET check_function_bodies = false; SET xmloption = content; SET client_min_messages = warning; SET row_security = off; CREATE EXTENSION IF NOT EXISTS "pgsodium"; COMMENT ON SCHEMA "public" IS 'standard public schema'; CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql"; CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions"; CREATE EXTENSION IF NOT EXISTS "pg_trgm" WITH SCHEMA "extensions"; CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions"; CREATE EXTENSION IF NOT EXISTS "pgjwt" WITH SCHEMA "extensions"; CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions"; CREATE TYPE "public"."comparison_operator" AS ENUM ( 'less_than', 'less_than_equal', 'greater_than', 'greater_than_equal', 'equal_to', 'not_equal_to' ); ALTER TYPE "public"."comparison_operator" OWNER TO "postgres"; CREATE TYPE "public"."metric_category" AS ENUM ( 'performance', 'seo', 'accessibility', 'best_practices', 'security', 'pwa' ); ALTER TYPE "public"."metric_category" OWNER TO "postgres"; CREATE TYPE "public"."notification_channel" AS ENUM ( 'email', 'slack', 'webhook', 'in_app' ); ALTER TYPE "public"."notification_channel" OWNER TO "postgres"; CREATE TYPE "public"."resource_type" AS ENUM ( 'script', 'stylesheet', 'image', 'font', 'document', 'media', 'other' ); ALTER TYPE "public"."resource_type" OWNER TO "postgres"; CREATE TYPE "public"."scan_status" AS ENUM ( 'pending', 'queued', 'running', 'completed', 'failed', 'cancelled' ); ALTER TYPE "public"."scan_status" OWNER TO "postgres"; CREATE TYPE "public"."severity_level" AS ENUM ( 'critical', 'high', 'medium', 'low', 'info' ); ALTER TYPE "public"."severity_level" OWNER TO "postgres"; CREATE TYPE "public"."subscription_tier" AS ENUM ( 'free', 'starter', 'professional', 'enterprise' ); ALTER TYPE "public"."subscription_tier" OWNER TO "postgres"; CREATE TYPE "public"."user_role" AS ENUM ( 'owner', 'admin', 'editor', 'viewer' ); ALTER TYPE "public"."user_role" OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."apply_data_retention"() RETURNS "void" LANGUAGE "plpgsql" SECURITY DEFINER SET "search_path" TO 'public', 'pg_temp' AS $$ BEGIN -- Delete old scan results (older than 90 days) DELETE FROM public.scan_results WHERE created_at < NOW() - INTERVAL '90 days'; -- Delete old metric values (older than 90 days) DELETE FROM public.metric_values WHERE created_at < NOW() - INTERVAL '90 days'; -- Delete old alert history (older than 365 days) DELETE FROM public.alert_history WHERE created_at < NOW() - INTERVAL '365 days'; -- Delete old audit logs (older than 365 days) DELETE FROM public.audit_logs WHERE created_at < NOW() - INTERVAL '365 days'; END; $$; ALTER FUNCTION "public"."apply_data_retention"() OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."calculate_competitor_comparison"("website_id" "uuid") RETURNS TABLE("metric_key" character varying, "your_score" numeric, "competitor_avg" numeric, "competitor_best" numeric, "percentile" numeric) LANGUAGE "plpgsql" SET "search_path" TO 'public', 'pg_temp' AS $$ BEGIN RETURN QUERY WITH your_metrics AS ( SELECT m.key, mv.value FROM scans s JOIN metric_values mv ON mv.scan_id = s.id JOIN metric_definitions m ON m.id = mv.metric_id WHERE s.website_id = calculate_competitor_comparison.website_id AND s.created_at = ( SELECT MAX(created_at) FROM scans WHERE website_id = calculate_competitor_comparison.website_id ) ), competitor_metrics AS ( SELECT m.key, mv.value, PERCENT_RANK() OVER (PARTITION BY m.key ORDER BY mv.value) as percentile FROM scans s JOIN metric_values mv ON mv.scan_id = s.id JOIN metric_definitions m ON m.id = mv.metric_id WHERE s.website_id IN ( SELECT competitor_url_id FROM competitor_websites WHERE website_id = calculate_competitor_comparison.website_id ) AND s.created_at >= NOW() - INTERVAL '30 days' ) SELECT ym.key, ym.value as your_score, AVG(cm.value) as competitor_avg, MAX(cm.value) as competitor_best, MAX(cm.percentile) * 100 as percentile FROM your_metrics ym LEFT JOIN competitor_metrics cm ON cm.key = ym.key GROUP BY ym.key, ym.value; END; $$; ALTER FUNCTION "public"."calculate_competitor_comparison"("website_id" "uuid") OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."calculate_competitor_comparison"("website_id_param" "uuid", "competitor_id_param" "uuid") RETURNS "jsonb" LANGUAGE "plpgsql" SECURITY DEFINER SET "search_path" TO 'public', 'pg_temp' AS $$ DECLARE result jsonb; BEGIN -- Calculate comparison metrics between website and competitor SELECT jsonb_build_object( 'performance_score_diff', COALESCE(w.performance_score - c.performance_score, 0), 'seo_score_diff', COALESCE(w.seo_score - c.seo_score, 0), 'accessibility_score_diff', COALESCE(w.accessibility_score - c.accessibility_score, 0) ) INTO result FROM public.websites w CROSS JOIN public.competitor_websites c WHERE w.id = website_id_param AND c.id = competitor_id_param; RETURN COALESCE(result, '{}'::jsonb); END; $$; ALTER FUNCTION "public"."calculate_competitor_comparison"("website_id_param" "uuid", "competitor_id_param" "uuid") OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."calculate_health_score"("website_id_param" "uuid") RETURNS numeric LANGUAGE "plpgsql" SECURITY DEFINER SET "search_path" TO 'public', 'pg_temp' AS $$ DECLARE health_score numeric; BEGIN -- Calculate weighted health score based on latest metrics SELECT COALESCE( (performance_score * 0.4 + seo_score * 0.3 + accessibility_score * 0.2 + best_practices_score * 0.1), 0 ) INTO health_score FROM public.websites WHERE id = website_id_param; RETURN COALESCE(health_score, 0); END; $$; ALTER FUNCTION "public"."calculate_health_score"("website_id_param" "uuid") OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."email_exists"("email_to_check" "text") RETURNS boolean LANGUAGE "sql" SECURITY DEFINER AS $$ select exists( select 1 from users where lower(email) = lower(email_to_check) ); $$; ALTER FUNCTION "public"."email_exists"("email_to_check" "text") OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."generate_performance_report"("website_id" "uuid", "days" integer) RETURNS "jsonb" LANGUAGE "plpgsql" SET "search_path" TO 'public', 'pg_temp' AS $$ DECLARE report JSONB; BEGIN SELECT jsonb_build_object( 'website_info', ( SELECT jsonb_build_object( 'name', name, 'url', base_url, 'report_period', jsonb_build_object( 'start', NOW() - (days || ' days')::interval, 'end', NOW() ) ) FROM websites WHERE id = website_id ), 'performance_summary', ( SELECT jsonb_build_object( 'average_performance_score', AVG(mv.value), 'best_performance_score', MAX(mv.value), 'worst_performance_score', MIN(mv.value), 'trend', jsonb_agg( jsonb_build_object( 'date', DATE(s.created_at), 'score', mv.value ) ORDER BY s.created_at ) ) FROM scans s JOIN metric_values mv ON mv.scan_id = s.id JOIN metric_definitions md ON md.id = mv.metric_id WHERE s.website_id = generate_performance_report.website_id AND md.key = 'performance' AND s.created_at >= NOW() - (days || ' days')::interval ), 'core_metrics', ( SELECT jsonb_object_agg( md.key, jsonb_build_object( 'average', AVG(mv.value), 'best', MAX(mv.value), 'worst', MIN(mv.value) ) ) FROM scans s JOIN metric_values mv ON mv.scan_id = s.id JOIN metric_definitions md ON md.id = mv.metric_id WHERE s.website_id = generate_performance_report.website_id AND md.is_core_metric = true AND s.created_at >= NOW() - (days || ' days')::interval ), 'resource_summary', ( SELECT jsonb_object_agg( resource_type, jsonb_build_object( 'count', COUNT(*), 'total_size', SUM(size_bytes), 'average_duration', AVG(duration_ms) ) ) FROM scans s JOIN resource_analysis ra ON ra.scan_id = s.id WHERE s.website_id = generate_performance_report.website_id AND s.created_at >= NOW() - (days || ' days')::interval ), 'alerts', ( SELECT jsonb_agg( jsonb_build_object( 'severity', severity, 'message', message, 'created_at', created_at ) ) FROM alerts WHERE website_id = generate_performance_report.website_id AND created_at >= NOW() - (days || ' days')::interval ) ) INTO report; RETURN report; END; $$; ALTER FUNCTION "public"."generate_performance_report"("website_id" "uuid", "days" integer) OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."generate_performance_report"("website_id_param" "uuid", "start_date" timestamp with time zone DEFAULT ("now"() - '30 days'::interval), "end_date" timestamp with time zone DEFAULT "now"()) RETURNS "jsonb" LANGUAGE "plpgsql" SECURITY DEFINER SET "search_path" TO 'public', 'pg_temp' AS $$ DECLARE report jsonb; BEGIN -- Generate comprehensive performance report SELECT jsonb_build_object( 'website_id', website_id_param, 'period', jsonb_build_object( 'start_date', start_date, 'end_date', end_date ), 'summary', jsonb_build_object( 'total_scans', COUNT(s.id), 'avg_performance_score', AVG(w.performance_score), 'avg_seo_score', AVG(w.seo_score), 'avg_accessibility_score', AVG(w.accessibility_score) ), 'trends', jsonb_agg( jsonb_build_object( 'date', DATE(s.created_at), 'performance_score', w.performance_score, 'seo_score', w.seo_score ) ) ) INTO report FROM public.scans s JOIN public.websites w ON w.id = s.website_id WHERE s.website_id = website_id_param AND s.created_at BETWEEN start_date AND end_date GROUP BY website_id_param; RETURN COALESCE(report, '{}'::jsonb); END; $$; ALTER FUNCTION "public"."generate_performance_report"("website_id_param" "uuid", "start_date" timestamp with time zone, "end_date" timestamp with time zone) OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."handle_new_user"() RETURNS "trigger" LANGUAGE "plpgsql" SECURITY DEFINER SET "search_path" TO 'public', 'pg_temp' AS $$ BEGIN -- Log the new row to Postgres logs for debugging RAISE LOG 'New user created: %', ROW(NEW.*); -- Insert into public.users table INSERT INTO public.users (id, email, organization_id, role, created_at, updated_at) VALUES ( NEW.id, NEW.email, NEW.raw_user_meta_data->>'organization_id', COALESCE(NEW.raw_user_meta_data->>'role', 'member'), NOW(), NOW() ); RETURN NEW; END; $$; ALTER FUNCTION "public"."handle_new_user"() OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."perform_maintenance"() RETURNS "void" LANGUAGE "plpgsql" SET "search_path" TO 'public', 'pg_temp' AS $$ BEGIN -- Clean up old rate limit records DELETE FROM rate_limits WHERE window_start < NOW() - INTERVAL '1 day'; -- Archive old notifications UPDATE notification_deliveries SET status = 'archived' WHERE created_at < NOW() - INTERVAL '30 days'; -- Clean up expired API keys UPDATE api_keys SET is_active = false WHERE expires_at < NOW(); -- Update statistics ANALYZE websites; ANALYZE scans; ANALYZE metric_values; ANALYZE resource_analysis; -- Vacuum analyze for better query planning VACUUM ANALYZE websites; VACUUM ANALYZE scans; VACUUM ANALYZE metric_values; VACUUM ANALYZE resource_analysis; END; $$; ALTER FUNCTION "public"."perform_maintenance"() OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."process_alert_notifications"() RETURNS "trigger" LANGUAGE "plpgsql" SET "search_path" TO 'public', 'pg_temp' AS $$ DECLARE website_record RECORD; user_record RECORD; BEGIN -- Get website details SELECT * INTO website_record FROM websites WHERE id = NEW.website_id; -- Insert notification for each user in the organization FOR user_record IN SELECT u.* FROM users u WHERE u.organization_id = website_record.organization_id AND (u.settings->>'email_notifications')::boolean = true LOOP INSERT INTO notification_deliveries ( alert_id, channel, recipient, content ) VALUES ( NEW.id, 'email', user_record.email, format( 'Alert for %s: %s. Severity: %s', website_record.name, NEW.message, NEW.severity ) ); END LOOP; RETURN NEW; END; $$; ALTER FUNCTION "public"."process_alert_notifications"() OWNER TO "postgres"; CREATE OR REPLACE FUNCTION "public"."update_scan_status"() RETURNS "trigger" LANGUAGE "plpgsql" SET "search_path" TO 'public', 'pg_temp' AS $$ BEGIN IF NEW.status = 'completed' THEN -- Update website's last scan timestamp UPDATE websites SET last_scan_at = NOW() WHERE id = NEW.website_id; -- Check for alerts INSERT INTO alerts (website_id, page_id, severity, title, message) SELECT NEW.website_id, NEW.page_id, ac.severity, 'Metric threshold exceeded', format('%s is %s threshold of %s', m.name, ac.comparison, ac.threshold) FROM metric_values mv JOIN metric_definitions m ON m.id = mv.metric_id JOIN alert_configurations ac ON ac.metric_id = m.id WHERE mv.scan_id = NEW.id AND ac.website_id = NEW.website_id AND ( CASE ac.comparison WHEN 'less_than' THEN mv.value < ac.threshold WHEN 'greater_than' THEN mv.value > ac.threshold WHEN 'equal_to' THEN mv.value = ac.threshold END ); END IF; RETURN NEW; END; $$; ALTER FUNCTION "public"."update_scan_status"() OWNER TO "postgres"; SET default_tablespace = ''; SET default_table_access_method = "heap"; CREATE TABLE IF NOT EXISTS "public"."alert_configurations" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "name" character varying NOT NULL, "description" "text", "metric_id" "uuid" NOT NULL, "threshold" numeric NOT NULL, "comparison" "public"."comparison_operator" DEFAULT 'less_than'::"public"."comparison_operator", "severity" "public"."severity_level" DEFAULT 'medium'::"public"."severity_level", "consecutive_count" integer DEFAULT 1, "cooldown_minutes" integer DEFAULT 60, "notification_channels" "public"."notification_channel"[] DEFAULT ARRAY['email'::"public"."notification_channel"], "notification_template" "text", "is_active" boolean DEFAULT true, "last_triggered_at" timestamp with time zone, "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."alert_configurations" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."alert_history" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "alert_id" "uuid" NOT NULL, "event_type" character varying NOT NULL, "event_data" "jsonb" NOT NULL, "created_by" "uuid", "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."alert_history" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."alerts" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "page_id" "uuid", "config_id" "uuid", "metric_id" "uuid", "severity" "public"."severity_level" DEFAULT 'medium'::"public"."severity_level", "title" character varying NOT NULL, "message" "text" NOT NULL, "details" "jsonb" DEFAULT '{}'::"jsonb", "status" character varying DEFAULT 'open'::character varying, "acknowledged_by" "uuid", "acknowledged_at" timestamp with time zone, "resolved_at" timestamp with time zone, "resolution_note" "text", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."alerts" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."api_keys" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "organization_id" "uuid" NOT NULL, "name" character varying NOT NULL, "key_hash" character varying NOT NULL, "scopes" character varying[] DEFAULT ARRAY['read'::"text"], "rate_limit_per_minute" integer DEFAULT 60, "is_active" boolean DEFAULT true, "last_used_at" timestamp with time zone, "expires_at" timestamp with time zone, "created_by" "uuid", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."api_keys" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."audit_logs" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "organization_id" "uuid", "user_id" "uuid", "action" character varying NOT NULL, "entity_type" character varying NOT NULL, "entity_id" "uuid", "changes" "jsonb", "ip_address" character varying, "user_agent" character varying, "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."audit_logs" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."budget_violations" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "budget_id" "uuid" NOT NULL, "scan_id" "uuid" NOT NULL, "actual_value" numeric NOT NULL, "threshold_value" numeric NOT NULL, "percentage_over" numeric, "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."budget_violations" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."competitor_websites" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "competitor_url" character varying NOT NULL, "name" character varying NOT NULL, "is_active" boolean DEFAULT true, "scan_frequency" character varying DEFAULT 'daily'::character varying, "metrics_to_track" character varying[] DEFAULT ARRAY['performance'::"text", 'seo'::"text", 'accessibility'::"text", 'best_practices'::"text"], "last_scan_at" timestamp with time zone, "metadata" "jsonb" DEFAULT '{}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."competitor_websites" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."crawl_queue" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "url" character varying NOT NULL, "priority" integer DEFAULT 1, "status" character varying DEFAULT 'pending'::character varying, "parent_url" character varying, "discovery_depth" integer DEFAULT 0, "attempts" integer DEFAULT 0, "last_attempt_at" timestamp with time zone, "next_attempt_at" timestamp with time zone, "error_message" "text", "metadata" "jsonb" DEFAULT '{}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."crawl_queue" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."crawl_sessions" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "status" character varying DEFAULT 'running'::character varying, "pages_discovered" integer DEFAULT 0, "pages_processed" integer DEFAULT 0, "start_url" character varying NOT NULL, "max_depth" integer, "started_at" timestamp with time zone DEFAULT "now"(), "completed_at" timestamp with time zone, "error_message" "text", "metadata" "jsonb" DEFAULT '{}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."crawl_sessions" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."dashboard_widgets" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "dashboard_id" "uuid" NOT NULL, "widget_type" character varying NOT NULL, "name" character varying NOT NULL, "config" "jsonb" NOT NULL, "position" "jsonb" NOT NULL, "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."dashboard_widgets" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."dashboards" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "organization_id" "uuid" NOT NULL, "name" character varying NOT NULL, "description" "text", "layout" "jsonb" DEFAULT '[]'::"jsonb", "is_default" boolean DEFAULT false, "created_by" "uuid", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."dashboards" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."generated_reports" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "template_id" "uuid" NOT NULL, "website_id" "uuid", "generated_by" "uuid", "report_data" "jsonb" NOT NULL, "format" character varying DEFAULT 'pdf'::character varying, "file_url" character varying, "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."generated_reports" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."metric_definitions" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "key" character varying NOT NULL, "name" character varying NOT NULL, "description" "text" NOT NULL, "category" "public"."metric_category" NOT NULL, "unit" character varying, "is_core_metric" boolean DEFAULT false, "default_threshold" numeric, "warning_threshold" numeric, "critical_threshold" numeric, "direction" character varying DEFAULT 'higher_is_better'::character varying NOT NULL, "weight" numeric DEFAULT 1.0, "documentation_url" "text", "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."metric_definitions" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."metric_values" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "scan_id" "uuid" NOT NULL, "metric_id" "uuid" NOT NULL, "value" numeric NOT NULL, "raw_value" character varying, "unit" character varying, "is_passing" boolean, "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."metric_values" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."notification_deliveries" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "alert_id" "uuid" NOT NULL, "channel" "public"."notification_channel" NOT NULL, "recipient" character varying NOT NULL, "content" "text" NOT NULL, "status" character varying DEFAULT 'pending'::character varying, "sent_at" timestamp with time zone, "error_message" "text", "retry_count" integer DEFAULT 0, "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."notification_deliveries" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."organizations" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "name" character varying NOT NULL, "subscription_tier" character varying DEFAULT 'free'::character varying, "subscription_status" character varying DEFAULT 'active'::character varying, "settings" "jsonb" DEFAULT '{"retention_days": 90, "alert_email_digest": "daily", "default_scan_depth": 3, "enable_competitor_analysis": false}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."organizations" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."pages" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "url" character varying NOT NULL, "path" character varying NOT NULL, "title" character varying, "description" "text", "content_hash" character varying, "content_type" character varying, "status_code" integer, "is_active" boolean DEFAULT true, "priority" integer DEFAULT 1, "depth" integer DEFAULT 0, "parent_page_id" "uuid", "discovery_method" character varying DEFAULT 'crawl'::character varying, "last_seen_at" timestamp with time zone, "metadata" "jsonb" DEFAULT '{"word_count": 0, "is_indexable": true, "has_canonical": false, "inbound_links": 0, "outbound_links": 0}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."pages" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."performance_budgets" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "name" character varying NOT NULL, "description" "text", "metric_id" "uuid", "budget_type" character varying NOT NULL, "threshold" numeric NOT NULL, "applies_to" "jsonb" DEFAULT '{"paths": ["/*"], "resource_types": ["all"]}'::"jsonb", "is_active" boolean DEFAULT true, "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."performance_budgets" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."scans" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "page_id" "uuid" NOT NULL, "triggered_by" "uuid", "scan_type" character varying DEFAULT 'full'::character varying NOT NULL, "status" "public"."scan_status" DEFAULT 'pending'::"public"."scan_status", "priority" integer DEFAULT 1, "categories" "public"."metric_category"[] DEFAULT ARRAY['performance'::"public"."metric_category", 'seo'::"public"."metric_category", 'accessibility'::"public"."metric_category", 'best_practices'::"public"."metric_category"], "device_type" character varying DEFAULT 'desktop'::character varying, "user_agent" character varying, "lighthouse_version" character varying, "chrome_version" character varying, "environment" "jsonb" DEFAULT '{}'::"jsonb", "started_at" timestamp with time zone DEFAULT "now"(), "completed_at" timestamp with time zone, "duration_ms" integer, "error_message" "text", "retry_count" integer DEFAULT 0, "metadata" "jsonb" DEFAULT '{}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"(), "scheduled_at" timestamp with time zone ); ALTER TABLE "public"."scans" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."websites" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "organization_id" "uuid" NOT NULL, "base_url" character varying NOT NULL, "name" character varying NOT NULL, "is_active" boolean DEFAULT true, "crawl_settings" "jsonb" DEFAULT '{"max_depth": 3, "max_pages": 100, "crawl_timing": "off_peak", "crawl_frequency": "daily", "exclude_patterns": ["/admin/*", "/api/*", "*.pdf", "*.jpg", "*.png"], "include_patterns": ["/*"], "respect_robots_txt": true}'::"jsonb", "scan_schedule" "jsonb" DEFAULT '{"days": ["monday", "tuesday", "wednesday", "thursday", "friday"], "frequency": "hourly", "time_windows": ["0-6", "20-23"]}'::"jsonb", "performance_budgets" "jsonb" DEFAULT '{"max_requests": 100, "page_weight_kb": 1000, "time_to_interactive_ms": 3000, "first_contentful_paint_ms": 1000}'::"jsonb", "notifications" "jsonb" DEFAULT '{"channels": ["email"], "thresholds": {"seo": 90, "performance": 90, "accessibility": 90, "best_practices": 90}}'::"jsonb", "last_crawl_at" timestamp with time zone, "last_scan_at" timestamp with time zone, "metadata" "jsonb" DEFAULT '{}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."websites" OWNER TO "postgres"; CREATE OR REPLACE VIEW "public"."performance_trends" WITH ("security_invoker"='on') AS SELECT "w"."id" AS "website_id", "w"."name" AS "website_name", "p"."url" AS "page_url", "m"."key" AS "metric_key", "mv"."value" AS "metric_value", "s"."created_at" AS "scan_date", "avg"("mv"."value") OVER (PARTITION BY "w"."id", "p"."id", "m"."id" ORDER BY "s"."created_at" ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS "rolling_average" FROM (((("public"."websites" "w" JOIN "public"."pages" "p" ON (("p"."website_id" = "w"."id"))) JOIN "public"."scans" "s" ON (("s"."page_id" = "p"."id"))) JOIN "public"."metric_values" "mv" ON (("mv"."scan_id" = "s"."id"))) JOIN "public"."metric_definitions" "m" ON (("m"."id" = "mv"."metric_id"))) WHERE ("s"."created_at" >= ("now"() - '30 days'::interval)); ALTER TABLE "public"."performance_trends" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."rate_limits" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "key_type" character varying NOT NULL, "key_value" character varying NOT NULL, "window_start" timestamp with time zone NOT NULL, "request_count" integer DEFAULT 1, "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."rate_limits" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."report_templates" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "organization_id" "uuid" NOT NULL, "name" character varying NOT NULL, "description" "text", "template_type" character varying NOT NULL, "content" "jsonb" NOT NULL, "schedule" "jsonb", "is_active" boolean DEFAULT true, "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."report_templates" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."resource_analysis" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "scan_id" "uuid" NOT NULL, "resource_type" "public"."resource_type" NOT NULL, "url" character varying NOT NULL, "size_bytes" integer NOT NULL, "transfer_size_bytes" integer, "duration_ms" integer, "is_third_party" boolean DEFAULT false, "is_cached" boolean, "compression_ratio" numeric, "mime_type" character varying, "protocol" character varying, "priority" character varying, "status_code" integer, "metadata" "jsonb" DEFAULT '{}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."resource_analysis" OWNER TO "postgres"; CREATE OR REPLACE VIEW "public"."resource_usage_summary" WITH ("security_invoker"='on') AS SELECT "w"."id" AS "website_id", "w"."name" AS "website_name", "ra"."resource_type", "count"(*) AS "resource_count", "avg"("ra"."size_bytes") AS "avg_size", "sum"("ra"."size_bytes") AS "total_size", "avg"("ra"."duration_ms") AS "avg_duration" FROM (("public"."websites" "w" JOIN "public"."scans" "s" ON (("s"."website_id" = "w"."id"))) JOIN "public"."resource_analysis" "ra" ON (("ra"."scan_id" = "s"."id"))) WHERE ("s"."created_at" >= ("now"() - '24:00:00'::interval)) GROUP BY "w"."id", "w"."name", "ra"."resource_type"; ALTER TABLE "public"."resource_usage_summary" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."scan_results" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "scan_id" "uuid" NOT NULL, "category" "public"."metric_category" NOT NULL, "score" numeric, "raw_data" "jsonb", "created_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."scan_results" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."url_patterns" ( "id" "uuid" DEFAULT "extensions"."uuid_generate_v4"() NOT NULL, "website_id" "uuid" NOT NULL, "pattern" character varying NOT NULL, "pattern_type" character varying NOT NULL, "description" "text", "is_regex" boolean DEFAULT false, "is_active" boolean DEFAULT true, "priority" integer DEFAULT 1, "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."url_patterns" OWNER TO "postgres"; CREATE TABLE IF NOT EXISTS "public"."users" ( "id" "uuid" NOT NULL, "email" character varying NOT NULL, "name" character varying, "organization_id" "uuid", "role" character varying DEFAULT 'viewer'::character varying, "is_active" boolean DEFAULT true, "settings" "jsonb" DEFAULT '{"dashboard_layout": "default", "email_notifications": true, "notification_frequency": "instant"}'::"jsonb", "created_at" timestamp with time zone DEFAULT "now"(), "updated_at" timestamp with time zone DEFAULT "now"() ); ALTER TABLE "public"."users" OWNER TO "postgres"; ALTER TABLE ONLY "public"."alert_configurations" ADD CONSTRAINT "alert_configurations_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."alert_history" ADD CONSTRAINT "alert_history_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."alerts" ADD CONSTRAINT "alerts_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."api_keys" ADD CONSTRAINT "api_keys_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."audit_logs" ADD CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."budget_violations" ADD CONSTRAINT "budget_violations_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."competitor_websites" ADD CONSTRAINT "competitor_websites_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."competitor_websites" ADD CONSTRAINT "competitor_websites_website_id_competitor_url_key" UNIQUE ("website_id", "competitor_url"); ALTER TABLE ONLY "public"."crawl_queue" ADD CONSTRAINT "crawl_queue_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."crawl_sessions" ADD CONSTRAINT "crawl_sessions_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."dashboard_widgets" ADD CONSTRAINT "dashboard_widgets_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."dashboards" ADD CONSTRAINT "dashboards_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."generated_reports" ADD CONSTRAINT "generated_reports_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."metric_definitions" ADD CONSTRAINT "metric_definitions_key_key" UNIQUE ("key"); ALTER TABLE ONLY "public"."metric_definitions" ADD CONSTRAINT "metric_definitions_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."metric_values" ADD CONSTRAINT "metric_values_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."notification_deliveries" ADD CONSTRAINT "notification_deliveries_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."organizations" ADD CONSTRAINT "organizations_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."pages" ADD CONSTRAINT "pages_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."pages" ADD CONSTRAINT "pages_website_id_url_key" UNIQUE ("website_id", "url"); ALTER TABLE ONLY "public"."performance_budgets" ADD CONSTRAINT "performance_budgets_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."rate_limits" ADD CONSTRAINT "rate_limits_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."report_templates" ADD CONSTRAINT "report_templates_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."resource_analysis" ADD CONSTRAINT "resource_analysis_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."scan_results" ADD CONSTRAINT "scan_results_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."scans" ADD CONSTRAINT "scans_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."url_patterns" ADD CONSTRAINT "url_patterns_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."users" ADD CONSTRAINT "users_email_key" UNIQUE ("email"); ALTER TABLE ONLY "public"."users" ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id"); ALTER TABLE ONLY "public"."websites" ADD CONSTRAINT "websites_organization_id_base_url_key" UNIQUE ("organization_id", "base_url"); ALTER TABLE ONLY "public"."websites" ADD CONSTRAINT "websites_pkey" PRIMARY KEY ("id"); CREATE INDEX "idx_alert_configurations_metric_id" ON "public"."alert_configurations" USING "btree" ("metric_id"); CREATE INDEX "idx_alert_configurations_website_id" ON "public"."alert_configurations" USING "btree" ("website_id"); CREATE INDEX "idx_alert_history_alert_id" ON "public"."alert_history" USING "btree" ("alert_id"); CREATE INDEX "idx_alerts_config_id" ON "public"."alerts" USING "btree" ("config_id"); CREATE INDEX "idx_alerts_metric_id" ON "public"."alerts" USING "btree" ("metric_id"); CREATE INDEX "idx_alerts_page_id" ON "public"."alerts" USING "btree" ("page_id"); CREATE INDEX "idx_alerts_website_id" ON "public"."alerts" USING "btree" ("website_id"); CREATE INDEX "idx_budget_violations_budget_id" ON "public"."budget_violations" USING "btree" ("budget_id"); CREATE INDEX "idx_budget_violations_scan_id" ON "public"."budget_violations" USING "btree" ("scan_id"); CREATE INDEX "idx_crawl_queue_website_id" ON "public"."crawl_queue" USING "btree" ("website_id"); CREATE INDEX "idx_crawl_sessions_website_id" ON "public"."crawl_sessions" USING "btree" ("website_id"); CREATE INDEX "idx_dashboard_widgets_dashboard_id" ON "public"."dashboard_widgets" USING "btree" ("dashboard_id"); CREATE INDEX "idx_generated_reports_template_id" ON "public"."generated_reports" USING "btree" ("template_id"); CREATE INDEX "idx_generated_reports_website_id" ON "public"."generated_reports" USING "btree" ("website_id"); CREATE INDEX "idx_metric_values_metric_id" ON "public"."metric_values" USING "btree" ("metric_id"); CREATE INDEX "idx_metric_values_scan_id" ON "public"."metric_values" USING "btree" ("scan_id"); CREATE INDEX "idx_notification_deliveries_alert_id" ON "public"."notification_deliveries" USING "btree" ("alert_id"); CREATE INDEX "idx_pages_parent_page_id" ON "public"."pages" USING "btree" ("parent_page_id"); CREATE INDEX "idx_performance_budgets_metric_id" ON "public"."performance_budgets" USING "btree" ("metric_id"); CREATE INDEX "idx_performance_budgets_website_id" ON "public"."performance_budgets" USING "btree" ("website_id"); CREATE INDEX "idx_resource_analysis_scan_id" ON "public"."resource_analysis" USING "btree" ("scan_id"); CREATE INDEX "idx_scan_results_scan_id" ON "public"."scan_results" USING "btree" ("scan_id"); CREATE INDEX "idx_scans_page_id" ON "public"."scans" USING "btree" ("page_id"); CREATE INDEX "idx_scans_website_id" ON "public"."scans" USING "btree" ("website_id"); CREATE INDEX "idx_url_patterns_website_id" ON "public"."url_patterns" USING "btree" ("website_id"); CREATE INDEX "idx_users_organization_id" ON "public"."users" USING "btree" ("organization_id"); CREATE OR REPLACE TRIGGER "alert_notification_trigger" AFTER INSERT ON "public"."alerts" FOR EACH ROW EXECUTE FUNCTION "public"."process_alert_notifications"(); CREATE OR REPLACE TRIGGER "scan_status_update" AFTER UPDATE OF "status" ON "public"."scans" FOR EACH ROW WHEN (("old"."status" IS DISTINCT FROM "new"."status")) EXECUTE FUNCTION "public"."update_scan_status"(); ALTER TABLE ONLY "public"."alert_configurations" ADD CONSTRAINT "alert_configurations_metric_id_fkey" FOREIGN KEY ("metric_id") REFERENCES "public"."metric_definitions"("id"); ALTER TABLE ONLY "public"."alert_configurations" ADD CONSTRAINT "alert_configurations_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id"); ALTER TABLE ONLY "public"."alert_history" ADD CONSTRAINT "alert_history_alert_id_fkey" FOREIGN KEY ("alert_id") REFERENCES "public"."alerts"("id"); ALTER TABLE ONLY "public"."alerts" ADD CONSTRAINT "alerts_config_id_fkey" FOREIGN KEY ("config_id") REFERENCES "public"."alert_configurations"("id"); ALTER TABLE ONLY "public"."alerts" ADD CONSTRAINT "alerts_metric_id_fkey" FOREIGN KEY ("metric_id") REFERENCES "public"."metric_definitions"("id"); ALTER TABLE ONLY "public"."alerts" ADD CONSTRAINT "alerts_page_id_fkey" FOREIGN KEY ("page_id") REFERENCES "public"."pages"("id"); ALTER TABLE ONLY "public"."alerts" ADD CONSTRAINT "alerts_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id"); ALTER TABLE ONLY "public"."budget_violations" ADD CONSTRAINT "budget_violations_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "public"."performance_budgets"("id"); ALTER TABLE ONLY "public"."budget_violations" ADD CONSTRAINT "budget_violations_scan_id_fkey" FOREIGN KEY ("scan_id") REFERENCES "public"."scans"("id"); ALTER TABLE ONLY "public"."competitor_websites" ADD CONSTRAINT "competitor_websites_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id"); ALTER TABLE ONLY "public"."crawl_queue" ADD CONSTRAINT "crawl_queue_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id") ON DELETE CASCADE; ALTER TABLE ONLY "public"."crawl_sessions" ADD CONSTRAINT "crawl_sessions_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id") ON DELETE CASCADE; ALTER TABLE ONLY "public"."dashboard_widgets" ADD CONSTRAINT "dashboard_widgets_dashboard_id_fkey" FOREIGN KEY ("dashboard_id") REFERENCES "public"."dashboards"("id"); ALTER TABLE ONLY "public"."users" ADD CONSTRAINT "fk_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE CASCADE; ALTER TABLE ONLY "public"."generated_reports" ADD CONSTRAINT "generated_reports_template_id_fkey" FOREIGN KEY ("template_id") REFERENCES "public"."report_templates"("id"); ALTER TABLE ONLY "public"."generated_reports" ADD CONSTRAINT "generated_reports_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id"); ALTER TABLE ONLY "public"."metric_values" ADD CONSTRAINT "metric_values_metric_id_fkey" FOREIGN KEY ("metric_id") REFERENCES "public"."metric_definitions"("id"); ALTER TABLE ONLY "public"."metric_values" ADD CONSTRAINT "metric_values_scan_id_fkey" FOREIGN KEY ("scan_id") REFERENCES "public"."scans"("id"); ALTER TABLE ONLY "public"."notification_deliveries" ADD CONSTRAINT "notification_deliveries_alert_id_fkey" FOREIGN KEY ("alert_id") REFERENCES "public"."alerts"("id"); ALTER TABLE ONLY "public"."pages" ADD CONSTRAINT "pages_parent_page_id_fkey" FOREIGN KEY ("parent_page_id") REFERENCES "public"."pages"("id"); ALTER TABLE ONLY "public"."pages" ADD CONSTRAINT "pages_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id") ON DELETE CASCADE; ALTER TABLE ONLY "public"."performance_budgets" ADD CONSTRAINT "performance_budgets_metric_id_fkey" FOREIGN KEY ("metric_id") REFERENCES "public"."metric_definitions"("id"); ALTER TABLE ONLY "public"."performance_budgets" ADD CONSTRAINT "performance_budgets_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id"); ALTER TABLE ONLY "public"."resource_analysis" ADD CONSTRAINT "resource_analysis_scan_id_fkey" FOREIGN KEY ("scan_id") REFERENCES "public"."scans"("id"); ALTER TABLE ONLY "public"."scan_results" ADD CONSTRAINT "scan_results_scan_id_fkey" FOREIGN KEY ("scan_id") REFERENCES "public"."scans"("id"); ALTER TABLE ONLY "public"."scans" ADD CONSTRAINT "scans_page_id_fkey" FOREIGN KEY ("page_id") REFERENCES "public"."pages"("id"); ALTER TABLE ONLY "public"."scans" ADD CONSTRAINT "scans_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id") ON DELETE CASCADE; ALTER TABLE ONLY "public"."url_patterns" ADD CONSTRAINT "url_patterns_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "public"."websites"("id"); ALTER TABLE ONLY "public"."users" ADD CONSTRAINT "users_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users"("id"); ALTER TABLE ONLY "public"."users" ADD CONSTRAINT "users_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id"); CREATE POLICY "Allow organization insert for authenticated" ON "public"."organizations" FOR INSERT TO "authenticated" WITH CHECK (true); CREATE POLICY "Allow read for authenticated users" ON "public"."alert_configurations" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."alert_history" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."audit_logs" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."budget_violations" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."competitor_websites" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."crawl_queue" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."crawl_sessions" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."dashboard_widgets" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."generated_reports" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."metric_definitions" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."notification_deliveries" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."performance_budgets" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."rate_limits" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."report_templates" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."resource_analysis" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."scan_results" FOR SELECT USING (true); CREATE POLICY "Allow read for authenticated users" ON "public"."url_patterns" FOR SELECT USING (true); CREATE POLICY "Allow user insert for anon" ON "public"."users" FOR INSERT TO "anon" WITH CHECK (true); CREATE POLICY "Allow user insert for authenticated" ON "public"."users" FOR INSERT TO "authenticated" WITH CHECK (true); CREATE POLICY "Allow user insert for authenticator" ON "public"."users" FOR INSERT TO "authenticator" WITH CHECK (true); CREATE POLICY "Allow user insert for dashboard_user" ON "public"."users" FOR INSERT TO "dashboard_user" WITH CHECK (true); CREATE POLICY "Allow user update for anon" ON "public"."users" FOR UPDATE TO "anon" USING (("id" = ( SELECT "auth"."uid"() AS "uid"))); CREATE POLICY "Allow user update for authenticated" ON "public"."users" FOR UPDATE TO "authenticated" USING (("id" = ( SELECT "auth"."uid"() AS "uid"))); CREATE POLICY "Allow user update for authenticator" ON "public"."users" FOR UPDATE TO "authenticator" USING (("id" = ( SELECT "auth"."uid"() AS "uid"))); CREATE POLICY "Allow user update for dashboard_user" ON "public"."users" FOR UPDATE TO "dashboard_user" USING (("id" = ( SELECT "auth"."uid"() AS "uid"))); CREATE POLICY "Users can delete pages of their organization" ON "public"."pages" FOR DELETE USING (("website_id" IN ( SELECT "websites"."id" FROM "public"."websites" WHERE ("websites"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can delete scans of their organization" ON "public"."scans" FOR DELETE USING (("website_id" IN ( SELECT "w"."id" FROM "public"."websites" "w" WHERE ("w"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can delete websites of their organization" ON "public"."websites" FOR DELETE USING (("organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))); CREATE POLICY "Users can insert pages for their organization" ON "public"."pages" FOR INSERT WITH CHECK (("website_id" IN ( SELECT "websites"."id" FROM "public"."websites" WHERE ("websites"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can insert scans for their organization" ON "public"."scans" FOR INSERT WITH CHECK (("website_id" IN ( SELECT "w"."id" FROM "public"."websites" "w" WHERE ("w"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can insert websites for their organization" ON "public"."websites" FOR INSERT WITH CHECK (("organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))); CREATE POLICY "Users can select their own data" ON "public"."users" FOR SELECT USING (("auth"."uid"() = "id")); CREATE POLICY "Users can update pages of their organization" ON "public"."pages" FOR UPDATE USING (("website_id" IN ( SELECT "websites"."id" FROM "public"."websites" WHERE ("websites"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can update scans of their organization" ON "public"."scans" FOR UPDATE USING (("website_id" IN ( SELECT "w"."id" FROM "public"."websites" "w" WHERE ("w"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can update websites of their organization" ON "public"."websites" FOR UPDATE USING (("organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))); CREATE POLICY "Users can view alerts they are allowed to" ON "public"."alerts" FOR SELECT USING (("website_id" IN ( SELECT "w"."id" FROM "public"."websites" "w" WHERE ("w"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can view dashboards of their organization" ON "public"."dashboards" FOR SELECT USING (("organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))); CREATE POLICY "Users can view metric_values of their organization" ON "public"."metric_values" FOR SELECT USING (("scan_id" IN ( SELECT "s"."id" FROM ("public"."scans" "s" JOIN "public"."websites" "w" ON (("w"."id" = "s"."website_id"))) WHERE ("w"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can view pages they are allowed to" ON "public"."pages" FOR SELECT USING (("website_id" IN ( SELECT "w"."id" FROM "public"."websites" "w" WHERE ("w"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can view scans they are allowed to" ON "public"."scans" FOR SELECT USING (("website_id" IN ( SELECT "w"."id" FROM "public"."websites" "w" WHERE ("w"."organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))))); CREATE POLICY "Users can view their org's API keys" ON "public"."api_keys" FOR SELECT USING (("organization_id" IN ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))); CREATE POLICY "Users can view their org's websites" ON "public"."websites" FOR SELECT USING (("organization_id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))); CREATE POLICY "Users can view their organization" ON "public"."organizations" FOR SELECT USING (("id" = ( SELECT "users"."organization_id" FROM "public"."users" WHERE ("users"."id" = ( SELECT "auth"."uid"() AS "uid"))))); CREATE POLICY "Users can view their own profile" ON "public"."users" FOR SELECT USING (("id" = ( SELECT "auth"."uid"() AS "uid"))); ALTER TABLE "public"."alert_configurations" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."alert_history" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."alerts" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."api_keys" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."audit_logs" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."budget_violations" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."competitor_websites" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."crawl_queue" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."crawl_sessions" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."dashboard_widgets" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."dashboards" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."generated_reports" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."metric_definitions" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."metric_values" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."notification_deliveries" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."organizations" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."pages" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."performance_budgets" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."rate_limits" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."report_templates" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."resource_analysis" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."scan_results" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."scans" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."url_patterns" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."users" ENABLE ROW LEVEL SECURITY; ALTER TABLE "public"."websites" ENABLE ROW LEVEL SECURITY; ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres"; GRANT USAGE ON SCHEMA "public" TO "postgres"; GRANT USAGE ON SCHEMA "public" TO "anon"; GRANT USAGE ON SCHEMA "public" TO "authenticated"; GRANT USAGE ON SCHEMA "public" TO "service_role"; GRANT ALL ON FUNCTION "public"."apply_data_retention"() TO "anon"; GRANT ALL ON FUNCTION "public"."apply_data_retention"() TO "authenticated"; GRANT ALL ON FUNCTION "public"."apply_data_retention"() TO "service_role"; GRANT ALL ON FUNCTION "public"."calculate_competitor_comparison"("website_id" "uuid") TO "anon"; GRANT ALL ON FUNCTION "public"."calculate_competitor_comparison"("website_id" "uuid") TO "authenticated"; GRANT ALL ON FUNCTION "public"."calculate_competitor_comparison"("website_id" "uuid") TO "service_role"; GRANT ALL ON FUNCTION "public"."calculate_competitor_comparison"("website_id_param" "uuid", "competitor_id_param" "uuid") TO "anon"; GRANT ALL ON FUNCTION "public"."calculate_competitor_comparison"("website_id_param" "uuid", "competitor_id_param" "uuid") TO "authenticated"; GRANT ALL ON FUNCTION "public"."calculate_competitor_comparison"("website_id_param" "uuid", "competitor_id_param" "uuid") TO "service_role"; GRANT ALL ON FUNCTION "public"."calculate_health_score"("website_id_param" "uuid") TO "anon"; GRANT ALL ON FUNCTION "public"."calculate_health_score"("website_id_param" "uuid") TO "authenticated"; GRANT ALL ON FUNCTION "public"."calculate_health_score"("website_id_param" "uuid") TO "service_role"; GRANT ALL ON FUNCTION "public"."email_exists"("email_to_check" "text") TO "anon"; GRANT ALL ON FUNCTION "public"."email_exists"("email_to_check" "text") TO "authenticated"; GRANT ALL ON FUNCTION "public"."email_exists"("email_to_check" "text") TO "service_role"; GRANT ALL ON FUNCTION "public"."generate_performance_report"("website_id" "uuid", "days" integer) TO "anon"; GRANT ALL ON FUNCTION "public"."generate_performance_report"("website_id" "uuid", "days" integer) TO "authenticated"; GRANT ALL ON FUNCTION "public"."generate_performance_report"("website_id" "uuid", "days" integer) TO "service_role"; GRANT ALL ON FUNCTION "public"."generate_performance_report"("website_id_param" "uuid", "start_date" timestamp with time zone, "end_date" timestamp with time zone) TO "anon"; GRANT ALL ON FUNCTION "public"."generate_performance_report"("website_id_param" "uuid", "start_date" timestamp with time zone, "end_date" timestamp with time zone) TO "authenticated"; GRANT ALL ON FUNCTION "public"."generate_performance_report"("website_id_param" "uuid", "start_date" timestamp with time zone, "end_date" timestamp with time zone) TO "service_role"; GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "anon"; GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "authenticated"; GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "service_role"; GRANT ALL ON FUNCTION "public"."perform_maintenance"() TO "anon"; GRANT ALL ON FUNCTION "public"."perform_maintenance"() TO "authenticated"; GRANT ALL ON FUNCTION "public"."perform_maintenance"() TO "service_role"; GRANT ALL ON FUNCTION "public"."process_alert_notifications"() TO "anon"; GRANT ALL ON FUNCTION "public"."process_alert_notifications"() TO "authenticated"; GRANT ALL ON FUNCTION "public"."process_alert_notifications"() TO "service_role"; GRANT ALL ON FUNCTION "public"."update_scan_status"() TO "anon"; GRANT ALL ON FUNCTION "public"."update_scan_status"() TO "authenticated"; GRANT ALL ON FUNCTION "public"."update_scan_status"() TO "service_role"; GRANT ALL ON TABLE "public"."alert_configurations" TO "anon"; GRANT ALL ON TABLE "public"."alert_configurations" TO "authenticated"; GRANT ALL ON TABLE "public"."alert_configurations" TO "service_role"; GRANT ALL ON TABLE "public"."alert_history" TO "anon"; GRANT ALL ON TABLE "public"."alert_history" TO "authenticated"; GRANT ALL ON TABLE "public"."alert_history" TO "service_role"; GRANT ALL ON TABLE "public"."alerts" TO "anon"; GRANT ALL ON TABLE "public"."alerts" TO "authenticated"; GRANT ALL ON TABLE "public"."alerts" TO "service_role"; GRANT ALL ON TABLE "public"."api_keys" TO "anon"; GRANT ALL ON TABLE "public"."api_keys" TO "authenticated"; GRANT ALL ON TABLE "public"."api_keys" TO "service_role"; GRANT ALL ON TABLE "public"."audit_logs" TO "anon"; GRANT ALL ON TABLE "public"."audit_logs" TO "authenticated"; GRANT ALL ON TABLE "public"."audit_logs" TO "service_role"; GRANT ALL ON TABLE "public"."budget_violations" TO "anon"; GRANT ALL ON TABLE "public"."budget_violations" TO "authenticated"; GRANT ALL ON TABLE "public"."budget_violations" TO "service_role"; GRANT ALL ON TABLE "public"."competitor_websites" TO "anon"; GRANT ALL ON TABLE "public"."competitor_websites" TO "authenticated"; GRANT ALL ON TABLE "public"."competitor_websites" TO "service_role"; GRANT ALL ON TABLE "public"."crawl_queue" TO "anon"; GRANT ALL ON TABLE "public"."crawl_queue" TO "authenticated"; GRANT ALL ON TABLE "public"."crawl_queue" TO "service_role"; GRANT ALL ON TABLE "public"."crawl_sessions" TO "anon"; GRANT ALL ON TABLE "public"."crawl_sessions" TO "authenticated"; GRANT ALL ON TABLE "public"."crawl_sessions" TO "service_role"; GRANT ALL ON TABLE "public"."dashboard_widgets" TO "anon"; GRANT ALL ON TABLE "public"."dashboard_widgets" TO "authenticated"; GRANT ALL ON TABLE "public"."dashboard_widgets" TO "service_role"; GRANT ALL ON TABLE "public"."dashboards" TO "anon"; GRANT ALL ON TABLE "public"."dashboards" TO "authenticated"; GRANT ALL ON TABLE "public"."dashboards" TO "service_role"; GRANT ALL ON TABLE "public"."generated_reports" TO "anon"; GRANT ALL ON TABLE "public"."generated_reports" TO "authenticated"; GRANT ALL ON TABLE "public"."generated_reports" TO "service_role"; GRANT ALL ON TABLE "public"."metric_definitions" TO "anon"; GRANT ALL ON TABLE "public"."metric_definitions" TO "authenticated"; GRANT ALL ON TABLE "public"."metric_definitions" TO "service_role"; GRANT ALL ON TABLE "public"."metric_values" TO "anon"; GRANT ALL ON TABLE "public"."metric_values" TO "authenticated"; GRANT ALL ON TABLE "public"."metric_values" TO "service_role"; GRANT ALL ON TABLE "public"."notification_deliveries" TO "anon"; GRANT ALL ON TABLE "public"."notification_deliveries" TO "authenticated"; GRANT ALL ON TABLE "public"."notification_deliveries" TO "service_role"; GRANT ALL ON TABLE "public"."organizations" TO "anon"; GRANT ALL ON TABLE "public"."organizations" TO "authenticated"; GRANT ALL ON TABLE "public"."organizations" TO "service_role"; GRANT ALL ON TABLE "public"."pages" TO "anon"; GRANT ALL ON TABLE "public"."pages" TO "authenticated"; GRANT ALL ON TABLE "public"."pages" TO "service_role"; GRANT ALL ON TABLE "public"."performance_budgets" TO "anon"; GRANT ALL ON TABLE "public"."performance_budgets" TO "authenticated"; GRANT ALL ON TABLE "public"."performance_budgets" TO "service_role"; GRANT ALL ON TABLE "public"."scans" TO "anon"; GRANT ALL ON TABLE "public"."scans" TO "authenticated"; GRANT ALL ON TABLE "public"."scans" TO "service_role"; GRANT ALL ON TABLE "public"."websites" TO "anon"; GRANT ALL ON TABLE "public"."websites" TO "authenticated"; GRANT ALL ON TABLE "public"."websites" TO "service_role"; GRANT ALL ON TABLE "public"."performance_trends" TO "anon"; GRANT ALL ON TABLE "public"."performance_trends" TO "authenticated"; GRANT ALL ON TABLE "public"."performance_trends" TO "service_role"; GRANT ALL ON TABLE "public"."rate_limits" TO "anon"; GRANT ALL ON TABLE "public"."rate_limits" TO "authenticated"; GRANT ALL ON TABLE "public"."rate_limits" TO "service_role"; GRANT ALL ON TABLE "public"."report_templates" TO "anon"; GRANT ALL ON TABLE "public"."report_templates" TO "authenticated"; GRANT ALL ON TABLE "public"."report_templates" TO "service_role"; GRANT ALL ON TABLE "public"."resource_analysis" TO "anon"; GRANT ALL ON TABLE "public"."resource_analysis" TO "authenticated"; GRANT ALL ON TABLE "public"."resource_analysis" TO "service_role"; GRANT ALL ON TABLE "public"."resource_usage_summary" TO "anon"; GRANT ALL ON TABLE "public"."resource_usage_summary" TO "authenticated"; GRANT ALL ON TABLE "public"."resource_usage_summary" TO "service_role"; GRANT ALL ON TABLE "public"."scan_results" TO "anon"; GRANT ALL ON TABLE "public"."scan_results" TO "authenticated"; GRANT ALL ON TABLE "public"."scan_results" TO "service_role"; GRANT ALL ON TABLE "public"."url_patterns" TO "anon"; GRANT ALL ON TABLE "public"."url_patterns" TO "authenticated"; GRANT ALL ON TABLE "public"."url_patterns" TO "service_role"; GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."users" TO "anon"; GRANT ALL ON TABLE "public"."users" TO "authenticated"; GRANT ALL ON TABLE "public"."users" TO "service_role"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated"; ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role"; RESET ALL;