summaryrefslogtreecommitdiff
path: root/supabase
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-09 23:59:08 -0800
committerFuwn <[email protected]>2026-02-09 23:59:08 -0800
commit7e45dd49e3b3ef4514530410489b044a9934ead0 (patch)
tree005ef3a3fb9561a8abffbe90d0a6970a96a9828a /supabase
parentfix: P0 correctness/security fixes and P1 lint error resolution (diff)
downloadasa.news-7e45dd49e3b3ef4514530410489b044a9934ead0.tar.xz
asa.news-7e45dd49e3b3ef4514530410489b044a9934ead0.zip
fix: resolve Supabase security and performance advisories
Enable RLS on rate_limits table (accessed only via SECURITY DEFINER). Fix api_keys delete policy to use (select auth.uid()) subquery to avoid per-row re-evaluation. Refresh schema dump from live database.
Diffstat (limited to 'supabase')
-rw-r--r--supabase/schema.sql4351
1 files changed, 3396 insertions, 955 deletions
diff --git a/supabase/schema.sql b/supabase/schema.sql
index fb5f3ab..f75fe9e 100644
--- a/supabase/schema.sql
+++ b/supabase/schema.sql
@@ -1,488 +1,204 @@
--- asa.news database schema
--- dumped 2026-02-08 from live supabase instance (kbugptrwjnenmgkhdofn)
--- this file is for reference only — migrations are applied via supabase mcp
-
--- =============================================================================
--- extensions
--- =============================================================================
-
-CREATE EXTENSION IF NOT EXISTS pg_trgm;
-CREATE EXTENSION IF NOT EXISTS moddatetime SCHEMA extensions;
-CREATE EXTENSION IF NOT EXISTS pgmq;
-CREATE EXTENSION IF NOT EXISTS pg_cron;
-CREATE EXTENSION IF NOT EXISTS pgsodium;
-
--- =============================================================================
--- custom types
--- =============================================================================
-
-CREATE TYPE public.feed_visibility AS ENUM ('public', 'authenticated');
-CREATE TYPE public.subscription_tier AS ENUM ('free', 'pro', 'developer');
-
--- =============================================================================
--- tables
--- =============================================================================
-
-CREATE TABLE public.feeds (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- url text NOT NULL,
- site_url text,
- title text,
- description text,
- language text,
- feed_type text,
- image_url text,
- visibility feed_visibility NOT NULL DEFAULT 'public'::feed_visibility,
- etag text,
- last_modified text,
- last_fetched_at timestamp with time zone,
- last_fetch_error text,
- last_fetch_error_at timestamp with time zone,
- consecutive_failures integer NOT NULL DEFAULT 0,
- next_fetch_at timestamp with time zone NOT NULL DEFAULT now(),
- fetch_interval_seconds integer NOT NULL DEFAULT 1800,
- subscriber_count integer NOT NULL DEFAULT 0,
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- updated_at timestamp with time zone NOT NULL DEFAULT now()
-);
+--
+-- PostgreSQL database dump
+--
-CREATE TABLE public.user_profiles (
- id uuid NOT NULL,
- display_name text,
- tier subscription_tier NOT NULL DEFAULT 'free'::subscription_tier,
- feed_count integer NOT NULL DEFAULT 0,
- folder_count integer NOT NULL DEFAULT 0,
- muted_keyword_count integer NOT NULL DEFAULT 0,
- custom_feed_count integer NOT NULL DEFAULT 0,
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- updated_at timestamp with time zone NOT NULL DEFAULT now(),
- stripe_customer_identifier text,
- stripe_subscription_identifier text,
- stripe_subscription_status text,
- stripe_current_period_end timestamp with time zone,
- highlight_count integer NOT NULL DEFAULT 0,
- webhook_url text,
- webhook_secret text,
- webhook_enabled boolean NOT NULL DEFAULT false,
- webhook_consecutive_failures integer NOT NULL DEFAULT 0
-);
+-- \restrict 3Un8h9VlAyPuVfPugYV7LM7XGnkxI3KC1nPU1aarC0scGxlDzBQB81bgO7tvPsv
-CREATE TABLE public.folders (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- user_id uuid NOT NULL,
- name text NOT NULL,
- position integer NOT NULL DEFAULT 0,
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- updated_at timestamp with time zone NOT NULL DEFAULT now(),
- icon_url text
-);
+-- Dumped from database version 17.6
+-- Dumped by pg_dump version 17.6
-CREATE TABLE public.subscriptions (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- user_id uuid NOT NULL,
- feed_id uuid NOT NULL,
- folder_id uuid,
- custom_title text,
- position integer NOT NULL DEFAULT 0,
- vault_secret_id uuid,
- hidden_from_timeline boolean NOT NULL DEFAULT false,
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- updated_at timestamp with time zone NOT NULL DEFAULT now()
-);
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+-- SET transaction_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 TABLE public.entries (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- feed_id uuid NOT NULL,
- owner_id uuid,
- guid text NOT NULL,
- url text,
- title text,
- author text,
- summary text,
- content_html text,
- content_text text,
- image_url text,
- published_at timestamp with time zone,
- word_count integer,
- fetched_at timestamp with time zone NOT NULL DEFAULT now(),
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- enclosure_url text,
- enclosure_type text,
- enclosure_length bigint
-);
+--
+-- Name: pg_cron; Type: EXTENSION; Schema: -; Owner: -
+--
-CREATE TABLE public.user_entry_states (
- user_id uuid NOT NULL,
- entry_id uuid NOT NULL,
- read boolean NOT NULL DEFAULT false,
- saved boolean NOT NULL DEFAULT false,
- read_at timestamp with time zone,
- saved_at timestamp with time zone
-);
+CREATE EXTENSION IF NOT EXISTS "pg_cron" WITH SCHEMA "pg_catalog";
-CREATE TABLE public.muted_keywords (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- user_id uuid NOT NULL,
- keyword text NOT NULL,
- created_at timestamp with time zone NOT NULL DEFAULT now()
-);
-CREATE TABLE public.custom_feeds (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- user_id uuid NOT NULL,
- name text NOT NULL,
- query text NOT NULL,
- position integer NOT NULL DEFAULT 0,
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- updated_at timestamp with time zone NOT NULL DEFAULT now(),
- source_folder_id uuid,
- match_mode text NOT NULL DEFAULT 'or'::text,
- icon_url text
-);
+--
+-- Name: EXTENSION "pg_cron"; Type: COMMENT; Schema: -; Owner:
+--
-CREATE TABLE public.shared_entries (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- user_id uuid NOT NULL,
- entry_id uuid NOT NULL,
- share_token text NOT NULL,
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- expires_at timestamp with time zone,
- note text,
- highlighted_text text,
- highlight_text_offset integer,
- highlight_text_length integer,
- highlight_text_prefix text DEFAULT ''::text,
- highlight_text_suffix text DEFAULT ''::text
-);
+-- COMMENT ON EXTENSION "pg_cron" IS 'Job scheduler for PostgreSQL';
-CREATE TABLE public.user_highlights (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- user_id uuid NOT NULL,
- entry_id uuid NOT NULL,
- highlighted_text text NOT NULL,
- note text,
- text_offset integer NOT NULL,
- text_length integer NOT NULL,
- text_prefix text NOT NULL DEFAULT ''::text,
- text_suffix text NOT NULL DEFAULT ''::text,
- color text NOT NULL DEFAULT 'yellow'::text,
- created_at timestamp with time zone NOT NULL DEFAULT now()
-);
-CREATE TABLE public.api_keys (
- id uuid NOT NULL DEFAULT gen_random_uuid(),
- user_id uuid NOT NULL,
- key_hash text NOT NULL,
- key_prefix character(8) NOT NULL,
- label text,
- created_at timestamp with time zone NOT NULL DEFAULT now(),
- last_used_at timestamp with time zone,
- revoked_at timestamp with time zone
-);
+--
+-- Name: pgsodium; Type: EXTENSION; Schema: -; Owner: -
+--
--- =============================================================================
--- primary keys
--- =============================================================================
-
-ALTER TABLE public.feeds ADD CONSTRAINT feeds_pkey PRIMARY KEY (id);
-ALTER TABLE public.user_profiles ADD CONSTRAINT user_profiles_pkey PRIMARY KEY (id);
-ALTER TABLE public.folders ADD CONSTRAINT folders_pkey PRIMARY KEY (id);
-ALTER TABLE public.subscriptions ADD CONSTRAINT subscriptions_pkey PRIMARY KEY (id);
-ALTER TABLE public.entries ADD CONSTRAINT entries_pkey PRIMARY KEY (id);
-ALTER TABLE public.user_entry_states ADD CONSTRAINT user_entry_states_pkey PRIMARY KEY (user_id, entry_id);
-ALTER TABLE public.muted_keywords ADD CONSTRAINT muted_keywords_pkey PRIMARY KEY (id);
-ALTER TABLE public.custom_feeds ADD CONSTRAINT custom_feeds_pkey PRIMARY KEY (id);
-ALTER TABLE public.shared_entries ADD CONSTRAINT shared_entries_pkey PRIMARY KEY (id);
-ALTER TABLE public.user_highlights ADD CONSTRAINT user_highlights_pkey PRIMARY KEY (id);
-ALTER TABLE public.api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
-
--- =============================================================================
--- unique constraints
--- =============================================================================
-
-ALTER TABLE public.api_keys ADD CONSTRAINT api_keys_key_hash_key UNIQUE (key_hash);
-ALTER TABLE public.folders ADD CONSTRAINT folders_user_id_name_key UNIQUE (user_id, name);
-ALTER TABLE public.shared_entries ADD CONSTRAINT shared_entries_share_token_key UNIQUE (share_token);
-ALTER TABLE public.shared_entries ADD CONSTRAINT shared_entries_user_id_entry_id_key UNIQUE (user_id, entry_id);
-ALTER TABLE public.subscriptions ADD CONSTRAINT subscriptions_user_id_feed_id_key UNIQUE (user_id, feed_id);
-ALTER TABLE public.user_profiles ADD CONSTRAINT user_profiles_stripe_customer_identifier_key UNIQUE (stripe_customer_identifier);
-
--- =============================================================================
--- foreign keys
--- =============================================================================
-
-ALTER TABLE public.api_keys ADD CONSTRAINT api_keys_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.custom_feeds ADD CONSTRAINT custom_feeds_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.custom_feeds ADD CONSTRAINT custom_feeds_source_folder_id_fkey FOREIGN KEY (source_folder_id) REFERENCES public.folders(id);
-ALTER TABLE public.entries ADD CONSTRAINT entries_feed_id_fkey FOREIGN KEY (feed_id) REFERENCES public.feeds(id);
-ALTER TABLE public.entries ADD CONSTRAINT entries_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES auth.users(id);
-ALTER TABLE public.folders ADD CONSTRAINT folders_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.muted_keywords ADD CONSTRAINT muted_keywords_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.shared_entries ADD CONSTRAINT shared_entries_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.shared_entries ADD CONSTRAINT shared_entries_entry_id_fkey FOREIGN KEY (entry_id) REFERENCES public.entries(id);
-ALTER TABLE public.subscriptions ADD CONSTRAINT subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.subscriptions ADD CONSTRAINT subscriptions_feed_id_fkey FOREIGN KEY (feed_id) REFERENCES public.feeds(id);
-ALTER TABLE public.subscriptions ADD CONSTRAINT subscriptions_folder_id_fkey FOREIGN KEY (folder_id) REFERENCES public.folders(id);
-ALTER TABLE public.user_entry_states ADD CONSTRAINT user_entry_states_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.user_entry_states ADD CONSTRAINT user_entry_states_entry_id_fkey FOREIGN KEY (entry_id) REFERENCES public.entries(id);
-ALTER TABLE public.user_highlights ADD CONSTRAINT user_highlights_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
-ALTER TABLE public.user_highlights ADD CONSTRAINT user_highlights_entry_id_fkey FOREIGN KEY (entry_id) REFERENCES public.entries(id);
-ALTER TABLE public.user_profiles ADD CONSTRAINT user_profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id);
-
--- =============================================================================
--- indexes
--- =============================================================================
-
--- feeds
-CREATE UNIQUE INDEX idx_feeds_url_public ON public.feeds USING btree (url) WHERE (visibility = 'public'::feed_visibility);
-CREATE INDEX idx_feeds_next_fetch ON public.feeds USING btree (next_fetch_at) WHERE (consecutive_failures < 10);
-
--- entries
-CREATE UNIQUE INDEX idx_entries_dedup_public ON public.entries USING btree (feed_id, guid) WHERE (owner_id IS NULL);
-CREATE UNIQUE INDEX idx_entries_dedup_authenticated ON public.entries USING btree (feed_id, owner_id, guid) WHERE (owner_id IS NOT NULL);
-CREATE INDEX idx_entries_feed_published ON public.entries USING btree (feed_id, published_at DESC NULLS LAST);
-CREATE INDEX idx_entries_published ON public.entries USING btree (published_at DESC NULLS LAST);
-CREATE INDEX idx_entries_owner ON public.entries USING btree (owner_id, published_at DESC NULLS LAST) WHERE (owner_id IS NOT NULL);
-CREATE INDEX idx_entries_title_trigram ON public.entries USING gin (title gin_trgm_ops);
-CREATE INDEX idx_entries_content_trigram ON public.entries USING gin (content_text gin_trgm_ops);
-
--- subscriptions
-CREATE INDEX idx_subscriptions_user ON public.subscriptions USING btree (user_id);
-CREATE INDEX idx_subscriptions_feed ON public.subscriptions USING btree (feed_id);
-CREATE INDEX idx_subscriptions_folder ON public.subscriptions USING btree (folder_id);
-
--- user_entry_states
-CREATE INDEX idx_user_entry_states_entry ON public.user_entry_states USING btree (entry_id);
-CREATE INDEX idx_user_entry_states_read ON public.user_entry_states USING btree (user_id, entry_id) WHERE (read = true);
-CREATE INDEX idx_user_entry_states_saved ON public.user_entry_states USING btree (user_id, saved_at DESC) WHERE (saved = true);
-
--- folders
-CREATE INDEX idx_folders_user ON public.folders USING btree (user_id);
-
--- muted_keywords
-CREATE INDEX idx_muted_keywords_user ON public.muted_keywords USING btree (user_id);
-CREATE UNIQUE INDEX idx_muted_keywords_user_keyword ON public.muted_keywords USING btree (user_id, lower(keyword));
-
--- custom_feeds
-CREATE INDEX idx_custom_feeds_user ON public.custom_feeds USING btree (user_id);
-CREATE INDEX idx_custom_feeds_source_folder ON public.custom_feeds USING btree (source_folder_id);
-
--- shared_entries
-CREATE INDEX idx_shared_entries_entry ON public.shared_entries USING btree (entry_id);
-
--- user_highlights
-CREATE INDEX user_highlights_user_entry_idx ON public.user_highlights USING btree (user_id, entry_id);
-CREATE INDEX user_highlights_user_created_idx ON public.user_highlights USING btree (user_id, created_at DESC);
-CREATE INDEX idx_user_highlights_entry ON public.user_highlights USING btree (entry_id);
-
--- api_keys
-CREATE INDEX idx_api_keys_user_id ON public.api_keys USING btree (user_id);
-CREATE INDEX idx_api_keys_key_hash ON public.api_keys USING btree (key_hash) WHERE (revoked_at IS NULL);
-
--- user_profiles
-CREATE INDEX idx_user_profiles_stripe_customer ON public.user_profiles USING btree (stripe_customer_identifier) WHERE (stripe_customer_identifier IS NOT NULL);
-
--- =============================================================================
--- row level security
--- =============================================================================
-
-ALTER TABLE public.api_keys ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.custom_feeds ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.entries ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.feeds ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.folders ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.muted_keywords ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.shared_entries ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.subscriptions ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.user_entry_states ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.user_highlights ENABLE ROW LEVEL SECURITY;
-ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;
-
--- =============================================================================
--- rls policies
--- =============================================================================
-
--- api_keys
-CREATE POLICY "Users can view own API keys" ON public.api_keys AS PERMISSIVE FOR SELECT USING ((auth.uid() = user_id));
-CREATE POLICY "Users can insert own API keys" ON public.api_keys AS PERMISSIVE FOR INSERT WITH CHECK ((auth.uid() = user_id));
-CREATE POLICY "Users can update own API keys" ON public.api_keys AS PERMISSIVE FOR UPDATE USING ((auth.uid() = user_id));
-CREATE POLICY "Users can delete own API keys" ON public.api_keys AS PERMISSIVE FOR DELETE USING ((auth.uid() = user_id));
-
--- custom_feeds
-CREATE POLICY "users can view own custom feeds" ON public.custom_feeds AS PERMISSIVE FOR SELECT USING ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can insert own custom feeds" ON public.custom_feeds AS PERMISSIVE FOR INSERT WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can update own custom feeds" ON public.custom_feeds AS PERMISSIVE FOR UPDATE USING ((user_id = (SELECT auth.uid()))) WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can delete own custom feeds" ON public.custom_feeds AS PERMISSIVE FOR DELETE USING ((user_id = (SELECT auth.uid())));
-
--- entries
-CREATE POLICY "users can view entries from subscribed public feeds" ON public.entries AS PERMISSIVE FOR SELECT
- USING ((
- (owner_id IS NULL AND EXISTS (SELECT 1 FROM subscriptions s WHERE s.feed_id = entries.feed_id AND s.user_id = (SELECT auth.uid())))
- OR (owner_id = (SELECT auth.uid()))
- ));
-
--- feeds
-CREATE POLICY "users can view accessible feeds" ON public.feeds AS PERMISSIVE FOR SELECT
- USING ((
- (visibility = 'public'::feed_visibility AND (SELECT auth.role()) = 'authenticated')
- OR (visibility = 'authenticated'::feed_visibility AND EXISTS (SELECT 1 FROM subscriptions s WHERE s.feed_id = feeds.id AND s.user_id = (SELECT auth.uid())))
- ));
-
--- folders
-CREATE POLICY "users can view own folders" ON public.folders AS PERMISSIVE FOR SELECT USING ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can insert own folders" ON public.folders AS PERMISSIVE FOR INSERT WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can update own folders" ON public.folders AS PERMISSIVE FOR UPDATE USING ((user_id = (SELECT auth.uid()))) WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can delete own folders" ON public.folders AS PERMISSIVE FOR DELETE USING ((user_id = (SELECT auth.uid())));
-
--- muted_keywords
-CREATE POLICY "users can view own muted keywords" ON public.muted_keywords AS PERMISSIVE FOR SELECT USING ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can insert own muted keywords" ON public.muted_keywords AS PERMISSIVE FOR INSERT WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can delete own muted keywords" ON public.muted_keywords AS PERMISSIVE FOR DELETE USING ((user_id = (SELECT auth.uid())));
-
--- shared_entries
-CREATE POLICY "authenticated users can view own shared entries" ON public.shared_entries AS PERMISSIVE FOR SELECT TO authenticated USING ((user_id = (SELECT auth.uid())));
-CREATE POLICY "owners can insert own shared entries" ON public.shared_entries AS PERMISSIVE FOR INSERT WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "owners can update own shared entries" ON public.shared_entries AS PERMISSIVE FOR UPDATE USING ((user_id = (SELECT auth.uid()))) WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "owners can delete own shared entries" ON public.shared_entries AS PERMISSIVE FOR DELETE USING ((user_id = (SELECT auth.uid())));
-
--- subscriptions
-CREATE POLICY "users can view own subscriptions" ON public.subscriptions AS PERMISSIVE FOR SELECT USING ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can insert own subscriptions" ON public.subscriptions AS PERMISSIVE FOR INSERT WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can update own subscriptions" ON public.subscriptions AS PERMISSIVE FOR UPDATE USING ((user_id = (SELECT auth.uid()))) WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can delete own subscriptions" ON public.subscriptions AS PERMISSIVE FOR DELETE USING ((user_id = (SELECT auth.uid())));
-
--- user_entry_states
-CREATE POLICY "users can manage own entry states" ON public.user_entry_states AS PERMISSIVE FOR ALL USING ((user_id = (SELECT auth.uid()))) WITH CHECK ((user_id = (SELECT auth.uid())));
-
--- user_highlights
-CREATE POLICY "users can view own highlights" ON public.user_highlights AS PERMISSIVE FOR SELECT USING ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can insert own highlights" ON public.user_highlights AS PERMISSIVE FOR INSERT WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can update own highlights" ON public.user_highlights AS PERMISSIVE FOR UPDATE USING ((user_id = (SELECT auth.uid()))) WITH CHECK ((user_id = (SELECT auth.uid())));
-CREATE POLICY "users can delete own highlights" ON public.user_highlights AS PERMISSIVE FOR DELETE USING ((user_id = (SELECT auth.uid())));
-
--- user_profiles
-CREATE POLICY "users can view own profile" ON public.user_profiles AS PERMISSIVE FOR SELECT USING ((id = (SELECT auth.uid())));
-CREATE POLICY "users can update own profile" ON public.user_profiles AS PERMISSIVE FOR UPDATE USING ((id = (SELECT auth.uid()))) WITH CHECK ((id = (SELECT auth.uid())));
-
--- =============================================================================
--- functions
--- =============================================================================
-
-CREATE OR REPLACE FUNCTION public.handle_new_user()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-begin
- insert into public.user_profiles (id, display_name)
- values (new.id, coalesce(new.raw_user_meta_data ->> 'display_name', split_part(new.email, '@', 1)));
+CREATE EXTENSION IF NOT EXISTS "pgsodium";
- return new;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.subscribe_to_feed(feed_url text, target_folder_id uuid DEFAULT NULL::uuid, feed_custom_title text DEFAULT NULL::text, feed_credential text DEFAULT NULL::text, feed_auth_type text DEFAULT 'bearer'::text)
- RETURNS uuid
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-declare
- resolved_feed_id uuid;
- new_subscription_id uuid;
- resolved_vault_id uuid;
- resolved_visibility public.feed_visibility;
- existing_subscription_id uuid;
-begin
- if feed_credential is not null and feed_auth_type not in ('bearer', 'basic', 'query_param') then
- raise exception 'Invalid authentication type: %. Must be bearer, basic, or query_param', feed_auth_type;
- end if;
- if feed_credential is not null then
- resolved_visibility := 'authenticated';
- else
- resolved_visibility := 'public';
- end if;
+--
+-- Name: EXTENSION "pgsodium"; Type: COMMENT; Schema: -; Owner:
+--
- if resolved_visibility = 'public' then
- select id into resolved_feed_id
- from public.feeds
- where url = feed_url and visibility = 'public';
+-- COMMENT ON EXTENSION "pgsodium" IS 'Pgsodium is a modern cryptography library for Postgres.';
- if resolved_feed_id is null then
- insert into public.feeds (url, visibility)
- values (feed_url, 'public')
- returning id into resolved_feed_id;
- end if;
- select id into existing_subscription_id
- from public.subscriptions
- where user_id = auth.uid() and feed_id = resolved_feed_id;
+--
+-- Name: SCHEMA "public"; Type: COMMENT; Schema: -; Owner: pg_database_owner
+--
- if existing_subscription_id is not null then
- raise exception 'You are already subscribed to this feed';
- end if;
- else
- insert into public.feeds (url, visibility, fetch_interval_seconds)
- values (feed_url, 'authenticated', 300)
- returning id into resolved_feed_id;
+COMMENT ON SCHEMA "public" IS 'standard public schema';
- resolved_vault_id := vault.create_secret(
- jsonb_build_object(
- 'authenticationType', feed_auth_type,
- 'authenticationValue', feed_credential
- )::text,
- 'feed_credential_' || resolved_feed_id::text,
- 'Credential for authenticated feed'
- );
- end if;
- insert into public.subscriptions (user_id, feed_id, folder_id, custom_title, vault_secret_id)
- values (auth.uid(), resolved_feed_id, target_folder_id, feed_custom_title, resolved_vault_id)
- returning id into new_subscription_id;
+--
+-- Name: moddatetime; Type: EXTENSION; Schema: -; Owner: -
+--
- return new_subscription_id;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.update_feed_credentials(p_feed_id uuid, p_auth_type text, p_credential text)
- RETURNS void
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-declare
- v_vault_secret_id uuid;
-begin
- if p_auth_type not in ('bearer', 'basic', 'query_param') then
- raise exception 'Invalid authentication type: %. Must be bearer, basic, or query_param', p_auth_type;
- end if;
+CREATE EXTENSION IF NOT EXISTS "moddatetime" WITH SCHEMA "extensions";
- select s.vault_secret_id into v_vault_secret_id
- from public.subscriptions s
- where s.feed_id = p_feed_id and s.user_id = auth.uid();
- if v_vault_secret_id is null then
- raise exception 'No authenticated subscription found for this feed';
- end if;
+--
+-- Name: EXTENSION "moddatetime"; Type: COMMENT; Schema: -; Owner:
+--
- perform vault.update_secret(
- v_vault_secret_id,
- jsonb_build_object(
- 'authenticationType', p_auth_type,
- 'authenticationValue', p_credential
- )::text
- );
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.add_feed_credentials(p_subscription_id uuid, p_auth_type text, p_credential text)
- RETURNS void
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
+-- COMMENT ON EXTENSION "moddatetime" IS 'functions for tracking last modification time';
+
+
+--
+-- Name: pg_graphql; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
+
+
+--
+-- Name: EXTENSION "pg_graphql"; Type: COMMENT; Schema: -; Owner:
+--
+
+-- COMMENT ON EXTENSION "pg_graphql" IS 'pg_graphql: GraphQL support';
+
+
+--
+-- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions";
+
+
+--
+-- Name: EXTENSION "pg_stat_statements"; Type: COMMENT; Schema: -; Owner:
+--
+
+-- COMMENT ON EXTENSION "pg_stat_statements" IS 'track planning and execution statistics of all SQL statements executed';
+
+
+--
+-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS "pg_trgm" WITH SCHEMA "extensions";
+
+
+--
+-- Name: EXTENSION "pg_trgm"; Type: COMMENT; Schema: -; Owner:
+--
+
+-- COMMENT ON EXTENSION "pg_trgm" IS 'text similarity measurement and index searching based on trigrams';
+
+
+--
+-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions";
+
+
+--
+-- Name: EXTENSION "pgcrypto"; Type: COMMENT; Schema: -; Owner:
+--
+
+-- COMMENT ON EXTENSION "pgcrypto" IS 'cryptographic functions';
+
+
+--
+-- Name: pgmq; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS "pgmq";
+
+
+--
+-- Name: EXTENSION "pgmq"; Type: COMMENT; Schema: -; Owner:
+--
+
+-- COMMENT ON EXTENSION "pgmq" IS 'A lightweight message queue. Like AWS SQS and RSMQ but on Postgres.';
+
+
+--
+-- Name: supabase_vault; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
+
+
+--
+-- Name: EXTENSION "supabase_vault"; Type: COMMENT; Schema: -; Owner:
+--
+
+-- COMMENT ON EXTENSION "supabase_vault" IS 'Supabase Vault Extension';
+
+
+--
+-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";
+
+
+--
+-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner:
+--
+
+-- COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)';
+
+
+--
+-- Name: feed_visibility; Type: TYPE; Schema: public; Owner: postgres
+--
+
+CREATE TYPE "public"."feed_visibility" AS ENUM (
+ 'public',
+ 'authenticated'
+);
+
+
+ALTER TYPE "public"."feed_visibility" OWNER TO "postgres";
+
+--
+-- Name: subscription_tier; Type: TYPE; Schema: public; Owner: postgres
+--
+
+CREATE TYPE "public"."subscription_tier" AS ENUM (
+ 'free',
+ 'pro',
+ 'developer'
+);
+
+
+ALTER TYPE "public"."subscription_tier" OWNER TO "postgres";
+
+--
+-- Name: add_feed_credentials("uuid", "text", "text"); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."add_feed_credentials"("p_subscription_id" "uuid", "p_auth_type" "text", "p_credential" "text") RETURNS "void"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
declare
v_old_feed_id uuid;
v_feed_url text;
@@ -530,83 +246,374 @@ begin
set subscriber_count = greatest(subscriber_count - 1, 0)
where id = v_old_feed_id;
end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.update_feed_url(target_feed_id uuid, new_url text)
- RETURNS void
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."add_feed_credentials"("p_subscription_id" "uuid", "p_auth_type" "text", "p_credential" "text") OWNER TO "postgres";
+
+--
+-- Name: check_custom_feed_limit(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."check_custom_feed_limit"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+declare
+ current_tier public.subscription_tier;
+ current_count int;
+ maximum_allowed int;
+begin
+ select tier, custom_feed_count into current_tier, current_count
+ from public.user_profiles where id = new.user_id;
+
+ maximum_allowed := case current_tier when 'free' then 1 when 'pro' then 1000 when 'developer' then 1000 else 1 end;
+
+ if current_count >= maximum_allowed then
+ raise exception 'You have reached the maximum number of custom feeds for your plan (% on % tier)', maximum_allowed, current_tier;
+ end if;
+
+ update public.user_profiles set custom_feed_count = custom_feed_count + 1 where id = new.user_id;
+
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."check_custom_feed_limit"() OWNER TO "postgres";
+
+--
+-- Name: check_folder_limit(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."check_folder_limit"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+declare
+ current_tier public.subscription_tier;
+ current_count int;
+ maximum_allowed int;
+begin
+ select tier, folder_count into current_tier, current_count
+ from public.user_profiles where id = new.user_id;
+
+ maximum_allowed := case current_tier when 'free' then 3 when 'pro' then 10000 when 'developer' then 10000 else 3 end;
+
+ if current_count >= maximum_allowed then
+ raise exception 'You have reached the maximum number of folders for your plan (% on % tier)', maximum_allowed, current_tier;
+ end if;
+
+ update public.user_profiles set folder_count = folder_count + 1 where id = new.user_id;
+
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."check_folder_limit"() OWNER TO "postgres";
+
+--
+-- Name: check_highlight_limit(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."check_highlight_limit"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+declare
+ current_tier public.subscription_tier;
+ current_count int;
+ maximum_allowed int;
+begin
+ select tier, highlight_count into current_tier, current_count
+ from public.user_profiles where id = new.user_id;
+
+ maximum_allowed := case current_tier when 'free' then 50 when 'pro' then 100000 when 'developer' then 100000 else 50 end;
+
+ if current_count >= maximum_allowed then
+ raise exception 'You have reached the maximum number of highlights for your plan (% on % tier)', maximum_allowed, current_tier;
+ end if;
+
+ update public.user_profiles set highlight_count = highlight_count + 1 where id = new.user_id;
+
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."check_highlight_limit"() OWNER TO "postgres";
+
+--
+-- Name: check_muted_keyword_limit(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."check_muted_keyword_limit"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+declare
+ current_tier public.subscription_tier;
+ current_count int;
+ maximum_allowed int;
+begin
+ select tier, muted_keyword_count into current_tier, current_count
+ from public.user_profiles where id = new.user_id;
+
+ maximum_allowed := case current_tier when 'free' then 5 when 'pro' then 10000 when 'developer' then 10000 else 5 end;
+
+ if current_count >= maximum_allowed then
+ raise exception 'You have reached the maximum number of muted keywords for your plan (% on % tier)', maximum_allowed, current_tier;
+ end if;
+
+ update public.user_profiles set muted_keyword_count = muted_keyword_count + 1 where id = new.user_id;
+
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."check_muted_keyword_limit"() OWNER TO "postgres";
+
+--
+-- Name: check_rate_limit("text", integer, integer); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."check_rate_limit"("p_identifier" "text", "p_limit" integer, "p_window_seconds" integer) RETURNS "jsonb"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO 'public'
+ AS $$
+DECLARE
+ v_window_start timestamptz;
+ v_current_count integer;
BEGIN
- IF NOT EXISTS (
- SELECT 1 FROM public.subscriptions
- WHERE feed_id = target_feed_id
- AND user_id = auth.uid()
- ) THEN
- RAISE EXCEPTION 'not subscribed to this feed';
- END IF;
+ v_window_start := to_timestamp(
+ floor(extract(epoch from now()) / p_window_seconds) * p_window_seconds
+ );
- UPDATE public.feeds SET url = new_url WHERE id = target_feed_id;
+ INSERT INTO public.rate_limits (identifier, window_start, request_count)
+ VALUES (p_identifier, v_window_start, 1)
+ ON CONFLICT (identifier, window_start)
+ DO UPDATE SET request_count = rate_limits.request_count + 1
+ RETURNING request_count INTO v_current_count;
+
+ RETURN jsonb_build_object(
+ 'success', v_current_count <= p_limit,
+ 'remaining', greatest(p_limit - v_current_count, 0)
+ );
END;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.get_timeline(target_folder_id uuid DEFAULT NULL::uuid, target_feed_id uuid DEFAULT NULL::uuid, result_limit integer DEFAULT 50, pagination_cursor timestamp with time zone DEFAULT NULL::timestamp with time zone, unread_only boolean DEFAULT false)
- RETURNS TABLE(entry_id uuid, feed_id uuid, feed_title text, custom_title text, entry_title text, entry_url text, author text, summary text, image_url text, published_at timestamp with time zone, is_read boolean, is_saved boolean, enclosure_url text, enclosure_type text)
- LANGUAGE sql
- STABLE SECURITY DEFINER
- SET search_path TO ''
-AS $function$
- SELECT
- e.id AS entry_id,
- e.feed_id,
- f.title AS feed_title,
- s.custom_title,
- e.title AS entry_title,
- e.url AS entry_url,
- e.author,
- e.summary,
- e.image_url,
- e.published_at,
- COALESCE(ues.read, false) AS is_read,
- COALESCE(ues.saved, false) AS is_saved,
- e.enclosure_url,
- e.enclosure_type
- FROM public.entries e
- INNER JOIN public.subscriptions s
- ON s.feed_id = e.feed_id AND s.user_id = auth.uid()
- INNER JOIN public.feeds f ON f.id = e.feed_id
- LEFT JOIN public.user_entry_states ues
- ON ues.entry_id = e.id AND ues.user_id = auth.uid()
- WHERE
- (e.owner_id IS NULL OR e.owner_id = auth.uid())
- AND (target_folder_id IS NULL OR s.folder_id = target_folder_id)
- AND (target_feed_id IS NULL OR e.feed_id = target_feed_id)
- AND (target_feed_id IS NOT NULL OR s.hidden_from_timeline = false)
- AND (pagination_cursor IS NULL OR e.published_at < pagination_cursor)
- AND (NOT unread_only OR COALESCE(ues.read, false) = false)
- AND NOT EXISTS (
- SELECT 1 FROM public.muted_keywords mk
- WHERE mk.user_id = auth.uid()
- AND (
- e.title ILIKE '%' || mk.keyword || '%'
- OR e.summary ILIKE '%' || mk.keyword || '%'
- )
- )
- AND (
- (SELECT tier FROM public.user_profiles WHERE id = auth.uid()) = 'pro'
- OR e.published_at >= now() - interval '14 days'
- )
- ORDER BY e.published_at DESC NULLS LAST
- LIMIT result_limit;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.get_custom_feed(target_custom_feed_id uuid, result_limit integer DEFAULT 50, pagination_cursor timestamp with time zone DEFAULT NULL::timestamp with time zone)
- RETURNS TABLE(entry_id uuid, feed_id uuid, feed_title text, entry_title text, entry_url text, author text, summary text, published_at timestamp with time zone, is_read boolean, is_saved boolean)
- LANGUAGE sql
- STABLE SECURITY DEFINER
- SET search_path TO ''
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."check_rate_limit"("p_identifier" "text", "p_limit" integer, "p_window_seconds" integer) OWNER TO "postgres";
+
+--
+-- Name: check_subscription_limit(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."check_subscription_limit"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+declare
+ current_tier public.subscription_tier;
+ current_count int;
+ maximum_allowed int;
+ feed_visibility public.feed_visibility;
+begin
+ select tier, feed_count into current_tier, current_count
+ from public.user_profiles where id = new.user_id;
+
+ maximum_allowed := case current_tier when 'free' then 10 when 'pro' then 200 when 'developer' then 500 else 10 end;
+
+ if current_count >= maximum_allowed then
+ raise exception 'You have reached the maximum number of feeds for your plan (% on % tier)', maximum_allowed, current_tier;
+ end if;
+
+ select visibility into feed_visibility from public.feeds where id = new.feed_id;
+
+ if current_tier = 'free' and feed_visibility = 'authenticated' then
+ raise exception 'Authenticated feeds require a Pro subscription';
+ end if;
+
+ update public.user_profiles set feed_count = feed_count + 1 where id = new.user_id;
+
+ update public.feeds
+ set subscriber_count = subscriber_count + 1
+ where id = new.feed_id;
+
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."check_subscription_limit"() OWNER TO "postgres";
+
+--
+-- Name: cleanup_stale_entries(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."cleanup_stale_entries"() RETURNS integer
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO 'public'
+ AS $$
+DECLARE
+ deleted_count integer;
+BEGIN
+ DELETE FROM public.entries
+ WHERE published_at < NOW() - INTERVAL '90 days'
+ AND id NOT IN (
+ SELECT entry_id FROM public.user_entry_states WHERE saved = true
+ );
+ GET DIAGNOSTICS deleted_count = ROW_COUNT;
+ RETURN deleted_count;
+END;
+$$;
+
+
+ALTER FUNCTION "public"."cleanup_stale_entries"() OWNER TO "postgres";
+
+--
+-- Name: decrement_custom_feed_count(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."decrement_custom_feed_count"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+begin
+ update public.user_profiles
+ set custom_feed_count = greatest(custom_feed_count - 1, 0)
+ where id = old.user_id;
+
+ return old;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."decrement_custom_feed_count"() OWNER TO "postgres";
+
+--
+-- Name: decrement_folder_count(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."decrement_folder_count"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+begin
+ update public.user_profiles
+ set folder_count = greatest(folder_count - 1, 0)
+ where id = old.user_id;
+
+ return old;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."decrement_folder_count"() OWNER TO "postgres";
+
+--
+-- Name: decrement_highlight_count(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."decrement_highlight_count"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+begin
+ update public.user_profiles
+ set highlight_count = greatest(highlight_count - 1, 0)
+ where id = old.user_id;
+
+ return old;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."decrement_highlight_count"() OWNER TO "postgres";
+
+--
+-- Name: decrement_muted_keyword_count(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."decrement_muted_keyword_count"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+begin
+ update public.user_profiles
+ set muted_keyword_count = greatest(muted_keyword_count - 1, 0)
+ where id = old.user_id;
+
+ return old;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."decrement_muted_keyword_count"() OWNER TO "postgres";
+
+--
+-- Name: decrement_subscription_count(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."decrement_subscription_count"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+declare
+ feed_vis public.feed_visibility;
+begin
+ update public.user_profiles
+ set feed_count = greatest(feed_count - 1, 0)
+ where id = old.user_id;
+
+ update public.feeds
+ set subscriber_count = greatest(subscriber_count - 1, 0)
+ where id = old.feed_id;
+
+ select visibility into feed_vis from public.feeds where id = old.feed_id;
+
+ if feed_vis = 'authenticated' then
+ delete from public.entries where feed_id = old.feed_id and owner_id = old.user_id;
+ if old.vault_secret_id is not null then
+ delete from vault.secrets where id = old.vault_secret_id;
+ end if;
+ delete from public.feeds where id = old.feed_id;
+ end if;
+
+ return old;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."decrement_subscription_count"() OWNER TO "postgres";
+
+--
+-- Name: enqueue_feed_refresh_on_subscribe(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."enqueue_feed_refresh_on_subscribe"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+BEGIN
+ PERFORM pgmq.send('feed_refresh', jsonb_build_object('feed_id', NEW.feed_id));
+ RETURN NEW;
+END;
+$$;
+
+
+ALTER FUNCTION "public"."enqueue_feed_refresh_on_subscribe"() OWNER TO "postgres";
+
+--
+-- Name: get_custom_feed("uuid", integer, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."get_custom_feed"("target_custom_feed_id" "uuid", "result_limit" integer DEFAULT 50, "pagination_cursor" timestamp with time zone DEFAULT NULL::timestamp with time zone) RETURNS TABLE("entry_id" "uuid", "feed_id" "uuid", "feed_title" "text", "entry_title" "text", "entry_url" "text", "author" "text", "summary" "text", "published_at" timestamp with time zone, "is_read" boolean, "is_saved" boolean)
+ LANGUAGE "sql" STABLE SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
select
e.id, e.feed_id, f.title, e.title, e.url, e.author, e.summary,
e.published_at,
@@ -629,14 +636,19 @@ AS $function$
)
order by e.published_at desc nulls last
limit result_limit;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.get_custom_feed_timeline(p_custom_feed_id uuid, p_result_limit integer DEFAULT 50, p_pagination_cursor timestamp with time zone DEFAULT NULL::timestamp with time zone)
- RETURNS TABLE(entry_id uuid, feed_id uuid, feed_title text, custom_title text, entry_title text, entry_url text, author text, summary text, image_url text, published_at timestamp with time zone, is_read boolean, is_saved boolean, enclosure_url text, enclosure_type text)
- LANGUAGE plpgsql
- STABLE SECURITY DEFINER
- SET search_path TO ''
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."get_custom_feed"("target_custom_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone) OWNER TO "postgres";
+
+--
+-- Name: get_custom_feed_timeline("uuid", integer, timestamp with time zone); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."get_custom_feed_timeline"("p_custom_feed_id" "uuid", "p_result_limit" integer DEFAULT 50, "p_pagination_cursor" timestamp with time zone DEFAULT NULL::timestamp with time zone) RETURNS TABLE("entry_id" "uuid", "feed_id" "uuid", "feed_title" "text", "custom_title" "text", "entry_title" "text", "entry_url" "text", "author" "text", "summary" "text", "image_url" "text", "published_at" timestamp with time zone, "is_read" boolean, "is_saved" boolean, "enclosure_url" "text", "enclosure_type" "text")
+ LANGUAGE "plpgsql" STABLE SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
DECLARE
v_query TEXT;
v_match_mode TEXT;
@@ -712,14 +724,74 @@ BEGIN
ORDER BY e.published_at DESC NULLS LAST
LIMIT p_result_limit;
END;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.get_unread_counts()
- RETURNS TABLE(feed_id uuid, unread_count bigint)
- LANGUAGE sql
- STABLE SECURITY DEFINER
- SET search_path TO 'public'
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."get_custom_feed_timeline"("p_custom_feed_id" "uuid", "p_result_limit" integer, "p_pagination_cursor" timestamp with time zone) OWNER TO "postgres";
+
+--
+-- Name: get_timeline("uuid", "uuid", integer, timestamp with time zone, boolean); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."get_timeline"("target_folder_id" "uuid" DEFAULT NULL::"uuid", "target_feed_id" "uuid" DEFAULT NULL::"uuid", "result_limit" integer DEFAULT 50, "pagination_cursor" timestamp with time zone DEFAULT NULL::timestamp with time zone, "unread_only" boolean DEFAULT false) RETURNS TABLE("entry_id" "uuid", "feed_id" "uuid", "feed_title" "text", "custom_title" "text", "entry_title" "text", "entry_url" "text", "author" "text", "summary" "text", "image_url" "text", "published_at" timestamp with time zone, "is_read" boolean, "is_saved" boolean, "enclosure_url" "text", "enclosure_type" "text")
+ LANGUAGE "sql" STABLE SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+ SELECT
+ e.id AS entry_id,
+ e.feed_id,
+ f.title AS feed_title,
+ s.custom_title,
+ e.title AS entry_title,
+ e.url AS entry_url,
+ e.author,
+ e.summary,
+ e.image_url,
+ e.published_at,
+ COALESCE(ues.read, false) AS is_read,
+ COALESCE(ues.saved, false) AS is_saved,
+ e.enclosure_url,
+ e.enclosure_type
+ FROM public.entries e
+ INNER JOIN public.subscriptions s
+ ON s.feed_id = e.feed_id AND s.user_id = auth.uid()
+ INNER JOIN public.feeds f ON f.id = e.feed_id
+ LEFT JOIN public.user_entry_states ues
+ ON ues.entry_id = e.id AND ues.user_id = auth.uid()
+ WHERE
+ (e.owner_id IS NULL OR e.owner_id = auth.uid())
+ AND (target_folder_id IS NULL OR s.folder_id = target_folder_id)
+ AND (target_feed_id IS NULL OR e.feed_id = target_feed_id)
+ AND (target_feed_id IS NOT NULL OR s.hidden_from_timeline = false)
+ AND (pagination_cursor IS NULL OR e.published_at < pagination_cursor)
+ AND (NOT unread_only OR COALESCE(ues.read, false) = false)
+ AND NOT EXISTS (
+ SELECT 1 FROM public.muted_keywords mk
+ WHERE mk.user_id = auth.uid()
+ AND (
+ e.title ILIKE '%' || mk.keyword || '%'
+ OR e.summary ILIKE '%' || mk.keyword || '%'
+ )
+ )
+ AND (
+ (SELECT tier FROM public.user_profiles WHERE id = auth.uid()) IN ('pro', 'developer')
+ OR e.published_at >= now() - interval '14 days'
+ )
+ ORDER BY e.published_at DESC NULLS LAST
+ LIMIT result_limit;
+$$;
+
+
+ALTER FUNCTION "public"."get_timeline"("target_folder_id" "uuid", "target_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone, "unread_only" boolean) OWNER TO "postgres";
+
+--
+-- Name: get_unread_counts(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."get_unread_counts"() RETURNS TABLE("feed_id" "uuid", "unread_count" bigint)
+ LANGUAGE "sql" STABLE SECURITY DEFINER
+ SET "search_path" TO 'public'
+ AS $$
SELECT e.feed_id, COUNT(*) AS unread_count
FROM entries e
INNER JOIN subscriptions s ON s.feed_id = e.feed_id AND s.user_id = auth.uid()
@@ -730,14 +802,38 @@ AS $function$
AND ues.read = true
)
GROUP BY e.feed_id;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.mark_all_as_read(p_feed_id uuid DEFAULT NULL::uuid, p_folder_id uuid DEFAULT NULL::uuid, p_read_state boolean DEFAULT true)
- RETURNS bigint
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO 'public'
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."get_unread_counts"() OWNER TO "postgres";
+
+--
+-- Name: handle_new_user(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."handle_new_user"() RETURNS "trigger"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+begin
+ insert into public.user_profiles (id, display_name)
+ values (new.id, coalesce(new.raw_user_meta_data ->> 'display_name', split_part(new.email, '@', 1)));
+
+ return new;
+end;
+$$;
+
+
+ALTER FUNCTION "public"."handle_new_user"() OWNER TO "postgres";
+
+--
+-- Name: mark_all_as_read("uuid", "uuid", boolean); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."mark_all_as_read"("p_feed_id" "uuid" DEFAULT NULL::"uuid", "p_folder_id" "uuid" DEFAULT NULL::"uuid", "p_read_state" boolean DEFAULT true) RETURNS bigint
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO 'public'
+ AS $$
DECLARE
affected_count bigint;
BEGIN
@@ -769,14 +865,85 @@ BEGIN
GET DIAGNOSTICS affected_count = ROW_COUNT;
RETURN affected_count;
END;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.search_entries(p_query text, p_result_limit integer DEFAULT 30)
- RETURNS TABLE(entry_id uuid, feed_id uuid, feed_title text, custom_title text, entry_title text, entry_url text, author text, summary text, image_url text, published_at timestamp with time zone, is_read boolean, is_saved boolean)
- LANGUAGE sql
- STABLE SECURITY DEFINER
- SET search_path TO ''
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."mark_all_as_read"("p_feed_id" "uuid", "p_folder_id" "uuid", "p_read_state" boolean) OWNER TO "postgres";
+
+--
+-- Name: prevent_billing_column_modification(); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."prevent_billing_column_modification"() RETURNS "trigger"
+ LANGUAGE "plpgsql"
+ SET "search_path" TO 'public'
+ AS $$
+BEGIN
+ IF coalesce(auth.role(), '') != 'service_role' THEN
+ IF NEW.tier IS DISTINCT FROM OLD.tier
+ OR NEW.stripe_customer_identifier IS DISTINCT FROM OLD.stripe_customer_identifier
+ OR NEW.stripe_subscription_identifier IS DISTINCT FROM OLD.stripe_subscription_identifier
+ OR NEW.stripe_subscription_status IS DISTINCT FROM OLD.stripe_subscription_status
+ OR NEW.stripe_current_period_end IS DISTINCT FROM OLD.stripe_current_period_end
+ THEN
+ RAISE EXCEPTION 'Billing columns cannot be modified directly';
+ END IF;
+ END IF;
+ RETURN NEW;
+END;
+$$;
+
+
+ALTER FUNCTION "public"."prevent_billing_column_modification"() OWNER TO "postgres";
+
+--
+-- Name: request_feed_refresh("uuid"); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."request_feed_refresh"("target_subscription_id" "uuid") RETURNS "void"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+declare
+ resolved_feed_id uuid;
+ resolved_tier public.subscription_tier;
+begin
+ select s.feed_id into resolved_feed_id
+ from public.subscriptions s
+ where s.id = target_subscription_id and s.user_id = auth.uid();
+
+ if resolved_feed_id is null then
+ raise exception 'Subscription not found';
+ end if;
+
+ select tier into resolved_tier from public.user_profiles where id = auth.uid();
+
+ if resolved_tier = 'free' then
+ raise exception 'Manual feed refresh requires a Pro subscription';
+ end if;
+
+ perform pgmq.send(
+ 'feed_refresh',
+ jsonb_build_object(
+ 'feed_id', resolved_feed_id,
+ 'user_id', auth.uid(),
+ 'requested_at', now()
+ )
+ );
+end;
+$$;
+
+
+ALTER FUNCTION "public"."request_feed_refresh"("target_subscription_id" "uuid") OWNER TO "postgres";
+
+--
+-- Name: search_entries("text", integer); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."search_entries"("p_query" "text", "p_result_limit" integer DEFAULT 30) RETURNS TABLE("entry_id" "uuid", "feed_id" "uuid", "feed_title" "text", "custom_title" "text", "entry_title" "text", "entry_url" "text", "author" "text", "summary" "text", "image_url" "text", "published_at" timestamp with time zone, "is_read" boolean, "is_saved" boolean)
+ LANGUAGE "sql" STABLE SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
SELECT
e.id AS entry_id,
e.feed_id,
@@ -806,441 +973,2715 @@ AS $function$
)
ORDER BY e.published_at DESC
LIMIT p_result_limit;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.request_feed_refresh(target_subscription_id uuid)
- RETURNS void
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."search_entries"("p_query" "text", "p_result_limit" integer) OWNER TO "postgres";
+
+--
+-- Name: subscribe_to_feed("text", "uuid", "text", "text", "text"); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."subscribe_to_feed"("feed_url" "text", "target_folder_id" "uuid" DEFAULT NULL::"uuid", "feed_custom_title" "text" DEFAULT NULL::"text", "feed_credential" "text" DEFAULT NULL::"text", "feed_auth_type" "text" DEFAULT 'bearer'::"text") RETURNS "uuid"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
declare
resolved_feed_id uuid;
- resolved_tier public.subscription_tier;
+ new_subscription_id uuid;
+ resolved_vault_id uuid;
+ resolved_visibility public.feed_visibility;
+ existing_subscription_id uuid;
begin
- select s.feed_id into resolved_feed_id
- from public.subscriptions s
- where s.id = target_subscription_id and s.user_id = auth.uid();
+ if feed_credential is not null and feed_auth_type not in ('bearer', 'basic', 'query_param') then
+ raise exception 'Invalid authentication type: %. Must be bearer, basic, or query_param', feed_auth_type;
+ end if;
- if resolved_feed_id is null then
- raise exception 'Subscription not found';
+ if feed_credential is not null then
+ resolved_visibility := 'authenticated';
+ else
+ resolved_visibility := 'public';
end if;
- select tier into resolved_tier from public.user_profiles where id = auth.uid();
+ if resolved_visibility = 'public' then
+ select id into resolved_feed_id
+ from public.feeds
+ where url = feed_url and visibility = 'public';
- if resolved_tier = 'free' then
- raise exception 'Manual feed refresh requires a Pro subscription';
+ if resolved_feed_id is null then
+ insert into public.feeds (url, visibility)
+ values (feed_url, 'public')
+ returning id into resolved_feed_id;
+ end if;
+
+ select id into existing_subscription_id
+ from public.subscriptions
+ where user_id = auth.uid() and feed_id = resolved_feed_id;
+
+ if existing_subscription_id is not null then
+ raise exception 'You are already subscribed to this feed';
+ end if;
+ else
+ insert into public.feeds (url, visibility, fetch_interval_seconds)
+ values (feed_url, 'authenticated', 300)
+ returning id into resolved_feed_id;
+
+ resolved_vault_id := vault.create_secret(
+ jsonb_build_object(
+ 'authenticationType', feed_auth_type,
+ 'authenticationValue', feed_credential
+ )::text,
+ 'feed_credential_' || resolved_feed_id::text,
+ 'Credential for authenticated feed'
+ );
end if;
- perform pgmq.send(
- 'feed_refresh',
- jsonb_build_object(
- 'feed_id', resolved_feed_id,
- 'user_id', auth.uid(),
- 'requested_at', now()
- )
- );
+ insert into public.subscriptions (user_id, feed_id, folder_id, custom_title, vault_secret_id)
+ values (auth.uid(), resolved_feed_id, target_folder_id, feed_custom_title, resolved_vault_id)
+ returning id into new_subscription_id;
+
+ return new_subscription_id;
end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.check_subscription_limit()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
+$$;
+
+
+ALTER FUNCTION "public"."subscribe_to_feed"("feed_url" "text", "target_folder_id" "uuid", "feed_custom_title" "text", "feed_credential" "text", "feed_auth_type" "text") OWNER TO "postgres";
+
+--
+-- Name: update_feed_credentials("uuid", "text", "text"); Type: FUNCTION; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE FUNCTION "public"."update_feed_credentials"("p_feed_id" "uuid", "p_auth_type" "text", "p_credential" "text") RETURNS "void"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
declare
- current_tier public.subscription_tier;
- current_count int;
- maximum_allowed int;
- feed_visibility public.feed_visibility;
+ v_vault_secret_id uuid;
begin
- select tier, feed_count into current_tier, current_count
- from public.user_profiles where id = new.user_id;
+ if p_auth_type not in ('bearer', 'basic', 'query_param') then
+ raise exception 'Invalid authentication type: %. Must be bearer, basic, or query_param', p_auth_type;
+ end if;
- maximum_allowed := case current_tier when 'free' then 10 when 'pro' then 200 when 'developer' then 500 else 10 end;
+ select s.vault_secret_id into v_vault_secret_id
+ from public.subscriptions s
+ where s.feed_id = p_feed_id and s.user_id = auth.uid();
- if current_count >= maximum_allowed then
- raise exception 'You have reached the maximum number of feeds for your plan (% on % tier)', maximum_allowed, current_tier;
+ if v_vault_secret_id is null then
+ raise exception 'No authenticated subscription found for this feed';
end if;
- select visibility into feed_visibility from public.feeds where id = new.feed_id;
+ perform vault.update_secret(
+ v_vault_secret_id,
+ jsonb_build_object(
+ 'authenticationType', p_auth_type,
+ 'authenticationValue', p_credential
+ )::text
+ );
+end;
+$$;
- if current_tier = 'free' and feed_visibility = 'authenticated' then
- raise exception 'Authenticated feeds require a Pro subscription';
- end if;
- update public.user_profiles set feed_count = feed_count + 1 where id = new.user_id;
+ALTER FUNCTION "public"."update_feed_credentials"("p_feed_id" "uuid", "p_auth_type" "text", "p_credential" "text") OWNER TO "postgres";
- update public.feeds
- set subscriber_count = subscriber_count + 1
- where id = new.feed_id;
+--
+-- Name: update_feed_url("uuid", "text"); Type: FUNCTION; Schema: public; Owner: postgres
+--
- return new;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.check_folder_limit()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-declare
- current_tier public.subscription_tier;
- current_count int;
- maximum_allowed int;
-begin
- select tier, folder_count into current_tier, current_count
- from public.user_profiles where id = new.user_id;
+CREATE OR REPLACE FUNCTION "public"."update_feed_url"("target_feed_id" "uuid", "new_url" "text") RETURNS "void"
+ LANGUAGE "plpgsql" SECURITY DEFINER
+ SET "search_path" TO ''
+ AS $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM public.subscriptions
+ WHERE feed_id = target_feed_id
+ AND user_id = auth.uid()
+ ) THEN
+ RAISE EXCEPTION 'not subscribed to this feed';
+ END IF;
- maximum_allowed := case current_tier when 'free' then 3 when 'pro' then 10000 when 'developer' then 10000 else 3 end;
+ UPDATE public.feeds SET url = new_url WHERE id = target_feed_id;
+END;
+$$;
- if current_count >= maximum_allowed then
- raise exception 'You have reached the maximum number of folders for your plan (% on % tier)', maximum_allowed, current_tier;
- end if;
- update public.user_profiles set folder_count = folder_count + 1 where id = new.user_id;
+ALTER FUNCTION "public"."update_feed_url"("target_feed_id" "uuid", "new_url" "text") OWNER TO "postgres";
- return new;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.check_muted_keyword_limit()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-declare
- current_tier public.subscription_tier;
- current_count int;
- maximum_allowed int;
-begin
- select tier, muted_keyword_count into current_tier, current_count
- from public.user_profiles where id = new.user_id;
+SET default_tablespace = '';
- maximum_allowed := case current_tier when 'free' then 5 when 'pro' then 10000 when 'developer' then 10000 else 5 end;
+SET default_table_access_method = "heap";
- if current_count >= maximum_allowed then
- raise exception 'You have reached the maximum number of muted keywords for your plan (% on % tier)', maximum_allowed, current_tier;
- end if;
+--
+-- Name: api_keys; Type: TABLE; Schema: public; Owner: postgres
+--
- update public.user_profiles set muted_keyword_count = muted_keyword_count + 1 where id = new.user_id;
+CREATE TABLE IF NOT EXISTS "public"."api_keys" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "user_id" "uuid" NOT NULL,
+ "key_hash" "text" NOT NULL,
+ "key_prefix" character(8) NOT NULL,
+ "label" "text",
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "last_used_at" timestamp with time zone,
+ "revoked_at" timestamp with time zone
+);
- return new;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.check_custom_feed_limit()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-declare
- current_tier public.subscription_tier;
- current_count int;
- maximum_allowed int;
-begin
- select tier, custom_feed_count into current_tier, current_count
- from public.user_profiles where id = new.user_id;
- maximum_allowed := case current_tier when 'free' then 1 when 'pro' then 1000 when 'developer' then 1000 else 1 end;
+ALTER TABLE "public"."api_keys" OWNER TO "postgres";
+
+--
+-- Name: custom_feeds; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."custom_feeds" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "user_id" "uuid" NOT NULL,
+ "name" "text" NOT NULL,
+ "query" "text" NOT NULL,
+ "position" integer DEFAULT 0 NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "source_folder_id" "uuid",
+ "match_mode" "text" DEFAULT 'or'::"text" NOT NULL,
+ "icon_url" "text",
+ CONSTRAINT "custom_feeds_match_mode_check" CHECK (("match_mode" = ANY (ARRAY['and'::"text", 'or'::"text"])))
+);
- if current_count >= maximum_allowed then
- raise exception 'You have reached the maximum number of custom feeds for your plan (% on % tier)', maximum_allowed, current_tier;
- end if;
- update public.user_profiles set custom_feed_count = custom_feed_count + 1 where id = new.user_id;
+ALTER TABLE "public"."custom_feeds" OWNER TO "postgres";
+
+--
+-- Name: entries; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."entries" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "feed_id" "uuid" NOT NULL,
+ "owner_id" "uuid",
+ "guid" "text" NOT NULL,
+ "url" "text",
+ "title" "text",
+ "author" "text",
+ "summary" "text",
+ "content_html" "text",
+ "content_text" "text",
+ "image_url" "text",
+ "published_at" timestamp with time zone,
+ "word_count" integer,
+ "fetched_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "enclosure_url" "text",
+ "enclosure_type" "text",
+ "enclosure_length" bigint
+);
- return new;
-end;
-$function$;
-CREATE OR REPLACE FUNCTION public.check_highlight_limit()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
-AS $function$
-declare
- current_tier public.subscription_tier;
- current_count int;
- maximum_allowed int;
-begin
- select tier, highlight_count into current_tier, current_count
- from public.user_profiles where id = new.user_id;
+ALTER TABLE "public"."entries" OWNER TO "postgres";
+
+--
+-- Name: feeds; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."feeds" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "url" "text" NOT NULL,
+ "site_url" "text",
+ "title" "text",
+ "description" "text",
+ "language" "text",
+ "feed_type" "text",
+ "image_url" "text",
+ "visibility" "public"."feed_visibility" DEFAULT 'public'::"public"."feed_visibility" NOT NULL,
+ "etag" "text",
+ "last_modified" "text",
+ "last_fetched_at" timestamp with time zone,
+ "last_fetch_error" "text",
+ "last_fetch_error_at" timestamp with time zone,
+ "consecutive_failures" integer DEFAULT 0 NOT NULL,
+ "next_fetch_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "fetch_interval_seconds" integer DEFAULT 1800 NOT NULL,
+ "subscriber_count" integer DEFAULT 0 NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL
+);
- maximum_allowed := case current_tier when 'free' then 50 when 'pro' then 100000 when 'developer' then 100000 else 50 end;
- if current_count >= maximum_allowed then
- raise exception 'You have reached the maximum number of highlights for your plan (% on % tier)', maximum_allowed, current_tier;
- end if;
+ALTER TABLE "public"."feeds" OWNER TO "postgres";
- update public.user_profiles set highlight_count = highlight_count + 1 where id = new.user_id;
+--
+-- Name: folders; Type: TABLE; Schema: public; Owner: postgres
+--
- return new;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.decrement_subscription_count()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-declare
- feed_vis public.feed_visibility;
-begin
- update public.user_profiles
- set feed_count = greatest(feed_count - 1, 0)
- where id = old.user_id;
+CREATE TABLE IF NOT EXISTS "public"."folders" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "user_id" "uuid" NOT NULL,
+ "name" "text" NOT NULL,
+ "position" integer DEFAULT 0 NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "icon_url" "text"
+);
- update public.feeds
- set subscriber_count = greatest(subscriber_count - 1, 0)
- where id = old.feed_id;
- select visibility into feed_vis from public.feeds where id = old.feed_id;
+ALTER TABLE "public"."folders" OWNER TO "postgres";
- if feed_vis = 'authenticated' then
- delete from public.entries where feed_id = old.feed_id and owner_id = old.user_id;
- if old.vault_secret_id is not null then
- delete from vault.secrets where id = old.vault_secret_id;
- end if;
- delete from public.feeds where id = old.feed_id;
- end if;
+--
+-- Name: muted_keywords; Type: TABLE; Schema: public; Owner: postgres
+--
- return old;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.decrement_folder_count()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-begin
- update public.user_profiles
- set folder_count = greatest(folder_count - 1, 0)
- where id = old.user_id;
+CREATE TABLE IF NOT EXISTS "public"."muted_keywords" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "user_id" "uuid" NOT NULL,
+ "keyword" "text" NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL
+);
- return old;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.decrement_muted_keyword_count()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-begin
- update public.user_profiles
- set muted_keyword_count = greatest(muted_keyword_count - 1, 0)
- where id = old.user_id;
- return old;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.decrement_custom_feed_count()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-begin
- update public.user_profiles
- set custom_feed_count = greatest(custom_feed_count - 1, 0)
- where id = old.user_id;
+ALTER TABLE "public"."muted_keywords" OWNER TO "postgres";
- return old;
-end;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.decrement_highlight_count()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO ''
-AS $function$
-begin
- update public.user_profiles
- set highlight_count = greatest(highlight_count - 1, 0)
- where id = old.user_id;
+--
+-- Name: rate_limits; Type: TABLE; Schema: public; Owner: postgres
+--
- return old;
-end;
-$function$;
+CREATE UNLOGGED TABLE "public"."rate_limits" (
+ "identifier" "text" NOT NULL,
+ "window_start" timestamp with time zone NOT NULL,
+ "request_count" integer DEFAULT 1 NOT NULL
+);
-CREATE OR REPLACE FUNCTION public.enqueue_feed_refresh_on_subscribe()
- RETURNS trigger
- LANGUAGE plpgsql
- SECURITY DEFINER
-AS $function$
-BEGIN
- PERFORM pgmq.send('feed_refresh', jsonb_build_object('feed_id', NEW.feed_id));
- RETURN NEW;
-END;
-$function$;
-CREATE OR REPLACE FUNCTION public.prevent_billing_column_modification()
- RETURNS trigger
- LANGUAGE plpgsql
- SET search_path TO 'public'
-AS $function$
-BEGIN
- IF coalesce(auth.role(), '') != 'service_role' THEN
- IF NEW.tier IS DISTINCT FROM OLD.tier
- OR NEW.stripe_customer_identifier IS DISTINCT FROM OLD.stripe_customer_identifier
- OR NEW.stripe_subscription_identifier IS DISTINCT FROM OLD.stripe_subscription_identifier
- OR NEW.stripe_subscription_status IS DISTINCT FROM OLD.stripe_subscription_status
- OR NEW.stripe_current_period_end IS DISTINCT FROM OLD.stripe_current_period_end
- THEN
- RAISE EXCEPTION 'Billing columns cannot be modified directly';
- END IF;
- END IF;
- RETURN NEW;
-END;
-$function$;
-
-CREATE OR REPLACE FUNCTION public.cleanup_stale_entries()
- RETURNS integer
- LANGUAGE plpgsql
- SECURITY DEFINER
- SET search_path TO 'public'
-AS $function$
-DECLARE
- deleted_count integer;
-BEGIN
- DELETE FROM public.entries
- WHERE published_at < NOW() - INTERVAL '90 days'
- AND id NOT IN (
- SELECT entry_id FROM public.user_entry_states WHERE saved = true
- );
- GET DIAGNOSTICS deleted_count = ROW_COUNT;
- RETURN deleted_count;
-END;
-$function$;
-
--- =============================================================================
--- triggers (public tables)
--- =============================================================================
-
--- feeds
-CREATE TRIGGER feeds_updated_at BEFORE UPDATE ON public.feeds FOR EACH ROW EXECUTE FUNCTION moddatetime('updated_at');
-
--- user_profiles
-CREATE TRIGGER prevent_billing_modification BEFORE UPDATE ON public.user_profiles FOR EACH ROW EXECUTE FUNCTION prevent_billing_column_modification();
-CREATE TRIGGER user_profiles_updated_at BEFORE UPDATE ON public.user_profiles FOR EACH ROW EXECUTE FUNCTION moddatetime('updated_at');
-
--- folders
-CREATE TRIGGER check_folder_limit_trigger BEFORE INSERT ON public.folders FOR EACH ROW EXECUTE FUNCTION check_folder_limit();
-CREATE TRIGGER decrement_folder_count_trigger AFTER DELETE ON public.folders FOR EACH ROW EXECUTE FUNCTION decrement_folder_count();
-CREATE TRIGGER folders_updated_at BEFORE UPDATE ON public.folders FOR EACH ROW EXECUTE FUNCTION moddatetime('updated_at');
-
--- subscriptions
-CREATE TRIGGER check_subscription_limit_trigger BEFORE INSERT ON public.subscriptions FOR EACH ROW EXECUTE FUNCTION check_subscription_limit();
-CREATE TRIGGER decrement_subscription_count_trigger AFTER DELETE ON public.subscriptions FOR EACH ROW EXECUTE FUNCTION decrement_subscription_count();
-CREATE TRIGGER subscriptions_updated_at BEFORE UPDATE ON public.subscriptions FOR EACH ROW EXECUTE FUNCTION moddatetime('updated_at');
-CREATE TRIGGER trigger_enqueue_feed_refresh_after_subscribe AFTER INSERT ON public.subscriptions FOR EACH ROW EXECUTE FUNCTION enqueue_feed_refresh_on_subscribe();
-
--- muted_keywords
-CREATE TRIGGER check_muted_keyword_limit_trigger BEFORE INSERT ON public.muted_keywords FOR EACH ROW EXECUTE FUNCTION check_muted_keyword_limit();
-CREATE TRIGGER decrement_muted_keyword_count_trigger AFTER DELETE ON public.muted_keywords FOR EACH ROW EXECUTE FUNCTION decrement_muted_keyword_count();
-
--- custom_feeds
-CREATE TRIGGER check_custom_feed_limit_trigger BEFORE INSERT ON public.custom_feeds FOR EACH ROW EXECUTE FUNCTION check_custom_feed_limit();
-CREATE TRIGGER decrement_custom_feed_count_trigger AFTER DELETE ON public.custom_feeds FOR EACH ROW EXECUTE FUNCTION decrement_custom_feed_count();
-CREATE TRIGGER custom_feeds_updated_at BEFORE UPDATE ON public.custom_feeds FOR EACH ROW EXECUTE FUNCTION moddatetime('updated_at');
-
--- user_highlights
-CREATE TRIGGER check_highlight_limit_trigger BEFORE INSERT ON public.user_highlights FOR EACH ROW EXECUTE FUNCTION check_highlight_limit();
-CREATE TRIGGER decrement_highlight_count_trigger AFTER DELETE ON public.user_highlights FOR EACH ROW EXECUTE FUNCTION decrement_highlight_count();
-
--- =============================================================================
--- triggers (auth schema -- creates user profile on signup)
--- =============================================================================
-
-CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION handle_new_user();
-
--- =============================================================================
--- pg_cron scheduled jobs
--- =============================================================================
-
--- daily at 3:00 AM UTC: purge old private entries for free-tier users (>14 days)
-SELECT cron.schedule('purge-old-private-entries', '0 3 * * *', $$
- delete from public.entries e
- where e.owner_id is not null
- and exists (
- select 1 from public.user_profiles p
- where p.id = e.owner_id
- and p.tier = 'free'
- )
- and e.published_at < now() - interval '14 days';
- $$);
-
--- daily at 3:00 AM UTC: cleanup stale entries (>90 days, not saved)
-SELECT cron.schedule('cleanup-stale-entries', '0 3 * * *', $$SELECT cleanup_stale_entries()$$);
-
--- daily at 4:00 AM UTC: purge orphaned entries (no subscribers)
-SELECT cron.schedule('purge-orphaned-entries', '0 4 * * *', $$
- delete from public.entries e
- where e.owner_id is null
- and not exists (
- select 1 from public.subscriptions s where s.feed_id = e.feed_id
- );
- $$);
-
--- daily at 4:30 AM UTC: purge orphaned public feeds (0 subscribers, stale >7 days)
-SELECT cron.schedule('purge-orphaned-feeds', '30 4 * * *', $$
- delete from public.feeds
- where visibility = 'public'
- and subscriber_count = 0
- and updated_at < now() - interval '7 days';
- $$);
-
--- every 5 minutes: cleanup expired rate limit windows
-SELECT cron.schedule('cleanup-rate-limits', '*/5 * * * *', $$DELETE FROM public.rate_limits WHERE window_start < now() - interval '10 minutes'$$);
-
--- =============================================================================
--- rate limiting
--- =============================================================================
-
-CREATE UNLOGGED TABLE public.rate_limits (
- identifier text NOT NULL,
- window_start timestamptz NOT NULL,
- request_count integer NOT NULL DEFAULT 1,
- PRIMARY KEY (identifier, window_start)
+ALTER TABLE "public"."rate_limits" OWNER TO "postgres";
+
+--
+-- Name: shared_entries; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."shared_entries" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "user_id" "uuid" NOT NULL,
+ "entry_id" "uuid" NOT NULL,
+ "share_token" "text" NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "expires_at" timestamp with time zone,
+ "note" "text",
+ "highlight_text" "text",
+ "highlighted_text" "text",
+ "highlight_text_offset" integer,
+ "highlight_text_length" integer,
+ "highlight_text_prefix" "text" DEFAULT ''::"text",
+ "highlight_text_suffix" "text" DEFAULT ''::"text"
);
-CREATE OR REPLACE FUNCTION public.check_rate_limit(
- p_identifier text,
- p_limit integer,
- p_window_seconds integer
-) RETURNS jsonb
-LANGUAGE plpgsql
-SECURITY DEFINER
-SET search_path TO 'public'
-AS $function$
-DECLARE
- v_window_start timestamptz;
- v_current_count integer;
-BEGIN
- v_window_start := to_timestamp(
- floor(extract(epoch from now()) / p_window_seconds) * p_window_seconds
- );
- INSERT INTO public.rate_limits (identifier, window_start, request_count)
- VALUES (p_identifier, v_window_start, 1)
- ON CONFLICT (identifier, window_start)
- DO UPDATE SET request_count = rate_limits.request_count + 1
- RETURNING request_count INTO v_current_count;
+ALTER TABLE "public"."shared_entries" OWNER TO "postgres";
+
+--
+-- Name: subscriptions; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."subscriptions" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "user_id" "uuid" NOT NULL,
+ "feed_id" "uuid" NOT NULL,
+ "folder_id" "uuid",
+ "custom_title" "text",
+ "position" integer DEFAULT 0 NOT NULL,
+ "vault_secret_id" "uuid",
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "hidden_from_timeline" boolean DEFAULT false NOT NULL
+);
+
+
+ALTER TABLE "public"."subscriptions" OWNER TO "postgres";
+
+--
+-- Name: user_entry_states; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."user_entry_states" (
+ "user_id" "uuid" NOT NULL,
+ "entry_id" "uuid" NOT NULL,
+ "read" boolean DEFAULT false NOT NULL,
+ "saved" boolean DEFAULT false NOT NULL,
+ "read_at" timestamp with time zone,
+ "saved_at" timestamp with time zone
+);
+
+
+ALTER TABLE "public"."user_entry_states" OWNER TO "postgres";
+
+--
+-- Name: user_highlights; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."user_highlights" (
+ "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
+ "user_id" "uuid" NOT NULL,
+ "entry_id" "uuid" NOT NULL,
+ "highlighted_text" "text" NOT NULL,
+ "note" "text",
+ "text_offset" integer NOT NULL,
+ "text_length" integer NOT NULL,
+ "text_prefix" "text" DEFAULT ''::"text" NOT NULL,
+ "text_suffix" "text" DEFAULT ''::"text" NOT NULL,
+ "color" "text" DEFAULT 'yellow'::"text" NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL
+);
+
+
+ALTER TABLE "public"."user_highlights" OWNER TO "postgres";
+
+--
+-- Name: user_profiles; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE IF NOT EXISTS "public"."user_profiles" (
+ "id" "uuid" NOT NULL,
+ "display_name" "text",
+ "tier" "public"."subscription_tier" DEFAULT 'free'::"public"."subscription_tier" NOT NULL,
+ "feed_count" integer DEFAULT 0 NOT NULL,
+ "folder_count" integer DEFAULT 0 NOT NULL,
+ "muted_keyword_count" integer DEFAULT 0 NOT NULL,
+ "custom_feed_count" integer DEFAULT 0 NOT NULL,
+ "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "stripe_customer_identifier" "text",
+ "stripe_subscription_identifier" "text",
+ "stripe_subscription_status" "text",
+ "stripe_current_period_end" timestamp with time zone,
+ "highlight_count" integer DEFAULT 0 NOT NULL,
+ "webhook_url" "text",
+ "webhook_secret" "text",
+ "webhook_enabled" boolean DEFAULT false NOT NULL,
+ "webhook_consecutive_failures" integer DEFAULT 0 NOT NULL
+);
+
+
+ALTER TABLE "public"."user_profiles" OWNER TO "postgres";
+
+--
+-- Name: api_keys api_keys_key_hash_key; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."api_keys"
+ ADD CONSTRAINT "api_keys_key_hash_key" UNIQUE ("key_hash");
+
+
+--
+-- Name: api_keys api_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."api_keys"
+ ADD CONSTRAINT "api_keys_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: custom_feeds custom_feeds_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."custom_feeds"
+ ADD CONSTRAINT "custom_feeds_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: entries entries_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."entries"
+ ADD CONSTRAINT "entries_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: feeds feeds_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."feeds"
+ ADD CONSTRAINT "feeds_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: folders folders_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."folders"
+ ADD CONSTRAINT "folders_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: folders folders_user_id_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."folders"
+ ADD CONSTRAINT "folders_user_id_name_key" UNIQUE ("user_id", "name");
+
+
+--
+-- Name: muted_keywords muted_keywords_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."muted_keywords"
+ ADD CONSTRAINT "muted_keywords_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: rate_limits rate_limits_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."rate_limits"
+ ADD CONSTRAINT "rate_limits_pkey" PRIMARY KEY ("identifier", "window_start");
+
+
+--
+-- Name: shared_entries shared_entries_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."shared_entries"
+ ADD CONSTRAINT "shared_entries_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: shared_entries shared_entries_share_token_key; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."shared_entries"
+ ADD CONSTRAINT "shared_entries_share_token_key" UNIQUE ("share_token");
+
+
+--
+-- Name: shared_entries shared_entries_user_id_entry_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."shared_entries"
+ ADD CONSTRAINT "shared_entries_user_id_entry_id_key" UNIQUE ("user_id", "entry_id");
+
+
+--
+-- Name: subscriptions subscriptions_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."subscriptions"
+ ADD CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: subscriptions subscriptions_user_id_feed_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."subscriptions"
+ ADD CONSTRAINT "subscriptions_user_id_feed_id_key" UNIQUE ("user_id", "feed_id");
+
+
+--
+-- Name: user_entry_states user_entry_states_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_entry_states"
+ ADD CONSTRAINT "user_entry_states_pkey" PRIMARY KEY ("user_id", "entry_id");
+
+
+--
+-- Name: user_highlights user_highlights_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_highlights"
+ ADD CONSTRAINT "user_highlights_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: user_profiles user_profiles_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_profiles"
+ ADD CONSTRAINT "user_profiles_pkey" PRIMARY KEY ("id");
+
+
+--
+-- Name: user_profiles user_profiles_stripe_customer_identifier_key; Type: CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_profiles"
+ ADD CONSTRAINT "user_profiles_stripe_customer_identifier_key" UNIQUE ("stripe_customer_identifier");
+
+
+--
+-- Name: idx_api_keys_key_hash; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_api_keys_key_hash" ON "public"."api_keys" USING "btree" ("key_hash") WHERE ("revoked_at" IS NULL);
+
+
+--
+-- Name: idx_api_keys_user_id; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_api_keys_user_id" ON "public"."api_keys" USING "btree" ("user_id");
+
+
+--
+-- Name: idx_custom_feeds_source_folder; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_custom_feeds_source_folder" ON "public"."custom_feeds" USING "btree" ("source_folder_id");
+
+
+--
+-- Name: idx_custom_feeds_user; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_custom_feeds_user" ON "public"."custom_feeds" USING "btree" ("user_id");
+
+
+--
+-- Name: idx_entries_content_trigram; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_entries_content_trigram" ON "public"."entries" USING "gin" ("content_text" "extensions"."gin_trgm_ops");
+
+
+--
+-- Name: idx_entries_dedup_authenticated; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE UNIQUE INDEX "idx_entries_dedup_authenticated" ON "public"."entries" USING "btree" ("feed_id", "owner_id", "guid") WHERE ("owner_id" IS NOT NULL);
+
+
+--
+-- Name: idx_entries_dedup_public; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE UNIQUE INDEX "idx_entries_dedup_public" ON "public"."entries" USING "btree" ("feed_id", "guid") WHERE ("owner_id" IS NULL);
+
+
+--
+-- Name: idx_entries_feed_published; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_entries_feed_published" ON "public"."entries" USING "btree" ("feed_id", "published_at" DESC NULLS LAST);
+
+
+--
+-- Name: idx_entries_owner; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_entries_owner" ON "public"."entries" USING "btree" ("owner_id", "published_at" DESC NULLS LAST) WHERE ("owner_id" IS NOT NULL);
+
+
+--
+-- Name: idx_entries_published; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_entries_published" ON "public"."entries" USING "btree" ("published_at" DESC NULLS LAST);
+
+
+--
+-- Name: idx_entries_title_trigram; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_entries_title_trigram" ON "public"."entries" USING "gin" ("title" "extensions"."gin_trgm_ops");
+
+
+--
+-- Name: idx_feeds_next_fetch; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_feeds_next_fetch" ON "public"."feeds" USING "btree" ("next_fetch_at") WHERE ("consecutive_failures" < 10);
+
+
+--
+-- Name: idx_feeds_url_public; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE UNIQUE INDEX "idx_feeds_url_public" ON "public"."feeds" USING "btree" ("url") WHERE ("visibility" = 'public'::"public"."feed_visibility");
+
+
+--
+-- Name: idx_folders_user; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_folders_user" ON "public"."folders" USING "btree" ("user_id");
+
+
+--
+-- Name: idx_muted_keywords_user; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_muted_keywords_user" ON "public"."muted_keywords" USING "btree" ("user_id");
+
+
+--
+-- Name: idx_muted_keywords_user_keyword; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE UNIQUE INDEX "idx_muted_keywords_user_keyword" ON "public"."muted_keywords" USING "btree" ("user_id", "lower"("keyword"));
+
+
+--
+-- Name: idx_shared_entries_entry; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_shared_entries_entry" ON "public"."shared_entries" USING "btree" ("entry_id");
+
+
+--
+-- Name: idx_subscriptions_feed; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_subscriptions_feed" ON "public"."subscriptions" USING "btree" ("feed_id");
+
+
+--
+-- Name: idx_subscriptions_folder; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_subscriptions_folder" ON "public"."subscriptions" USING "btree" ("folder_id");
+
+
+--
+-- Name: idx_subscriptions_user; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_subscriptions_user" ON "public"."subscriptions" USING "btree" ("user_id");
+
+
+--
+-- Name: idx_user_entry_states_entry; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_user_entry_states_entry" ON "public"."user_entry_states" USING "btree" ("entry_id");
+
+
+--
+-- Name: idx_user_entry_states_read; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_user_entry_states_read" ON "public"."user_entry_states" USING "btree" ("user_id", "entry_id") WHERE ("read" = true);
+
+
+--
+-- Name: idx_user_entry_states_saved; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_user_entry_states_saved" ON "public"."user_entry_states" USING "btree" ("user_id", "saved_at" DESC) WHERE ("saved" = true);
+
+
+--
+-- Name: idx_user_highlights_entry; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_user_highlights_entry" ON "public"."user_highlights" USING "btree" ("entry_id");
+
+
+--
+-- Name: idx_user_profiles_stripe_customer; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "idx_user_profiles_stripe_customer" ON "public"."user_profiles" USING "btree" ("stripe_customer_identifier") WHERE ("stripe_customer_identifier" IS NOT NULL);
+
+
+--
+-- Name: user_highlights_user_created_idx; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "user_highlights_user_created_idx" ON "public"."user_highlights" USING "btree" ("user_id", "created_at" DESC);
+
+
+--
+-- Name: user_highlights_user_entry_idx; Type: INDEX; Schema: public; Owner: postgres
+--
+
+CREATE INDEX "user_highlights_user_entry_idx" ON "public"."user_highlights" USING "btree" ("user_id", "entry_id");
+
+
+--
+-- Name: custom_feeds check_custom_feed_limit_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "check_custom_feed_limit_trigger" BEFORE INSERT ON "public"."custom_feeds" FOR EACH ROW EXECUTE FUNCTION "public"."check_custom_feed_limit"();
+
+
+--
+-- Name: folders check_folder_limit_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "check_folder_limit_trigger" BEFORE INSERT ON "public"."folders" FOR EACH ROW EXECUTE FUNCTION "public"."check_folder_limit"();
+
+
+--
+-- Name: user_highlights check_highlight_limit_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "check_highlight_limit_trigger" BEFORE INSERT ON "public"."user_highlights" FOR EACH ROW EXECUTE FUNCTION "public"."check_highlight_limit"();
+
+
+--
+-- Name: muted_keywords check_muted_keyword_limit_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "check_muted_keyword_limit_trigger" BEFORE INSERT ON "public"."muted_keywords" FOR EACH ROW EXECUTE FUNCTION "public"."check_muted_keyword_limit"();
+
+
+--
+-- Name: subscriptions check_subscription_limit_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "check_subscription_limit_trigger" BEFORE INSERT ON "public"."subscriptions" FOR EACH ROW EXECUTE FUNCTION "public"."check_subscription_limit"();
+
+
+--
+-- Name: custom_feeds custom_feeds_updated_at; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "custom_feeds_updated_at" BEFORE UPDATE ON "public"."custom_feeds" FOR EACH ROW EXECUTE FUNCTION "extensions"."moddatetime"('updated_at');
+
+
+--
+-- Name: custom_feeds decrement_custom_feed_count_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "decrement_custom_feed_count_trigger" AFTER DELETE ON "public"."custom_feeds" FOR EACH ROW EXECUTE FUNCTION "public"."decrement_custom_feed_count"();
+
+
+--
+-- Name: folders decrement_folder_count_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "decrement_folder_count_trigger" AFTER DELETE ON "public"."folders" FOR EACH ROW EXECUTE FUNCTION "public"."decrement_folder_count"();
+
+
+--
+-- Name: user_highlights decrement_highlight_count_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "decrement_highlight_count_trigger" AFTER DELETE ON "public"."user_highlights" FOR EACH ROW EXECUTE FUNCTION "public"."decrement_highlight_count"();
+
+
+--
+-- Name: muted_keywords decrement_muted_keyword_count_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "decrement_muted_keyword_count_trigger" AFTER DELETE ON "public"."muted_keywords" FOR EACH ROW EXECUTE FUNCTION "public"."decrement_muted_keyword_count"();
+
+
+--
+-- Name: subscriptions decrement_subscription_count_trigger; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "decrement_subscription_count_trigger" AFTER DELETE ON "public"."subscriptions" FOR EACH ROW EXECUTE FUNCTION "public"."decrement_subscription_count"();
+
+
+--
+-- Name: feeds feeds_updated_at; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "feeds_updated_at" BEFORE UPDATE ON "public"."feeds" FOR EACH ROW EXECUTE FUNCTION "extensions"."moddatetime"('updated_at');
+
+
+--
+-- Name: folders folders_updated_at; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "folders_updated_at" BEFORE UPDATE ON "public"."folders" FOR EACH ROW EXECUTE FUNCTION "extensions"."moddatetime"('updated_at');
+
+
+--
+-- Name: user_profiles prevent_billing_modification; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "prevent_billing_modification" BEFORE UPDATE ON "public"."user_profiles" FOR EACH ROW EXECUTE FUNCTION "public"."prevent_billing_column_modification"();
+
+
+--
+-- Name: subscriptions subscriptions_updated_at; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "subscriptions_updated_at" BEFORE UPDATE ON "public"."subscriptions" FOR EACH ROW EXECUTE FUNCTION "extensions"."moddatetime"('updated_at');
+
+
+--
+-- Name: subscriptions trigger_enqueue_feed_refresh_after_subscribe; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "trigger_enqueue_feed_refresh_after_subscribe" AFTER INSERT ON "public"."subscriptions" FOR EACH ROW EXECUTE FUNCTION "public"."enqueue_feed_refresh_on_subscribe"();
+
+
+--
+-- Name: user_profiles user_profiles_updated_at; Type: TRIGGER; Schema: public; Owner: postgres
+--
+
+CREATE OR REPLACE TRIGGER "user_profiles_updated_at" BEFORE UPDATE ON "public"."user_profiles" FOR EACH ROW EXECUTE FUNCTION "extensions"."moddatetime"('updated_at');
+
+
+--
+-- Name: api_keys api_keys_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."api_keys"
+ ADD CONSTRAINT "api_keys_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: custom_feeds custom_feeds_source_folder_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."custom_feeds"
+ ADD CONSTRAINT "custom_feeds_source_folder_id_fkey" FOREIGN KEY ("source_folder_id") REFERENCES "public"."folders"("id") ON DELETE SET NULL;
+
+
+--
+-- Name: custom_feeds custom_feeds_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."custom_feeds"
+ ADD CONSTRAINT "custom_feeds_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: entries entries_feed_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."entries"
+ ADD CONSTRAINT "entries_feed_id_fkey" FOREIGN KEY ("feed_id") REFERENCES "public"."feeds"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: entries entries_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."entries"
+ ADD CONSTRAINT "entries_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: folders folders_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."folders"
+ ADD CONSTRAINT "folders_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: muted_keywords muted_keywords_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."muted_keywords"
+ ADD CONSTRAINT "muted_keywords_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: shared_entries shared_entries_entry_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."shared_entries"
+ ADD CONSTRAINT "shared_entries_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "public"."entries"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: shared_entries shared_entries_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."shared_entries"
+ ADD CONSTRAINT "shared_entries_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: subscriptions subscriptions_feed_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."subscriptions"
+ ADD CONSTRAINT "subscriptions_feed_id_fkey" FOREIGN KEY ("feed_id") REFERENCES "public"."feeds"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: subscriptions subscriptions_folder_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."subscriptions"
+ ADD CONSTRAINT "subscriptions_folder_id_fkey" FOREIGN KEY ("folder_id") REFERENCES "public"."folders"("id") ON DELETE SET NULL;
+
+
+--
+-- Name: subscriptions subscriptions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."subscriptions"
+ ADD CONSTRAINT "subscriptions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: user_entry_states user_entry_states_entry_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_entry_states"
+ ADD CONSTRAINT "user_entry_states_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "public"."entries"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: user_entry_states user_entry_states_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_entry_states"
+ ADD CONSTRAINT "user_entry_states_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: user_highlights user_highlights_entry_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_highlights"
+ ADD CONSTRAINT "user_highlights_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "public"."entries"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: user_highlights user_highlights_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_highlights"
+ ADD CONSTRAINT "user_highlights_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: user_profiles user_profiles_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
+--
+
+ALTER TABLE ONLY "public"."user_profiles"
+ ADD CONSTRAINT "user_profiles_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
+
+
+--
+-- Name: api_keys Users can delete own API keys; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "Users can delete own API keys" ON "public"."api_keys" FOR DELETE USING ((( SELECT "auth"."uid"() AS "uid") = "user_id"));
+
+
+--
+-- Name: api_keys Users can insert own API keys; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "Users can insert own API keys" ON "public"."api_keys" FOR INSERT WITH CHECK ((( SELECT "auth"."uid"() AS "uid") = "user_id"));
+
+
+--
+-- Name: api_keys Users can update own API keys; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "Users can update own API keys" ON "public"."api_keys" FOR UPDATE USING ((( SELECT "auth"."uid"() AS "uid") = "user_id"));
+
+
+--
+-- Name: api_keys Users can view own API keys; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "Users can view own API keys" ON "public"."api_keys" FOR SELECT USING ((( SELECT "auth"."uid"() AS "uid") = "user_id"));
+
+
+--
+-- Name: api_keys; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."api_keys" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: shared_entries authenticated users can view own shared entries; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "authenticated users can view own shared entries" ON "public"."shared_entries" FOR SELECT TO "authenticated" USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: custom_feeds; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."custom_feeds" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: entries; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."entries" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: feeds; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."feeds" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: folders; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."folders" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: muted_keywords; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."muted_keywords" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: shared_entries owners can delete own shared entries; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "owners can delete own shared entries" ON "public"."shared_entries" FOR DELETE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: shared_entries owners can insert own shared entries; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "owners can insert own shared entries" ON "public"."shared_entries" FOR INSERT WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: shared_entries owners can update own shared entries; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "owners can update own shared entries" ON "public"."shared_entries" FOR UPDATE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid"))) WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: rate_limits; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."rate_limits" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: shared_entries; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."shared_entries" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: subscriptions; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."subscriptions" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: user_entry_states; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."user_entry_states" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: user_highlights; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."user_highlights" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: user_profiles; Type: ROW SECURITY; Schema: public; Owner: postgres
+--
+
+ALTER TABLE "public"."user_profiles" ENABLE ROW LEVEL SECURITY;
+
+--
+-- Name: custom_feeds users can delete own custom feeds; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can delete own custom feeds" ON "public"."custom_feeds" FOR DELETE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: folders users can delete own folders; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can delete own folders" ON "public"."folders" FOR DELETE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: user_highlights users can delete own highlights; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can delete own highlights" ON "public"."user_highlights" FOR DELETE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: muted_keywords users can delete own muted keywords; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can delete own muted keywords" ON "public"."muted_keywords" FOR DELETE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: subscriptions users can delete own subscriptions; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can delete own subscriptions" ON "public"."subscriptions" FOR DELETE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: custom_feeds users can insert own custom feeds; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can insert own custom feeds" ON "public"."custom_feeds" FOR INSERT WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: folders users can insert own folders; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can insert own folders" ON "public"."folders" FOR INSERT WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: user_highlights users can insert own highlights; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can insert own highlights" ON "public"."user_highlights" FOR INSERT WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: muted_keywords users can insert own muted keywords; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can insert own muted keywords" ON "public"."muted_keywords" FOR INSERT WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: subscriptions users can insert own subscriptions; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can insert own subscriptions" ON "public"."subscriptions" FOR INSERT WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: user_entry_states users can manage own entry states; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can manage own entry states" ON "public"."user_entry_states" USING (("user_id" = ( SELECT "auth"."uid"() AS "uid"))) WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: custom_feeds users can update own custom feeds; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can update own custom feeds" ON "public"."custom_feeds" FOR UPDATE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid"))) WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: folders users can update own folders; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can update own folders" ON "public"."folders" FOR UPDATE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid"))) WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: user_highlights users can update own highlights; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can update own highlights" ON "public"."user_highlights" FOR UPDATE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid"))) WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: user_profiles users can update own profile; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can update own profile" ON "public"."user_profiles" FOR UPDATE USING (("id" = ( SELECT "auth"."uid"() AS "uid"))) WITH CHECK (("id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: subscriptions users can update own subscriptions; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can update own subscriptions" ON "public"."subscriptions" FOR UPDATE USING (("user_id" = ( SELECT "auth"."uid"() AS "uid"))) WITH CHECK (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: feeds users can view accessible feeds; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view accessible feeds" ON "public"."feeds" FOR SELECT USING (((("visibility" = 'public'::"public"."feed_visibility") AND (( SELECT "auth"."role"() AS "role") = 'authenticated'::"text")) OR (("visibility" = 'authenticated'::"public"."feed_visibility") AND (EXISTS ( SELECT 1
+ FROM "public"."subscriptions" "s"
+ WHERE (("s"."feed_id" = "feeds"."id") AND ("s"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))))));
+
+
+--
+-- Name: entries users can view entries from subscribed public feeds; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view entries from subscribed public feeds" ON "public"."entries" FOR SELECT USING (((("owner_id" IS NULL) AND (EXISTS ( SELECT 1
+ FROM "public"."subscriptions" "s"
+ WHERE (("s"."feed_id" = "entries"."feed_id") AND ("s"."user_id" = ( SELECT "auth"."uid"() AS "uid")))))) OR ("owner_id" = ( SELECT "auth"."uid"() AS "uid"))));
+
+
+--
+-- Name: custom_feeds users can view own custom feeds; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view own custom feeds" ON "public"."custom_feeds" FOR SELECT USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: folders users can view own folders; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view own folders" ON "public"."folders" FOR SELECT USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: user_highlights users can view own highlights; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view own highlights" ON "public"."user_highlights" FOR SELECT USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: muted_keywords users can view own muted keywords; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view own muted keywords" ON "public"."muted_keywords" FOR SELECT USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: user_profiles users can view own profile; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view own profile" ON "public"."user_profiles" FOR SELECT USING (("id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: subscriptions users can view own subscriptions; Type: POLICY; Schema: public; Owner: postgres
+--
+
+CREATE POLICY "users can view own subscriptions" ON "public"."subscriptions" FOR SELECT USING (("user_id" = ( SELECT "auth"."uid"() AS "uid")));
+
+
+--
+-- Name: supabase_realtime; Type: PUBLICATION; Schema: -; Owner: postgres
+--
+
+-- CREATE PUBLICATION "supabase_realtime" WITH (publish = 'insert, update, delete, truncate');
+
+
+ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres";
+
+--
+-- Name: supabase_realtime_messages_publication; Type: PUBLICATION; Schema: -; Owner: supabase_admin
+--
+
+-- CREATE PUBLICATION "supabase_realtime_messages_publication" WITH (publish = 'insert, update, delete, truncate');
+
+
+-- ALTER PUBLICATION "supabase_realtime_messages_publication" OWNER TO "supabase_admin";
+
+--
+-- Name: supabase_realtime entries; Type: PUBLICATION TABLE; Schema: public; Owner: postgres
+--
+
+ALTER PUBLICATION "supabase_realtime" ADD TABLE ONLY "public"."entries";
+
+
+--
+-- Name: SCHEMA "cron"; Type: ACL; Schema: -; Owner: supabase_admin
+--
+
+-- GRANT USAGE ON SCHEMA "cron" TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: SCHEMA "public"; Type: ACL; Schema: -; Owner: pg_database_owner
+--
+
+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";
+
+
+--
+-- Name: FUNCTION "gtrgm_in"("cstring"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_in"("cstring") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_in"("cstring") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_in"("cstring") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_in"("cstring") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_out"("extensions"."gtrgm"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_out"("extensions"."gtrgm") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_out"("extensions"."gtrgm") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_out"("extensions"."gtrgm") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_out"("extensions"."gtrgm") TO "service_role";
+
+
+--
+-- Name: FUNCTION "alter_job"("job_id" bigint, "schedule" "text", "command" "text", "database" "text", "username" "text", "active" boolean); Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "cron"."alter_job"("job_id" bigint, "schedule" "text", "command" "text", "database" "text", "username" "text", "active" boolean) TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: FUNCTION "job_cache_invalidate"(); Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "cron"."job_cache_invalidate"() TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: FUNCTION "schedule"("schedule" "text", "command" "text"); Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "cron"."schedule"("schedule" "text", "command" "text") TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: FUNCTION "schedule"("job_name" "text", "schedule" "text", "command" "text"); Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "cron"."schedule"("job_name" "text", "schedule" "text", "command" "text") TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: FUNCTION "schedule_in_database"("job_name" "text", "schedule" "text", "command" "text", "database" "text", "username" "text", "active" boolean); Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "cron"."schedule_in_database"("job_name" "text", "schedule" "text", "command" "text", "database" "text", "username" "text", "active" boolean) TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: FUNCTION "unschedule"("job_id" bigint); Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "cron"."unschedule"("job_id" bigint) TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: FUNCTION "unschedule"("job_name" "text"); Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "cron"."unschedule"("job_name" "text") TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: FUNCTION "armor"("bytea"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."armor"("bytea") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."armor"("bytea") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."armor"("bytea") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "armor"("bytea", "text"[], "text"[]); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."armor"("bytea", "text"[], "text"[]) FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."armor"("bytea", "text"[], "text"[]) TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."armor"("bytea", "text"[], "text"[]) TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "crypt"("text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."crypt"("text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."crypt"("text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."crypt"("text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "dearmor"("text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."dearmor"("text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."dearmor"("text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."dearmor"("text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "decrypt"("bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."decrypt"("bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."decrypt"("bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."decrypt"("bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "decrypt_iv"("bytea", "bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."decrypt_iv"("bytea", "bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."decrypt_iv"("bytea", "bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."decrypt_iv"("bytea", "bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "digest"("bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."digest"("bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."digest"("bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."digest"("bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "digest"("text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."digest"("text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."digest"("text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."digest"("text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "encrypt"("bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."encrypt"("bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."encrypt"("bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."encrypt"("bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "encrypt_iv"("bytea", "bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."encrypt_iv"("bytea", "bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."encrypt_iv"("bytea", "bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."encrypt_iv"("bytea", "bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "gen_random_bytes"(integer); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."gen_random_bytes"(integer) FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gen_random_bytes"(integer) TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."gen_random_bytes"(integer) TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "gen_random_uuid"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."gen_random_uuid"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gen_random_uuid"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."gen_random_uuid"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "gen_salt"("text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."gen_salt"("text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gen_salt"("text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."gen_salt"("text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "gen_salt"("text", integer); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."gen_salt"("text", integer) FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gen_salt"("text", integer) TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."gen_salt"("text", integer) TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_query_trgm"("text", "internal", smallint, "internal", "internal", "internal", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gin_extract_value_trgm"("text", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_value_trgm"("text", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_value_trgm"("text", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_value_trgm"("text", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gin_extract_value_trgm"("text", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_consistent"("internal", smallint, "text", integer, "internal", "internal", "internal", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gin_trgm_triconsistent"("internal", smallint, "text", integer, "internal", "internal", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_compress"("internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_compress"("internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_compress"("internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_compress"("internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_compress"("internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_consistent"("internal", "text", smallint, "oid", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_consistent"("internal", "text", smallint, "oid", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_decompress"("internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_decompress"("internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_decompress"("internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_decompress"("internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_decompress"("internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_distance"("internal", "text", smallint, "oid", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_distance"("internal", "text", smallint, "oid", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_options"("internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_options"("internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_options"("internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_options"("internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_options"("internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_penalty"("internal", "internal", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_penalty"("internal", "internal", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_penalty"("internal", "internal", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_penalty"("internal", "internal", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_penalty"("internal", "internal", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_picksplit"("internal", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_picksplit"("internal", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_picksplit"("internal", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_picksplit"("internal", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_picksplit"("internal", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_same"("extensions"."gtrgm", "extensions"."gtrgm", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_same"("extensions"."gtrgm", "extensions"."gtrgm", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_same"("extensions"."gtrgm", "extensions"."gtrgm", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_same"("extensions"."gtrgm", "extensions"."gtrgm", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_same"("extensions"."gtrgm", "extensions"."gtrgm", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "gtrgm_union"("internal", "internal"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_union"("internal", "internal") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_union"("internal", "internal") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_union"("internal", "internal") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."gtrgm_union"("internal", "internal") TO "service_role";
+
+
+--
+-- Name: FUNCTION "hmac"("bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."hmac"("bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."hmac"("bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."hmac"("bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "hmac"("text", "text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."hmac"("text", "text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."hmac"("text", "text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."hmac"("text", "text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "moddatetime"(); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."moddatetime"() TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."moddatetime"() TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."moddatetime"() TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."moddatetime"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "pg_stat_statements"("showtext" boolean, OUT "userid" "oid", OUT "dbid" "oid", OUT "toplevel" boolean, OUT "queryid" bigint, OUT "query" "text", OUT "plans" bigint, OUT "total_plan_time" double precision, OUT "min_plan_time" double precision, OUT "max_plan_time" double precision, OUT "mean_plan_time" double precision, OUT "stddev_plan_time" double precision, OUT "calls" bigint, OUT "total_exec_time" double precision, OUT "min_exec_time" double precision, OUT "max_exec_time" double precision, OUT "mean_exec_time" double precision, OUT "stddev_exec_time" double precision, OUT "rows" bigint, OUT "shared_blks_hit" bigint, OUT "shared_blks_read" bigint, OUT "shared_blks_dirtied" bigint, OUT "shared_blks_written" bigint, OUT "local_blks_hit" bigint, OUT "local_blks_read" bigint, OUT "local_blks_dirtied" bigint, OUT "local_blks_written" bigint, OUT "temp_blks_read" bigint, OUT "temp_blks_written" bigint, OUT "shared_blk_read_time" double precision, OUT "shared_blk_write_time" double precision, OUT "local_blk_read_time" double precision, OUT "local_blk_write_time" double precision, OUT "temp_blk_read_time" double precision, OUT "temp_blk_write_time" double precision, OUT "wal_records" bigint, OUT "wal_fpi" bigint, OUT "wal_bytes" numeric, OUT "jit_functions" bigint, OUT "jit_generation_time" double precision, OUT "jit_inlining_count" bigint, OUT "jit_inlining_time" double precision, OUT "jit_optimization_count" bigint, OUT "jit_optimization_time" double precision, OUT "jit_emission_count" bigint, OUT "jit_emission_time" double precision, OUT "jit_deform_count" bigint, OUT "jit_deform_time" double precision, OUT "stats_since" timestamp with time zone, OUT "minmax_stats_since" timestamp with time zone); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pg_stat_statements"("showtext" boolean, OUT "userid" "oid", OUT "dbid" "oid", OUT "toplevel" boolean, OUT "queryid" bigint, OUT "query" "text", OUT "plans" bigint, OUT "total_plan_time" double precision, OUT "min_plan_time" double precision, OUT "max_plan_time" double precision, OUT "mean_plan_time" double precision, OUT "stddev_plan_time" double precision, OUT "calls" bigint, OUT "total_exec_time" double precision, OUT "min_exec_time" double precision, OUT "max_exec_time" double precision, OUT "mean_exec_time" double precision, OUT "stddev_exec_time" double precision, OUT "rows" bigint, OUT "shared_blks_hit" bigint, OUT "shared_blks_read" bigint, OUT "shared_blks_dirtied" bigint, OUT "shared_blks_written" bigint, OUT "local_blks_hit" bigint, OUT "local_blks_read" bigint, OUT "local_blks_dirtied" bigint, OUT "local_blks_written" bigint, OUT "temp_blks_read" bigint, OUT "temp_blks_written" bigint, OUT "shared_blk_read_time" double precision, OUT "shared_blk_write_time" double precision, OUT "local_blk_read_time" double precision, OUT "local_blk_write_time" double precision, OUT "temp_blk_read_time" double precision, OUT "temp_blk_write_time" double precision, OUT "wal_records" bigint, OUT "wal_fpi" bigint, OUT "wal_bytes" numeric, OUT "jit_functions" bigint, OUT "jit_generation_time" double precision, OUT "jit_inlining_count" bigint, OUT "jit_inlining_time" double precision, OUT "jit_optimization_count" bigint, OUT "jit_optimization_time" double precision, OUT "jit_emission_count" bigint, OUT "jit_emission_time" double precision, OUT "jit_deform_count" bigint, OUT "jit_deform_time" double precision, OUT "stats_since" timestamp with time zone, OUT "minmax_stats_since" timestamp with time zone) FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pg_stat_statements"("showtext" boolean, OUT "userid" "oid", OUT "dbid" "oid", OUT "toplevel" boolean, OUT "queryid" bigint, OUT "query" "text", OUT "plans" bigint, OUT "total_plan_time" double precision, OUT "min_plan_time" double precision, OUT "max_plan_time" double precision, OUT "mean_plan_time" double precision, OUT "stddev_plan_time" double precision, OUT "calls" bigint, OUT "total_exec_time" double precision, OUT "min_exec_time" double precision, OUT "max_exec_time" double precision, OUT "mean_exec_time" double precision, OUT "stddev_exec_time" double precision, OUT "rows" bigint, OUT "shared_blks_hit" bigint, OUT "shared_blks_read" bigint, OUT "shared_blks_dirtied" bigint, OUT "shared_blks_written" bigint, OUT "local_blks_hit" bigint, OUT "local_blks_read" bigint, OUT "local_blks_dirtied" bigint, OUT "local_blks_written" bigint, OUT "temp_blks_read" bigint, OUT "temp_blks_written" bigint, OUT "shared_blk_read_time" double precision, OUT "shared_blk_write_time" double precision, OUT "local_blk_read_time" double precision, OUT "local_blk_write_time" double precision, OUT "temp_blk_read_time" double precision, OUT "temp_blk_write_time" double precision, OUT "wal_records" bigint, OUT "wal_fpi" bigint, OUT "wal_bytes" numeric, OUT "jit_functions" bigint, OUT "jit_generation_time" double precision, OUT "jit_inlining_count" bigint, OUT "jit_inlining_time" double precision, OUT "jit_optimization_count" bigint, OUT "jit_optimization_time" double precision, OUT "jit_emission_count" bigint, OUT "jit_emission_time" double precision, OUT "jit_deform_count" bigint, OUT "jit_deform_time" double precision, OUT "stats_since" timestamp with time zone, OUT "minmax_stats_since" timestamp with time zone) TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pg_stat_statements"("showtext" boolean, OUT "userid" "oid", OUT "dbid" "oid", OUT "toplevel" boolean, OUT "queryid" bigint, OUT "query" "text", OUT "plans" bigint, OUT "total_plan_time" double precision, OUT "min_plan_time" double precision, OUT "max_plan_time" double precision, OUT "mean_plan_time" double precision, OUT "stddev_plan_time" double precision, OUT "calls" bigint, OUT "total_exec_time" double precision, OUT "min_exec_time" double precision, OUT "max_exec_time" double precision, OUT "mean_exec_time" double precision, OUT "stddev_exec_time" double precision, OUT "rows" bigint, OUT "shared_blks_hit" bigint, OUT "shared_blks_read" bigint, OUT "shared_blks_dirtied" bigint, OUT "shared_blks_written" bigint, OUT "local_blks_hit" bigint, OUT "local_blks_read" bigint, OUT "local_blks_dirtied" bigint, OUT "local_blks_written" bigint, OUT "temp_blks_read" bigint, OUT "temp_blks_written" bigint, OUT "shared_blk_read_time" double precision, OUT "shared_blk_write_time" double precision, OUT "local_blk_read_time" double precision, OUT "local_blk_write_time" double precision, OUT "temp_blk_read_time" double precision, OUT "temp_blk_write_time" double precision, OUT "wal_records" bigint, OUT "wal_fpi" bigint, OUT "wal_bytes" numeric, OUT "jit_functions" bigint, OUT "jit_generation_time" double precision, OUT "jit_inlining_count" bigint, OUT "jit_inlining_time" double precision, OUT "jit_optimization_count" bigint, OUT "jit_optimization_time" double precision, OUT "jit_emission_count" bigint, OUT "jit_emission_time" double precision, OUT "jit_deform_count" bigint, OUT "jit_deform_time" double precision, OUT "stats_since" timestamp with time zone, OUT "minmax_stats_since" timestamp with time zone) TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pg_stat_statements_info"(OUT "dealloc" bigint, OUT "stats_reset" timestamp with time zone); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pg_stat_statements_info"(OUT "dealloc" bigint, OUT "stats_reset" timestamp with time zone) FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pg_stat_statements_info"(OUT "dealloc" bigint, OUT "stats_reset" timestamp with time zone) TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pg_stat_statements_info"(OUT "dealloc" bigint, OUT "stats_reset" timestamp with time zone) TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pg_stat_statements_reset"("userid" "oid", "dbid" "oid", "queryid" bigint, "minmax_only" boolean); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pg_stat_statements_reset"("userid" "oid", "dbid" "oid", "queryid" bigint, "minmax_only" boolean) FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pg_stat_statements_reset"("userid" "oid", "dbid" "oid", "queryid" bigint, "minmax_only" boolean) TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pg_stat_statements_reset"("userid" "oid", "dbid" "oid", "queryid" bigint, "minmax_only" boolean) TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_armor_headers"("text", OUT "key" "text", OUT "value" "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_armor_headers"("text", OUT "key" "text", OUT "value" "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_armor_headers"("text", OUT "key" "text", OUT "value" "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_armor_headers"("text", OUT "key" "text", OUT "value" "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_key_id"("bytea"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_key_id"("bytea") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_key_id"("bytea") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_key_id"("bytea") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_decrypt"("bytea", "bytea"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_decrypt"("bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_decrypt"("bytea", "bytea", "text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea", "text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea", "text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt"("bytea", "bytea", "text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_decrypt_bytea"("bytea", "bytea"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_decrypt_bytea"("bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_decrypt_bytea"("bytea", "bytea", "text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea", "text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea", "text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_decrypt_bytea"("bytea", "bytea", "text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_encrypt"("text", "bytea"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_encrypt"("text", "bytea") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt"("text", "bytea") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt"("text", "bytea") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_encrypt"("text", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_encrypt"("text", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt"("text", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt"("text", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_encrypt_bytea"("bytea", "bytea"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_encrypt_bytea"("bytea", "bytea") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt_bytea"("bytea", "bytea") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt_bytea"("bytea", "bytea") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_pub_encrypt_bytea"("bytea", "bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_pub_encrypt_bytea"("bytea", "bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt_bytea"("bytea", "bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_pub_encrypt_bytea"("bytea", "bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_decrypt"("bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_decrypt"("bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt"("bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt"("bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_decrypt"("bytea", "text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_decrypt"("bytea", "text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt"("bytea", "text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt"("bytea", "text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_decrypt_bytea"("bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_decrypt_bytea"("bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt_bytea"("bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt_bytea"("bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_decrypt_bytea"("bytea", "text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_decrypt_bytea"("bytea", "text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt_bytea"("bytea", "text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_decrypt_bytea"("bytea", "text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_encrypt"("text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_encrypt"("text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt"("text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt"("text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_encrypt"("text", "text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_encrypt"("text", "text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt"("text", "text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt"("text", "text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_encrypt_bytea"("bytea", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_encrypt_bytea"("bytea", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt_bytea"("bytea", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt_bytea"("bytea", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "pgp_sym_encrypt_bytea"("bytea", "text", "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."pgp_sym_encrypt_bytea"("bytea", "text", "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt_bytea"("bytea", "text", "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."pgp_sym_encrypt_bytea"("bytea", "text", "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "set_limit"(real); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."set_limit"(real) TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."set_limit"(real) TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."set_limit"(real) TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."set_limit"(real) TO "service_role";
+
+
+--
+-- Name: FUNCTION "show_limit"(); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."show_limit"() TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."show_limit"() TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."show_limit"() TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."show_limit"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "show_trgm"("text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."show_trgm"("text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."show_trgm"("text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."show_trgm"("text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."show_trgm"("text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "similarity"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."similarity"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."similarity"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."similarity"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."similarity"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "similarity_dist"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."similarity_dist"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."similarity_dist"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."similarity_dist"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."similarity_dist"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "similarity_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."similarity_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."similarity_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."similarity_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."similarity_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "strict_word_similarity"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "strict_word_similarity_commutator_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_commutator_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_commutator_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_commutator_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_commutator_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "strict_word_similarity_dist_commutator_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_commutator_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_commutator_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_commutator_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_commutator_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "strict_word_similarity_dist_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_dist_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "strict_word_similarity_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."strict_word_similarity_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "uuid_generate_v1"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_generate_v1"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v1"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v1"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_generate_v1mc"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_generate_v1mc"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v1mc"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v1mc"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_generate_v3"("namespace" "uuid", "name" "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_generate_v3"("namespace" "uuid", "name" "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v3"("namespace" "uuid", "name" "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v3"("namespace" "uuid", "name" "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_generate_v4"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_generate_v4"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v4"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v4"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_generate_v5"("namespace" "uuid", "name" "text"); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_generate_v5"("namespace" "uuid", "name" "text") FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v5"("namespace" "uuid", "name" "text") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_generate_v5"("namespace" "uuid", "name" "text") TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_nil"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_nil"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_nil"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_nil"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_ns_dns"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_ns_dns"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_dns"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_dns"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_ns_oid"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_ns_oid"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_oid"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_oid"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_ns_url"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_ns_url"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_url"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_url"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "uuid_ns_x500"(); Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON FUNCTION "extensions"."uuid_ns_x500"() FROM "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_x500"() TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "extensions"."uuid_ns_x500"() TO "dashboard_user";
+
+
+--
+-- Name: FUNCTION "word_similarity"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "word_similarity_commutator_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_commutator_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_commutator_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_commutator_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_commutator_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "word_similarity_dist_commutator_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_commutator_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_commutator_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_commutator_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_commutator_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "word_similarity_dist_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_dist_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "word_similarity_op"("text", "text"); Type: ACL; Schema: extensions; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_op"("text", "text") TO "postgres";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_op"("text", "text") TO "anon";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_op"("text", "text") TO "authenticated";
+-- GRANT ALL ON FUNCTION "extensions"."word_similarity_op"("text", "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "graphql"("operationName" "text", "query" "text", "variables" "jsonb", "extensions" "jsonb"); Type: ACL; Schema: graphql_public; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "graphql_public"."graphql"("operationName" "text", "query" "text", "variables" "jsonb", "extensions" "jsonb") TO "postgres";
+-- GRANT ALL ON FUNCTION "graphql_public"."graphql"("operationName" "text", "query" "text", "variables" "jsonb", "extensions" "jsonb") TO "anon";
+-- GRANT ALL ON FUNCTION "graphql_public"."graphql"("operationName" "text", "query" "text", "variables" "jsonb", "extensions" "jsonb") TO "authenticated";
+-- GRANT ALL ON FUNCTION "graphql_public"."graphql"("operationName" "text", "query" "text", "variables" "jsonb", "extensions" "jsonb") TO "service_role";
+
+
+--
+-- Name: FUNCTION "crypto_aead_det_decrypt"("message" "bytea", "additional" "bytea", "key_uuid" "uuid", "nonce" "bytea"); Type: ACL; Schema: pgsodium; Owner: pgsodium_keymaker
+--
+
+-- GRANT ALL ON FUNCTION "pgsodium"."crypto_aead_det_decrypt"("message" "bytea", "additional" "bytea", "key_uuid" "uuid", "nonce" "bytea") TO "service_role";
+
+
+--
+-- Name: FUNCTION "crypto_aead_det_encrypt"("message" "bytea", "additional" "bytea", "key_uuid" "uuid", "nonce" "bytea"); Type: ACL; Schema: pgsodium; Owner: pgsodium_keymaker
+--
+
+-- GRANT ALL ON FUNCTION "pgsodium"."crypto_aead_det_encrypt"("message" "bytea", "additional" "bytea", "key_uuid" "uuid", "nonce" "bytea") TO "service_role";
+
+
+--
+-- Name: FUNCTION "crypto_aead_det_keygen"(); Type: ACL; Schema: pgsodium; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "pgsodium"."crypto_aead_det_keygen"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "add_feed_credentials"("p_subscription_id" "uuid", "p_auth_type" "text", "p_credential" "text"); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."add_feed_credentials"("p_subscription_id" "uuid", "p_auth_type" "text", "p_credential" "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."add_feed_credentials"("p_subscription_id" "uuid", "p_auth_type" "text", "p_credential" "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."add_feed_credentials"("p_subscription_id" "uuid", "p_auth_type" "text", "p_credential" "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "check_custom_feed_limit"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."check_custom_feed_limit"() TO "anon";
+GRANT ALL ON FUNCTION "public"."check_custom_feed_limit"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."check_custom_feed_limit"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "check_folder_limit"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."check_folder_limit"() TO "anon";
+GRANT ALL ON FUNCTION "public"."check_folder_limit"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."check_folder_limit"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "check_highlight_limit"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."check_highlight_limit"() TO "anon";
+GRANT ALL ON FUNCTION "public"."check_highlight_limit"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."check_highlight_limit"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "check_muted_keyword_limit"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."check_muted_keyword_limit"() TO "anon";
+GRANT ALL ON FUNCTION "public"."check_muted_keyword_limit"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."check_muted_keyword_limit"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "check_rate_limit"("p_identifier" "text", "p_limit" integer, "p_window_seconds" integer); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."check_rate_limit"("p_identifier" "text", "p_limit" integer, "p_window_seconds" integer) TO "anon";
+GRANT ALL ON FUNCTION "public"."check_rate_limit"("p_identifier" "text", "p_limit" integer, "p_window_seconds" integer) TO "authenticated";
+GRANT ALL ON FUNCTION "public"."check_rate_limit"("p_identifier" "text", "p_limit" integer, "p_window_seconds" integer) TO "service_role";
+
+
+--
+-- Name: FUNCTION "check_subscription_limit"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."check_subscription_limit"() TO "anon";
+GRANT ALL ON FUNCTION "public"."check_subscription_limit"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."check_subscription_limit"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "cleanup_stale_entries"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."cleanup_stale_entries"() FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."cleanup_stale_entries"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "decrement_custom_feed_count"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."decrement_custom_feed_count"() TO "anon";
+GRANT ALL ON FUNCTION "public"."decrement_custom_feed_count"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."decrement_custom_feed_count"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "decrement_folder_count"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."decrement_folder_count"() TO "anon";
+GRANT ALL ON FUNCTION "public"."decrement_folder_count"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."decrement_folder_count"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "decrement_highlight_count"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."decrement_highlight_count"() TO "anon";
+GRANT ALL ON FUNCTION "public"."decrement_highlight_count"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."decrement_highlight_count"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "decrement_muted_keyword_count"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."decrement_muted_keyword_count"() TO "anon";
+GRANT ALL ON FUNCTION "public"."decrement_muted_keyword_count"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."decrement_muted_keyword_count"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "decrement_subscription_count"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."decrement_subscription_count"() TO "anon";
+GRANT ALL ON FUNCTION "public"."decrement_subscription_count"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."decrement_subscription_count"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "enqueue_feed_refresh_on_subscribe"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."enqueue_feed_refresh_on_subscribe"() TO "anon";
+GRANT ALL ON FUNCTION "public"."enqueue_feed_refresh_on_subscribe"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."enqueue_feed_refresh_on_subscribe"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "get_custom_feed"("target_custom_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."get_custom_feed"("target_custom_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone) FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."get_custom_feed"("target_custom_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone) TO "authenticated";
+GRANT ALL ON FUNCTION "public"."get_custom_feed"("target_custom_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone) TO "service_role";
+
+
+--
+-- Name: FUNCTION "get_custom_feed_timeline"("p_custom_feed_id" "uuid", "p_result_limit" integer, "p_pagination_cursor" timestamp with time zone); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."get_custom_feed_timeline"("p_custom_feed_id" "uuid", "p_result_limit" integer, "p_pagination_cursor" timestamp with time zone) FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."get_custom_feed_timeline"("p_custom_feed_id" "uuid", "p_result_limit" integer, "p_pagination_cursor" timestamp with time zone) TO "authenticated";
+GRANT ALL ON FUNCTION "public"."get_custom_feed_timeline"("p_custom_feed_id" "uuid", "p_result_limit" integer, "p_pagination_cursor" timestamp with time zone) TO "service_role";
+
+
+--
+-- Name: FUNCTION "get_timeline"("target_folder_id" "uuid", "target_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone, "unread_only" boolean); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."get_timeline"("target_folder_id" "uuid", "target_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone, "unread_only" boolean) FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."get_timeline"("target_folder_id" "uuid", "target_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone, "unread_only" boolean) TO "authenticated";
+GRANT ALL ON FUNCTION "public"."get_timeline"("target_folder_id" "uuid", "target_feed_id" "uuid", "result_limit" integer, "pagination_cursor" timestamp with time zone, "unread_only" boolean) TO "service_role";
+
+
+--
+-- Name: FUNCTION "get_unread_counts"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."get_unread_counts"() FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."get_unread_counts"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."get_unread_counts"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "handle_new_user"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+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";
+
+
+--
+-- Name: FUNCTION "mark_all_as_read"("p_feed_id" "uuid", "p_folder_id" "uuid", "p_read_state" boolean); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."mark_all_as_read"("p_feed_id" "uuid", "p_folder_id" "uuid", "p_read_state" boolean) FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."mark_all_as_read"("p_feed_id" "uuid", "p_folder_id" "uuid", "p_read_state" boolean) TO "authenticated";
+GRANT ALL ON FUNCTION "public"."mark_all_as_read"("p_feed_id" "uuid", "p_folder_id" "uuid", "p_read_state" boolean) TO "service_role";
+
+
+--
+-- Name: FUNCTION "prevent_billing_column_modification"(); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."prevent_billing_column_modification"() TO "anon";
+GRANT ALL ON FUNCTION "public"."prevent_billing_column_modification"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."prevent_billing_column_modification"() TO "service_role";
+
+
+--
+-- Name: FUNCTION "request_feed_refresh"("target_subscription_id" "uuid"); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."request_feed_refresh"("target_subscription_id" "uuid") FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."request_feed_refresh"("target_subscription_id" "uuid") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."request_feed_refresh"("target_subscription_id" "uuid") TO "service_role";
+
+
+--
+-- Name: FUNCTION "search_entries"("p_query" "text", "p_result_limit" integer); Type: ACL; Schema: public; Owner: postgres
+--
+
+REVOKE ALL ON FUNCTION "public"."search_entries"("p_query" "text", "p_result_limit" integer) FROM PUBLIC;
+GRANT ALL ON FUNCTION "public"."search_entries"("p_query" "text", "p_result_limit" integer) TO "authenticated";
+GRANT ALL ON FUNCTION "public"."search_entries"("p_query" "text", "p_result_limit" integer) TO "service_role";
+
+
+--
+-- Name: FUNCTION "subscribe_to_feed"("feed_url" "text", "target_folder_id" "uuid", "feed_custom_title" "text", "feed_credential" "text", "feed_auth_type" "text"); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."subscribe_to_feed"("feed_url" "text", "target_folder_id" "uuid", "feed_custom_title" "text", "feed_credential" "text", "feed_auth_type" "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."subscribe_to_feed"("feed_url" "text", "target_folder_id" "uuid", "feed_custom_title" "text", "feed_credential" "text", "feed_auth_type" "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."subscribe_to_feed"("feed_url" "text", "target_folder_id" "uuid", "feed_custom_title" "text", "feed_credential" "text", "feed_auth_type" "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "update_feed_credentials"("p_feed_id" "uuid", "p_auth_type" "text", "p_credential" "text"); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."update_feed_credentials"("p_feed_id" "uuid", "p_auth_type" "text", "p_credential" "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."update_feed_credentials"("p_feed_id" "uuid", "p_auth_type" "text", "p_credential" "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."update_feed_credentials"("p_feed_id" "uuid", "p_auth_type" "text", "p_credential" "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "update_feed_url"("target_feed_id" "uuid", "new_url" "text"); Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON FUNCTION "public"."update_feed_url"("target_feed_id" "uuid", "new_url" "text") TO "anon";
+GRANT ALL ON FUNCTION "public"."update_feed_url"("target_feed_id" "uuid", "new_url" "text") TO "authenticated";
+GRANT ALL ON FUNCTION "public"."update_feed_url"("target_feed_id" "uuid", "new_url" "text") TO "service_role";
+
+
+--
+-- Name: FUNCTION "_crypto_aead_det_decrypt"("message" "bytea", "additional" "bytea", "key_id" bigint, "context" "bytea", "nonce" "bytea"); Type: ACL; Schema: vault; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "vault"."_crypto_aead_det_decrypt"("message" "bytea", "additional" "bytea", "key_id" bigint, "context" "bytea", "nonce" "bytea") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "vault"."_crypto_aead_det_decrypt"("message" "bytea", "additional" "bytea", "key_id" bigint, "context" "bytea", "nonce" "bytea") TO "service_role";
+
+
+--
+-- Name: FUNCTION "create_secret"("new_secret" "text", "new_name" "text", "new_description" "text", "new_key_id" "uuid"); Type: ACL; Schema: vault; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "vault"."create_secret"("new_secret" "text", "new_name" "text", "new_description" "text", "new_key_id" "uuid") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "vault"."create_secret"("new_secret" "text", "new_name" "text", "new_description" "text", "new_key_id" "uuid") TO "service_role";
+
+
+--
+-- Name: FUNCTION "update_secret"("secret_id" "uuid", "new_secret" "text", "new_name" "text", "new_description" "text", "new_key_id" "uuid"); Type: ACL; Schema: vault; Owner: supabase_admin
+--
+
+-- GRANT ALL ON FUNCTION "vault"."update_secret"("secret_id" "uuid", "new_secret" "text", "new_name" "text", "new_description" "text", "new_key_id" "uuid") TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON FUNCTION "vault"."update_secret"("secret_id" "uuid", "new_secret" "text", "new_name" "text", "new_description" "text", "new_key_id" "uuid") TO "service_role";
+
+
+--
+-- Name: TABLE "job"; Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT SELECT ON TABLE "cron"."job" TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: TABLE "job_run_details"; Type: ACL; Schema: cron; Owner: supabase_admin
+--
+
+-- GRANT ALL ON TABLE "cron"."job_run_details" TO "postgres" WITH GRANT OPTION;
+
+
+--
+-- Name: TABLE "pg_stat_statements"; Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON TABLE "extensions"."pg_stat_statements" FROM "postgres";
+-- GRANT ALL ON TABLE "extensions"."pg_stat_statements" TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON TABLE "extensions"."pg_stat_statements" TO "dashboard_user";
+
+
+--
+-- Name: TABLE "pg_stat_statements_info"; Type: ACL; Schema: extensions; Owner: postgres
+--
+
+-- REVOKE ALL ON TABLE "extensions"."pg_stat_statements_info" FROM "postgres";
+-- GRANT ALL ON TABLE "extensions"."pg_stat_statements_info" TO "postgres" WITH GRANT OPTION;
+-- GRANT ALL ON TABLE "extensions"."pg_stat_statements_info" TO "dashboard_user";
+
+
+--
+-- Name: TABLE "decrypted_key"; Type: ACL; Schema: pgsodium; Owner: postgres
+--
+
+-- GRANT ALL ON TABLE "pgsodium"."decrypted_key" TO "pgsodium_keyholder";
+
+
+--
+-- Name: TABLE "masking_rule"; Type: ACL; Schema: pgsodium; Owner: supabase_admin
+--
+
+-- GRANT ALL ON TABLE "pgsodium"."masking_rule" TO "pgsodium_keyholder";
+
+
+--
+-- Name: TABLE "mask_columns"; Type: ACL; Schema: pgsodium; Owner: supabase_admin
+--
+
+-- GRANT ALL ON TABLE "pgsodium"."mask_columns" TO "pgsodium_keyholder";
+
+
+--
+-- Name: TABLE "api_keys"; Type: ACL; Schema: public; Owner: postgres
+--
+
+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";
+
+
+--
+-- Name: TABLE "custom_feeds"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."custom_feeds" TO "service_role";
+GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE "public"."custom_feeds" TO "authenticated";
+
+
+--
+-- Name: TABLE "entries"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."entries" TO "service_role";
+GRANT SELECT ON TABLE "public"."entries" TO "authenticated";
+
+
+--
+-- Name: TABLE "feeds"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."feeds" TO "service_role";
+GRANT SELECT ON TABLE "public"."feeds" TO "authenticated";
+
+
+--
+-- Name: TABLE "folders"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."folders" TO "service_role";
+GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE "public"."folders" TO "authenticated";
+
+
+--
+-- Name: TABLE "muted_keywords"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."muted_keywords" TO "service_role";
+GRANT SELECT,INSERT,DELETE ON TABLE "public"."muted_keywords" TO "authenticated";
+
+
+--
+-- Name: TABLE "rate_limits"; Type: ACL; Schema: public; Owner: postgres
+--
+
+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";
+
+
+--
+-- Name: TABLE "shared_entries"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."shared_entries" TO "service_role";
+GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE "public"."shared_entries" TO "authenticated";
+
+
+--
+-- Name: TABLE "subscriptions"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."subscriptions" TO "service_role";
+GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE "public"."subscriptions" TO "authenticated";
+
+
+--
+-- Name: TABLE "user_entry_states"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."user_entry_states" TO "service_role";
+GRANT SELECT,INSERT,UPDATE ON TABLE "public"."user_entry_states" TO "authenticated";
+
+
+--
+-- Name: TABLE "user_highlights"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."user_highlights" TO "service_role";
+GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE "public"."user_highlights" TO "authenticated";
+
+
+--
+-- Name: TABLE "user_profiles"; Type: ACL; Schema: public; Owner: postgres
+--
+
+GRANT ALL ON TABLE "public"."user_profiles" TO "service_role";
+GRANT SELECT,UPDATE ON TABLE "public"."user_profiles" TO "authenticated";
+
+
+--
+-- Name: TABLE "secrets"; Type: ACL; Schema: vault; Owner: supabase_admin
+--
+
+-- GRANT SELECT,REFERENCES,DELETE,TRUNCATE ON TABLE "vault"."secrets" TO "postgres" WITH GRANT OPTION;
+-- GRANT SELECT,DELETE ON TABLE "vault"."secrets" TO "service_role";
+
+
+--
+-- Name: TABLE "decrypted_secrets"; Type: ACL; Schema: vault; Owner: supabase_admin
+--
+
+-- GRANT SELECT,REFERENCES,DELETE,TRUNCATE ON TABLE "vault"."decrypted_secrets" TO "postgres" WITH GRANT OPTION;
+-- GRANT SELECT,DELETE ON TABLE "vault"."decrypted_secrets" TO "service_role";
+
+
+--
+-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: public; Owner: postgres
+--
+
+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";
+
+
+--
+-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: public; Owner: supabase_admin
+--
+
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role";
+
+
+--
+-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: public; Owner: postgres
+--
+
+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";
+
+
+--
+-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: public; Owner: supabase_admin
+--
+
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role";
+
+
+--
+-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: public; Owner: postgres
+--
+
+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";
+
+
+--
+-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: public; Owner: supabase_admin
+--
+
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated";
+-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role";
+
+
+--
+-- Name: issue_graphql_placeholder; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin
+--
+
+-- CREATE EVENT TRIGGER "issue_graphql_placeholder" ON "sql_drop"
+-- WHEN TAG IN ('DROP EXTENSION')
+-- EXECUTE FUNCTION "extensions"."set_graphql_placeholder"();
+
+
+-- ALTER EVENT TRIGGER "issue_graphql_placeholder" OWNER TO "supabase_admin";
+
+--
+-- Name: issue_pg_cron_access; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin
+--
+
+-- CREATE EVENT TRIGGER "issue_pg_cron_access" ON "ddl_command_end"
+-- WHEN TAG IN ('CREATE EXTENSION')
+-- EXECUTE FUNCTION "extensions"."grant_pg_cron_access"();
+
+
+-- ALTER EVENT TRIGGER "issue_pg_cron_access" OWNER TO "supabase_admin";
+
+--
+-- Name: issue_pg_graphql_access; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin
+--
+
+-- CREATE EVENT TRIGGER "issue_pg_graphql_access" ON "ddl_command_end"
+-- WHEN TAG IN ('CREATE FUNCTION')
+-- EXECUTE FUNCTION "extensions"."grant_pg_graphql_access"();
+
+
+-- ALTER EVENT TRIGGER "issue_pg_graphql_access" OWNER TO "supabase_admin";
+
+--
+-- Name: issue_pg_net_access; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin
+--
+
+-- CREATE EVENT TRIGGER "issue_pg_net_access" ON "ddl_command_end"
+-- WHEN TAG IN ('CREATE EXTENSION')
+-- EXECUTE FUNCTION "extensions"."grant_pg_net_access"();
+
+
+-- ALTER EVENT TRIGGER "issue_pg_net_access" OWNER TO "supabase_admin";
+
+--
+-- Name: pgrst_ddl_watch; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin
+--
+
+-- CREATE EVENT TRIGGER "pgrst_ddl_watch" ON "ddl_command_end"
+-- EXECUTE FUNCTION "extensions"."pgrst_ddl_watch"();
+
+
+-- ALTER EVENT TRIGGER "pgrst_ddl_watch" OWNER TO "supabase_admin";
+
+--
+-- Name: pgrst_drop_watch; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin
+--
+
+-- CREATE EVENT TRIGGER "pgrst_drop_watch" ON "sql_drop"
+-- EXECUTE FUNCTION "extensions"."pgrst_drop_watch"();
+
+
+-- ALTER EVENT TRIGGER "pgrst_drop_watch" OWNER TO "supabase_admin";
+
+--
+-- PostgreSQL database dump complete
+--
+
+-- \unrestrict 3Un8h9VlAyPuVfPugYV7LM7XGnkxI3KC1nPU1aarC0scGxlDzBQB81bgO7tvPsv
- RETURN jsonb_build_object(
- 'success', v_current_count <= p_limit,
- 'remaining', greatest(p_limit - v_current_count, 0)
- );
-END;
-$function$;