7c20b518d4
Repositorio estava ha ~5 sessoes sem commit. Consolida tudo desde d088a89.
Ver commit.md na raiz para descricao completa por sessao.
# Numeros
- A# auditoria abertos: 0/30
- V# verificacoes abertos: 5/52 (todos adiados com plano)
- T# testes escritos: 10/10
- Vitest: 192/192
- SQL integration: 33/33
- E2E (Playwright, novo): 5/5
- Migrations: 17 (10 novas Sessao 6)
- Areas auditadas: 7 (+documentos com 10 V#)
# Highlights Sessao 6 (hoje)
- V#34/V#41 Opcao B2: tenant_features com plano + override (RPC SECURITY DEFINER, tela /saas/tenant-features)
- A#20 rev2 self-hosted: defesa em 5 camadas (honeypot + rate limit + math captcha condicional + paranoid mode + dashboard /saas/security)
- Documentos hardening (V#43-V#49): tenant scoping em storage policies (vazamento entre clinicas eliminado), RPC validate_share_token, signatures policy granular
- SaaS Twilio Config (/saas/twilio-config): UI editavel para SID/webhook/cotacao; AUTH_TOKEN permanece em env var
- T#9 + T#10: useAgendaEvents.spec.js + Playwright E2E (descobriu bug no front que foi corrigido)
# Sessoes anteriores (1-5) consolidadas
- Sessao 1: auth/router/session, normalizeRole extraido
- Sessao 2: agenda - composables/services consolidados
- Sessao 3: pacientes - tenant_id em todas queries
- Sessao 4: security review pagina publica - 14/15 vulnerabilidades corrigidas
- Sessao 5: SaaS - P0 (A#30: 7 tabelas com RLS off corrigidas)
# .gitignore ajustado
- supabase/* + !supabase/functions/ (mantem 10 edge functions, ignora .temp/migrations gerados pelo CLI)
- database-novo/backups/ (regeneravel via db.cjs backup)
- test-results/ + playwright-report/
- .claude/settings.local.json (config local com senha de dev removida do tracking)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
276 lines
14 KiB
PL/PgSQL
276 lines
14 KiB
PL/PgSQL
-- =============================================================================
|
||
-- Migration: 20260417000001_dev_tables
|
||
-- Área de Desenvolvimento (dev_*) — roadmap, auditoria, concorrentes, logs
|
||
-- -----------------------------------------------------------------------------
|
||
-- Tabelas usadas pela página /saas/desenvolvimento. Todas restritas a
|
||
-- saas_admins via RLS (helper public.is_saas_admin()).
|
||
-- =============================================================================
|
||
|
||
-- -----------------------------------------------------------------------------
|
||
-- Helper trigger: updated_at
|
||
-- -----------------------------------------------------------------------------
|
||
CREATE OR REPLACE FUNCTION public.dev_set_updated_at()
|
||
RETURNS TRIGGER
|
||
LANGUAGE plpgsql
|
||
AS $$
|
||
BEGIN
|
||
NEW.updated_at := now();
|
||
RETURN NEW;
|
||
END;
|
||
$$;
|
||
|
||
-- =============================================================================
|
||
-- 1. dev_roadmap_phases — Fases (1, 2, 3...)
|
||
-- =============================================================================
|
||
CREATE TABLE IF NOT EXISTS public.dev_roadmap_phases (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
numero INTEGER NOT NULL UNIQUE,
|
||
nome VARCHAR(160) NOT NULL,
|
||
objetivo TEXT,
|
||
timeline_sugerida VARCHAR(160),
|
||
criterio_saida TEXT,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'planejada'
|
||
CHECK (status IN ('planejada','em_andamento','concluida','arquivada')),
|
||
data_inicio DATE,
|
||
data_fim DATE,
|
||
ordem INTEGER NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_roadmap_phases_status ON public.dev_roadmap_phases(status);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_roadmap_phases_ordem ON public.dev_roadmap_phases(ordem);
|
||
|
||
DROP TRIGGER IF EXISTS trg_dev_roadmap_phases_updated_at ON public.dev_roadmap_phases;
|
||
CREATE TRIGGER trg_dev_roadmap_phases_updated_at
|
||
BEFORE UPDATE ON public.dev_roadmap_phases
|
||
FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();
|
||
|
||
-- =============================================================================
|
||
-- 2. dev_roadmap_items — Itens das fases
|
||
-- =============================================================================
|
||
CREATE TABLE IF NOT EXISTS public.dev_roadmap_items (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
phase_id BIGINT NOT NULL REFERENCES public.dev_roadmap_phases(id) ON DELETE CASCADE,
|
||
numero INTEGER,
|
||
bloco VARCHAR(160),
|
||
feature TEXT NOT NULL,
|
||
descricao TEXT,
|
||
esforco VARCHAR(4)
|
||
CHECK (esforco IS NULL OR esforco IN ('S','M','L','XL')),
|
||
prioridade VARCHAR(20)
|
||
CHECK (prioridade IS NULL OR prioridade IN ('bloqueador','alta','media','diferencial')),
|
||
status VARCHAR(20) NOT NULL DEFAULT 'pendente'
|
||
CHECK (status IN ('pendente','em_andamento','concluido','cancelado','bloqueado')),
|
||
notas TEXT,
|
||
assignee VARCHAR(120),
|
||
data_inicio DATE,
|
||
data_conclusao DATE,
|
||
ordem INTEGER NOT NULL DEFAULT 0,
|
||
tags TEXT[] DEFAULT '{}',
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_phase ON public.dev_roadmap_items(phase_id);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_status ON public.dev_roadmap_items(status);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_prior ON public.dev_roadmap_items(prioridade);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_ordem ON public.dev_roadmap_items(phase_id, ordem);
|
||
|
||
DROP TRIGGER IF EXISTS trg_dev_roadmap_items_updated_at ON public.dev_roadmap_items;
|
||
CREATE TRIGGER trg_dev_roadmap_items_updated_at
|
||
BEFORE UPDATE ON public.dev_roadmap_items
|
||
FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();
|
||
|
||
-- =============================================================================
|
||
-- 3. dev_auditoria_items — Bugs / débitos técnicos / decisões
|
||
-- =============================================================================
|
||
CREATE TABLE IF NOT EXISTS public.dev_auditoria_items (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
categoria VARCHAR(120),
|
||
titulo TEXT NOT NULL,
|
||
descricao_problema TEXT,
|
||
solucao TEXT,
|
||
severidade VARCHAR(20)
|
||
CHECK (severidade IS NULL OR severidade IN ('critico','alto','medio','baixo')),
|
||
status VARCHAR(20) NOT NULL DEFAULT 'aberto'
|
||
CHECK (status IN ('aberto','em_analise','resolvido','wontfix','duplicado')),
|
||
resolvido_em DATE,
|
||
sessao_resolucao VARCHAR(160),
|
||
arquivo_afetado TEXT,
|
||
tags TEXT[] DEFAULT '{}',
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_auditoria_items_status ON public.dev_auditoria_items(status);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_auditoria_items_severidade ON public.dev_auditoria_items(severidade);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_auditoria_items_categoria ON public.dev_auditoria_items(categoria);
|
||
|
||
DROP TRIGGER IF EXISTS trg_dev_auditoria_items_updated_at ON public.dev_auditoria_items;
|
||
CREATE TRIGGER trg_dev_auditoria_items_updated_at
|
||
BEFORE UPDATE ON public.dev_auditoria_items
|
||
FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();
|
||
|
||
-- =============================================================================
|
||
-- 4. dev_competitors — Concorrentes
|
||
-- =============================================================================
|
||
CREATE TABLE IF NOT EXISTS public.dev_competitors (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
slug VARCHAR(80) NOT NULL UNIQUE,
|
||
nome VARCHAR(160) NOT NULL,
|
||
pais VARCHAR(40),
|
||
foco VARCHAR(160),
|
||
pricing TEXT,
|
||
posicionamento TEXT,
|
||
url TEXT,
|
||
ultima_pesquisa DATE,
|
||
notas TEXT,
|
||
ativo BOOLEAN NOT NULL DEFAULT true,
|
||
ordem INTEGER NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_competitors_ativo ON public.dev_competitors(ativo);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_competitors_pais ON public.dev_competitors(pais);
|
||
|
||
DROP TRIGGER IF EXISTS trg_dev_competitors_updated_at ON public.dev_competitors;
|
||
CREATE TRIGGER trg_dev_competitors_updated_at
|
||
BEFORE UPDATE ON public.dev_competitors
|
||
FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();
|
||
|
||
-- =============================================================================
|
||
-- 5. dev_competitor_features — features de cada concorrente
|
||
-- =============================================================================
|
||
CREATE TABLE IF NOT EXISTS public.dev_competitor_features (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
competitor_id BIGINT NOT NULL REFERENCES public.dev_competitors(id) ON DELETE CASCADE,
|
||
categoria VARCHAR(120),
|
||
nome TEXT NOT NULL,
|
||
descricao TEXT,
|
||
fonte VARCHAR(20) NOT NULL DEFAULT 'publico'
|
||
CHECK (fonte IN ('fetched','observacao','publico','hipotese')),
|
||
fonte_url TEXT,
|
||
data_fonte DATE,
|
||
destaque BOOLEAN NOT NULL DEFAULT false,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_competitor_features_comp ON public.dev_competitor_features(competitor_id);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_competitor_features_cat ON public.dev_competitor_features(categoria);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_competitor_features_destaque ON public.dev_competitor_features(destaque);
|
||
|
||
DROP TRIGGER IF EXISTS trg_dev_competitor_features_updated_at ON public.dev_competitor_features;
|
||
CREATE TRIGGER trg_dev_competitor_features_updated_at
|
||
BEFORE UPDATE ON public.dev_competitor_features
|
||
FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();
|
||
|
||
-- =============================================================================
|
||
-- 6. dev_comparison_matrix — AgenciaPsi × features-de-concorrente
|
||
-- =============================================================================
|
||
CREATE TABLE IF NOT EXISTS public.dev_comparison_matrix (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
dominio VARCHAR(120),
|
||
feature TEXT NOT NULL,
|
||
nosso_status VARCHAR(20) NOT NULL DEFAULT 'a_definir'
|
||
CHECK (nosso_status IN ('tem','parcial','gap','na','a_definir')),
|
||
nossa_nota TEXT,
|
||
importancia VARCHAR(20)
|
||
CHECK (importancia IS NULL OR importancia IN ('alta','media','baixa')),
|
||
ordem INTEGER NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_comparison_matrix_dominio ON public.dev_comparison_matrix(dominio);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_comparison_matrix_status ON public.dev_comparison_matrix(nosso_status);
|
||
|
||
DROP TRIGGER IF EXISTS trg_dev_comparison_matrix_updated_at ON public.dev_comparison_matrix;
|
||
CREATE TRIGGER trg_dev_comparison_matrix_updated_at
|
||
BEFORE UPDATE ON public.dev_comparison_matrix
|
||
FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();
|
||
|
||
-- dev_comparison_competitor_status — opcional: status por concorrente por feature
|
||
-- (se quisermos marcar que competitor X tem feature Y). Tabela ponte N-N.
|
||
CREATE TABLE IF NOT EXISTS public.dev_comparison_competitor_status (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
comparison_id BIGINT NOT NULL REFERENCES public.dev_comparison_matrix(id) ON DELETE CASCADE,
|
||
competitor_id BIGINT NOT NULL REFERENCES public.dev_competitors(id) ON DELETE CASCADE,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'a_definir'
|
||
CHECK (status IN ('tem','parcial','gap','na','a_definir')),
|
||
nota TEXT,
|
||
fonte VARCHAR(20)
|
||
CHECK (fonte IS NULL OR fonte IN ('fetched','observacao','publico','hipotese')),
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
UNIQUE (comparison_id, competitor_id)
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_ccs_comp ON public.dev_comparison_competitor_status(competitor_id);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_ccs_comparison ON public.dev_comparison_competitor_status(comparison_id);
|
||
|
||
DROP TRIGGER IF EXISTS trg_dev_ccs_updated_at ON public.dev_comparison_competitor_status;
|
||
CREATE TRIGGER trg_dev_ccs_updated_at
|
||
BEFORE UPDATE ON public.dev_comparison_competitor_status
|
||
FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at();
|
||
|
||
-- =============================================================================
|
||
-- 7. dev_generation_log — histórico de execuções (backup, dashboard, export...)
|
||
-- =============================================================================
|
||
CREATE TABLE IF NOT EXISTS public.dev_generation_log (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
tipo VARCHAR(40) NOT NULL,
|
||
comando TEXT,
|
||
sucesso BOOLEAN NOT NULL DEFAULT false,
|
||
stdout TEXT,
|
||
stderr TEXT,
|
||
duration_ms INTEGER,
|
||
metadata JSONB DEFAULT '{}'::jsonb,
|
||
trigger_user_id UUID,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_generation_log_tipo ON public.dev_generation_log(tipo);
|
||
CREATE INDEX IF NOT EXISTS idx_dev_generation_log_created ON public.dev_generation_log(created_at DESC);
|
||
|
||
-- =============================================================================
|
||
-- RLS — tudo restrito a saas_admins (helper existente: public.is_saas_admin())
|
||
-- =============================================================================
|
||
DO $$
|
||
DECLARE
|
||
t TEXT;
|
||
dev_tables TEXT[] := ARRAY[
|
||
'dev_roadmap_phases',
|
||
'dev_roadmap_items',
|
||
'dev_auditoria_items',
|
||
'dev_competitors',
|
||
'dev_competitor_features',
|
||
'dev_comparison_matrix',
|
||
'dev_comparison_competitor_status',
|
||
'dev_generation_log'
|
||
];
|
||
BEGIN
|
||
FOREACH t IN ARRAY dev_tables
|
||
LOOP
|
||
EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY;', t);
|
||
|
||
-- Drop policy se existir (idempotente)
|
||
EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I;', t || '_saas_admin_all', t);
|
||
|
||
-- Cria policy que permite tudo pra saas_admin
|
||
EXECUTE format(
|
||
'CREATE POLICY %I ON public.%I FOR ALL TO authenticated
|
||
USING (public.is_saas_admin())
|
||
WITH CHECK (public.is_saas_admin());',
|
||
t || '_saas_admin_all',
|
||
t
|
||
);
|
||
END LOOP;
|
||
END $$;
|
||
|
||
-- =============================================================================
|
||
-- Comentários
|
||
-- =============================================================================
|
||
COMMENT ON TABLE public.dev_roadmap_phases IS 'Fases do roadmap (MVP, Paridade, Diferenciação). Visível só pra saas_admins.';
|
||
COMMENT ON TABLE public.dev_roadmap_items IS 'Itens de cada fase do roadmap.';
|
||
COMMENT ON TABLE public.dev_auditoria_items IS 'Bugs, dívidas técnicas e decisões arquiteturais.';
|
||
COMMENT ON TABLE public.dev_competitors IS 'Concorrentes analisados no benchmark.';
|
||
COMMENT ON TABLE public.dev_competitor_features IS 'Features catalogadas de cada concorrente.';
|
||
COMMENT ON TABLE public.dev_comparison_matrix IS 'Matriz de comparação AgenciaPsi × features esperadas do mercado.';
|
||
COMMENT ON TABLE public.dev_comparison_competitor_status IS 'Qual concorrente tem qual feature (ponte N-N com matrix).';
|
||
COMMENT ON TABLE public.dev_generation_log IS 'Histórico de execuções (backup, dashboard, export, seed, etc).';
|