From 7a13c08f54060ee7afa405a286528f452046a8a1 Mon Sep 17 00:00:00 2001 From: Victoria Choi <121906670+vicchois@users.noreply.github.com> Date: Mon, 4 May 2026 15:06:17 -0700 Subject: [PATCH] database integrity trigger functions --- .../20260504220555_protect_cols_triggers.sql | 175 +++++++++++++++++ supabase/schemas/functions/protect_cols.sql | 183 ++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 supabase/migrations/20260504220555_protect_cols_triggers.sql create mode 100644 supabase/schemas/functions/protect_cols.sql diff --git a/supabase/migrations/20260504220555_protect_cols_triggers.sql b/supabase/migrations/20260504220555_protect_cols_triggers.sql new file mode 100644 index 00000000..d09d865b --- /dev/null +++ b/supabase/migrations/20260504220555_protect_cols_triggers.sql @@ -0,0 +1,175 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION private.protect_categories_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.category_id := old.category_id; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_donations_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.donation_id := old.donation_id; + new.date_submitted := old.date_submitted; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_inventory_items_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.inventory_item_id := old.inventory_item_id; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_store_items_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.store_item_id := old.store_item_id; + new.store_id := old.store_id; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_stores_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.store_id := old.store_id; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_subcategories_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.subcategory_id := old.subcategory_id; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_ticket_items_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.ticket_item_id := old.ticket_item_id; + new.ticket_id := old.ticket_id; + new.store_item_id := old.store_item_id; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_tickets_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.ticket_id := old.ticket_id; + new.store_id := old.store_id; + new.date_submitted := old.date_submitted; + + -- old.status is "fulfilled" + if old.status = 'fulfilled' then + new.status := old.status; + -- can_manage_store returns false and old.status is "requested" + elsif not private.can_manage_store(old.store_id) and old.status = 'requested' then + new.status := old.status; + -- can_manage_store returns true, old.status is "requested", and new.status is "fulfilled" + elsif private.can_manage_store(old.store_id) and old.status = 'requested' and new.status = 'fulfilled' then + new.status := old.status; + end if; + + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_user_roles_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.user_id := old.user_id; + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION private.protect_users_cols() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO '' +AS $function$ +begin + new.user_id := old.user_id; + if auth.role() = 'authenticated' then + new.email := old.email; + end if; + + return new; +end; +$function$ +; + +CREATE TRIGGER protect_categories_trigger BEFORE UPDATE ON public.categories FOR EACH ROW EXECUTE FUNCTION private.protect_categories_cols(); + +CREATE TRIGGER protect_donations_trigger BEFORE UPDATE ON public.donations FOR EACH ROW EXECUTE FUNCTION private.protect_donations_cols(); + +CREATE TRIGGER protect_inventory_items_trigger BEFORE UPDATE ON public.inventory_items FOR EACH ROW EXECUTE FUNCTION private.protect_inventory_items_cols(); + +CREATE TRIGGER protect_store_items_trigger BEFORE UPDATE ON public.store_items FOR EACH ROW EXECUTE FUNCTION private.protect_store_items_cols(); + +CREATE TRIGGER protect_stores_trigger BEFORE UPDATE ON public.stores FOR EACH ROW EXECUTE FUNCTION private.protect_stores_cols(); + +CREATE TRIGGER protect_subcategories_trigger BEFORE UPDATE ON public.subcategories FOR EACH ROW EXECUTE FUNCTION private.protect_subcategories_cols(); + +CREATE TRIGGER protect_ticket_items_trigger BEFORE UPDATE ON public.ticket_items FOR EACH ROW EXECUTE FUNCTION private.protect_ticket_items_cols(); + +CREATE TRIGGER protect_tickets_trigger BEFORE UPDATE ON public.tickets FOR EACH ROW EXECUTE FUNCTION private.protect_tickets_cols(); + +CREATE TRIGGER protect_user_roles_trigger BEFORE UPDATE ON public.user_roles FOR EACH ROW EXECUTE FUNCTION private.protect_user_roles_cols(); + +CREATE TRIGGER protect_users_trigger BEFORE UPDATE ON public.users FOR EACH ROW EXECUTE FUNCTION private.protect_users_cols(); + + diff --git a/supabase/schemas/functions/protect_cols.sql b/supabase/schemas/functions/protect_cols.sql new file mode 100644 index 00000000..4f5d69fb --- /dev/null +++ b/supabase/schemas/functions/protect_cols.sql @@ -0,0 +1,183 @@ +create schema if not exists private; + +-- users +create or replace function private.protect_users_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.user_id := old.user_id; + if auth.role() = 'authenticated' then + new.email := old.email; + end if; + + return new; +end; +$$; + +drop trigger if exists protect_users_trigger on users; + +create trigger protect_users_trigger before +update on users for each row +execute function private.protect_users_cols (); + +-- user_roles +create or replace function private.protect_user_roles_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.user_id := old.user_id; + return new; +end; +$$; + +drop trigger if exists protect_user_roles_trigger on user_roles; + +create trigger protect_user_roles_trigger before +update on user_roles for each row +execute function private.protect_user_roles_cols (); + +-- stores +create or replace function private.protect_stores_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.store_id := old.store_id; + return new; +end; +$$; + +drop trigger if exists protect_stores_trigger on stores; + +create trigger protect_stores_trigger before +update on stores for each row +execute function private.protect_stores_cols (); + +-- store_items +create or replace function private.protect_store_items_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.store_item_id := old.store_item_id; + new.store_id := old.store_id; + return new; +end; +$$; + +drop trigger if exists protect_store_items_trigger on store_items; + +create trigger protect_store_items_trigger before +update on store_items for each row +execute function private.protect_store_items_cols (); + +-- inventory_items +create or replace function private.protect_inventory_items_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.inventory_item_id := old.inventory_item_id; + return new; +end; +$$; + +drop trigger if exists protect_inventory_items_trigger on inventory_items; + +create trigger protect_inventory_items_trigger before +update on inventory_items for each row +execute function private.protect_inventory_items_cols (); + +-- subcategories +create or replace function private.protect_subcategories_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.subcategory_id := old.subcategory_id; + return new; +end; +$$; + +drop trigger if exists protect_subcategories_trigger on subcategories; + +create trigger protect_subcategories_trigger before +update on subcategories for each row +execute function private.protect_subcategories_cols (); + +-- categories +create or replace function private.protect_categories_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.category_id := old.category_id; + return new; +end; +$$; + +drop trigger if exists protect_categories_trigger on categories; + +create trigger protect_categories_trigger before +update on categories for each row +execute function private.protect_categories_cols (); + +-- tickets +create or replace function private.protect_tickets_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.ticket_id := old.ticket_id; + new.store_id := old.store_id; + new.date_submitted := old.date_submitted; + + -- old.status is "fulfilled" + if old.status = 'fulfilled' then + new.status := old.status; + -- can_manage_store returns false and old.status is "requested" + elsif not private.can_manage_store(old.store_id) and old.status = 'requested' then + new.status := old.status; + -- can_manage_store returns true, old.status is "requested", and new.status is "fulfilled" + elsif private.can_manage_store(old.store_id) and old.status = 'requested' and new.status = 'fulfilled' then + new.status := old.status; + end if; + + return new; +end; +$$; + +drop trigger if exists protect_tickets_trigger on tickets; + +create trigger protect_tickets_trigger before +update on tickets for each row +execute function private.protect_tickets_cols (); + +-- ticket_items +create or replace function private.protect_ticket_items_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.ticket_item_id := old.ticket_item_id; + new.ticket_id := old.ticket_id; + new.store_item_id := old.store_item_id; + return new; +end; +$$; + +drop trigger if exists protect_ticket_items_trigger on ticket_items; + +create trigger protect_ticket_items_trigger before +update on ticket_items for each row +execute function private.protect_ticket_items_cols (); + +-- donations +create or replace function private.protect_donations_cols () returns trigger language plpgsql security definer +set + search_path = '' as $$ +begin + new.donation_id := old.donation_id; + new.date_submitted := old.date_submitted; + return new; +end; +$$; + +drop trigger if exists protect_donations_trigger on donations; + +create trigger protect_donations_trigger before +update on donations for each row +execute function private.protect_donations_cols ();