166 lines
5.3 KiB
PL/PgSQL
166 lines
5.3 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: tenants
|
|
-- TABLE: tenants, tenant_settings
|
|
-- DESCRIPTION: Sistema de Multi-Tenancy para organizaciones/empresas
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-10
|
|
-- ============================================================================
|
|
|
|
-- Crear schema si no existe
|
|
CREATE SCHEMA IF NOT EXISTS tenants;
|
|
|
|
-- Enum para estado de tenant
|
|
DO $$ BEGIN
|
|
CREATE TYPE tenants.tenant_status AS ENUM (
|
|
'pending', -- Pendiente de activacion
|
|
'active', -- Activo y operativo
|
|
'suspended', -- Suspendido (impago o violacion)
|
|
'deleted' -- Eliminado (soft delete)
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Enum para estado de subscription
|
|
DO $$ BEGIN
|
|
CREATE TYPE tenants.subscription_status AS ENUM (
|
|
'trialing', -- En periodo de prueba
|
|
'active', -- Activo con pagos al dia
|
|
'past_due', -- Pago vencido
|
|
'canceled', -- Cancelado
|
|
'incomplete' -- Configuracion incompleta
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Tabla principal de Tenants (Organizaciones)
|
|
CREATE TABLE IF NOT EXISTS tenants.tenants (
|
|
-- Identificadores
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Informacion basica
|
|
name VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(100) NOT NULL UNIQUE,
|
|
domain VARCHAR(255),
|
|
logo_url TEXT,
|
|
|
|
-- Estado
|
|
status tenants.tenant_status NOT NULL DEFAULT 'pending',
|
|
|
|
-- Subscription info (referencia a billing)
|
|
plan_id UUID, -- FK a plans.plans (se agrega despues)
|
|
subscription_status tenants.subscription_status DEFAULT 'trialing',
|
|
trial_ends_at TIMESTAMPTZ DEFAULT (NOW() + INTERVAL '14 days'),
|
|
|
|
-- Stripe integration
|
|
stripe_customer_id VARCHAR(255),
|
|
stripe_subscription_id VARCHAR(255),
|
|
|
|
-- Settings (JSONB flexible)
|
|
settings JSONB NOT NULL DEFAULT '{
|
|
"timezone": "America/Mexico_City",
|
|
"locale": "es-MX",
|
|
"currency": "MXN",
|
|
"date_format": "DD/MM/YYYY"
|
|
}'::JSONB,
|
|
|
|
-- Metadata adicional
|
|
metadata JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Soft delete
|
|
deleted_at TIMESTAMPTZ,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
COMMENT ON TABLE tenants.tenants IS
|
|
'Organizaciones/empresas que usan la plataforma. Base del sistema multi-tenant.';
|
|
|
|
COMMENT ON COLUMN tenants.tenants.slug IS
|
|
'Identificador URL-safe unico para el tenant (ej: acme-corp)';
|
|
|
|
COMMENT ON COLUMN tenants.tenants.settings IS
|
|
'Configuracion del tenant: timezone, locale, currency, etc.';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_tenants_slug
|
|
ON tenants.tenants(slug);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tenants_status
|
|
ON tenants.tenants(status) WHERE deleted_at IS NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tenants_stripe_customer
|
|
ON tenants.tenants(stripe_customer_id) WHERE stripe_customer_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tenants_subscription_status
|
|
ON tenants.tenants(subscription_status);
|
|
|
|
-- Trigger para updated_at
|
|
CREATE OR REPLACE FUNCTION tenants.update_tenant_timestamp()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS tenant_updated_at ON tenants.tenants;
|
|
CREATE TRIGGER tenant_updated_at
|
|
BEFORE UPDATE ON tenants.tenants
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION tenants.update_tenant_timestamp();
|
|
|
|
-- ============================================================================
|
|
-- TABLA: tenant_settings (configuracion extendida)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS tenants.tenant_settings (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Configuracion por categoria
|
|
category VARCHAR(50) NOT NULL, -- 'branding', 'notifications', 'security', etc.
|
|
key VARCHAR(100) NOT NULL,
|
|
value JSONB NOT NULL,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Unique por tenant + category + key
|
|
UNIQUE (tenant_id, category, key)
|
|
);
|
|
|
|
COMMENT ON TABLE tenants.tenant_settings IS
|
|
'Configuraciones extendidas por tenant, organizadas por categoria';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_tenant_settings_tenant
|
|
ON tenants.tenant_settings(tenant_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tenant_settings_category
|
|
ON tenants.tenant_settings(tenant_id, category);
|
|
|
|
-- Trigger para updated_at
|
|
DROP TRIGGER IF EXISTS tenant_settings_updated_at ON tenants.tenant_settings;
|
|
CREATE TRIGGER tenant_settings_updated_at
|
|
BEFORE UPDATE ON tenants.tenant_settings
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION tenants.update_tenant_timestamp();
|
|
|
|
-- RLS Policy (tenant_settings aislado por tenant)
|
|
ALTER TABLE tenants.tenant_settings ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_settings_isolation ON tenants.tenant_settings
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE ON tenants.tenants TO trading_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON tenants.tenant_settings TO trading_app;
|
|
GRANT SELECT ON tenants.tenants TO trading_readonly;
|
|
GRANT SELECT ON tenants.tenant_settings TO trading_readonly;
|