Categorias
Histórico Completo
-- ══════════════════════════════════════════════════════════════
-- CASA DA PRAIA · Sistema Pessoal CDP — script de provisionamento
-- Rode uma vez, num projeto Supabase novo e vazio.
-- Antes de rodar: troque SEU-EMAIL-AQUI@gmail.com pelo Google que
-- vai ser o admin desta empresa (linha logo abaixo).
-- ══════════════════════════════════════════════════════════════
-- 1) Login com Google + aprovação manual do admin
create table if not exists usuarios_permitidos (
email text primary key,
nome text,
status text not null default 'pendente' check (status in ('pendente','aprovado','negado')),
is_admin boolean not null default false,
criado_em timestamptz not null default now(),
decidido_em timestamptz
);
alter table usuarios_permitidos enable row level security;
insert into usuarios_permitidos (email, status, is_admin, decidido_em)
values ('SEU-EMAIL-AQUI@gmail.com', 'aprovado', true, now())
on conflict (email) do update set status='aprovado', is_admin=true, decidido_em=now();
create or replace function usuarios_permitidos_forcar_pendente()
returns trigger language plpgsql security definer set search_path = public as $$
begin
new.status := 'pendente'; new.is_admin := false; new.decidido_em := null;
return new;
end;
$$;
drop trigger if exists trg_usuarios_permitidos_forcar_pendente on usuarios_permitidos;
create trigger trg_usuarios_permitidos_forcar_pendente
before insert on usuarios_permitidos
for each row execute function usuarios_permitidos_forcar_pendente();
revoke execute on function usuarios_permitidos_forcar_pendente() from public;
create or replace function usuario_e_admin(check_email text)
returns boolean language sql security definer set search_path = public stable as $$
select exists (select 1 from usuarios_permitidos where email = check_email and is_admin = true);
$$;
create or replace function usuario_esta_aprovado(check_email text)
returns boolean language sql security definer set search_path = public stable as $$
select exists (select 1 from usuarios_permitidos where email = check_email and status = 'aprovado');
$$;
revoke all on function usuario_e_admin(text) from public;
revoke all on function usuario_esta_aprovado(text) from public;
grant execute on function usuario_e_admin(text) to authenticated;
grant execute on function usuario_esta_aprovado(text) to authenticated;
drop policy if exists up_insert_proprio on usuarios_permitidos;
create policy up_insert_proprio on usuarios_permitidos for insert to authenticated
with check (email = (auth.jwt()->>'email'));
drop policy if exists up_select_proprio_ou_admin on usuarios_permitidos;
create policy up_select_proprio_ou_admin on usuarios_permitidos for select to authenticated
using (email = (auth.jwt()->>'email') or usuario_e_admin(auth.jwt()->>'email'));
drop policy if exists up_update_admin on usuarios_permitidos;
create policy up_update_admin on usuarios_permitidos for update to authenticated
using (usuario_e_admin(auth.jwt()->>'email')) with check (usuario_e_admin(auth.jwt()->>'email'));
-- 2) Tabelas internas — uma linha por usuário aprovado, nunca compartilhada
create table if not exists estoque_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
produtos jsonb default '[]', categorias jsonb default '[]', historico jsonb default '[]',
subcategorias jsonb default '[]', notas_fiscais jsonb default '[]', nf_id int default 1,
prod_id int default 1, sub_id int default 1000, hist_id int default 1, cat_id int default 10,
subcat_id int default 1, updated_at timestamptz default now()
);
alter table estoque_dados enable row level security;
drop policy if exists estoque_aprovados on estoque_dados;
create policy estoque_aprovados on estoque_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists checklist_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
tasks jsonb default '[]', lists jsonb default '[]', task_id int default 1, list_id int default 10,
daily_tasks jsonb default '[]', daily_done jsonb default '{}', planner_tasks jsonb default '[]',
planner_done jsonb default '{}', updated_at timestamptz default now()
);
alter table checklist_dados enable row level security;
drop policy if exists checklist_aprovados on checklist_dados;
create policy checklist_aprovados on checklist_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists perdas_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
perdas jsonb default '[]', motivos jsonb default '[]', relatorios jsonb default '[]',
catalogo jsonb default '[]', horti_selecionados jsonb default '[]', updated_at timestamptz default now()
);
alter table perdas_dados enable row level security;
drop policy if exists perdas_aprovados on perdas_dados;
create policy perdas_aprovados on perdas_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists proteinas_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
proteinas jsonb default '[]', periodos jsonb default '[]', updated_at timestamptz default now()
);
alter table proteinas_dados enable row level security;
drop policy if exists proteinas_aprovados on proteinas_dados;
create policy proteinas_aprovados on proteinas_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists conf_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
conf_atual jsonb default '{}', conf_hist jsonb default '[]', updated_at timestamptz default now()
);
alter table conf_dados enable row level security;
drop policy if exists conf_aprovados on conf_dados;
create policy conf_aprovados on conf_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists comodato_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
equipamentos jsonb default '[]', updated_at timestamptz default now()
);
alter table comodato_dados enable row level security;
drop policy if exists comodato_aprovados on comodato_dados;
create policy comodato_aprovados on comodato_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists fichas_tecnicas_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
fichas jsonb default '[]', categorias jsonb default '[]', subcategorias jsonb default '[]',
subpratos jsonb default '[]', updated_at timestamptz default now()
);
alter table fichas_tecnicas_dados enable row level security;
drop policy if exists fichas_aprovados on fichas_tecnicas_dados;
create policy fichas_aprovados on fichas_tecnicas_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists cmv_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
contagens jsonb default '[]', setores jsonb default '[]', excluidos jsonb default '[]', updated_at timestamptz default now()
);
alter table cmv_dados enable row level security;
drop policy if exists cmv_aprovados on cmv_dados;
create policy cmv_aprovados on cmv_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists apresentacao_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
unidade text default 'Lounge', snapshots jsonb default '{}', updated_at timestamptz default now()
);
alter table apresentacao_dados enable row level security;
drop policy if exists apresentacao_aprovados on apresentacao_dados;
create policy apresentacao_aprovados on apresentacao_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
-- 3) Cotações — por empresa. Fornecedores NUNCA logam: acessam só pelas
-- 3 funções abaixo, nunca direto nas tabelas (por isso não tem
-- policy nenhuma pra 'anon' aqui).
create table if not exists cotacoes_dados (
id text default 'main', user_email text not null default (auth.jwt()->>'email') unique,
cotacoes jsonb default '[]', fornecedores jsonb default '[]', preco_hist jsonb default '[]',
cot_next_id int default 1, forn_next_id int default 1, updated_at timestamptz default now()
);
alter table cotacoes_dados enable row level security;
drop policy if exists cotacoes_aprovados on cotacoes_dados;
create policy cotacoes_aprovados on cotacoes_dados for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create table if not exists cotacoes_tokens (
token text primary key, payload jsonb not null,
user_email text not null default (auth.jwt()->>'email'),
cot_id text, forn_id text, updated_at timestamptz default now()
);
alter table cotacoes_tokens enable row level security;
drop policy if exists tokens_aprovados on cotacoes_tokens;
create policy tokens_aprovados on cotacoes_tokens for all to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = (auth.jwt()->>'email'));
create or replace function cotacao_forn_payload(p_token text)
returns jsonb language sql security definer set search_path = public stable as $$
select payload from cotacoes_tokens where token = p_token;
$$;
create or replace function cotacao_forn_status(p_token text, p_forn_id text default null)
returns jsonb language plpgsql security definer set search_path = public as $$
declare
v_owner text; v_cotid text; v_fornid_token text; v_fornid text; v_lista jsonb; v_cot jsonb; v_submitted boolean := false;
begin
select user_email, cot_id, forn_id into v_owner, v_cotid, v_fornid_token
from cotacoes_tokens where token = p_token;
if v_owner is null then raise exception 'Token inválido ou expirado'; end if;
-- link específico já tem forn_id fixo no token; link geral usa o
-- que o fornecedor escolheu na própria tela (p_forn_id)
v_fornid := coalesce(v_fornid_token, p_forn_id);
if v_fornid is null then return jsonb_build_object('submitted', false); end if;
select cotacoes into v_lista from cotacoes_dados where user_email = v_owner;
if v_lista is not null then
for i in 0 .. coalesce(jsonb_array_length(v_lista),1) - 1 loop
if (v_lista->i->>'id') = v_cotid then v_cot := v_lista->i; exit; end if;
end loop;
end if;
if v_cot is not null and (v_cot->'fornSubmitted') ? v_fornid then
v_submitted := true;
end if;
return jsonb_build_object('submitted', v_submitted);
end;
$$;
create or replace function cotacao_forn_submeter(p_token text, p_forn_id text, p_precos jsonb, p_prazo jsonb)
returns boolean language plpgsql security definer set search_path = public as $$
declare
v_owner text; v_cotid text; v_fornid_token text; v_fornid text; v_lista jsonb; v_idx int; v_cot jsonb; v_precos jsonb; v_key text;
begin
select user_email, cot_id, forn_id into v_owner, v_cotid, v_fornid_token
from cotacoes_tokens where token = p_token;
if v_owner is null then raise exception 'Token inválido ou expirado'; end if;
-- link específico: ignora o que o cliente mandar, usa sempre o do token
-- (mais seguro). Link geral: usa o escolhido na tela.
v_fornid := coalesce(v_fornid_token, p_forn_id);
if v_fornid is null then raise exception 'Fornecedor não identificado'; end if;
select cotacoes into v_lista from cotacoes_dados where user_email = v_owner for update;
if v_lista is null then raise exception 'Cotação não encontrada'; end if;
v_idx := null;
for i in 0 .. coalesce(jsonb_array_length(v_lista),1) - 1 loop
if (v_lista->i->>'id') = v_cotid then v_idx := i; exit; end if;
end loop;
if v_idx is null then raise exception 'Cotação não encontrada'; end if;
v_cot := v_lista -> v_idx;
v_precos := coalesce(v_cot->'precos', '{}'::jsonb);
for v_key in select jsonb_object_keys(p_precos) loop
v_precos := jsonb_set(v_precos, array[v_key],
coalesce(v_precos->v_key, '{}'::jsonb) || jsonb_build_object(v_fornid, p_precos->v_key), true);
end loop;
v_cot := jsonb_set(v_cot, '{precos}', v_precos, true);
v_cot := jsonb_set(v_cot, '{fornPrazoPagamento}',
coalesce(v_cot->'fornPrazoPagamento', '{}'::jsonb) || jsonb_build_object(v_fornid, coalesce(p_prazo,'[]'::jsonb)), true);
v_cot := jsonb_set(v_cot, '{fornSubmitted}',
coalesce(v_cot->'fornSubmitted', '{}'::jsonb) || jsonb_build_object(v_fornid, now()::text), true);
v_lista := jsonb_set(v_lista, array[v_idx::text], v_cot);
update cotacoes_dados set cotacoes = v_lista where user_email = v_owner;
return true;
end;
$$;
revoke all on function cotacao_forn_payload(text) from public;
revoke all on function cotacao_forn_status(text, text) from public;
revoke all on function cotacao_forn_submeter(text, text, jsonb, jsonb) from public;
grant execute on function cotacao_forn_payload(text) to anon, authenticated;
grant execute on function cotacao_forn_status(text, text) to anon, authenticated;
grant execute on function cotacao_forn_submeter(text, text, jsonb, jsonb) to anon, authenticated;
-- 4) Dados compartilhados + permissao por aba ────────────────────
-- Existe UMA conta que guarda os dados reais da casa (is_conta_dados).
-- Todo login aprovado le e grava nessa mesma linha - ninguem cai mais
-- num banco vazio ao entrar pela primeira vez. Quem precisar de dados
-- proprios recebe dono_email = o proprio e-mail.
-- O que muda de pessoa pra pessoa e a PERMISSAO, nao o acervo:
-- somente_leitura controla se pode gravar, abas_permitidas controla
-- quais paginas aparecem ([] = nenhuma, null = todas).
alter table usuarios_permitidos
add column if not exists dono_email text,
add column if not exists somente_leitura boolean not null default false,
add column if not exists is_conta_dados boolean not null default false;
create unique index if not exists ux_uma_conta_dados
on usuarios_permitidos ((true)) where is_conta_dados;
create or replace function conta_dados_padrao()
returns text language sql security definer set search_path = public stable as $$
select email from usuarios_permitidos where is_conta_dados limit 1;
$$;
create or replace function dono_dos_dados(check_email text)
returns text language sql security definer set search_path = public stable as $$
select coalesce(
nullif((select dono_email from usuarios_permitidos where email = check_email), ''),
conta_dados_padrao(),
check_email
);
$$;
revoke all on function conta_dados_padrao() from public;
grant execute on function conta_dados_padrao() to authenticated;
create or replace function usuario_somente_leitura(check_email text)
returns boolean language sql security definer set search_path = public stable as $$
select coalesce((select somente_leitura from usuarios_permitidos where email = check_email), false);
$$;
revoke all on function dono_dos_dados(text) from public;
revoke all on function usuario_somente_leitura(text) from public;
grant execute on function dono_dos_dados(text) to authenticated;
grant execute on function usuario_somente_leitura(text) to authenticated;
do $$
declare
t text; p record;
tabelas text[] := array['estoque_dados','checklist_dados','perdas_dados','proteinas_dados',
'conf_dados','comodato_dados','fichas_tecnicas_dados','cmv_dados','apresentacao_dados',
'cotacoes_dados','cotacoes_tokens'];
begin
foreach t in array tabelas loop
if to_regclass('public.'||t) is null then continue; end if;
for p in select policyname from pg_policies where schemaname='public' and tablename=t loop
execute format('drop policy if exists %I on public.%I', p.policyname, t);
end loop;
execute format($f$create policy %I on public.%I for select to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'))$f$, t||'_ler', t);
execute format($f$create policy %I on public.%I for insert to authenticated
with check (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'))$f$, t||'_inserir', t);
execute format($f$create policy %I on public.%I for update to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'))$f$, t||'_atualizar', t);
execute format($f$create policy %I on public.%I for delete to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'))$f$, t||'_apagar', t);
end loop;
end $$;
-- 5) Abas visiveis por leitor - qual parte do sistema cada conta enxerga
alter table usuarios_permitidos add column if not exists abas_permitidas jsonb;
-- 6) Modulo Gerencia (Organograma, Setores/Funcionarios, Equipamentos)
create table if not exists gerencia_dados (
id text not null default 'main', user_email text not null default (auth.jwt() ->> 'email'::text),
setores jsonb not null default '[]'::jsonb,
funcionarios jsonb not null default '[]'::jsonb,
equipamentos jsonb not null default '[]'::jsonb,
organograma jsonb not null default '{"caixas":[],"conexoes":[]}'::jsonb,
reunioes jsonb not null default '[]'::jsonb,
setor_next_id integer not null default 1, func_next_id integer not null default 1,
equip_next_id integer not null default 1, caixa_next_id integer not null default 1,
conexao_next_id integer not null default 1,
reuniao_next_id integer not null default 1, acao_next_id integer not null default 1,
updated_at timestamptz not null default now(), unique(user_email)
);
-- quem ja tinha a tabela criada antes das reunioes: adiciona so o que falta
alter table gerencia_dados add column if not exists reunioes jsonb not null default '[]'::jsonb;
alter table gerencia_dados add column if not exists reuniao_next_id integer not null default 1;
alter table gerencia_dados add column if not exists acao_next_id integer not null default 1;
alter table gerencia_dados enable row level security;
create or replace function gerencia_dados_set_updated_at()
returns trigger language plpgsql as $$ begin new.updated_at = now(); return new; end; $$;
drop trigger if exists trg_gerencia_dados_updated_at on gerencia_dados;
create trigger trg_gerencia_dados_updated_at before update on gerencia_dados
for each row execute function gerencia_dados_set_updated_at();
drop policy if exists gerencia_dados_ler on gerencia_dados;
drop policy if exists gerencia_dados_inserir on gerencia_dados;
drop policy if exists gerencia_dados_atualizar on gerencia_dados;
drop policy if exists gerencia_dados_apagar on gerencia_dados;
create policy gerencia_dados_ler on gerencia_dados for select to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'));
create policy gerencia_dados_inserir on gerencia_dados for insert to authenticated
with check (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'));
create policy gerencia_dados_atualizar on gerencia_dados for update to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'))
with check (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'));
create policy gerencia_dados_apagar on gerencia_dados for delete to authenticated
using (usuario_esta_aprovado(auth.jwt()->>'email') and not usuario_somente_leitura(auth.jwt()->>'email') and user_email = dono_dos_dados(auth.jwt()->>'email'));
alter publication supabase_realtime add table gerencia_dados;
-- 7) Notas fiscais e historico em tabelas proprias (uma linha por registro)
create table if not exists estoque_notas (
k text primary key,
user_email text not null default (auth.jwt()->>'email'),
dados jsonb not null,
updated_at timestamptz not null default now()
);
create table if not exists estoque_historico (
k text primary key,
user_email text not null default (auth.jwt()->>'email'),
dados jsonb not null,
updated_at timestamptz not null default now()
);
create index if not exists ix_estoque_notas_email on estoque_notas (user_email, updated_at desc);
create index if not exists ix_estoque_historico_email on estoque_historico (user_email, updated_at desc);
grant select, insert, update, delete on estoque_notas, estoque_historico to authenticated;
create or replace function estoque_filhos_set_updated_at()
returns trigger language plpgsql as $$ begin new.updated_at = now(); return new; end; $$;
drop trigger if exists trg_estoque_notas_updated_at on estoque_notas;
create trigger trg_estoque_notas_updated_at before update on estoque_notas
for each row execute function estoque_filhos_set_updated_at();
drop trigger if exists trg_estoque_historico_updated_at on estoque_historico;
create trigger trg_estoque_historico_updated_at before update on estoque_historico
for each row execute function estoque_filhos_set_updated_at();
-- Permissoes: mesmas regras das outras tabelas. As funcoes vao dentro de
-- (select ...) para o Postgres calcular UMA vez por consulta, e nao uma
-- vez por linha - com milhares de linhas isso faz diferenca na CPU.
do $$
declare t text; p record;
begin
foreach t in array array['estoque_notas','estoque_historico'] loop
execute format('alter table public.%I enable row level security', t);
for p in select policyname from pg_policies where schemaname='public' and tablename=t loop
execute format('drop policy if exists %I on public.%I', p.policyname, t);
end loop;
execute format($f$create policy %I on public.%I for select to authenticated
using ((select usuario_esta_aprovado(auth.jwt()->>'email'))
and user_email = (select dono_dos_dados(auth.jwt()->>'email')))$f$, t||'_ler', t);
execute format($f$create policy %I on public.%I for insert to authenticated
with check ((select usuario_esta_aprovado(auth.jwt()->>'email'))
and not (select usuario_somente_leitura(auth.jwt()->>'email'))
and user_email = (select dono_dos_dados(auth.jwt()->>'email')))$f$, t||'_inserir', t);
execute format($f$create policy %I on public.%I for update to authenticated
using ((select usuario_esta_aprovado(auth.jwt()->>'email'))
and not (select usuario_somente_leitura(auth.jwt()->>'email'))
and user_email = (select dono_dos_dados(auth.jwt()->>'email')))
with check ((select usuario_esta_aprovado(auth.jwt()->>'email'))
and not (select usuario_somente_leitura(auth.jwt()->>'email'))
and user_email = (select dono_dos_dados(auth.jwt()->>'email')))$f$, t||'_atualizar', t);
execute format($f$create policy %I on public.%I for delete to authenticated
using ((select usuario_esta_aprovado(auth.jwt()->>'email'))
and not (select usuario_somente_leitura(auth.jwt()->>'email'))
and user_email = (select dono_dos_dados(auth.jwt()->>'email')))$f$, t||'_apagar', t);
begin
execute format('alter publication supabase_realtime add table public.%I', t);
exception when duplicate_object then null;
end;
end loop;
end $$;