snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
This commit is contained in:
185
supabase/schemas/01_tables.sql
Normal file
185
supabase/schemas/01_tables.sql
Normal file
@@ -0,0 +1,185 @@
|
||||
--
|
||||
-- Tables
|
||||
-- This file declares all tables in the public schema.
|
||||
--
|
||||
|
||||
-- Extensions
|
||||
create extension if not exists "http" with schema "extensions";
|
||||
create extension if not exists "citext" with schema "extensions";
|
||||
|
||||
-- Private schema (used by sales policies migration)
|
||||
create schema if not exists "private";
|
||||
|
||||
create table public.companies (
|
||||
id bigint generated by default as identity primary key,
|
||||
created_at timestamp with time zone not null default now(),
|
||||
name text not null,
|
||||
sector text,
|
||||
size smallint,
|
||||
linkedin_url text,
|
||||
website extensions.citext,
|
||||
phone_number text,
|
||||
address text,
|
||||
zipcode text,
|
||||
city text,
|
||||
state_abbr text,
|
||||
sales_id bigint,
|
||||
context_links json,
|
||||
country text,
|
||||
description text,
|
||||
revenue text,
|
||||
tax_identifier text,
|
||||
logo jsonb
|
||||
);
|
||||
|
||||
create table public.contacts (
|
||||
id bigint generated by default as identity primary key,
|
||||
first_name text,
|
||||
last_name text,
|
||||
gender text,
|
||||
title text,
|
||||
background text,
|
||||
avatar jsonb,
|
||||
first_seen timestamp with time zone,
|
||||
last_seen timestamp with time zone,
|
||||
has_newsletter boolean,
|
||||
status text,
|
||||
tags bigint[],
|
||||
company_id bigint,
|
||||
sales_id bigint,
|
||||
linkedin_url text,
|
||||
email_jsonb jsonb,
|
||||
phone_jsonb jsonb
|
||||
);
|
||||
|
||||
create table public.contact_notes (
|
||||
id bigint generated by default as identity (sequence name public."contactNotes_id_seq") not null,
|
||||
contact_id bigint not null,
|
||||
text text,
|
||||
date timestamp with time zone default now(),
|
||||
sales_id bigint,
|
||||
status text,
|
||||
attachments jsonb[]
|
||||
);
|
||||
|
||||
create table public.deals (
|
||||
id bigint generated by default as identity primary key,
|
||||
name text not null,
|
||||
company_id bigint,
|
||||
contact_ids bigint[],
|
||||
category text,
|
||||
stage text not null,
|
||||
description text,
|
||||
amount bigint,
|
||||
created_at timestamp with time zone not null default now(),
|
||||
updated_at timestamp with time zone not null default now(),
|
||||
archived_at timestamp with time zone,
|
||||
expected_closing_date date,
|
||||
sales_id bigint,
|
||||
index smallint
|
||||
);
|
||||
|
||||
create table public.deal_notes (
|
||||
id bigint generated by default as identity (sequence name public."dealNotes_id_seq") not null,
|
||||
deal_id bigint not null,
|
||||
type text,
|
||||
text text,
|
||||
date timestamp with time zone default now(),
|
||||
sales_id bigint,
|
||||
attachments jsonb[]
|
||||
);
|
||||
|
||||
create table public.sales (
|
||||
id bigint generated by default as identity primary key,
|
||||
first_name text not null default 'Pending'::text,
|
||||
last_name text not null default 'Pending'::text,
|
||||
email extensions.citext not null,
|
||||
administrator boolean not null,
|
||||
user_id uuid not null,
|
||||
avatar jsonb,
|
||||
disabled boolean not null default false,
|
||||
secondary_emails jsonb not null default '[]'::jsonb,
|
||||
constraint sales_secondary_emails_is_array check (jsonb_typeof(secondary_emails) = 'array')
|
||||
);
|
||||
|
||||
create unique index uq__sales__user_id on public.sales using btree (user_id);
|
||||
|
||||
create table public.tags (
|
||||
id bigint generated by default as identity primary key,
|
||||
name text not null,
|
||||
color text not null
|
||||
);
|
||||
|
||||
create table public.tasks (
|
||||
id bigint generated by default as identity primary key,
|
||||
contact_id bigint not null,
|
||||
type text,
|
||||
text text,
|
||||
due_date timestamp with time zone,
|
||||
done_date timestamp with time zone,
|
||||
sales_id bigint
|
||||
);
|
||||
|
||||
create table public.configuration (
|
||||
id integer not null default 1 primary key,
|
||||
config jsonb not null default '{}'::jsonb,
|
||||
constraint configuration_singleton check (id = 1)
|
||||
);
|
||||
|
||||
create table public.favicons_excluded_domains (
|
||||
id bigint generated by default as identity primary key,
|
||||
domain text not null
|
||||
);
|
||||
|
||||
--
|
||||
-- Foreign keys
|
||||
--
|
||||
|
||||
alter table public.companies
|
||||
add constraint companies_sales_id_fkey foreign key (sales_id) references public.sales(id);
|
||||
|
||||
alter table public.contacts
|
||||
add constraint contacts_company_id_fkey foreign key (company_id) references public.companies(id) on update cascade on delete cascade;
|
||||
|
||||
alter table public.contacts
|
||||
add constraint contacts_sales_id_fkey foreign key (sales_id) references public.sales(id);
|
||||
|
||||
alter table public.contact_notes
|
||||
add constraint "contactNotes_contact_id_fkey" foreign key (contact_id) references public.contacts(id) on update cascade on delete cascade;
|
||||
|
||||
alter table public.contact_notes
|
||||
add constraint "contactNotes_sales_id_fkey" foreign key (sales_id) references public.sales(id) on update cascade on delete cascade;
|
||||
|
||||
alter table public.deals
|
||||
add constraint deals_company_id_fkey foreign key (company_id) references public.companies(id) on update cascade on delete cascade;
|
||||
|
||||
alter table public.deals
|
||||
add constraint deals_sales_id_fkey foreign key (sales_id) references public.sales(id);
|
||||
|
||||
alter table public.deal_notes
|
||||
add constraint "dealNotes_deal_id_fkey" foreign key (deal_id) references public.deals(id) on update cascade on delete cascade;
|
||||
|
||||
alter table public.deal_notes
|
||||
add constraint "dealNotes_sales_id_fkey" foreign key (sales_id) references public.sales(id);
|
||||
|
||||
alter table public.sales
|
||||
add constraint sales_user_id_fkey foreign key (user_id) references auth.users(id);
|
||||
|
||||
alter table public.tasks
|
||||
add constraint tasks_contact_id_fkey foreign key (contact_id) references public.contacts(id) on update cascade on delete cascade;
|
||||
|
||||
-- Legacy primary key constraint names (from before snake_case rename)
|
||||
alter table only public.contact_notes
|
||||
add constraint "contactNotes_pkey" primary key (id);
|
||||
|
||||
alter table only public.deal_notes
|
||||
add constraint "dealNotes_pkey" primary key (id);
|
||||
|
||||
--
|
||||
-- Indexes on foreign keys
|
||||
--
|
||||
|
||||
create index contact_notes_contact_id_idx on public.contact_notes using btree (contact_id);
|
||||
create index contacts_company_id_idx on public.contacts using btree (company_id);
|
||||
create index deal_notes_deal_id_idx on public.deal_notes using btree (deal_id);
|
||||
create index deals_company_id_idx on public.deals using btree (company_id);
|
||||
455
supabase/schemas/02_functions.sql
Normal file
455
supabase/schemas/02_functions.sql
Normal file
@@ -0,0 +1,455 @@
|
||||
--
|
||||
-- Functions
|
||||
-- This file declares all PL/pgSQL functions in the public schema.
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."cleanup_note_attachments"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER
|
||||
SET "search_path" TO ''
|
||||
AS $$
|
||||
DECLARE
|
||||
payload jsonb;
|
||||
request_headers jsonb;
|
||||
auth_header text;
|
||||
BEGIN
|
||||
request_headers := coalesce(
|
||||
nullif(current_setting('request.headers', true), '')::jsonb,
|
||||
'{}'::jsonb
|
||||
);
|
||||
auth_header := request_headers ->> 'authorization';
|
||||
|
||||
IF auth_header IS NULL OR auth_header = '' THEN
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
payload := jsonb_build_object(
|
||||
'old_record', OLD,
|
||||
'record', NEW,
|
||||
'type', TG_OP
|
||||
);
|
||||
|
||||
PERFORM net.http_post(
|
||||
url := public.get_note_attachments_function_url(),
|
||||
body := payload,
|
||||
params := '{}'::jsonb,
|
||||
headers := jsonb_build_object(
|
||||
'Content-Type',
|
||||
'application/json',
|
||||
'Authorization',
|
||||
auth_header
|
||||
),
|
||||
timeout_milliseconds := 10000
|
||||
);
|
||||
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."get_avatar_for_email"("email" "text") RETURNS "text"
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$
|
||||
declare email_hash text;
|
||||
declare gravatar_url text;
|
||||
declare gravatar_status int8;
|
||||
declare email_domain text;
|
||||
declare favicon_url text;
|
||||
declare domain_status int8;
|
||||
|
||||
begin
|
||||
-- Try to fetch a gravatar image
|
||||
email_hash = encode(extensions.digest(email, 'sha256'), 'hex');
|
||||
gravatar_url = concat('https://www.gravatar.com/avatar/', email_hash, '?d=404');
|
||||
|
||||
select status from extensions.http_get(gravatar_url) into gravatar_status;
|
||||
|
||||
if gravatar_status = 200 then
|
||||
return gravatar_url;
|
||||
end if;
|
||||
|
||||
-- Fallback to email's domain favicon if not excluded
|
||||
email_domain = split_part(email, '@', 2);
|
||||
return get_domain_favicon(email_domain);
|
||||
exception
|
||||
when others then
|
||||
return 'ERROR';
|
||||
end;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."get_domain_favicon"("domain_name" "text") RETURNS "text"
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$
|
||||
declare domain_status int8;
|
||||
|
||||
begin
|
||||
if exists (select from favicons_excluded_domains as fav where fav.domain = domain_name) then
|
||||
return null;
|
||||
end if;
|
||||
|
||||
return concat(
|
||||
'https://favicon.show/',
|
||||
(regexp_matches(domain_name, '^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/?\n]+)', 'i'))[1]
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."get_note_attachments_function_url"() RETURNS "text"
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$
|
||||
DECLARE
|
||||
issuer text;
|
||||
function_url text;
|
||||
BEGIN
|
||||
issuer := coalesce(
|
||||
nullif(current_setting('request.jwt.claim.iss', true), ''),
|
||||
(
|
||||
coalesce(
|
||||
nullif(current_setting('request.jwt.claims', true), ''),
|
||||
'{}'
|
||||
)::jsonb ->> 'iss'
|
||||
)
|
||||
);
|
||||
issuer := nullif(issuer, '');
|
||||
IF issuer IS NOT NULL THEN
|
||||
issuer := rtrim(issuer, '/');
|
||||
IF right(issuer, 8) = '/auth/v1' THEN
|
||||
function_url :=
|
||||
left(issuer, length(issuer) - 8) || '/functions/v1/delete_note_attachments';
|
||||
|
||||
IF function_url LIKE 'http://127.0.0.1:%' THEN
|
||||
RETURN replace(
|
||||
function_url,
|
||||
'http://127.0.0.1:',
|
||||
'http://host.docker.internal:'
|
||||
);
|
||||
END IF;
|
||||
|
||||
IF function_url LIKE 'http://localhost:%' THEN
|
||||
RETURN replace(
|
||||
function_url,
|
||||
'http://localhost:',
|
||||
'http://host.docker.internal:'
|
||||
);
|
||||
END IF;
|
||||
|
||||
RETURN function_url;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN 'http://host.docker.internal:54321/functions/v1/delete_note_attachments';
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."get_user_id_by_email"("email" "text") RETURNS TABLE("id" "uuid")
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER
|
||||
SET "search_path" TO 'public'
|
||||
AS $_$
|
||||
BEGIN
|
||||
RETURN QUERY SELECT au.id FROM auth.users au WHERE au.email = $1;
|
||||
END;
|
||||
$_$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."handle_company_saved"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$
|
||||
declare company_logo text;
|
||||
|
||||
begin
|
||||
if new.logo is not null then
|
||||
return new;
|
||||
end if;
|
||||
|
||||
company_logo = get_domain_favicon(new.website);
|
||||
if company_logo is null then
|
||||
return new;
|
||||
end if;
|
||||
|
||||
new.logo = concat('{"src":"', company_logo, '","title":"Company favicon"}');
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."handle_contact_note_created_or_updated"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER
|
||||
SET "search_path" TO ''
|
||||
AS $$
|
||||
begin
|
||||
update public.contacts set last_seen = new.date where contacts.id = new.contact_id and contacts.last_seen < new.date;
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."handle_contact_saved"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$declare contact_avatar text;
|
||||
declare emails_length int8;
|
||||
declare item jsonb;
|
||||
|
||||
begin
|
||||
if new.avatar is not null then
|
||||
return new;
|
||||
end if;
|
||||
|
||||
select coalesce(jsonb_array_length(new.email_jsonb), 0) into emails_length;
|
||||
|
||||
if emails_length = 0 then
|
||||
return new;
|
||||
end if;
|
||||
|
||||
for item in select jsonb_array_elements(new.email_jsonb)
|
||||
loop
|
||||
select public.get_avatar_for_email(item->>'email') into contact_avatar;
|
||||
if (contact_avatar is not null) then
|
||||
exit;
|
||||
end if;
|
||||
end loop;
|
||||
|
||||
if contact_avatar is null then
|
||||
return new;
|
||||
end if;
|
||||
|
||||
new.avatar = concat('{"src":"', contact_avatar, '"}');
|
||||
return new;
|
||||
end;$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."handle_new_user"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER
|
||||
SET "search_path" TO ''
|
||||
AS $$
|
||||
declare
|
||||
sales_count int;
|
||||
begin
|
||||
select count(id) into sales_count
|
||||
from public.sales;
|
||||
|
||||
insert into public.sales (first_name, last_name, email, user_id, administrator)
|
||||
values (
|
||||
coalesce(new.raw_user_meta_data ->> 'first_name', new.raw_user_meta_data -> 'custom_claims' ->> 'first_name', 'Pending'),
|
||||
coalesce(new.raw_user_meta_data ->> 'last_name', new.raw_user_meta_data -> 'custom_claims' ->> 'last_name', 'Pending'),
|
||||
new.email,
|
||||
new.id,
|
||||
case when sales_count > 0 then FALSE else TRUE end
|
||||
);
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."handle_update_user"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER
|
||||
SET "search_path" TO ''
|
||||
AS $$
|
||||
begin
|
||||
update public.sales
|
||||
set
|
||||
first_name = coalesce(new.raw_user_meta_data ->> 'first_name', new.raw_user_meta_data -> 'custom_claims' ->> 'first_name', 'Pending'),
|
||||
last_name = coalesce(new.raw_user_meta_data ->> 'last_name', new.raw_user_meta_data -> 'custom_claims' ->> 'last_name', 'Pending'),
|
||||
email = new.email
|
||||
where user_id = new.id;
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."is_admin"() RETURNS boolean
|
||||
LANGUAGE "plpgsql" SECURITY DEFINER
|
||||
SET "search_path" TO ''
|
||||
AS $$
|
||||
begin
|
||||
return exists (
|
||||
select 1 from public.sales where user_id = auth.uid() and administrator = true
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."merge_contacts"("loser_id" bigint, "winner_id" bigint) RETURNS bigint
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$
|
||||
DECLARE
|
||||
winner_contact contacts%ROWTYPE;
|
||||
loser_contact contacts%ROWTYPE;
|
||||
deal_record RECORD;
|
||||
merged_emails jsonb;
|
||||
merged_phones jsonb;
|
||||
merged_tags bigint[];
|
||||
winner_emails jsonb;
|
||||
loser_emails jsonb;
|
||||
winner_phones jsonb;
|
||||
loser_phones jsonb;
|
||||
email_map jsonb;
|
||||
phone_map jsonb;
|
||||
BEGIN
|
||||
-- Fetch both contacts
|
||||
SELECT * INTO winner_contact FROM contacts WHERE id = winner_id;
|
||||
SELECT * INTO loser_contact FROM contacts WHERE id = loser_id;
|
||||
|
||||
IF winner_contact IS NULL OR loser_contact IS NULL THEN
|
||||
RAISE EXCEPTION 'Contact not found';
|
||||
END IF;
|
||||
|
||||
-- 1. Reassign tasks from loser to winner
|
||||
UPDATE tasks SET contact_id = winner_id WHERE contact_id = loser_id;
|
||||
|
||||
-- 2. Reassign contact notes from loser to winner
|
||||
UPDATE contact_notes SET contact_id = winner_id WHERE contact_id = loser_id;
|
||||
|
||||
-- 3. Update deals - replace loser with winner in contact_ids array
|
||||
FOR deal_record IN
|
||||
SELECT id, contact_ids
|
||||
FROM deals
|
||||
WHERE contact_ids @> ARRAY[loser_id]
|
||||
LOOP
|
||||
UPDATE deals
|
||||
SET contact_ids = (
|
||||
SELECT ARRAY(
|
||||
SELECT DISTINCT unnest(
|
||||
array_remove(deal_record.contact_ids, loser_id) || ARRAY[winner_id]
|
||||
)
|
||||
)
|
||||
)
|
||||
WHERE id = deal_record.id;
|
||||
END LOOP;
|
||||
|
||||
-- 4. Merge contact data
|
||||
|
||||
-- Get email arrays
|
||||
winner_emails := COALESCE(winner_contact.email_jsonb, '[]'::jsonb);
|
||||
loser_emails := COALESCE(loser_contact.email_jsonb, '[]'::jsonb);
|
||||
|
||||
-- Merge emails with deduplication by email address
|
||||
-- Build a map of email -> email object, then convert back to array
|
||||
email_map := '{}'::jsonb;
|
||||
|
||||
-- Add winner emails to map
|
||||
IF jsonb_array_length(winner_emails) > 0 THEN
|
||||
FOR i IN 0..jsonb_array_length(winner_emails)-1 LOOP
|
||||
email_map := email_map || jsonb_build_object(
|
||||
winner_emails->i->>'email',
|
||||
winner_emails->i
|
||||
);
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Add loser emails to map (won't overwrite existing keys)
|
||||
IF jsonb_array_length(loser_emails) > 0 THEN
|
||||
FOR i IN 0..jsonb_array_length(loser_emails)-1 LOOP
|
||||
IF NOT email_map ? (loser_emails->i->>'email') THEN
|
||||
email_map := email_map || jsonb_build_object(
|
||||
loser_emails->i->>'email',
|
||||
loser_emails->i
|
||||
);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Convert map back to array
|
||||
merged_emails := (SELECT jsonb_agg(value) FROM jsonb_each(email_map));
|
||||
merged_emails := COALESCE(merged_emails, '[]'::jsonb);
|
||||
|
||||
-- Get phone arrays
|
||||
winner_phones := COALESCE(winner_contact.phone_jsonb, '[]'::jsonb);
|
||||
loser_phones := COALESCE(loser_contact.phone_jsonb, '[]'::jsonb);
|
||||
|
||||
-- Merge phones with deduplication by number
|
||||
phone_map := '{}'::jsonb;
|
||||
|
||||
-- Add winner phones to map
|
||||
IF jsonb_array_length(winner_phones) > 0 THEN
|
||||
FOR i IN 0..jsonb_array_length(winner_phones)-1 LOOP
|
||||
phone_map := phone_map || jsonb_build_object(
|
||||
winner_phones->i->>'number',
|
||||
winner_phones->i
|
||||
);
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Add loser phones to map (won't overwrite existing keys)
|
||||
IF jsonb_array_length(loser_phones) > 0 THEN
|
||||
FOR i IN 0..jsonb_array_length(loser_phones)-1 LOOP
|
||||
IF NOT phone_map ? (loser_phones->i->>'number') THEN
|
||||
phone_map := phone_map || jsonb_build_object(
|
||||
loser_phones->i->>'number',
|
||||
loser_phones->i
|
||||
);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Convert map back to array
|
||||
merged_phones := (SELECT jsonb_agg(value) FROM jsonb_each(phone_map));
|
||||
merged_phones := COALESCE(merged_phones, '[]'::jsonb);
|
||||
|
||||
-- Merge tags (remove duplicates)
|
||||
merged_tags := ARRAY(
|
||||
SELECT DISTINCT unnest(
|
||||
COALESCE(winner_contact.tags, ARRAY[]::bigint[]) ||
|
||||
COALESCE(loser_contact.tags, ARRAY[]::bigint[])
|
||||
)
|
||||
);
|
||||
|
||||
-- 5. Update winner with merged data
|
||||
UPDATE contacts SET
|
||||
avatar = COALESCE(winner_contact.avatar, loser_contact.avatar),
|
||||
gender = COALESCE(winner_contact.gender, loser_contact.gender),
|
||||
first_name = COALESCE(winner_contact.first_name, loser_contact.first_name),
|
||||
last_name = COALESCE(winner_contact.last_name, loser_contact.last_name),
|
||||
title = COALESCE(winner_contact.title, loser_contact.title),
|
||||
company_id = COALESCE(winner_contact.company_id, loser_contact.company_id),
|
||||
email_jsonb = merged_emails,
|
||||
phone_jsonb = merged_phones,
|
||||
linkedin_url = COALESCE(winner_contact.linkedin_url, loser_contact.linkedin_url),
|
||||
background = COALESCE(winner_contact.background, loser_contact.background),
|
||||
has_newsletter = COALESCE(winner_contact.has_newsletter, loser_contact.has_newsletter),
|
||||
first_seen = LEAST(COALESCE(winner_contact.first_seen, loser_contact.first_seen), COALESCE(loser_contact.first_seen, winner_contact.first_seen)),
|
||||
last_seen = GREATEST(COALESCE(winner_contact.last_seen, loser_contact.last_seen), COALESCE(loser_contact.last_seen, winner_contact.last_seen)),
|
||||
sales_id = COALESCE(winner_contact.sales_id, loser_contact.sales_id),
|
||||
tags = merged_tags
|
||||
WHERE id = winner_id;
|
||||
|
||||
-- 6. Delete loser contact
|
||||
DELETE FROM contacts WHERE id = loser_id;
|
||||
|
||||
RETURN winner_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."lowercase_email_jsonb"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.email_jsonb IS NOT NULL THEN
|
||||
NEW.email_jsonb = COALESCE((
|
||||
SELECT jsonb_agg(
|
||||
jsonb_set(elem, '{email}', to_jsonb(LOWER(elem->>'email')))
|
||||
)
|
||||
FROM jsonb_array_elements(NEW.email_jsonb) AS elem
|
||||
), '[]'::jsonb);
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."set_sales_id_default"() RETURNS "trigger"
|
||||
LANGUAGE "plpgsql"
|
||||
SET "search_path" TO 'public'
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.sales_id IS NULL THEN
|
||||
SELECT id INTO NEW.sales_id FROM sales WHERE user_id = auth.uid();
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
134
supabase/schemas/03_views.sql
Normal file
134
supabase/schemas/03_views.sql
Normal file
@@ -0,0 +1,134 @@
|
||||
--
|
||||
-- Views
|
||||
-- This file declares all views in the public schema.
|
||||
--
|
||||
|
||||
create or replace view public.activity_log with (security_invoker = on) as
|
||||
select
|
||||
('company.' || c.id || '.created') as id,
|
||||
'company.created' as type,
|
||||
c.created_at as date,
|
||||
c.id as company_id,
|
||||
c.sales_id,
|
||||
to_json(c.*) as company,
|
||||
null::json as contact,
|
||||
null::json as deal,
|
||||
null::json as contact_note,
|
||||
null::json as deal_note
|
||||
from public.companies c
|
||||
union all
|
||||
select
|
||||
('contact.' || co.id || '.created') as id,
|
||||
'contact.created' as type,
|
||||
co.first_seen as date,
|
||||
co.company_id,
|
||||
co.sales_id,
|
||||
null::json as company,
|
||||
to_json(co.*) as contact,
|
||||
null::json as deal,
|
||||
null::json as contact_note,
|
||||
null::json as deal_note
|
||||
from public.contacts co
|
||||
union all
|
||||
select
|
||||
('contactNote.' || cn.id || '.created') as id,
|
||||
'contactNote.created' as type,
|
||||
cn.date,
|
||||
co.company_id,
|
||||
cn.sales_id,
|
||||
null::json as company,
|
||||
null::json as contact,
|
||||
null::json as deal,
|
||||
to_json(cn.*) as contact_note,
|
||||
null::json as deal_note
|
||||
from public.contact_notes cn
|
||||
left join public.contacts co on co.id = cn.contact_id
|
||||
union all
|
||||
select
|
||||
('deal.' || d.id || '.created') as id,
|
||||
'deal.created' as type,
|
||||
d.created_at as date,
|
||||
d.company_id,
|
||||
d.sales_id,
|
||||
null::json as company,
|
||||
null::json as contact,
|
||||
to_json(d.*) as deal,
|
||||
null::json as contact_note,
|
||||
null::json as deal_note
|
||||
from public.deals d
|
||||
union all
|
||||
select
|
||||
('dealNote.' || dn.id || '.created') as id,
|
||||
'dealNote.created' as type,
|
||||
dn.date,
|
||||
d.company_id,
|
||||
dn.sales_id,
|
||||
null::json as company,
|
||||
null::json as contact,
|
||||
null::json as deal,
|
||||
null::json as contact_note,
|
||||
to_json(dn.*) as deal_note
|
||||
from public.deal_notes dn
|
||||
left join public.deals d on d.id = dn.deal_id;
|
||||
|
||||
create or replace view public.companies_summary with (security_invoker = on) as
|
||||
select
|
||||
c.id,
|
||||
c.created_at,
|
||||
c.name,
|
||||
c.sector,
|
||||
c.size,
|
||||
c.linkedin_url,
|
||||
c.website,
|
||||
c.phone_number,
|
||||
c.address,
|
||||
c.zipcode,
|
||||
c.city,
|
||||
c.state_abbr,
|
||||
c.sales_id,
|
||||
c.context_links,
|
||||
c.country,
|
||||
c.description,
|
||||
c.revenue,
|
||||
c.tax_identifier,
|
||||
c.logo,
|
||||
count(distinct d.id) as nb_deals,
|
||||
count(distinct co.id) as nb_contacts
|
||||
from public.companies c
|
||||
left join public.deals d on c.id = d.company_id
|
||||
left join public.contacts co on c.id = co.company_id
|
||||
group by c.id;
|
||||
|
||||
create or replace view public.contacts_summary with (security_invoker = on) as
|
||||
select
|
||||
co.id,
|
||||
co.first_name,
|
||||
co.last_name,
|
||||
co.gender,
|
||||
co.title,
|
||||
co.background,
|
||||
co.avatar,
|
||||
co.first_seen,
|
||||
co.last_seen,
|
||||
co.has_newsletter,
|
||||
co.status,
|
||||
co.tags,
|
||||
co.company_id,
|
||||
co.sales_id,
|
||||
co.linkedin_url,
|
||||
co.email_jsonb,
|
||||
co.phone_jsonb,
|
||||
(jsonb_path_query_array(co.email_jsonb, '$[*]."email"'))::text as email_fts,
|
||||
(jsonb_path_query_array(co.phone_jsonb, '$[*]."number"'))::text as phone_fts,
|
||||
c.name as company_name,
|
||||
count(distinct t.id) filter (where t.done_date is null) as nb_tasks
|
||||
from public.contacts co
|
||||
left join public.tasks t on co.id = t.contact_id
|
||||
left join public.companies c on co.company_id = c.id
|
||||
group by co.id, c.name;
|
||||
|
||||
create or replace view public.init_state with (security_invoker = off) as
|
||||
select count(sub.id) as is_initialized
|
||||
from (
|
||||
select sales.id from public.sales limit 1
|
||||
) sub;
|
||||
80
supabase/schemas/04_triggers.sql
Normal file
80
supabase/schemas/04_triggers.sql
Normal file
@@ -0,0 +1,80 @@
|
||||
--
|
||||
-- Triggers
|
||||
-- This file declares all triggers.
|
||||
--
|
||||
|
||||
-- Auto-populate sales_id from current auth user on insert
|
||||
create or replace trigger set_company_sales_id_trigger
|
||||
before insert on public.companies
|
||||
for each row execute function public.set_sales_id_default();
|
||||
|
||||
create or replace trigger set_contact_sales_id_trigger
|
||||
before insert on public.contacts
|
||||
for each row execute function public.set_sales_id_default();
|
||||
|
||||
create or replace trigger set_contact_notes_sales_id_trigger
|
||||
before insert on public.contact_notes
|
||||
for each row execute function public.set_sales_id_default();
|
||||
|
||||
create or replace trigger set_deal_sales_id_trigger
|
||||
before insert on public.deals
|
||||
for each row execute function public.set_sales_id_default();
|
||||
|
||||
create or replace trigger set_deal_notes_sales_id_trigger
|
||||
before insert on public.deal_notes
|
||||
for each row execute function public.set_sales_id_default();
|
||||
|
||||
create or replace trigger set_task_sales_id_trigger
|
||||
before insert on public.tasks
|
||||
for each row execute function public.set_sales_id_default();
|
||||
|
||||
-- Auto-fetch company logo from website favicon on save
|
||||
create or replace trigger company_saved
|
||||
before insert or update on public.companies
|
||||
for each row execute function public.handle_company_saved();
|
||||
|
||||
-- Lowercase contact emails before insert or update (must run before contact_saved)
|
||||
create or replace trigger "10_lowercase_contact_emails"
|
||||
before insert or update on public.contacts
|
||||
for each row execute function public.lowercase_email_jsonb();
|
||||
|
||||
-- Auto-fetch contact avatar from email on save (runs after lowercase_contact_emails)
|
||||
create or replace trigger "20_contact_saved"
|
||||
before insert or update on public.contacts
|
||||
for each row execute function public.handle_contact_saved();
|
||||
|
||||
-- Update contact.last_seen when a contact note is created
|
||||
create or replace trigger on_public_contact_notes_created_or_updated
|
||||
after insert on public.contact_notes
|
||||
for each row execute function public.handle_contact_note_created_or_updated();
|
||||
|
||||
-- Cleanup storage attachments when contact notes are updated or deleted
|
||||
create or replace trigger on_contact_notes_attachments_updated_delete_note_attachments
|
||||
after update on public.contact_notes
|
||||
for each row
|
||||
when (old.attachments is distinct from new.attachments)
|
||||
execute function public.cleanup_note_attachments();
|
||||
|
||||
create or replace trigger on_contact_notes_deleted_delete_note_attachments
|
||||
after delete on public.contact_notes
|
||||
for each row execute function public.cleanup_note_attachments();
|
||||
|
||||
-- Cleanup storage attachments when deal notes are updated or deleted
|
||||
create or replace trigger on_deal_notes_attachments_updated_delete_note_attachments
|
||||
after update on public.deal_notes
|
||||
for each row
|
||||
when (old.attachments is distinct from new.attachments)
|
||||
execute function public.cleanup_note_attachments();
|
||||
|
||||
create or replace trigger on_deal_notes_deleted_delete_note_attachments
|
||||
after delete on public.deal_notes
|
||||
for each row execute function public.cleanup_note_attachments();
|
||||
|
||||
-- Auth triggers: sync auth.users to public.sales
|
||||
create or replace trigger on_auth_user_created
|
||||
after insert on auth.users
|
||||
for each row execute function public.handle_new_user();
|
||||
|
||||
create or replace trigger on_auth_user_updated
|
||||
after update on auth.users
|
||||
for each row execute function public.handle_update_user();
|
||||
69
supabase/schemas/05_policies.sql
Normal file
69
supabase/schemas/05_policies.sql
Normal file
@@ -0,0 +1,69 @@
|
||||
--
|
||||
-- Row Level Security
|
||||
-- This file declares RLS policies for all tables.
|
||||
--
|
||||
|
||||
-- Enable RLS on all tables
|
||||
alter table public.companies enable row level security;
|
||||
alter table public.contacts enable row level security;
|
||||
alter table public.contact_notes enable row level security;
|
||||
alter table public.deals enable row level security;
|
||||
alter table public.deal_notes enable row level security;
|
||||
alter table public.sales enable row level security;
|
||||
alter table public.tags enable row level security;
|
||||
alter table public.tasks enable row level security;
|
||||
alter table public.configuration enable row level security;
|
||||
alter table public.favicons_excluded_domains enable row level security;
|
||||
|
||||
-- Companies
|
||||
create policy "Enable read access for authenticated users" on public.companies for select to authenticated using (true);
|
||||
create policy "Enable insert for authenticated users only" on public.companies for insert to authenticated with check (true);
|
||||
create policy "Enable update for authenticated users only" on public.companies for update to authenticated using (true) with check (true);
|
||||
create policy "Company Delete Policy" on public.companies for delete to authenticated using (true);
|
||||
|
||||
-- Contacts
|
||||
create policy "Enable read access for authenticated users" on public.contacts for select to authenticated using (true);
|
||||
create policy "Enable insert for authenticated users only" on public.contacts for insert to authenticated with check (true);
|
||||
create policy "Enable update for authenticated users only" on public.contacts for update to authenticated using (true) with check (true);
|
||||
create policy "Contact Delete Policy" on public.contacts for delete to authenticated using (true);
|
||||
|
||||
-- Contact Notes
|
||||
create policy "Enable read access for authenticated users" on public.contact_notes for select to authenticated using (true);
|
||||
create policy "Enable insert for authenticated users only" on public.contact_notes for insert to authenticated with check (true);
|
||||
create policy "Contact Notes Update policy" on public.contact_notes for update to authenticated using (true);
|
||||
create policy "Contact Notes Delete Policy" on public.contact_notes for delete to authenticated using (true);
|
||||
|
||||
-- Deals
|
||||
create policy "Enable read access for authenticated users" on public.deals for select to authenticated using (true);
|
||||
create policy "Enable insert for authenticated users only" on public.deals for insert to authenticated with check (true);
|
||||
create policy "Enable update for authenticated users only" on public.deals for update to authenticated using (true) with check (true);
|
||||
create policy "Deals Delete Policy" on public.deals for delete to authenticated using (true);
|
||||
|
||||
-- Deal Notes
|
||||
create policy "Enable read access for authenticated users" on public.deal_notes for select to authenticated using (true);
|
||||
create policy "Enable insert for authenticated users only" on public.deal_notes for insert to authenticated with check (true);
|
||||
create policy "Deal Notes Update Policy" on public.deal_notes for update to authenticated using (true);
|
||||
create policy "Deal Notes Delete Policy" on public.deal_notes for delete to authenticated using (true);
|
||||
|
||||
-- Sales
|
||||
create policy "Enable read access for authenticated users" on public.sales for select to authenticated using (true);
|
||||
|
||||
-- Tags
|
||||
create policy "Enable read access for authenticated users" on public.tags for select to authenticated using (true);
|
||||
create policy "Enable insert for authenticated users only" on public.tags for insert to authenticated with check (true);
|
||||
create policy "Enable update for authenticated users only" on public.tags for update to authenticated using (true);
|
||||
create policy "Enable delete for authenticated users only" on public.tags for delete to authenticated using (true);
|
||||
|
||||
-- Tasks
|
||||
create policy "Enable read access for authenticated users" on public.tasks for select to authenticated using (true);
|
||||
create policy "Enable insert for authenticated users only" on public.tasks for insert to authenticated with check (true);
|
||||
create policy "Task Update Policy" on public.tasks for update to authenticated using (true);
|
||||
create policy "Task Delete Policy" on public.tasks for delete to authenticated using (true);
|
||||
|
||||
-- Configuration (admin-only for writes)
|
||||
create policy "Enable read for authenticated" on public.configuration for select to authenticated using (true);
|
||||
create policy "Enable insert for admins" on public.configuration for insert to authenticated with check (public.is_admin());
|
||||
create policy "Enable update for admins" on public.configuration for update to authenticated using (public.is_admin()) with check (public.is_admin());
|
||||
|
||||
-- Favicons excluded domains
|
||||
create policy "Enable access for authenticated users only" on public.favicons_excluded_domains to authenticated using (true) with check (true);
|
||||
177
supabase/schemas/06_grants.sql
Normal file
177
supabase/schemas/06_grants.sql
Normal file
@@ -0,0 +1,177 @@
|
||||
--
|
||||
-- Grants
|
||||
-- This file declares all grants and default privileges for the public schema.
|
||||
--
|
||||
|
||||
-- Schema usage
|
||||
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;
|
||||
|
||||
-- Function grants
|
||||
grant all on function public.cleanup_note_attachments() to anon;
|
||||
grant all on function public.cleanup_note_attachments() to authenticated;
|
||||
grant all on function public.cleanup_note_attachments() to service_role;
|
||||
|
||||
grant all on function public.get_avatar_for_email(text) to anon;
|
||||
grant all on function public.get_avatar_for_email(text) to authenticated;
|
||||
grant all on function public.get_avatar_for_email(text) to service_role;
|
||||
|
||||
grant all on function public.get_domain_favicon(text) to anon;
|
||||
grant all on function public.get_domain_favicon(text) to authenticated;
|
||||
grant all on function public.get_domain_favicon(text) to service_role;
|
||||
|
||||
grant all on function public.get_note_attachments_function_url() to anon;
|
||||
grant all on function public.get_note_attachments_function_url() to authenticated;
|
||||
grant all on function public.get_note_attachments_function_url() to service_role;
|
||||
|
||||
revoke all on function public.get_user_id_by_email(text) from public;
|
||||
grant all on function public.get_user_id_by_email(text) to service_role;
|
||||
|
||||
grant all on function public.handle_company_saved() to anon;
|
||||
grant all on function public.handle_company_saved() to authenticated;
|
||||
grant all on function public.handle_company_saved() to service_role;
|
||||
|
||||
grant all on function public.handle_contact_note_created_or_updated() to anon;
|
||||
grant all on function public.handle_contact_note_created_or_updated() to authenticated;
|
||||
grant all on function public.handle_contact_note_created_or_updated() to service_role;
|
||||
|
||||
grant all on function public.handle_contact_saved() to anon;
|
||||
grant all on function public.handle_contact_saved() to authenticated;
|
||||
grant all on function public.handle_contact_saved() to service_role;
|
||||
|
||||
grant all on function public.handle_new_user() to anon;
|
||||
grant all on function public.handle_new_user() to authenticated;
|
||||
grant all on function public.handle_new_user() to service_role;
|
||||
|
||||
grant all on function public.handle_update_user() to anon;
|
||||
grant all on function public.handle_update_user() to authenticated;
|
||||
grant all on function public.handle_update_user() to service_role;
|
||||
|
||||
grant all on function public.is_admin() to anon;
|
||||
grant all on function public.is_admin() to authenticated;
|
||||
grant all on function public.is_admin() to service_role;
|
||||
|
||||
grant all on function public.lowercase_email_jsonb() to anon;
|
||||
grant all on function public.lowercase_email_jsonb() to authenticated;
|
||||
grant all on function public.lowercase_email_jsonb() to service_role;
|
||||
|
||||
grant all on function public.merge_contacts(bigint, bigint) to anon;
|
||||
grant all on function public.merge_contacts(bigint, bigint) to authenticated;
|
||||
grant all on function public.merge_contacts(bigint, bigint) to service_role;
|
||||
|
||||
grant all on function public.set_sales_id_default() to anon;
|
||||
grant all on function public.set_sales_id_default() to authenticated;
|
||||
grant all on function public.set_sales_id_default() to service_role;
|
||||
|
||||
-- Table grants
|
||||
grant all on table public.companies to anon;
|
||||
grant all on table public.companies to authenticated;
|
||||
grant all on table public.companies to service_role;
|
||||
|
||||
grant all on table public.contacts to anon;
|
||||
grant all on table public.contacts to authenticated;
|
||||
grant all on table public.contacts to service_role;
|
||||
|
||||
grant all on table public.contact_notes to anon;
|
||||
grant all on table public.contact_notes to authenticated;
|
||||
grant all on table public.contact_notes to service_role;
|
||||
|
||||
grant all on table public.deals to anon;
|
||||
grant all on table public.deals to authenticated;
|
||||
grant all on table public.deals to service_role;
|
||||
|
||||
grant all on table public.deal_notes to anon;
|
||||
grant all on table public.deal_notes to authenticated;
|
||||
grant all on table public.deal_notes to service_role;
|
||||
|
||||
grant all on table public.sales to anon;
|
||||
grant all on table public.sales to authenticated;
|
||||
grant all on table public.sales to service_role;
|
||||
|
||||
grant all on table public.tags to anon;
|
||||
grant all on table public.tags to authenticated;
|
||||
grant all on table public.tags to service_role;
|
||||
|
||||
grant all on table public.tasks to anon;
|
||||
grant all on table public.tasks to authenticated;
|
||||
grant all on table public.tasks to service_role;
|
||||
|
||||
grant all on table public.configuration to anon;
|
||||
grant all on table public.configuration to authenticated;
|
||||
grant all on table public.configuration to service_role;
|
||||
|
||||
grant all on table public.favicons_excluded_domains to anon;
|
||||
grant all on table public.favicons_excluded_domains to authenticated;
|
||||
grant all on table public.favicons_excluded_domains to service_role;
|
||||
|
||||
-- View grants
|
||||
grant all on table public.activity_log to anon;
|
||||
grant all on table public.activity_log to authenticated;
|
||||
grant all on table public.activity_log to service_role;
|
||||
|
||||
grant all on table public.companies_summary to anon;
|
||||
grant all on table public.companies_summary to authenticated;
|
||||
grant all on table public.companies_summary to service_role;
|
||||
|
||||
grant all on table public.contacts_summary to anon;
|
||||
grant all on table public.contacts_summary to authenticated;
|
||||
grant all on table public.contacts_summary to service_role;
|
||||
|
||||
grant all on table public.init_state to anon;
|
||||
grant all on table public.init_state to authenticated;
|
||||
grant all on table public.init_state to service_role;
|
||||
|
||||
-- Sequence grants
|
||||
grant all on sequence public.companies_id_seq to anon;
|
||||
grant all on sequence public.companies_id_seq to authenticated;
|
||||
grant all on sequence public.companies_id_seq to service_role;
|
||||
|
||||
grant all on sequence public."contactNotes_id_seq" to anon;
|
||||
grant all on sequence public."contactNotes_id_seq" to authenticated;
|
||||
grant all on sequence public."contactNotes_id_seq" to service_role;
|
||||
|
||||
grant all on sequence public.contacts_id_seq to anon;
|
||||
grant all on sequence public.contacts_id_seq to authenticated;
|
||||
grant all on sequence public.contacts_id_seq to service_role;
|
||||
|
||||
grant all on sequence public."dealNotes_id_seq" to anon;
|
||||
grant all on sequence public."dealNotes_id_seq" to authenticated;
|
||||
grant all on sequence public."dealNotes_id_seq" to service_role;
|
||||
|
||||
grant all on sequence public.deals_id_seq to anon;
|
||||
grant all on sequence public.deals_id_seq to authenticated;
|
||||
grant all on sequence public.deals_id_seq to service_role;
|
||||
|
||||
grant all on sequence public.favicons_excluded_domains_id_seq to anon;
|
||||
grant all on sequence public.favicons_excluded_domains_id_seq to authenticated;
|
||||
grant all on sequence public.favicons_excluded_domains_id_seq to service_role;
|
||||
|
||||
grant all on sequence public.sales_id_seq to anon;
|
||||
grant all on sequence public.sales_id_seq to authenticated;
|
||||
grant all on sequence public.sales_id_seq to service_role;
|
||||
|
||||
grant all on sequence public.tags_id_seq to anon;
|
||||
grant all on sequence public.tags_id_seq to authenticated;
|
||||
grant all on sequence public.tags_id_seq to service_role;
|
||||
|
||||
grant all on sequence public.tasks_id_seq to anon;
|
||||
grant all on sequence public.tasks_id_seq to authenticated;
|
||||
grant all on sequence public.tasks_id_seq to service_role;
|
||||
|
||||
-- Default privileges
|
||||
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;
|
||||
8
supabase/schemas/07_storage.sql
Normal file
8
supabase/schemas/07_storage.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
--
|
||||
-- Storage
|
||||
-- This file declares storage bucket policies.
|
||||
--
|
||||
|
||||
create policy "Attachments 1mt4rzk_0" on storage.objects for select to authenticated using (bucket_id = 'attachments');
|
||||
create policy "Attachments 1mt4rzk_1" on storage.objects for insert to authenticated with check (bucket_id = 'attachments');
|
||||
create policy "Attachments 1mt4rzk_3" on storage.objects for delete to authenticated using (bucket_id = 'attachments');
|
||||
Reference in New Issue
Block a user