From 71a37ec5cf2f76b2517664a08a2f7f4cd775abeb Mon Sep 17 00:00:00 2001 From: mlnps Date: Tue, 1 Sep 2026 17:08:47 +0200 Subject: [PATCH 1/2] refactor: split declarative db schema into per-object files The single 2790-line schema.sql was hard to review and diff. Adds supabase/scripts/split-schema.ts to break it into type-ordered files (extensions, settings, schema, functions, one file per table, foreign keys, RLS policies, orphaned objects) that respect the dependency order Postgres needs, and updates config.toml's schema_paths to match. schema.sql is removed; db:split-schema is the one-off tool to regenerate this layout from a future db:dump-schema run if needed. --- apps/backend/package.json | 1 + apps/backend/supabase/config.toml | 22 +- apps/backend/supabase/schemas/00_settings.sql | 35 + apps/backend/supabase/schemas/01_schema.sql | 49 + .../schemas/{schema.sql => 02_functions.sql} | 1178 ----------------- .../20_tables/access_group_members.sql | 24 + .../schemas/20_tables/access_groups.sql | 25 + .../20_tables/allowed_email_domains.sql | 50 + .../20_tables/allowed_individual_emails.sql | 47 + .../schemas/20_tables/application_admins.sql | 31 + .../schemas/20_tables/chat_messages.sql | 52 + .../supabase/schemas/20_tables/chats.sql | 37 + .../schemas/20_tables/document_chunks.sql | 55 + .../schemas/20_tables/document_folders.sql | 39 + .../schemas/20_tables/document_summaries.sql | 52 + .../supabase/schemas/20_tables/documents.sql | 64 + .../schemas/20_tables/favorite_documents.sql | 9 + .../schemas/20_tables/maintenance_mode.sql | 57 + .../supabase/schemas/20_tables/profiles.sql | 35 + .../user_hidden_default_documents.sql | 16 + .../supabase/schemas/30_foreign_keys.sql | 62 + apps/backend/supabase/schemas/40_policies.sql | 408 ++++++ apps/backend/supabase/schemas/90_orphaned.sql | 14 + apps/backend/supabase/scripts/split-schema.ts | 405 ++++++ 24 files changed, 1588 insertions(+), 1179 deletions(-) create mode 100644 apps/backend/supabase/schemas/00_settings.sql create mode 100644 apps/backend/supabase/schemas/01_schema.sql rename apps/backend/supabase/schemas/{schema.sql => 02_functions.sql} (59%) create mode 100644 apps/backend/supabase/schemas/20_tables/access_group_members.sql create mode 100644 apps/backend/supabase/schemas/20_tables/access_groups.sql create mode 100644 apps/backend/supabase/schemas/20_tables/allowed_email_domains.sql create mode 100644 apps/backend/supabase/schemas/20_tables/allowed_individual_emails.sql create mode 100644 apps/backend/supabase/schemas/20_tables/application_admins.sql create mode 100644 apps/backend/supabase/schemas/20_tables/chat_messages.sql create mode 100644 apps/backend/supabase/schemas/20_tables/chats.sql create mode 100644 apps/backend/supabase/schemas/20_tables/document_chunks.sql create mode 100644 apps/backend/supabase/schemas/20_tables/document_folders.sql create mode 100644 apps/backend/supabase/schemas/20_tables/document_summaries.sql create mode 100644 apps/backend/supabase/schemas/20_tables/documents.sql create mode 100644 apps/backend/supabase/schemas/20_tables/favorite_documents.sql create mode 100644 apps/backend/supabase/schemas/20_tables/maintenance_mode.sql create mode 100644 apps/backend/supabase/schemas/20_tables/profiles.sql create mode 100644 apps/backend/supabase/schemas/20_tables/user_hidden_default_documents.sql create mode 100644 apps/backend/supabase/schemas/30_foreign_keys.sql create mode 100644 apps/backend/supabase/schemas/40_policies.sql create mode 100644 apps/backend/supabase/schemas/90_orphaned.sql create mode 100644 apps/backend/supabase/scripts/split-schema.ts diff --git a/apps/backend/package.json b/apps/backend/package.json index 1ed25328..563efeb6 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -22,6 +22,7 @@ "db:backfill-mistral": "tsx ./supabase/backfill-mistral-embeddings.ts", "db:up": "supabase migration up --local && npm run db:typegen", "db:dump-schema": "supabase db dump --local --schema public > supabase/schemas/schema.sql && prettier --write supabase/schemas/schema.sql", + "db:split-schema": "tsx supabase/scripts/split-schema.ts", "db:generate-migration": "supabase db diff --schema public -f", "mistral:assess-orphans": "tsx supabase/scripts/cleanup-mistral-orphans.ts -- --dry-run", "mistral:cleanup-orphans": "tsx supabase/scripts/cleanup-mistral-orphans.ts", diff --git a/apps/backend/supabase/config.toml b/apps/backend/supabase/config.toml index c4d6e575..2bef5145 100644 --- a/apps/backend/supabase/config.toml +++ b/apps/backend/supabase/config.toml @@ -42,7 +42,27 @@ max_client_conn = 100 [db.migrations] schema_paths = [ "./schemas/00_extensions.sql", - "./schemas/schema.sql", + "./schemas/00_settings.sql", + "./schemas/01_schema.sql", + "./schemas/02_functions.sql", + "./schemas/20_tables/access_group_members.sql", + "./schemas/20_tables/access_groups.sql", + "./schemas/20_tables/allowed_email_domains.sql", + "./schemas/20_tables/allowed_individual_emails.sql", + "./schemas/20_tables/application_admins.sql", + "./schemas/20_tables/chat_messages.sql", + "./schemas/20_tables/chats.sql", + "./schemas/20_tables/document_chunks.sql", + "./schemas/20_tables/document_folders.sql", + "./schemas/20_tables/document_summaries.sql", + "./schemas/20_tables/documents.sql", + "./schemas/20_tables/favorite_documents.sql", + "./schemas/20_tables/maintenance_mode.sql", + "./schemas/20_tables/profiles.sql", + "./schemas/20_tables/user_hidden_default_documents.sql", + "./schemas/30_foreign_keys.sql", + "./schemas/40_policies.sql", + "./schemas/90_orphaned.sql", ] [realtime] diff --git a/apps/backend/supabase/schemas/00_settings.sql b/apps/backend/supabase/schemas/00_settings.sql new file mode 100644 index 00000000..b1b34e7d --- /dev/null +++ b/apps/backend/supabase/schemas/00_settings.sql @@ -0,0 +1,35 @@ +SET + statement_timeout = 0; + +SET + lock_timeout = 0; + +SET + idle_in_transaction_session_timeout = 0; + +SET + client_encoding = 'UTF8'; + +SET + standard_conforming_strings = ON; + +SELECT + pg_catalog.set_config ('search_path', '', FALSE); + +SET + check_function_bodies = FALSE; + +SET + xmloption = content; + +SET + client_min_messages = warning; + +SET + row_security = off; + +SET + default_tablespace = ''; + +SET + default_table_access_method = "heap"; diff --git a/apps/backend/supabase/schemas/01_schema.sql b/apps/backend/supabase/schemas/01_schema.sql new file mode 100644 index 00000000..9bab1c97 --- /dev/null +++ b/apps/backend/supabase/schemas/01_schema.sql @@ -0,0 +1,49 @@ +CREATE SCHEMA IF NOT EXISTS "public"; + +ALTER SCHEMA "public" OWNER TO "pg_database_owner"; + +COMMENT ON SCHEMA "public" IS 'standard public schema'; + +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"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON SEQUENCES TO "postgres"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON SEQUENCES TO "anon"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON SEQUENCES TO "authenticated"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON SEQUENCES TO "service_role"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON FUNCTIONS TO "postgres"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON FUNCTIONS TO "anon"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON FUNCTIONS TO "authenticated"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON FUNCTIONS TO "service_role"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON TABLES TO "postgres"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON TABLES TO "anon"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON TABLES TO "authenticated"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" +GRANT ALL ON TABLES TO "service_role"; diff --git a/apps/backend/supabase/schemas/schema.sql b/apps/backend/supabase/schemas/02_functions.sql similarity index 59% rename from apps/backend/supabase/schemas/schema.sql rename to apps/backend/supabase/schemas/02_functions.sql index 78e2df65..86789b1b 100644 --- a/apps/backend/supabase/schemas/schema.sql +++ b/apps/backend/supabase/schemas/02_functions.sql @@ -1,39 +1,3 @@ -SET - statement_timeout = 0; - -SET - lock_timeout = 0; - -SET - idle_in_transaction_session_timeout = 0; - -SET - client_encoding = 'UTF8'; - -SET - standard_conforming_strings = ON; - -SELECT - pg_catalog.set_config ('search_path', '', FALSE); - -SET - check_function_bodies = FALSE; - -SET - xmloption = content; - -SET - client_min_messages = warning; - -SET - row_security = off; - -CREATE SCHEMA IF NOT EXISTS "public"; - -ALTER SCHEMA "public" OWNER TO "pg_database_owner"; - -COMMENT ON SCHEMA "public" IS 'standard public schema'; - CREATE OR REPLACE FUNCTION "public"."activate_allowed_domain" ("p_domain" "text") RETURNS "void" LANGUAGE "plpgsql" SECURITY DEFINER SET "search_path" TO '' AS $$ @@ -1269,940 +1233,6 @@ $$; ALTER FUNCTION "public"."verify_own_password" ("plain_password" "text") OWNER TO "postgres"; -SET - default_tablespace = ''; - -SET - default_table_access_method = "heap"; - -CREATE TABLE IF NOT EXISTS "public"."access_group_members" ( - "user_id" "uuid" NOT NULL, - "access_group_id" "uuid" NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" (), - "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () -); - -ALTER TABLE "public"."access_group_members" OWNER TO "postgres"; - -CREATE TABLE IF NOT EXISTS "public"."access_groups" ( - "id" "uuid" DEFAULT "gen_random_uuid" () NOT NULL, - "name" "text" NOT NULL, - "subset_of" "uuid", - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" (), - "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () -); - -ALTER TABLE "public"."access_groups" OWNER TO "postgres"; - -CREATE TABLE IF NOT EXISTS "public"."allowed_email_domains" ( - "id" INTEGER NOT NULL, - "domain" "text" NOT NULL, - "is_active" BOOLEAN DEFAULT TRUE NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, - "created_by" "uuid", - "last_status_change_at" TIMESTAMP WITH TIME ZONE, - "last_status_change_by" "uuid", - CONSTRAINT "allowed_email_domains_exact_format" CHECK ( - ( - "domain" ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$'::"text" - ) - ) -); - -ALTER TABLE "public"."allowed_email_domains" OWNER TO "postgres"; - -COMMENT ON TABLE "public"."allowed_email_domains" IS 'Table containing allowed email domains for user registration'; - -CREATE SEQUENCE IF NOT EXISTS "public"."allowed_email_domains_id_seq" AS INTEGER START -WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; - -ALTER TABLE "public"."allowed_email_domains_id_seq" OWNER TO "postgres"; - -ALTER SEQUENCE "public"."allowed_email_domains_id_seq" OWNED BY "public"."allowed_email_domains"."id"; - -CREATE TABLE IF NOT EXISTS "public"."allowed_individual_emails" ( - "id" INTEGER NOT NULL, - "email" "text" NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, - "created_by" "uuid", - CONSTRAINT "allowed_individual_emails_format" CHECK ( - ( - "email" ~ '^[a-zA-Z0-9._%+\-]+@[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)+$'::"text" - ) - ) -); - -ALTER TABLE "public"."allowed_individual_emails" OWNER TO "postgres"; - -COMMENT ON TABLE "public"."allowed_individual_emails" IS 'Individual email addresses explicitly permitted to sign up, independently of allowed_email_domains.'; - -CREATE SEQUENCE IF NOT EXISTS "public"."allowed_individual_emails_id_seq" AS INTEGER START -WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; - -ALTER TABLE "public"."allowed_individual_emails_id_seq" OWNER TO "postgres"; - -ALTER SEQUENCE "public"."allowed_individual_emails_id_seq" OWNED BY "public"."allowed_individual_emails"."id"; - -CREATE TABLE IF NOT EXISTS "public"."application_admins" ("id" BIGINT NOT NULL, "user_id" "uuid" NOT NULL); - -ALTER TABLE "public"."application_admins" OWNER TO "postgres"; - -COMMENT ON TABLE "public"."application_admins" IS 'Application admins to manage users via the dashboard.'; - -ALTER TABLE "public"."application_admins" -ALTER COLUMN "id" -ADD GENERATED BY DEFAULT AS IDENTITY ( - SEQUENCE NAME "public"."application_admins_id_seq" START - WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 -); - -CREATE TABLE IF NOT EXISTS "public"."chat_messages" ( - "id" INTEGER NOT NULL, - "chat_id" INTEGER NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, - "role" "text" NOT NULL, - "type" "text" NOT NULL, - "content" "text" NOT NULL, - "allowed_document_ids" INTEGER[], - "allowed_folder_ids" INTEGER[], - "citations" "jsonb", - "web_citations" "jsonb", - "parla_citations" "jsonb", - "open_data_citations" "jsonb", - "external_tool_context" BOOLEAN DEFAULT FALSE NOT NULL -); - -ALTER TABLE "public"."chat_messages" OWNER TO "postgres"; - -CREATE SEQUENCE IF NOT EXISTS "public"."chat_messages_id_seq" AS INTEGER START -WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; - -ALTER TABLE "public"."chat_messages_id_seq" OWNER TO "postgres"; - -ALTER SEQUENCE "public"."chat_messages_id_seq" OWNED BY "public"."chat_messages"."id"; - -CREATE TABLE IF NOT EXISTS "public"."chats" ( - "id" INTEGER NOT NULL, - "user_id" "uuid" NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, - "name" "text" NOT NULL -); - -ALTER TABLE "public"."chats" OWNER TO "postgres"; - -CREATE SEQUENCE IF NOT EXISTS "public"."chats_id_seq" AS INTEGER START -WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; - -ALTER TABLE "public"."chats_id_seq" OWNER TO "postgres"; - -ALTER SEQUENCE "public"."chats_id_seq" OWNED BY "public"."chats"."id"; - -CREATE TABLE IF NOT EXISTS "public"."document_chunks" ( - "id" INTEGER NOT NULL, - "owned_by_user_id" "uuid", - "content" "text" NOT NULL, - "page" INTEGER NOT NULL, - "chunk_index" INTEGER NOT NULL, - "document_id" INTEGER, - "folder_id" INTEGER, - "full_text_search" "tsvector" GENERATED ALWAYS AS ("to_tsvector" ('"german"'::"regconfig", "content")) STORED, - "access_group_id" "uuid", - "chunk_mistral_embedding" "extensions"."vector" (1024), - CONSTRAINT "chk_document_chunks_ownership" CHECK ( - ( - ( - ("owned_by_user_id" IS NOT NULL) - AND ("access_group_id" IS NULL) - ) - OR ( - ("owned_by_user_id" IS NULL) - AND ("access_group_id" IS NOT NULL) - ) - ) - ) -); - -ALTER TABLE "public"."document_chunks" OWNER TO "postgres"; - -CREATE TABLE IF NOT EXISTS "public"."document_folders" ( - "id" INTEGER NOT NULL, - "user_id" "uuid" NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, - "name" "text" NOT NULL -); - -ALTER TABLE "public"."document_folders" OWNER TO "postgres"; - -CREATE SEQUENCE IF NOT EXISTS "public"."document_folders_id_seq" AS INTEGER START -WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; - -ALTER TABLE "public"."document_folders_id_seq" OWNER TO "postgres"; - -ALTER SEQUENCE "public"."document_folders_id_seq" OWNED BY "public"."document_folders"."id"; - -CREATE TABLE IF NOT EXISTS "public"."document_summaries" ( - "id" INTEGER NOT NULL, - "owned_by_user_id" "uuid", - "summary" "text" NOT NULL, - "tags" "text" [] NOT NULL, - "document_id" INTEGER, - "short_summary" "text", - "folder_id" INTEGER, - "summary_jina_embedding" "extensions"."vector" (1024), - "access_group_id" "uuid", - CONSTRAINT "chk_document_summaries_ownership" CHECK ( - ( - ( - ("owned_by_user_id" IS NOT NULL) - AND ("access_group_id" IS NULL) - ) - OR ( - ("owned_by_user_id" IS NULL) - AND ("access_group_id" IS NOT NULL) - ) - ) - ) -); - -ALTER TABLE "public"."document_summaries" OWNER TO "postgres"; - -CREATE TABLE IF NOT EXISTS "public"."documents" ( - "id" INTEGER NOT NULL, - "owned_by_user_id" "uuid", - "source_url" "text" NOT NULL, - "source_type" "text" NOT NULL, - "file_name" "text", - "file_checksum" "text", - "file_size" INTEGER, - "num_pages" INTEGER, - "folder_id" INTEGER, - "processing_finished_at" TIMESTAMP WITH TIME ZONE, - "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" (), - "access_group_id" "uuid", - "uploaded_by_user_id" "uuid", - CONSTRAINT "chk_documents_ownership" CHECK ( - ( - ( - ("owned_by_user_id" IS NOT NULL) - AND ("access_group_id" IS NULL) - ) - OR ( - ("owned_by_user_id" IS NULL) - AND ("access_group_id" IS NOT NULL) - ) - ) - ) -); - -ALTER TABLE "public"."documents" OWNER TO "postgres"; - -ALTER TABLE "public"."documents" -ALTER COLUMN "id" -ADD GENERATED BY DEFAULT AS IDENTITY ( - SEQUENCE NAME "public"."documents_id_seq" START - WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 -); - -CREATE TABLE IF NOT EXISTS "public"."favorite_documents" ("user_id" "uuid" NOT NULL, "processed_document_id" INTEGER NOT NULL); - -ALTER TABLE "public"."favorite_documents" OWNER TO "postgres"; - -CREATE TABLE IF NOT EXISTS "public"."maintenance_mode" ( - "onerow_id" BOOLEAN DEFAULT TRUE NOT NULL, - "is_enabled" BOOLEAN DEFAULT FALSE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, - CONSTRAINT "onerow_uni" CHECK ("onerow_id") -); - -ALTER TABLE "public"."maintenance_mode" OWNER TO "postgres"; - -COMMENT ON TABLE "public"."maintenance_mode" IS 'Controls application-wide maintenance mode status'; - -COMMENT ON COLUMN "public"."maintenance_mode"."is_enabled" IS 'Whether maintenance mode is currently active'; - -COMMENT ON COLUMN "public"."maintenance_mode"."updated_at" IS 'When this record was last updated'; - -ALTER TABLE "public"."document_chunks" -ALTER COLUMN "id" -ADD GENERATED BY DEFAULT AS IDENTITY ( - SEQUENCE NAME "public"."processed_document_chunks_id_seq" START - WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 -); - -ALTER TABLE "public"."document_summaries" -ALTER COLUMN "id" -ADD GENERATED BY DEFAULT AS IDENTITY ( - SEQUENCE NAME "public"."processed_document_summaries_id_seq" START - WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 -); - -CREATE TABLE IF NOT EXISTS "public"."profiles" ( - "id" "uuid" NOT NULL, - "first_name" "text", - "last_name" "text", - "academic_title" "text", - "personal_title" "text", - "num_inferences" INTEGER DEFAULT 0, - "num_inference_tokens" BIGINT DEFAULT 0, - "num_documents" INTEGER DEFAULT 0, - "num_embedding_tokens" BIGINT DEFAULT 0, - "is_addressed_formal" BOOLEAN DEFAULT TRUE, - "personal_system_prompt" "text", - CONSTRAINT "personal_system_prompt_length" CHECK (("char_length" ("personal_system_prompt") <= 500)) -); - -ALTER TABLE "public"."profiles" OWNER TO "postgres"; - -COMMENT ON COLUMN "public"."profiles"."personal_system_prompt" IS 'User-defined personal system prompt, merged into the global system prompt on every chat request. NULL means none set.'; - -CREATE TABLE IF NOT EXISTS "public"."user_hidden_default_documents" ( - "user_id" "uuid" NOT NULL, - "document_id" INTEGER NOT NULL, - "created_at" TIMESTAMP WITHOUT TIME ZONE DEFAULT "now" () -); - -ALTER TABLE "public"."user_hidden_default_documents" OWNER TO "postgres"; - -CREATE SEQUENCE IF NOT EXISTS "public"."user_requests_id_seq" START -WITH - 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; - -ALTER TABLE "public"."user_requests_id_seq" OWNER TO "postgres"; - -ALTER TABLE ONLY "public"."allowed_email_domains" -ALTER COLUMN "id" -SET DEFAULT "nextval" ('"public"."allowed_email_domains_id_seq"'::"regclass"); - -ALTER TABLE ONLY "public"."allowed_individual_emails" -ALTER COLUMN "id" -SET DEFAULT "nextval" ('"public"."allowed_individual_emails_id_seq"'::"regclass"); - -ALTER TABLE ONLY "public"."chat_messages" -ALTER COLUMN "id" -SET DEFAULT "nextval" ('"public"."chat_messages_id_seq"'::"regclass"); - -ALTER TABLE ONLY "public"."chats" -ALTER COLUMN "id" -SET DEFAULT "nextval" ('"public"."chats_id_seq"'::"regclass"); - -ALTER TABLE ONLY "public"."document_folders" -ALTER COLUMN "id" -SET DEFAULT "nextval" ('"public"."document_folders_id_seq"'::"regclass"); - -ALTER TABLE ONLY "public"."access_group_members" -ADD CONSTRAINT "access_group_members_pkey" PRIMARY KEY ("user_id"); - -ALTER TABLE ONLY "public"."access_group_members" -ADD CONSTRAINT "access_group_members_user_access_group_key" UNIQUE ("user_id", "access_group_id"); - -ALTER TABLE ONLY "public"."access_groups" -ADD CONSTRAINT "access_groups_name_key" UNIQUE ("name"); - -ALTER TABLE ONLY "public"."access_groups" -ADD CONSTRAINT "access_groups_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."allowed_email_domains" -ADD CONSTRAINT "allowed_email_domains_domain_key" UNIQUE ("domain"); - -ALTER TABLE ONLY "public"."allowed_email_domains" -ADD CONSTRAINT "allowed_email_domains_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."allowed_individual_emails" -ADD CONSTRAINT "allowed_individual_emails_email_key" UNIQUE ("email"); - -ALTER TABLE ONLY "public"."allowed_individual_emails" -ADD CONSTRAINT "allowed_individual_emails_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."application_admins" -ADD CONSTRAINT "application_admins_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."application_admins" -ADD CONSTRAINT "application_admins_user_id_key" UNIQUE ("user_id"); - -ALTER TABLE ONLY "public"."chat_messages" -ADD CONSTRAINT "chat_messages_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."chats" -ADD CONSTRAINT "chats_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."document_folders" -ADD CONSTRAINT "document_folders_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."documents" -ADD CONSTRAINT "documents_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."maintenance_mode" -ADD CONSTRAINT "maintenance_mode_pkey" PRIMARY KEY ("onerow_id"); - -ALTER TABLE ONLY "public"."document_chunks" -ADD CONSTRAINT "processed_document_chunks_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."document_summaries" -ADD CONSTRAINT "processed_document_summaries_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."profiles" -ADD CONSTRAINT "profiles_pkey" PRIMARY KEY ("id"); - -ALTER TABLE ONLY "public"."user_hidden_default_documents" -ADD CONSTRAINT "user_hidden_default_documents_pkey" PRIMARY KEY ("user_id", "document_id"); - -CREATE INDEX "idx_allowed_email_domains_is_active" ON "public"."allowed_email_domains" USING "btree" ("is_active"); - -CREATE INDEX "idx_allowed_individual_emails_email" ON "public"."allowed_individual_emails" USING "btree" ("lower" ("email")); - -CREATE INDEX "idx_chat_messages_allowed_document_ids_gin" ON "public"."chat_messages" USING "gin" ("allowed_document_ids"); - -CREATE INDEX "idx_chat_messages_chat_id" ON "public"."chat_messages" USING "btree" ("chat_id"); - -CREATE INDEX "idx_chat_messages_content_trgm" ON "public"."chat_messages" USING "gin" ("content" "extensions"."gin_trgm_ops"); - -CREATE INDEX "idx_chat_messages_role" ON "public"."chat_messages" USING "btree" ("role"); - -CREATE INDEX "idx_chats_user_id" ON "public"."chats" USING "btree" ("user_id"); - -CREATE INDEX "idx_document_chunks_document_id" ON "public"."document_chunks" USING "btree" ("document_id"); - -CREATE INDEX "idx_document_chunks_full_text_search" ON "public"."document_chunks" USING "gin" ("full_text_search"); - -CREATE INDEX "idx_document_chunks_owned_by_user_id" ON "public"."document_chunks" USING "btree" ("owned_by_user_id"); - -CREATE INDEX "idx_document_summaries_document_id" ON "public"."document_summaries" USING "btree" ("document_id"); - -CREATE INDEX "idx_document_summaries_owned_by_user_id" ON "public"."document_summaries" USING "btree" ("owned_by_user_id"); - -CREATE INDEX "idx_documents_owned_by_user_id" ON "public"."documents" USING "btree" ("owned_by_user_id"); - -CREATE INDEX "idx_documents_source_type" ON "public"."documents" USING "btree" ("source_type"); - -CREATE OR REPLACE TRIGGER "add_new_user_to_access_group_trigger" -AFTER INSERT ON "public"."profiles" FOR EACH ROW -EXECUTE FUNCTION "public"."add_user_to_access_group" (); - -CREATE OR REPLACE TRIGGER "auto_set_updated_at_for_access_group_members" -BEFORE INSERT OR UPDATE ON "public"."access_group_members" FOR EACH ROW -EXECUTE FUNCTION "public"."tg_set_updated_at" (); - -CREATE OR REPLACE TRIGGER "auto_set_updated_at_on_access_groups" -BEFORE INSERT OR UPDATE ON "public"."access_groups" FOR EACH ROW -EXECUTE FUNCTION "public"."tg_set_updated_at" (); - -CREATE OR REPLACE TRIGGER "normalize_personal_system_prompt_trigger" -BEFORE INSERT OR UPDATE OF "personal_system_prompt" ON "public"."profiles" FOR EACH ROW -EXECUTE FUNCTION "public"."normalize_personal_system_prompt" (); - -CREATE OR REPLACE TRIGGER "trg_maintain_chat_messages_document_references" -BEFORE DELETE ON "public"."documents" FOR EACH ROW -EXECUTE FUNCTION "public"."maintain_chat_messages_document_references" (); - -CREATE OR REPLACE TRIGGER "trg_maintain_chat_messages_folder_references" -BEFORE DELETE ON "public"."document_folders" FOR EACH ROW -EXECUTE FUNCTION "public"."maintain_chat_messages_folder_references" (); - -CREATE OR REPLACE TRIGGER "trg_update_folder_id_cascading" -AFTER UPDATE OF "folder_id" ON "public"."documents" FOR EACH ROW WHEN (("old"."folder_id" IS DISTINCT FROM "new"."folder_id")) -EXECUTE FUNCTION "public"."update_folder_id_cascading" (); - -CREATE OR REPLACE TRIGGER "trigger_prevent_maintenance_mode_delete" -BEFORE DELETE ON "public"."maintenance_mode" FOR EACH ROW -EXECUTE FUNCTION "public"."prevent_maintenance_mode_delete" (); - -CREATE OR REPLACE TRIGGER "trigger_prevent_maintenance_mode_truncate" -BEFORE TRUNCATE ON "public"."maintenance_mode" FOR EACH STATEMENT -EXECUTE FUNCTION "public"."prevent_maintenance_mode_truncate" (); - -CREATE OR REPLACE TRIGGER "trigger_update_maintenance_mode_updated_at" -BEFORE UPDATE ON "public"."maintenance_mode" FOR EACH ROW -EXECUTE FUNCTION "public"."update_maintenance_mode_updated_at" (); - -ALTER TABLE ONLY "public"."access_group_members" -ADD CONSTRAINT "access_group_members_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."access_group_members" -ADD CONSTRAINT "access_group_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."access_groups" -ADD CONSTRAINT "access_groups_subset_of_fkey" FOREIGN KEY ("subset_of") REFERENCES "public"."access_groups" ("id"); - -ALTER TABLE ONLY "public"."allowed_email_domains" -ADD CONSTRAINT "allowed_email_domains_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; - -ALTER TABLE ONLY "public"."allowed_email_domains" -ADD CONSTRAINT "allowed_email_domains_last_status_change_by_fkey" FOREIGN KEY ("last_status_change_by") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; - -ALTER TABLE ONLY "public"."allowed_individual_emails" -ADD CONSTRAINT "allowed_individual_emails_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; - -ALTER TABLE ONLY "public"."application_admins" -ADD CONSTRAINT "application_admins_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."chat_messages" -ADD CONSTRAINT "chat_messages_chat_id_fkey" FOREIGN KEY ("chat_id") REFERENCES "public"."chats" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."chats" -ADD CONSTRAINT "chats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."document_chunks" -ADD CONSTRAINT "document_chunks_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE SET NULL; - -ALTER TABLE ONLY "public"."document_chunks" -ADD CONSTRAINT "document_chunks_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."documents" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."document_folders" -ADD CONSTRAINT "document_folders_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."document_summaries" -ADD CONSTRAINT "document_summaries_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE SET NULL; - -ALTER TABLE ONLY "public"."document_summaries" -ADD CONSTRAINT "document_summaries_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."documents" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."documents" -ADD CONSTRAINT "documents_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE SET NULL; - -ALTER TABLE ONLY "public"."documents" -ADD CONSTRAINT "documents_owner_user_id_fkey" FOREIGN KEY ("owned_by_user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."documents" -ADD CONSTRAINT "documents_uploaded_by_user_id_fkey" FOREIGN KEY ("uploaded_by_user_id") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; - -ALTER TABLE ONLY "public"."favorite_documents" -ADD CONSTRAINT "favorite_documents_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."profiles" -ADD CONSTRAINT "profiles_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; - -ALTER TABLE ONLY "public"."user_hidden_default_documents" -ADD CONSTRAINT "user_hidden_default_documents_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."documents" ("id"); - -ALTER TABLE ONLY "public"."user_hidden_default_documents" -ADD CONSTRAINT "user_hidden_default_documents_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."profiles" ("id") ON DELETE CASCADE; - -CREATE POLICY "Admins can delete hidden default docs" ON "public"."user_hidden_default_documents" FOR DELETE TO "authenticated" USING ("public"."is_application_admin" ()); - -CREATE POLICY "Allow authenticated users to CRUD their own chat_messages" ON "public"."chat_messages" TO "authenticated" USING ( - ( - ( - EXISTS ( - SELECT - 1 - FROM - "public"."chats" - WHERE - ( - ("chats"."id" = "chat_messages"."chat_id") - AND ( - "chats"."user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - ) - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) -) -WITH - CHECK ( - ( - ( - EXISTS ( - SELECT - 1 - FROM - "public"."chats" - WHERE - ( - ("chats"."id" = "chat_messages"."chat_id") - AND ( - "chats"."user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - ) - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Allow authenticated users to CRUD their own chats" ON "public"."chats" TO "authenticated" USING ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "user_id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) -) -WITH - CHECK ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "user_id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Allow authenticated users to CRUD their own document_folders" ON "public"."document_folders" TO "authenticated" USING ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "user_id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) -) -WITH - CHECK ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "user_id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Allow authenticated users to CRUD their own rows" ON "public"."favorite_documents" TO "authenticated" USING ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "user_id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) -) -WITH - CHECK ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "user_id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Allow authenticated users to access own or public document_chun" ON "public"."document_chunks" TO "authenticated" USING ( - ( - ( - ("owned_by_user_id" IS NULL) - OR ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) -) -WITH - CHECK ( - ( - ( - ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - OR ( - "public"."is_application_admin" () - AND ("owned_by_user_id" IS NULL) - ) - ) - ); - -CREATE POLICY "Allow authenticated users to access own or public document_summ" ON "public"."document_summaries" TO "authenticated" USING ( - ( - ( - ("owned_by_user_id" IS NULL) - OR ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) -) -WITH - CHECK ( - ( - ( - ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - OR ( - "public"."is_application_admin" () - AND ("owned_by_user_id" IS NULL) - ) - ) - ); - -CREATE POLICY "Allow authenticated users to access own profile" ON "public"."profiles" FOR -SELECT - TO "authenticated" USING ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Allow authenticated users to insert documents" ON "public"."documents" FOR INSERT TO "authenticated" -WITH - CHECK ( - ( - ( - ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - OR ( - "public"."is_application_admin" () - AND ("owned_by_user_id" IS NULL) - ) - ) - ); - -CREATE POLICY "Allow authenticated users to read documents" ON "public"."documents" FOR -SELECT - TO "authenticated" USING ( - ( - ( - ("owned_by_user_id" IS NULL) - OR ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Allow authenticated users to update documents" ON "public"."documents" -FOR UPDATE - TO "authenticated" USING ( - ( - ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Allow owners to delete documents and admins to delete base know" ON "public"."documents" FOR DELETE TO "authenticated" USING ( - ( - ( - ( - ( - "owned_by_user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - OR ( - "public"."is_application_admin" () - AND ("owned_by_user_id" IS NULL) - ) - ) - AND ("source_type" <> 'default_document'::"text") - ) -); - -CREATE POLICY "Users can insert their own hidden default docs" ON "public"."user_hidden_default_documents" FOR INSERT TO "authenticated" -WITH - CHECK ( - ( - ( - "user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Users can insert their own profile." ON "public"."profiles" FOR INSERT -WITH - CHECK ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Users can update own profile." ON "public"."profiles" -FOR UPDATE - USING ( - ( - ( - ( - SELECT - "auth"."uid" () AS "uid" - ) = "id" - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -CREATE POLICY "Users can view their own hidden default docs" ON "public"."user_hidden_default_documents" FOR -SELECT - TO "authenticated" USING ( - ( - ( - "user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ); - -ALTER TABLE "public"."access_group_members" ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "access_group_members_delete_admin" ON "public"."access_group_members" FOR DELETE USING ("public"."is_application_admin" ()); - -CREATE POLICY "access_group_members_insert_admin" ON "public"."access_group_members" FOR INSERT -WITH - CHECK ("public"."is_application_admin" ()); - -CREATE POLICY "access_group_members_select" ON "public"."access_group_members" FOR -SELECT - USING ( - ( - "public"."is_application_admin" () - OR ( - ( - "user_id" = ( - SELECT - "auth"."uid" () AS "uid" - ) - ) - AND (NOT "public"."is_current_user_banned" ()) - ) - ) - ); - -CREATE POLICY "access_group_members_update_admin" ON "public"."access_group_members" -FOR UPDATE - USING ("public"."is_application_admin" ()) -WITH - CHECK ("public"."is_application_admin" ()); - -ALTER TABLE "public"."access_groups" ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "access_groups_delete_admin" ON "public"."access_groups" FOR DELETE USING ("public"."is_application_admin" ()); - -CREATE POLICY "access_groups_insert_admin" ON "public"."access_groups" FOR INSERT -WITH - CHECK ("public"."is_application_admin" ()); - -CREATE POLICY "access_groups_select_all" ON "public"."access_groups" FOR -SELECT - USING (TRUE); - -CREATE POLICY "access_groups_update_admin" ON "public"."access_groups" -FOR UPDATE - USING ("public"."is_application_admin" ()) -WITH - CHECK ("public"."is_application_admin" ()); - -ALTER TABLE "public"."allowed_email_domains" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."allowed_individual_emails" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."application_admins" ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "application_admins_no_direct_access" ON "public"."application_admins" USING (FALSE) -WITH - CHECK (FALSE); - -ALTER TABLE "public"."chat_messages" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."chats" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."document_chunks" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."document_folders" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."document_summaries" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."documents" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."favorite_documents" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."maintenance_mode" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."profiles" ENABLE ROW LEVEL SECURITY; - -ALTER TABLE "public"."user_hidden_default_documents" ENABLE ROW LEVEL SECURITY; - -GRANT USAGE ON SCHEMA "public" TO "postgres"; - -GRANT USAGE ON SCHEMA "public" TO "anon"; - -GRANT USAGE ON SCHEMA "public" TO "authenticated"; - -GRANT USAGE ON SCHEMA "public" TO "service_role"; - GRANT ALL ON FUNCTION "public"."activate_allowed_domain" ("p_domain" "text") TO "anon"; GRANT ALL ON FUNCTION "public"."activate_allowed_domain" ("p_domain" "text") TO "authenticated"; @@ -2580,211 +1610,3 @@ GRANT ALL ON FUNCTION "public"."verify_own_password" ("plain_password" "text") T GRANT ALL ON FUNCTION "public"."verify_own_password" ("plain_password" "text") TO "authenticated"; GRANT ALL ON FUNCTION "public"."verify_own_password" ("plain_password" "text") TO "service_role"; - -GRANT ALL ON TABLE "public"."access_group_members" TO "anon"; - -GRANT ALL ON TABLE "public"."access_group_members" TO "authenticated"; - -GRANT ALL ON TABLE "public"."access_group_members" TO "service_role"; - -GRANT ALL ON TABLE "public"."access_groups" TO "anon"; - -GRANT ALL ON TABLE "public"."access_groups" TO "authenticated"; - -GRANT ALL ON TABLE "public"."access_groups" TO "service_role"; - -GRANT ALL ON TABLE "public"."allowed_email_domains" TO "anon"; - -GRANT ALL ON TABLE "public"."allowed_email_domains" TO "authenticated"; - -GRANT ALL ON TABLE "public"."allowed_email_domains" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."allowed_email_domains_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."allowed_email_domains_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."allowed_email_domains_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."allowed_individual_emails" TO "anon"; - -GRANT ALL ON TABLE "public"."allowed_individual_emails" TO "authenticated"; - -GRANT ALL ON TABLE "public"."allowed_individual_emails" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."allowed_individual_emails_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."allowed_individual_emails_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."allowed_individual_emails_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."application_admins" TO "anon"; - -GRANT ALL ON TABLE "public"."application_admins" TO "authenticated"; - -GRANT ALL ON TABLE "public"."application_admins" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."application_admins_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."application_admins_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."application_admins_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."chat_messages" TO "anon"; - -GRANT ALL ON TABLE "public"."chat_messages" TO "authenticated"; - -GRANT ALL ON TABLE "public"."chat_messages" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."chat_messages_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."chat_messages_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."chat_messages_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."chats" TO "anon"; - -GRANT ALL ON TABLE "public"."chats" TO "authenticated"; - -GRANT ALL ON TABLE "public"."chats" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."chats_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."chats_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."chats_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."document_chunks" TO "anon"; - -GRANT ALL ON TABLE "public"."document_chunks" TO "authenticated"; - -GRANT ALL ON TABLE "public"."document_chunks" TO "service_role"; - -GRANT ALL ON TABLE "public"."document_folders" TO "anon"; - -GRANT ALL ON TABLE "public"."document_folders" TO "authenticated"; - -GRANT ALL ON TABLE "public"."document_folders" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."document_folders_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."document_folders_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."document_folders_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."document_summaries" TO "anon"; - -GRANT ALL ON TABLE "public"."document_summaries" TO "authenticated"; - -GRANT ALL ON TABLE "public"."document_summaries" TO "service_role"; - -GRANT ALL ON TABLE "public"."documents" TO "anon"; - -GRANT ALL ON TABLE "public"."documents" TO "authenticated"; - -GRANT ALL ON TABLE "public"."documents" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."documents_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."documents_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."documents_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."favorite_documents" TO "anon"; - -GRANT ALL ON TABLE "public"."favorite_documents" TO "authenticated"; - -GRANT ALL ON TABLE "public"."favorite_documents" TO "service_role"; - -GRANT -SELECT -, - REFERENCES, - TRIGGER, -TRUNCATE ON TABLE "public"."maintenance_mode" TO "anon"; - -GRANT -SELECT -, - REFERENCES, - TRIGGER, -TRUNCATE ON TABLE "public"."maintenance_mode" TO "authenticated"; - -REVOKE INSERT, -UPDATE, -DELETE ON TABLE "public"."maintenance_mode" -FROM - "anon"; - -REVOKE INSERT, -UPDATE, -DELETE ON TABLE "public"."maintenance_mode" -FROM - "authenticated"; - -GRANT ALL ON TABLE "public"."maintenance_mode" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."processed_document_chunks_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."processed_document_chunks_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."processed_document_chunks_id_seq" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."processed_document_summaries_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."processed_document_summaries_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."processed_document_summaries_id_seq" TO "service_role"; - -GRANT ALL ON TABLE "public"."profiles" TO "anon"; - -GRANT ALL ON TABLE "public"."profiles" TO "authenticated"; - -GRANT ALL ON TABLE "public"."profiles" TO "service_role"; - -GRANT ALL ON TABLE "public"."user_hidden_default_documents" TO "anon"; - -GRANT ALL ON TABLE "public"."user_hidden_default_documents" TO "authenticated"; - -GRANT ALL ON TABLE "public"."user_hidden_default_documents" TO "service_role"; - -GRANT ALL ON SEQUENCE "public"."user_requests_id_seq" TO "anon"; - -GRANT ALL ON SEQUENCE "public"."user_requests_id_seq" TO "authenticated"; - -GRANT ALL ON SEQUENCE "public"."user_requests_id_seq" TO "service_role"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON SEQUENCES TO "postgres"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON SEQUENCES TO "anon"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON SEQUENCES TO "authenticated"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON SEQUENCES TO "service_role"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON FUNCTIONS TO "postgres"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON FUNCTIONS TO "anon"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON FUNCTIONS TO "authenticated"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON FUNCTIONS TO "service_role"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON TABLES TO "postgres"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON TABLES TO "anon"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON TABLES TO "authenticated"; - -ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" -GRANT ALL ON TABLES TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/access_group_members.sql b/apps/backend/supabase/schemas/20_tables/access_group_members.sql new file mode 100644 index 00000000..a88275d1 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/access_group_members.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS "public"."access_group_members" ( + "user_id" "uuid" NOT NULL, + "access_group_id" "uuid" NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" (), + "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () +); + +ALTER TABLE "public"."access_group_members" OWNER TO "postgres"; + +ALTER TABLE ONLY "public"."access_group_members" +ADD CONSTRAINT "access_group_members_pkey" PRIMARY KEY ("user_id"); + +ALTER TABLE ONLY "public"."access_group_members" +ADD CONSTRAINT "access_group_members_user_access_group_key" UNIQUE ("user_id", "access_group_id"); + +CREATE OR REPLACE TRIGGER "auto_set_updated_at_for_access_group_members" +BEFORE INSERT OR UPDATE ON "public"."access_group_members" FOR EACH ROW +EXECUTE FUNCTION "public"."tg_set_updated_at" (); + +GRANT ALL ON TABLE "public"."access_group_members" TO "anon"; + +GRANT ALL ON TABLE "public"."access_group_members" TO "authenticated"; + +GRANT ALL ON TABLE "public"."access_group_members" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/access_groups.sql b/apps/backend/supabase/schemas/20_tables/access_groups.sql new file mode 100644 index 00000000..794716cc --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/access_groups.sql @@ -0,0 +1,25 @@ +CREATE TABLE IF NOT EXISTS "public"."access_groups" ( + "id" "uuid" DEFAULT "gen_random_uuid" () NOT NULL, + "name" "text" NOT NULL, + "subset_of" "uuid", + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" (), + "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () +); + +ALTER TABLE "public"."access_groups" OWNER TO "postgres"; + +ALTER TABLE ONLY "public"."access_groups" +ADD CONSTRAINT "access_groups_name_key" UNIQUE ("name"); + +ALTER TABLE ONLY "public"."access_groups" +ADD CONSTRAINT "access_groups_pkey" PRIMARY KEY ("id"); + +CREATE OR REPLACE TRIGGER "auto_set_updated_at_on_access_groups" +BEFORE INSERT OR UPDATE ON "public"."access_groups" FOR EACH ROW +EXECUTE FUNCTION "public"."tg_set_updated_at" (); + +GRANT ALL ON TABLE "public"."access_groups" TO "anon"; + +GRANT ALL ON TABLE "public"."access_groups" TO "authenticated"; + +GRANT ALL ON TABLE "public"."access_groups" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/allowed_email_domains.sql b/apps/backend/supabase/schemas/20_tables/allowed_email_domains.sql new file mode 100644 index 00000000..7b388b49 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/allowed_email_domains.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS "public"."allowed_email_domains" ( + "id" INTEGER NOT NULL, + "domain" "text" NOT NULL, + "is_active" BOOLEAN DEFAULT TRUE NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, + "created_by" "uuid", + "last_status_change_at" TIMESTAMP WITH TIME ZONE, + "last_status_change_by" "uuid", + CONSTRAINT "allowed_email_domains_exact_format" CHECK ( + ( + "domain" ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$'::"text" + ) + ) +); + +ALTER TABLE "public"."allowed_email_domains" OWNER TO "postgres"; + +COMMENT ON TABLE "public"."allowed_email_domains" IS 'Table containing allowed email domains for user registration'; + +CREATE SEQUENCE IF NOT EXISTS "public"."allowed_email_domains_id_seq" AS INTEGER START +WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +ALTER TABLE "public"."allowed_email_domains_id_seq" OWNER TO "postgres"; + +ALTER SEQUENCE "public"."allowed_email_domains_id_seq" OWNED BY "public"."allowed_email_domains"."id"; + +ALTER TABLE ONLY "public"."allowed_email_domains" +ALTER COLUMN "id" +SET DEFAULT "nextval" ('"public"."allowed_email_domains_id_seq"'::"regclass"); + +ALTER TABLE ONLY "public"."allowed_email_domains" +ADD CONSTRAINT "allowed_email_domains_domain_key" UNIQUE ("domain"); + +ALTER TABLE ONLY "public"."allowed_email_domains" +ADD CONSTRAINT "allowed_email_domains_pkey" PRIMARY KEY ("id"); + +CREATE INDEX "idx_allowed_email_domains_is_active" ON "public"."allowed_email_domains" USING "btree" ("is_active"); + +GRANT ALL ON TABLE "public"."allowed_email_domains" TO "anon"; + +GRANT ALL ON TABLE "public"."allowed_email_domains" TO "authenticated"; + +GRANT ALL ON TABLE "public"."allowed_email_domains" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."allowed_email_domains_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."allowed_email_domains_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."allowed_email_domains_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/allowed_individual_emails.sql b/apps/backend/supabase/schemas/20_tables/allowed_individual_emails.sql new file mode 100644 index 00000000..73ac5dd2 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/allowed_individual_emails.sql @@ -0,0 +1,47 @@ +CREATE TABLE IF NOT EXISTS "public"."allowed_individual_emails" ( + "id" INTEGER NOT NULL, + "email" "text" NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, + "created_by" "uuid", + CONSTRAINT "allowed_individual_emails_format" CHECK ( + ( + "email" ~ '^[a-zA-Z0-9._%+\-]+@[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?)+$'::"text" + ) + ) +); + +ALTER TABLE "public"."allowed_individual_emails" OWNER TO "postgres"; + +COMMENT ON TABLE "public"."allowed_individual_emails" IS 'Individual email addresses explicitly permitted to sign up, independently of allowed_email_domains.'; + +CREATE SEQUENCE IF NOT EXISTS "public"."allowed_individual_emails_id_seq" AS INTEGER START +WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +ALTER TABLE "public"."allowed_individual_emails_id_seq" OWNER TO "postgres"; + +ALTER SEQUENCE "public"."allowed_individual_emails_id_seq" OWNED BY "public"."allowed_individual_emails"."id"; + +ALTER TABLE ONLY "public"."allowed_individual_emails" +ALTER COLUMN "id" +SET DEFAULT "nextval" ('"public"."allowed_individual_emails_id_seq"'::"regclass"); + +ALTER TABLE ONLY "public"."allowed_individual_emails" +ADD CONSTRAINT "allowed_individual_emails_email_key" UNIQUE ("email"); + +ALTER TABLE ONLY "public"."allowed_individual_emails" +ADD CONSTRAINT "allowed_individual_emails_pkey" PRIMARY KEY ("id"); + +CREATE INDEX "idx_allowed_individual_emails_email" ON "public"."allowed_individual_emails" USING "btree" ("lower" ("email")); + +GRANT ALL ON TABLE "public"."allowed_individual_emails" TO "anon"; + +GRANT ALL ON TABLE "public"."allowed_individual_emails" TO "authenticated"; + +GRANT ALL ON TABLE "public"."allowed_individual_emails" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."allowed_individual_emails_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."allowed_individual_emails_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."allowed_individual_emails_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/application_admins.sql b/apps/backend/supabase/schemas/20_tables/application_admins.sql new file mode 100644 index 00000000..cda8db71 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/application_admins.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS "public"."application_admins" ("id" BIGINT NOT NULL, "user_id" "uuid" NOT NULL); + +ALTER TABLE "public"."application_admins" OWNER TO "postgres"; + +COMMENT ON TABLE "public"."application_admins" IS 'Application admins to manage users via the dashboard.'; + +ALTER TABLE "public"."application_admins" +ALTER COLUMN "id" +ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."application_admins_id_seq" START + WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 +); + +ALTER TABLE ONLY "public"."application_admins" +ADD CONSTRAINT "application_admins_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."application_admins" +ADD CONSTRAINT "application_admins_user_id_key" UNIQUE ("user_id"); + +GRANT ALL ON TABLE "public"."application_admins" TO "anon"; + +GRANT ALL ON TABLE "public"."application_admins" TO "authenticated"; + +GRANT ALL ON TABLE "public"."application_admins" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."application_admins_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."application_admins_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."application_admins_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/chat_messages.sql b/apps/backend/supabase/schemas/20_tables/chat_messages.sql new file mode 100644 index 00000000..de62cad5 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/chat_messages.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS "public"."chat_messages" ( + "id" INTEGER NOT NULL, + "chat_id" INTEGER NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, + "role" "text" NOT NULL, + "type" "text" NOT NULL, + "content" "text" NOT NULL, + "allowed_document_ids" INTEGER[], + "allowed_folder_ids" INTEGER[], + "citations" "jsonb", + "web_citations" "jsonb", + "parla_citations" "jsonb", + "open_data_citations" "jsonb", + "external_tool_context" BOOLEAN DEFAULT FALSE NOT NULL +); + +ALTER TABLE "public"."chat_messages" OWNER TO "postgres"; + +CREATE SEQUENCE IF NOT EXISTS "public"."chat_messages_id_seq" AS INTEGER START +WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +ALTER TABLE "public"."chat_messages_id_seq" OWNER TO "postgres"; + +ALTER SEQUENCE "public"."chat_messages_id_seq" OWNED BY "public"."chat_messages"."id"; + +ALTER TABLE ONLY "public"."chat_messages" +ALTER COLUMN "id" +SET DEFAULT "nextval" ('"public"."chat_messages_id_seq"'::"regclass"); + +ALTER TABLE ONLY "public"."chat_messages" +ADD CONSTRAINT "chat_messages_pkey" PRIMARY KEY ("id"); + +CREATE INDEX "idx_chat_messages_allowed_document_ids_gin" ON "public"."chat_messages" USING "gin" ("allowed_document_ids"); + +CREATE INDEX "idx_chat_messages_chat_id" ON "public"."chat_messages" USING "btree" ("chat_id"); + +CREATE INDEX "idx_chat_messages_content_trgm" ON "public"."chat_messages" USING "gin" ("content" "extensions"."gin_trgm_ops"); + +CREATE INDEX "idx_chat_messages_role" ON "public"."chat_messages" USING "btree" ("role"); + +GRANT ALL ON TABLE "public"."chat_messages" TO "anon"; + +GRANT ALL ON TABLE "public"."chat_messages" TO "authenticated"; + +GRANT ALL ON TABLE "public"."chat_messages" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."chat_messages_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."chat_messages_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."chat_messages_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/chats.sql b/apps/backend/supabase/schemas/20_tables/chats.sql new file mode 100644 index 00000000..f652065d --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/chats.sql @@ -0,0 +1,37 @@ +CREATE TABLE IF NOT EXISTS "public"."chats" ( + "id" INTEGER NOT NULL, + "user_id" "uuid" NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, + "name" "text" NOT NULL +); + +ALTER TABLE "public"."chats" OWNER TO "postgres"; + +CREATE SEQUENCE IF NOT EXISTS "public"."chats_id_seq" AS INTEGER START +WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +ALTER TABLE "public"."chats_id_seq" OWNER TO "postgres"; + +ALTER SEQUENCE "public"."chats_id_seq" OWNED BY "public"."chats"."id"; + +ALTER TABLE ONLY "public"."chats" +ALTER COLUMN "id" +SET DEFAULT "nextval" ('"public"."chats_id_seq"'::"regclass"); + +ALTER TABLE ONLY "public"."chats" +ADD CONSTRAINT "chats_pkey" PRIMARY KEY ("id"); + +CREATE INDEX "idx_chats_user_id" ON "public"."chats" USING "btree" ("user_id"); + +GRANT ALL ON TABLE "public"."chats" TO "anon"; + +GRANT ALL ON TABLE "public"."chats" TO "authenticated"; + +GRANT ALL ON TABLE "public"."chats" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."chats_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."chats_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."chats_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/document_chunks.sql b/apps/backend/supabase/schemas/20_tables/document_chunks.sql new file mode 100644 index 00000000..6eb814f5 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/document_chunks.sql @@ -0,0 +1,55 @@ +CREATE TABLE IF NOT EXISTS "public"."document_chunks" ( + "id" INTEGER NOT NULL, + "owned_by_user_id" "uuid", + "content" "text" NOT NULL, + "page" INTEGER NOT NULL, + "chunk_index" INTEGER NOT NULL, + "document_id" INTEGER, + "folder_id" INTEGER, + "full_text_search" "tsvector" GENERATED ALWAYS AS ("to_tsvector" ('"german"'::"regconfig", "content")) STORED, + "access_group_id" "uuid", + "chunk_mistral_embedding" "extensions"."vector" (1024), + CONSTRAINT "chk_document_chunks_ownership" CHECK ( + ( + ( + ("owned_by_user_id" IS NOT NULL) + AND ("access_group_id" IS NULL) + ) + OR ( + ("owned_by_user_id" IS NULL) + AND ("access_group_id" IS NOT NULL) + ) + ) + ) +); + +ALTER TABLE "public"."document_chunks" OWNER TO "postgres"; + +ALTER TABLE "public"."document_chunks" +ALTER COLUMN "id" +ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."processed_document_chunks_id_seq" START + WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 +); + +ALTER TABLE ONLY "public"."document_chunks" +ADD CONSTRAINT "processed_document_chunks_pkey" PRIMARY KEY ("id"); + +CREATE INDEX "idx_document_chunks_document_id" ON "public"."document_chunks" USING "btree" ("document_id"); + +CREATE INDEX "idx_document_chunks_full_text_search" ON "public"."document_chunks" USING "gin" ("full_text_search"); + +CREATE INDEX "idx_document_chunks_owned_by_user_id" ON "public"."document_chunks" USING "btree" ("owned_by_user_id"); + +GRANT ALL ON TABLE "public"."document_chunks" TO "anon"; + +GRANT ALL ON TABLE "public"."document_chunks" TO "authenticated"; + +GRANT ALL ON TABLE "public"."document_chunks" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."processed_document_chunks_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."processed_document_chunks_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."processed_document_chunks_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/document_folders.sql b/apps/backend/supabase/schemas/20_tables/document_folders.sql new file mode 100644 index 00000000..f8d47386 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/document_folders.sql @@ -0,0 +1,39 @@ +CREATE TABLE IF NOT EXISTS "public"."document_folders" ( + "id" INTEGER NOT NULL, + "user_id" "uuid" NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, + "name" "text" NOT NULL +); + +ALTER TABLE "public"."document_folders" OWNER TO "postgres"; + +CREATE SEQUENCE IF NOT EXISTS "public"."document_folders_id_seq" AS INTEGER START +WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +ALTER TABLE "public"."document_folders_id_seq" OWNER TO "postgres"; + +ALTER SEQUENCE "public"."document_folders_id_seq" OWNED BY "public"."document_folders"."id"; + +ALTER TABLE ONLY "public"."document_folders" +ALTER COLUMN "id" +SET DEFAULT "nextval" ('"public"."document_folders_id_seq"'::"regclass"); + +ALTER TABLE ONLY "public"."document_folders" +ADD CONSTRAINT "document_folders_pkey" PRIMARY KEY ("id"); + +CREATE OR REPLACE TRIGGER "trg_maintain_chat_messages_folder_references" +BEFORE DELETE ON "public"."document_folders" FOR EACH ROW +EXECUTE FUNCTION "public"."maintain_chat_messages_folder_references" (); + +GRANT ALL ON TABLE "public"."document_folders" TO "anon"; + +GRANT ALL ON TABLE "public"."document_folders" TO "authenticated"; + +GRANT ALL ON TABLE "public"."document_folders" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."document_folders_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."document_folders_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."document_folders_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/document_summaries.sql b/apps/backend/supabase/schemas/20_tables/document_summaries.sql new file mode 100644 index 00000000..44344044 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/document_summaries.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS "public"."document_summaries" ( + "id" INTEGER NOT NULL, + "owned_by_user_id" "uuid", + "summary" "text" NOT NULL, + "tags" "text" [] NOT NULL, + "document_id" INTEGER, + "short_summary" "text", + "folder_id" INTEGER, + "summary_jina_embedding" "extensions"."vector" (1024), + "access_group_id" "uuid", + CONSTRAINT "chk_document_summaries_ownership" CHECK ( + ( + ( + ("owned_by_user_id" IS NOT NULL) + AND ("access_group_id" IS NULL) + ) + OR ( + ("owned_by_user_id" IS NULL) + AND ("access_group_id" IS NOT NULL) + ) + ) + ) +); + +ALTER TABLE "public"."document_summaries" OWNER TO "postgres"; + +ALTER TABLE "public"."document_summaries" +ALTER COLUMN "id" +ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."processed_document_summaries_id_seq" START + WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 +); + +ALTER TABLE ONLY "public"."document_summaries" +ADD CONSTRAINT "processed_document_summaries_pkey" PRIMARY KEY ("id"); + +CREATE INDEX "idx_document_summaries_document_id" ON "public"."document_summaries" USING "btree" ("document_id"); + +CREATE INDEX "idx_document_summaries_owned_by_user_id" ON "public"."document_summaries" USING "btree" ("owned_by_user_id"); + +GRANT ALL ON TABLE "public"."document_summaries" TO "anon"; + +GRANT ALL ON TABLE "public"."document_summaries" TO "authenticated"; + +GRANT ALL ON TABLE "public"."document_summaries" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."processed_document_summaries_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."processed_document_summaries_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."processed_document_summaries_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/documents.sql b/apps/backend/supabase/schemas/20_tables/documents.sql new file mode 100644 index 00000000..d5856c06 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/documents.sql @@ -0,0 +1,64 @@ +CREATE TABLE IF NOT EXISTS "public"."documents" ( + "id" INTEGER NOT NULL, + "owned_by_user_id" "uuid", + "source_url" "text" NOT NULL, + "source_type" "text" NOT NULL, + "file_name" "text", + "file_checksum" "text", + "file_size" INTEGER, + "num_pages" INTEGER, + "folder_id" INTEGER, + "processing_finished_at" TIMESTAMP WITH TIME ZONE, + "created_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" (), + "access_group_id" "uuid", + "uploaded_by_user_id" "uuid", + CONSTRAINT "chk_documents_ownership" CHECK ( + ( + ( + ("owned_by_user_id" IS NOT NULL) + AND ("access_group_id" IS NULL) + ) + OR ( + ("owned_by_user_id" IS NULL) + AND ("access_group_id" IS NOT NULL) + ) + ) + ) +); + +ALTER TABLE "public"."documents" OWNER TO "postgres"; + +ALTER TABLE "public"."documents" +ALTER COLUMN "id" +ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."documents_id_seq" START + WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 +); + +ALTER TABLE ONLY "public"."documents" +ADD CONSTRAINT "documents_pkey" PRIMARY KEY ("id"); + +CREATE INDEX "idx_documents_owned_by_user_id" ON "public"."documents" USING "btree" ("owned_by_user_id"); + +CREATE INDEX "idx_documents_source_type" ON "public"."documents" USING "btree" ("source_type"); + +CREATE OR REPLACE TRIGGER "trg_maintain_chat_messages_document_references" +BEFORE DELETE ON "public"."documents" FOR EACH ROW +EXECUTE FUNCTION "public"."maintain_chat_messages_document_references" (); + +CREATE OR REPLACE TRIGGER "trg_update_folder_id_cascading" +AFTER UPDATE OF "folder_id" ON "public"."documents" FOR EACH ROW WHEN (("old"."folder_id" IS DISTINCT FROM "new"."folder_id")) +EXECUTE FUNCTION "public"."update_folder_id_cascading" (); + +GRANT ALL ON TABLE "public"."documents" TO "anon"; + +GRANT ALL ON TABLE "public"."documents" TO "authenticated"; + +GRANT ALL ON TABLE "public"."documents" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."documents_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."documents_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."documents_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/favorite_documents.sql b/apps/backend/supabase/schemas/20_tables/favorite_documents.sql new file mode 100644 index 00000000..ffd5cc71 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/favorite_documents.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "public"."favorite_documents" ("user_id" "uuid" NOT NULL, "processed_document_id" INTEGER NOT NULL); + +ALTER TABLE "public"."favorite_documents" OWNER TO "postgres"; + +GRANT ALL ON TABLE "public"."favorite_documents" TO "anon"; + +GRANT ALL ON TABLE "public"."favorite_documents" TO "authenticated"; + +GRANT ALL ON TABLE "public"."favorite_documents" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/maintenance_mode.sql b/apps/backend/supabase/schemas/20_tables/maintenance_mode.sql new file mode 100644 index 00000000..9db7c212 --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/maintenance_mode.sql @@ -0,0 +1,57 @@ +CREATE TABLE IF NOT EXISTS "public"."maintenance_mode" ( + "onerow_id" BOOLEAN DEFAULT TRUE NOT NULL, + "is_enabled" BOOLEAN DEFAULT FALSE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT "now" () NOT NULL, + CONSTRAINT "onerow_uni" CHECK ("onerow_id") +); + +ALTER TABLE "public"."maintenance_mode" OWNER TO "postgres"; + +COMMENT ON TABLE "public"."maintenance_mode" IS 'Controls application-wide maintenance mode status'; + +COMMENT ON COLUMN "public"."maintenance_mode"."is_enabled" IS 'Whether maintenance mode is currently active'; + +COMMENT ON COLUMN "public"."maintenance_mode"."updated_at" IS 'When this record was last updated'; + +ALTER TABLE ONLY "public"."maintenance_mode" +ADD CONSTRAINT "maintenance_mode_pkey" PRIMARY KEY ("onerow_id"); + +CREATE OR REPLACE TRIGGER "trigger_prevent_maintenance_mode_delete" +BEFORE DELETE ON "public"."maintenance_mode" FOR EACH ROW +EXECUTE FUNCTION "public"."prevent_maintenance_mode_delete" (); + +CREATE OR REPLACE TRIGGER "trigger_prevent_maintenance_mode_truncate" +BEFORE TRUNCATE ON "public"."maintenance_mode" FOR EACH STATEMENT +EXECUTE FUNCTION "public"."prevent_maintenance_mode_truncate" (); + +CREATE OR REPLACE TRIGGER "trigger_update_maintenance_mode_updated_at" +BEFORE UPDATE ON "public"."maintenance_mode" FOR EACH ROW +EXECUTE FUNCTION "public"."update_maintenance_mode_updated_at" (); + +GRANT +SELECT +, + REFERENCES, + TRIGGER, +TRUNCATE ON TABLE "public"."maintenance_mode" TO "anon"; + +GRANT +SELECT +, + REFERENCES, + TRIGGER, +TRUNCATE ON TABLE "public"."maintenance_mode" TO "authenticated"; + +REVOKE INSERT, +UPDATE, +DELETE ON TABLE "public"."maintenance_mode" +FROM + "anon"; + +REVOKE INSERT, +UPDATE, +DELETE ON TABLE "public"."maintenance_mode" +FROM + "authenticated"; + +GRANT ALL ON TABLE "public"."maintenance_mode" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/profiles.sql b/apps/backend/supabase/schemas/20_tables/profiles.sql new file mode 100644 index 00000000..5064eecd --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/profiles.sql @@ -0,0 +1,35 @@ +CREATE TABLE IF NOT EXISTS "public"."profiles" ( + "id" "uuid" NOT NULL, + "first_name" "text", + "last_name" "text", + "academic_title" "text", + "personal_title" "text", + "num_inferences" INTEGER DEFAULT 0, + "num_inference_tokens" BIGINT DEFAULT 0, + "num_documents" INTEGER DEFAULT 0, + "num_embedding_tokens" BIGINT DEFAULT 0, + "is_addressed_formal" BOOLEAN DEFAULT TRUE, + "personal_system_prompt" "text", + CONSTRAINT "personal_system_prompt_length" CHECK (("char_length" ("personal_system_prompt") <= 500)) +); + +ALTER TABLE "public"."profiles" OWNER TO "postgres"; + +COMMENT ON COLUMN "public"."profiles"."personal_system_prompt" IS 'User-defined personal system prompt, merged into the global system prompt on every chat request. NULL means none set.'; + +ALTER TABLE ONLY "public"."profiles" +ADD CONSTRAINT "profiles_pkey" PRIMARY KEY ("id"); + +CREATE OR REPLACE TRIGGER "add_new_user_to_access_group_trigger" +AFTER INSERT ON "public"."profiles" FOR EACH ROW +EXECUTE FUNCTION "public"."add_user_to_access_group" (); + +CREATE OR REPLACE TRIGGER "normalize_personal_system_prompt_trigger" +BEFORE INSERT OR UPDATE OF "personal_system_prompt" ON "public"."profiles" FOR EACH ROW +EXECUTE FUNCTION "public"."normalize_personal_system_prompt" (); + +GRANT ALL ON TABLE "public"."profiles" TO "anon"; + +GRANT ALL ON TABLE "public"."profiles" TO "authenticated"; + +GRANT ALL ON TABLE "public"."profiles" TO "service_role"; diff --git a/apps/backend/supabase/schemas/20_tables/user_hidden_default_documents.sql b/apps/backend/supabase/schemas/20_tables/user_hidden_default_documents.sql new file mode 100644 index 00000000..e57d4ebf --- /dev/null +++ b/apps/backend/supabase/schemas/20_tables/user_hidden_default_documents.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS "public"."user_hidden_default_documents" ( + "user_id" "uuid" NOT NULL, + "document_id" INTEGER NOT NULL, + "created_at" TIMESTAMP WITHOUT TIME ZONE DEFAULT "now" () +); + +ALTER TABLE "public"."user_hidden_default_documents" OWNER TO "postgres"; + +ALTER TABLE ONLY "public"."user_hidden_default_documents" +ADD CONSTRAINT "user_hidden_default_documents_pkey" PRIMARY KEY ("user_id", "document_id"); + +GRANT ALL ON TABLE "public"."user_hidden_default_documents" TO "anon"; + +GRANT ALL ON TABLE "public"."user_hidden_default_documents" TO "authenticated"; + +GRANT ALL ON TABLE "public"."user_hidden_default_documents" TO "service_role"; diff --git a/apps/backend/supabase/schemas/30_foreign_keys.sql b/apps/backend/supabase/schemas/30_foreign_keys.sql new file mode 100644 index 00000000..76e4c1a1 --- /dev/null +++ b/apps/backend/supabase/schemas/30_foreign_keys.sql @@ -0,0 +1,62 @@ +ALTER TABLE ONLY "public"."access_group_members" +ADD CONSTRAINT "access_group_members_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."access_group_members" +ADD CONSTRAINT "access_group_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."access_groups" +ADD CONSTRAINT "access_groups_subset_of_fkey" FOREIGN KEY ("subset_of") REFERENCES "public"."access_groups" ("id"); + +ALTER TABLE ONLY "public"."allowed_email_domains" +ADD CONSTRAINT "allowed_email_domains_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; + +ALTER TABLE ONLY "public"."allowed_email_domains" +ADD CONSTRAINT "allowed_email_domains_last_status_change_by_fkey" FOREIGN KEY ("last_status_change_by") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; + +ALTER TABLE ONLY "public"."allowed_individual_emails" +ADD CONSTRAINT "allowed_individual_emails_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; + +ALTER TABLE ONLY "public"."application_admins" +ADD CONSTRAINT "application_admins_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."chat_messages" +ADD CONSTRAINT "chat_messages_chat_id_fkey" FOREIGN KEY ("chat_id") REFERENCES "public"."chats" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."chats" +ADD CONSTRAINT "chats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."document_chunks" +ADD CONSTRAINT "document_chunks_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE SET NULL; + +ALTER TABLE ONLY "public"."document_chunks" +ADD CONSTRAINT "document_chunks_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."documents" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."document_folders" +ADD CONSTRAINT "document_folders_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."document_summaries" +ADD CONSTRAINT "document_summaries_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE SET NULL; + +ALTER TABLE ONLY "public"."document_summaries" +ADD CONSTRAINT "document_summaries_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."documents" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."documents" +ADD CONSTRAINT "documents_access_group_id_fkey" FOREIGN KEY ("access_group_id") REFERENCES "public"."access_groups" ("id") ON DELETE SET NULL; + +ALTER TABLE ONLY "public"."documents" +ADD CONSTRAINT "documents_owner_user_id_fkey" FOREIGN KEY ("owned_by_user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."documents" +ADD CONSTRAINT "documents_uploaded_by_user_id_fkey" FOREIGN KEY ("uploaded_by_user_id") REFERENCES "auth"."users" ("id") ON DELETE SET NULL; + +ALTER TABLE ONLY "public"."favorite_documents" +ADD CONSTRAINT "favorite_documents_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."profiles" +ADD CONSTRAINT "profiles_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users" ("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."user_hidden_default_documents" +ADD CONSTRAINT "user_hidden_default_documents_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."documents" ("id"); + +ALTER TABLE ONLY "public"."user_hidden_default_documents" +ADD CONSTRAINT "user_hidden_default_documents_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."profiles" ("id") ON DELETE CASCADE; diff --git a/apps/backend/supabase/schemas/40_policies.sql b/apps/backend/supabase/schemas/40_policies.sql new file mode 100644 index 00000000..70739e55 --- /dev/null +++ b/apps/backend/supabase/schemas/40_policies.sql @@ -0,0 +1,408 @@ +CREATE POLICY "Admins can delete hidden default docs" ON "public"."user_hidden_default_documents" FOR DELETE TO "authenticated" USING ("public"."is_application_admin" ()); + +CREATE POLICY "Allow authenticated users to CRUD their own chat_messages" ON "public"."chat_messages" TO "authenticated" USING ( + ( + ( + EXISTS ( + SELECT + 1 + FROM + "public"."chats" + WHERE + ( + ("chats"."id" = "chat_messages"."chat_id") + AND ( + "chats"."user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + ) + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) +) +WITH + CHECK ( + ( + ( + EXISTS ( + SELECT + 1 + FROM + "public"."chats" + WHERE + ( + ("chats"."id" = "chat_messages"."chat_id") + AND ( + "chats"."user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + ) + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Allow authenticated users to CRUD their own chats" ON "public"."chats" TO "authenticated" USING ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "user_id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) +) +WITH + CHECK ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "user_id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Allow authenticated users to CRUD their own document_folders" ON "public"."document_folders" TO "authenticated" USING ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "user_id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) +) +WITH + CHECK ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "user_id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Allow authenticated users to CRUD their own rows" ON "public"."favorite_documents" TO "authenticated" USING ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "user_id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) +) +WITH + CHECK ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "user_id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Allow authenticated users to access own or public document_chun" ON "public"."document_chunks" TO "authenticated" USING ( + ( + ( + ("owned_by_user_id" IS NULL) + OR ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) +) +WITH + CHECK ( + ( + ( + ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + OR ( + "public"."is_application_admin" () + AND ("owned_by_user_id" IS NULL) + ) + ) + ); + +CREATE POLICY "Allow authenticated users to access own or public document_summ" ON "public"."document_summaries" TO "authenticated" USING ( + ( + ( + ("owned_by_user_id" IS NULL) + OR ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) +) +WITH + CHECK ( + ( + ( + ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + OR ( + "public"."is_application_admin" () + AND ("owned_by_user_id" IS NULL) + ) + ) + ); + +CREATE POLICY "Allow authenticated users to access own profile" ON "public"."profiles" FOR +SELECT + TO "authenticated" USING ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Allow authenticated users to insert documents" ON "public"."documents" FOR INSERT TO "authenticated" +WITH + CHECK ( + ( + ( + ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + OR ( + "public"."is_application_admin" () + AND ("owned_by_user_id" IS NULL) + ) + ) + ); + +CREATE POLICY "Allow authenticated users to read documents" ON "public"."documents" FOR +SELECT + TO "authenticated" USING ( + ( + ( + ("owned_by_user_id" IS NULL) + OR ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Allow authenticated users to update documents" ON "public"."documents" +FOR UPDATE + TO "authenticated" USING ( + ( + ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Allow owners to delete documents and admins to delete base know" ON "public"."documents" FOR DELETE TO "authenticated" USING ( + ( + ( + ( + ( + "owned_by_user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + OR ( + "public"."is_application_admin" () + AND ("owned_by_user_id" IS NULL) + ) + ) + AND ("source_type" <> 'default_document'::"text") + ) +); + +CREATE POLICY "Users can insert their own hidden default docs" ON "public"."user_hidden_default_documents" FOR INSERT TO "authenticated" +WITH + CHECK ( + ( + ( + "user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Users can insert their own profile." ON "public"."profiles" FOR INSERT +WITH + CHECK ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Users can update own profile." ON "public"."profiles" +FOR UPDATE + USING ( + ( + ( + ( + SELECT + "auth"."uid" () AS "uid" + ) = "id" + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +CREATE POLICY "Users can view their own hidden default docs" ON "public"."user_hidden_default_documents" FOR +SELECT + TO "authenticated" USING ( + ( + ( + "user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ); + +ALTER TABLE "public"."access_group_members" ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "access_group_members_delete_admin" ON "public"."access_group_members" FOR DELETE USING ("public"."is_application_admin" ()); + +CREATE POLICY "access_group_members_insert_admin" ON "public"."access_group_members" FOR INSERT +WITH + CHECK ("public"."is_application_admin" ()); + +CREATE POLICY "access_group_members_select" ON "public"."access_group_members" FOR +SELECT + USING ( + ( + "public"."is_application_admin" () + OR ( + ( + "user_id" = ( + SELECT + "auth"."uid" () AS "uid" + ) + ) + AND (NOT "public"."is_current_user_banned" ()) + ) + ) + ); + +CREATE POLICY "access_group_members_update_admin" ON "public"."access_group_members" +FOR UPDATE + USING ("public"."is_application_admin" ()) +WITH + CHECK ("public"."is_application_admin" ()); + +ALTER TABLE "public"."access_groups" ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "access_groups_delete_admin" ON "public"."access_groups" FOR DELETE USING ("public"."is_application_admin" ()); + +CREATE POLICY "access_groups_insert_admin" ON "public"."access_groups" FOR INSERT +WITH + CHECK ("public"."is_application_admin" ()); + +CREATE POLICY "access_groups_select_all" ON "public"."access_groups" FOR +SELECT + USING (TRUE); + +CREATE POLICY "access_groups_update_admin" ON "public"."access_groups" +FOR UPDATE + USING ("public"."is_application_admin" ()) +WITH + CHECK ("public"."is_application_admin" ()); + +ALTER TABLE "public"."allowed_email_domains" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."allowed_individual_emails" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."application_admins" ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "application_admins_no_direct_access" ON "public"."application_admins" USING (FALSE) +WITH + CHECK (FALSE); + +ALTER TABLE "public"."chat_messages" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."chats" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."document_chunks" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."document_folders" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."document_summaries" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."documents" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."favorite_documents" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."maintenance_mode" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."profiles" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."user_hidden_default_documents" ENABLE ROW LEVEL SECURITY; diff --git a/apps/backend/supabase/schemas/90_orphaned.sql b/apps/backend/supabase/schemas/90_orphaned.sql new file mode 100644 index 00000000..e3d7a9fc --- /dev/null +++ b/apps/backend/supabase/schemas/90_orphaned.sql @@ -0,0 +1,14 @@ +-- Objects with no resolvable owning table (e.g. a sequence left behind by a dropped table). +-- Kept so nothing from the original dump is silently lost -- review and decide whether to +-- drop them via a migration instead of carrying them forward declaratively. +CREATE SEQUENCE IF NOT EXISTS "public"."user_requests_id_seq" START +WITH + 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +ALTER TABLE "public"."user_requests_id_seq" OWNER TO "postgres"; + +GRANT ALL ON SEQUENCE "public"."user_requests_id_seq" TO "anon"; + +GRANT ALL ON SEQUENCE "public"."user_requests_id_seq" TO "authenticated"; + +GRANT ALL ON SEQUENCE "public"."user_requests_id_seq" TO "service_role"; diff --git a/apps/backend/supabase/scripts/split-schema.ts b/apps/backend/supabase/scripts/split-schema.ts new file mode 100644 index 00000000..3e336467 --- /dev/null +++ b/apps/backend/supabase/scripts/split-schema.ts @@ -0,0 +1,405 @@ +/* eslint-disable no-console */ +import { execSync } from "node:child_process"; +import { + existsSync, + mkdirSync, + readdirSync, + readFileSync, + rmSync, + unlinkSync, + writeFileSync, +} from "node:fs"; +import { join } from "node:path"; + +const SCHEMAS_DIR = join(__dirname, "..", "schemas"); +const SOURCE_FILE = join(SCHEMAS_DIR, "schema.sql"); +const CONFIG_FILE = join(__dirname, "..", "config.toml"); +const TABLES_DIR = join(SCHEMAS_DIR, "20_tables"); +const EXTENSIONS_FILE = "00_extensions.sql"; +const ORPHANED_FILE = "90_orphaned.sql"; + +const isDryRun = process.argv.includes("--dry-run"); + +type Statement = { text: string; startLine: number }; + +// Splits a SQL dump into top-level statements, tracking single-quoted strings, +// double-quoted identifiers, dollar-quoted function bodies and line comments so +// semicolons inside them don't get treated as statement terminators. +function splitStatements(sql: string): Statement[] { + const statements: Statement[] = []; + const n = sql.length; + let i = 0; + let line = 1; + let statementStart = 0; + let statementStartLine = 1; + let inSingleQuote = false; + let inDoubleQuote = false; + let dollarTag: string | null = null; + + while (i < n) { + const ch = sql[i]; + if (ch === "\n") { + line++; + } + + if (dollarTag) { + if (sql.startsWith(dollarTag, i)) { + i += dollarTag.length; + dollarTag = null; + } else { + i++; + } + continue; + } + + if (inSingleQuote) { + if (ch === "'") { + if (sql[i + 1] === "'") { + i += 2; + continue; + } + inSingleQuote = false; + } + i++; + continue; + } + + if (inDoubleQuote) { + if (ch === '"') { + if (sql[i + 1] === '"') { + i += 2; + continue; + } + inDoubleQuote = false; + } + i++; + continue; + } + + if (ch === "-" && sql[i + 1] === "-") { + const nl = sql.indexOf("\n", i); + i = nl === -1 ? n : nl; + continue; + } + + if (ch === "'") { + inSingleQuote = true; + i++; + continue; + } + if (ch === '"') { + inDoubleQuote = true; + i++; + continue; + } + if (ch === "$") { + const match = /^\$[A-Za-z_0-9]*\$/.exec(sql.slice(i)); + if (match) { + dollarTag = match[0]; + i += match[0].length; + continue; + } + } + + if (ch === ";") { + const text = sql.slice(statementStart, i + 1).trim(); + if (text.length > 0) { + statements.push({ text, startLine: statementStartLine }); + } + i++; + statementStart = i; + statementStartLine = line; + continue; + } + + i++; + } + + const rest = sql.slice(statementStart).trim(); + if (rest.length > 0) { + statements.push({ text: rest, startLine: statementStartLine }); + } + + return statements; +} + +const QUALIFIED_RE = /"public"\s*\.\s*"((?:[^"]|"")+)"/; + +function firstPublicQualifiedName(text: string): string | null { + return QUALIFIED_RE.exec(text)?.[1] ?? null; +} + +const statements = splitStatements(readFileSync(SOURCE_FILE, "utf8")); + +// Pass 1: learn every table/sequence name and how sequences map to owning tables, +// so pass 2 can route ALTER/GRANT/COMMENT statements without guessing from names. +const tableNames = new Set(); +const sequenceNames = new Set(); +const sequenceToTable = new Map(); + +for (const { text } of statements) { + const createTable = + /^CREATE TABLE(?: IF NOT EXISTS)?\s+"public"\s*\.\s*"((?:[^"]|"")+)"/i.exec( + text, + ); + if (createTable) { + tableNames.add(createTable[1]); + } + + // GENERATED ... AS IDENTITY columns declare their backing sequence via + // `SEQUENCE NAME "public"."x_id_seq"`, either inline in CREATE TABLE or (as in this + // dump) in a separate `ALTER TABLE ... ALTER COLUMN ... ADD GENERATED ...` statement. + // Either way the owning table is whichever "public"."X" appears first in the statement. + const identitySeqRe = /SEQUENCE NAME\s+"public"\s*\.\s*"((?:[^"]|"")+)"/gi; + if (identitySeqRe.test(text)) { + const owningTable = firstPublicQualifiedName(text); + if (owningTable) { + identitySeqRe.lastIndex = 0; + let identityMatch: RegExpExecArray | null; + while ((identityMatch = identitySeqRe.exec(text))) { + sequenceNames.add(identityMatch[1]); + sequenceToTable.set(identityMatch[1], owningTable); + } + } + } + + const createSeq = + /^CREATE SEQUENCE(?: IF NOT EXISTS)?\s+"public"\s*\.\s*"((?:[^"]|"")+)"/i.exec( + text, + ); + if (createSeq) { + sequenceNames.add(createSeq[1]); + } + + const ownedBy = + /^ALTER SEQUENCE\s+"public"\s*\.\s*"((?:[^"]|"")+)"\s+OWNED BY\s+"public"\s*\.\s*"((?:[^"]|"")+)"/i.exec( + text, + ); + if (ownedBy) { + sequenceToTable.set(ownedBy[1], ownedBy[2]); + } +} + +type Bucket = + | { kind: "settings" } + | { kind: "schema" } + | { kind: "functions" } + | { kind: "foreign_keys" } + | { kind: "policies" } + | { kind: "table"; table: string } + | { kind: "orphaned" } + | { kind: "unclassified" }; + +const SETTINGS_RE = [/^SET\b/i, /^SELECT\s+pg_catalog\.set_config/i]; +const SCHEMA_RE = [ + /^CREATE SCHEMA\b/i, + /^ALTER SCHEMA\b/i, + /^COMMENT ON SCHEMA\b/i, + /^ALTER DEFAULT PRIVILEGES\b/i, + /\bON SCHEMA\b/i, +]; +const FUNCTIONS_RE = [ + /^CREATE (OR REPLACE )?FUNCTION\b/i, + /^ALTER FUNCTION\b/i, + /^COMMENT ON FUNCTION\b/i, + /\bON FUNCTION\b/i, +]; +const FOREIGN_KEY_RE = /ADD CONSTRAINT\s+"[^"]+"\s+FOREIGN KEY\b/i; +// RLS's on/off switch travels with its policies, not with the table definition: +// neither means anything without the other, so they're reviewed as one unit. +const POLICIES_RE = [ + /^CREATE POLICY\b/i, + /\bENABLE ROW LEVEL SECURITY\b/i, + /\bDISABLE ROW LEVEL SECURITY\b/i, +]; +const matchesAny = (patterns: RegExp[], text: string) => + patterns.some((pattern) => pattern.test(text)); + +function classify(text: string): Bucket { + if (matchesAny(SETTINGS_RE, text)) { + return { kind: "settings" }; + } + if (/^CREATE EXTENSION\b/i.test(text)) { + // already hand-maintained in 00_extensions.sql + return { kind: "unclassified" }; + } + if (matchesAny(SCHEMA_RE, text)) { + return { kind: "schema" }; + } + if (matchesAny(FUNCTIONS_RE, text)) { + return { kind: "functions" }; + } + if (FOREIGN_KEY_RE.test(text)) { + return { kind: "foreign_keys" }; + } + if (matchesAny(POLICIES_RE, text)) { + return { kind: "policies" }; + } + + const name = firstPublicQualifiedName(text); + if (name) { + if (tableNames.has(name)) { + return { kind: "table", table: name }; + } + if (sequenceNames.has(name)) { + const owner = sequenceToTable.get(name); + return owner ? { kind: "table", table: owner } : { kind: "orphaned" }; + } + } + + return { kind: "unclassified" }; +} + +const settings: Statement[] = []; +const schema: Statement[] = []; +const functions: Statement[] = []; +const foreignKeys: Statement[] = []; +const policies: Statement[] = []; +const orphaned: Statement[] = []; +const unclassified: Statement[] = []; +const byTable = new Map(); +for (const table of tableNames) { + byTable.set(table, []); +} + +for (const statement of statements) { + const bucket = classify(statement.text); + switch (bucket.kind) { + case "settings": + settings.push(statement); + break; + case "schema": + schema.push(statement); + break; + case "functions": + functions.push(statement); + break; + case "foreign_keys": + foreignKeys.push(statement); + break; + case "policies": + policies.push(statement); + break; + case "table": + byTable.get(bucket.table)!.push(statement); + break; + case "orphaned": + orphaned.push(statement); + break; + case "unclassified": + unclassified.push(statement); + break; + } +} + +if (unclassified.length > 0) { + console.error( + `\nFailed: ${unclassified.length} statement(s) could not be classified. Nothing was written.\n`, + ); + for (const { text, startLine } of unclassified) { + console.error(`--- schema.sql:${startLine} ---`); + console.error(text.slice(0, 300)); + console.error(""); + } + console.error("Add a routing rule for these in classify() and re-run."); + process.exit(1); +} + +const render = (bucketStatements: Statement[]) => + `${bucketStatements.map((s) => s.text).join("\n\n")}\n`; + +const filesToWrite = new Map(); +filesToWrite.set("00_settings.sql", render(settings)); +filesToWrite.set("01_schema.sql", render(schema)); +filesToWrite.set("02_functions.sql", render(functions)); +for (const [table, tableStatements] of byTable) { + filesToWrite.set(`20_tables/${table}.sql`, render(tableStatements)); +} +filesToWrite.set("30_foreign_keys.sql", render(foreignKeys)); +filesToWrite.set("40_policies.sql", render(policies)); +if (orphaned.length > 0) { + const banner = + "-- Objects with no resolvable owning table (e.g. a sequence left behind by a dropped table).\n" + + "-- Kept so nothing from the original dump is silently lost -- review and decide whether to\n" + + "-- drop them via a migration instead of carrying them forward declaratively.\n\n"; + filesToWrite.set(ORPHANED_FILE, banner + render(orphaned)); +} + +console.log("Classification summary:"); +console.log(` settings.......... ${settings.length}`); +console.log(` schema............ ${schema.length}`); +console.log(` functions......... ${functions.length}`); +for (const [table, tableStatements] of byTable) { + console.log( + `${` table ${table}`.padEnd(35, ".")} ${tableStatements.length}`, + ); +} +console.log(` foreign_keys...... ${foreignKeys.length}`); +console.log(` policies.......... ${policies.length}`); +if (orphaned.length > 0) { + console.log(` orphaned (needs review)... ${orphaned.length}`); +} + +if (isDryRun) { + console.log("\nDry run: no files were written."); + process.exit(0); +} + +// Clear out anything we previously generated so a renamed/dropped table +// doesn't leave a stale file behind. Never touches 00_extensions.sql or +// schema.sql itself (removed explicitly, below, only after a clean write). +if (existsSync(TABLES_DIR)) { + rmSync(TABLES_DIR, { recursive: true }); +} +mkdirSync(TABLES_DIR, { recursive: true }); +for (const entry of readdirSync(SCHEMAS_DIR)) { + if ( + entry === EXTENSIONS_FILE || + entry === "schema.sql" || + entry === "20_tables" + ) { + continue; + } + if (/\.sql$/.test(entry)) { + unlinkSync(join(SCHEMAS_DIR, entry)); + } +} + +for (const [relativePath, content] of filesToWrite) { + writeFileSync(join(SCHEMAS_DIR, relativePath), content); +} + +unlinkSync(SOURCE_FILE); + +const schemaPaths = [ + `./schemas/${EXTENSIONS_FILE}`, + ...[...filesToWrite.keys()].filter((f) => f !== ORPHANED_FILE), + ...(orphaned.length > 0 ? [ORPHANED_FILE] : []), +].map((f) => (f.startsWith("./") ? f : `./schemas/${f}`)); + +const configToml = readFileSync(CONFIG_FILE, "utf8"); +const schemaPathsBlock = `schema_paths = [\n${schemaPaths.map((p) => ` "${p}",`).join("\n")}\n]`; +const updatedConfig = configToml.replace( + /schema_paths\s*=\s*\[[^\]]*\]/, + schemaPathsBlock, +); +if (updatedConfig === configToml) { + console.error( + "Could not find a schema_paths = [...] block in config.toml -- update it manually.", + ); + process.exit(1); +} +writeFileSync(CONFIG_FILE, updatedConfig); + +execSync(`npx prettier --write "${SCHEMAS_DIR}"`, { + stdio: "inherit", +}); + +console.log( + `\nWrote ${filesToWrite.size} file(s) under ${SCHEMAS_DIR}, removed schema.sql, updated config.toml.`, +); +if (orphaned.length > 0) { + console.log( + `Review ${ORPHANED_FILE} -- ${orphaned.length} statement(s) had no owning table.`, + ); +} From 2b99941a12b6e5899e32f719aa3dbebdbe7e95fb Mon Sep 17 00:00:00 2001 From: mlnps Date: Thu, 3 Sep 2026 16:35:48 +0200 Subject: [PATCH 2/2] refactor: enhance documentation and remove automatic deletion of schema.sql in split-schema script --- apps/backend/supabase/scripts/split-schema.ts | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/apps/backend/supabase/scripts/split-schema.ts b/apps/backend/supabase/scripts/split-schema.ts index 3e336467..a24dfd93 100644 --- a/apps/backend/supabase/scripts/split-schema.ts +++ b/apps/backend/supabase/scripts/split-schema.ts @@ -1,3 +1,51 @@ +/* + * Splits supabase/schemas/schema.sql (one big pg_dump) into per-object declarative + * schema files and updates config.toml's schema_paths to match. schema.sql itself + * is left untouched -- delete it manually once you've reviewed the result. + * + * - Tokenizes schema.sql into top-level statements (split on `;`, aware of + * string/identifier quotes, `--` comments, and `$tag$...$tag$` function bodies) + * - Pass 1: learns every table name, sequence name (incl. identity-column + * sequences), and sequence -> owning-table mapping, so routing doesn't have + * to guess from filename patterns + * - Pass 2: classifies each statement, first match wins: + * settings -> extensions (untouched) -> schema -> functions -> + * foreign keys (any table, deferred so no topo-sort is needed) -> + * policies + RLS toggles (kept together) -> per-table (via the table/sequence + * maps from pass 1) -> orphaned (sequence with no owning table) -> unclassified + * - Anything unclassified aborts the run with line numbers -- nothing is ever + * silently dropped + * - Writes 00_settings.sql, 01_schema.sql, 02_functions.sql, 20_tables/.sql, + * 30_foreign_keys.sql, 40_policies.sql, 90_orphaned.sql (if needed), then + * rewrites config.toml's schema_paths as an explicit ordered list and runs + * prettier on the new files + * + * File layout. Actual load order comes from config.toml's schema_paths (an + * explicit array, applied in listed order) -- the numeric prefixes don't + * control anything themselves, they just make that order visible in a plain + * directory listing (ls, Finder, GitHub, ...) so it matches what config.toml + * says without having to open it. Each tier may only reference objects a + * lower tier already created: + * 00_extensions.sql vector/pgcrypto/pg_trgm -- hand-maintained, untouched + * 00_settings.sql session SET statements from the dump preamble + * 01_schema.sql CREATE SCHEMA + schema-level grants/privileges + * 02_functions.sql every function -- after extensions (some signatures + * take an `extensions.vector` param) + * 20_tables/*.sql one file per table -- after functions (RLS policies + * used to sit here and call functions; columns can + * also use extension types) + * 30_foreign_keys.sql FK constraints for every table, deferred here so a + * table never has to wait on another table's file + * (same trick pg_dump itself uses) + * 40_policies.sql RLS toggles + CREATE POLICY -- needs the tables + * 90_orphaned.sql objects with no owning table (e.g. a sequence left + * behind by a dropped table) -- nothing depends on + * this, so it's last + * + * Numbers jump (00 -> 01 -> 02 -> 20 -> 30 -> 40 -> 90) instead of counting + * 1,2,3,4,5,6,7 on purpose: it leaves headroom to insert a new tier later + * (e.g. 10_types.sql for CREATE TYPE) without renumbering everything after it. + */ /* eslint-disable no-console */ import { execSync } from "node:child_process"; import { @@ -369,8 +417,6 @@ for (const [relativePath, content] of filesToWrite) { writeFileSync(join(SCHEMAS_DIR, relativePath), content); } -unlinkSync(SOURCE_FILE); - const schemaPaths = [ `./schemas/${EXTENSIONS_FILE}`, ...[...filesToWrite.keys()].filter((f) => f !== ORPHANED_FILE), @@ -396,7 +442,10 @@ execSync(`npx prettier --write "${SCHEMAS_DIR}"`, { }); console.log( - `\nWrote ${filesToWrite.size} file(s) under ${SCHEMAS_DIR}, removed schema.sql, updated config.toml.`, + `\nWrote ${filesToWrite.size} file(s) under ${SCHEMAS_DIR} and updated config.toml.`, +); +console.log( + "schema.sql was left in place -- once you've reviewed the split, delete it manually.", ); if (orphaned.length > 0) { console.log(