schema-per-tenant: F0 categorizacao + F1 template/helpers + F2 provisionamento
- docs/F0_categorizacao.md: varredura completa (137 tabelas -> 84 tenant + 53 global, 66 funcoes, FKs, policies, edge functions) + decisoes Q1-Q4 - F1 (migrations 01-05): tenants.slug, helpers de schema, _tenant_template (84 tabelas sem tenant_id, singletons, views __SCHEMA__/__TENANT_ID__), clone_tenant_template/drop_tenant_schema, channel_routing, tenant_schemas - F2 (migration 06): provision_account_tenant/create_clinic_tenant/ ensure_personal_tenant_for_user clonam schema na mesma transacao - db.cjs: psqlFile agora usa ON_ERROR_STOP=1 (falha de migration nao passa mais como sucesso silencioso) - blueprint original em novo-rumo.txt; wiki Obsidian atualizada Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
-- =============================================================================
|
||||
-- F1.1 — Schema-per-tenant: coluna tenants.slug
|
||||
-- Plano: novo-rumo.txt + docs/F0_categorizacao.md (decisão Q1)
|
||||
-- Slug é a base do nome do schema físico: tenant_<slug>. Unico e imutável.
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE public.tenants ADD COLUMN IF NOT EXISTS slug text;
|
||||
|
||||
-- Geração de slug a partir do nome (sanitizado pra identificador Postgres)
|
||||
CREATE OR REPLACE FUNCTION public.generate_tenant_slug(p_name text)
|
||||
RETURNS text
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
SECURITY DEFINER
|
||||
SET search_path TO 'public', 'pg_temp'
|
||||
AS $$
|
||||
DECLARE
|
||||
base text;
|
||||
cand text;
|
||||
n int := 1;
|
||||
BEGIN
|
||||
base := lower(coalesce(nullif(trim(p_name), ''), 'tenant'));
|
||||
base := translate(base,
|
||||
'áàâãäåéèêëíìîïóòôõöúùûüçñýÿ',
|
||||
'aaaaaaeeeeiiiiooooouuuucnyy');
|
||||
base := regexp_replace(base, '[^a-z0-9]+', '_', 'g');
|
||||
base := regexp_replace(base, '^_+|_+$', '', 'g');
|
||||
base := left(base, 48);
|
||||
IF base = '' OR base !~ '^[a-z]' THEN
|
||||
base := 't_' || base;
|
||||
base := left(base, 48);
|
||||
END IF;
|
||||
cand := base;
|
||||
WHILE EXISTS (SELECT 1 FROM public.tenants WHERE slug = cand) LOOP
|
||||
n := n + 1;
|
||||
cand := left(base, 44) || '_' || n;
|
||||
END LOOP;
|
||||
RETURN cand;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Backfill dos tenants existentes
|
||||
DO $$
|
||||
DECLARE r record;
|
||||
BEGIN
|
||||
FOR r IN SELECT id, name FROM public.tenants WHERE slug IS NULL ORDER BY created_at, id LOOP
|
||||
UPDATE public.tenants SET slug = public.generate_tenant_slug(r.name) WHERE id = r.id;
|
||||
RAISE NOTICE 'tenant % -> slug %', r.id, (SELECT slug FROM public.tenants WHERE id = r.id);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE public.tenants ALTER COLUMN slug SET NOT NULL;
|
||||
ALTER TABLE public.tenants ADD CONSTRAINT tenants_slug_key UNIQUE (slug);
|
||||
ALTER TABLE public.tenants ADD CONSTRAINT tenants_slug_format CHECK (slug ~ '^[a-z][a-z0-9_]{1,47}$');
|
||||
|
||||
-- Auto-gera no INSERT (provisionamento atual não conhece slug); imutável no UPDATE
|
||||
CREATE OR REPLACE FUNCTION public.trg_tenants_slug()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path TO 'public', 'pg_temp'
|
||||
AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
IF NEW.slug IS NULL OR trim(NEW.slug) = '' THEN
|
||||
NEW.slug := public.generate_tenant_slug(NEW.name);
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
IF NEW.slug IS DISTINCT FROM OLD.slug THEN
|
||||
RAISE EXCEPTION 'tenants.slug é imutável (tenant %, % -> %)', OLD.id, OLD.slug, NEW.slug;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_tenants_slug_ins ON public.tenants;
|
||||
CREATE TRIGGER trg_tenants_slug_ins BEFORE INSERT ON public.tenants
|
||||
FOR EACH ROW EXECUTE FUNCTION public.trg_tenants_slug();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_tenants_slug_upd ON public.tenants;
|
||||
CREATE TRIGGER trg_tenants_slug_upd BEFORE UPDATE OF slug ON public.tenants
|
||||
FOR EACH ROW EXECUTE FUNCTION public.trg_tenants_slug();
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user