platform-marketing-content/database/schemas/001_initial_schema.sql

473 lines
17 KiB
PL/PgSQL

-- ================================================================
-- PMC Database Schema - Initial Setup
-- Version: 1.0.0
-- Date: 2025-12-08
-- ================================================================
-- Extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ================================================================
-- SCHEMAS
-- ================================================================
-- Auth schema: usuarios, tenants, sesiones
CREATE SCHEMA IF NOT EXISTS auth;
-- CRM schema: clientes, marcas, productos
CREATE SCHEMA IF NOT EXISTS crm;
-- Projects schema: proyectos, campanas, briefs
CREATE SCHEMA IF NOT EXISTS projects;
-- Generation schema: jobs, workflows, modelos
CREATE SCHEMA IF NOT EXISTS generation;
-- Assets schema: biblioteca de archivos
CREATE SCHEMA IF NOT EXISTS assets;
-- ================================================================
-- AUTH SCHEMA
-- ================================================================
-- Planes de tenant
CREATE TABLE auth.tenant_plans (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
code VARCHAR(50) NOT NULL UNIQUE,
description TEXT,
price_monthly DECIMAL(10,2) DEFAULT 0,
price_yearly DECIMAL(10,2) DEFAULT 0,
max_users INT DEFAULT 5,
max_clients INT DEFAULT 10,
max_brands INT DEFAULT 20,
max_generations_month INT DEFAULT 100,
max_storage_bytes BIGINT DEFAULT 5368709120, -- 5GB
max_custom_models INT DEFAULT 0,
max_training_month INT DEFAULT 0,
features JSONB,
is_active BOOLEAN DEFAULT true,
sort_order INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Tenants (organizaciones)
CREATE TABLE auth.tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
plan_id UUID NOT NULL REFERENCES auth.tenant_plans(id),
status VARCHAR(20) DEFAULT 'trial' CHECK (status IN ('trial', 'active', 'suspended', 'cancelled')),
logo_url VARCHAR(500),
settings JSONB,
trial_ends_at DATE,
billing_cycle_start DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_tenants_slug ON auth.tenants(slug);
CREATE INDEX idx_tenants_status ON auth.tenants(status);
-- Usuarios
CREATE TABLE auth.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
avatar_url VARCHAR(500),
role VARCHAR(20) DEFAULT 'viewer' CHECK (role IN ('owner', 'admin', 'creative', 'analyst', 'viewer', 'client_portal')),
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'suspended')),
last_login_at TIMESTAMPTZ,
preferences JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, email)
);
CREATE INDEX idx_users_tenant ON auth.users(tenant_id);
CREATE INDEX idx_users_email ON auth.users(email);
CREATE INDEX idx_users_status ON auth.users(status);
-- Sesiones
CREATE TABLE auth.sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
refresh_token_hash VARCHAR(500) NOT NULL,
device_info VARCHAR(100),
ip_address VARCHAR(50),
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
last_used_at TIMESTAMPTZ,
is_active BOOLEAN DEFAULT true
);
CREATE INDEX idx_sessions_user ON auth.sessions(user_id);
CREATE INDEX idx_sessions_expires ON auth.sessions(expires_at);
-- Invitaciones
CREATE TABLE auth.invitations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL,
token VARCHAR(100) NOT NULL UNIQUE,
invited_by UUID REFERENCES auth.users(id),
expires_at TIMESTAMPTZ NOT NULL,
accepted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_invitations_token ON auth.invitations(token);
CREATE INDEX idx_invitations_tenant ON auth.invitations(tenant_id);
-- ================================================================
-- CRM SCHEMA
-- ================================================================
-- Clientes
CREATE TABLE crm.clients (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
type VARCHAR(20) DEFAULT 'company' CHECK (type IN ('company', 'individual')),
industry VARCHAR(100),
website VARCHAR(500),
logo_url VARCHAR(500),
contact_name VARCHAR(200),
contact_email VARCHAR(255),
contact_phone VARCHAR(50),
notes TEXT,
is_active BOOLEAN DEFAULT true,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, slug)
);
CREATE INDEX idx_clients_tenant ON crm.clients(tenant_id);
CREATE INDEX idx_clients_slug ON crm.clients(tenant_id, slug);
-- Marcas
CREATE TABLE crm.brands (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
client_id UUID NOT NULL REFERENCES crm.clients(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
description TEXT,
logo_url VARCHAR(500),
primary_color VARCHAR(7),
secondary_color VARCHAR(7),
brand_voice TEXT,
target_audience TEXT,
guidelines_url VARCHAR(500),
is_active BOOLEAN DEFAULT true,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, slug)
);
CREATE INDEX idx_brands_tenant ON crm.brands(tenant_id);
CREATE INDEX idx_brands_client ON crm.brands(client_id);
-- Productos
CREATE TABLE crm.products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
brand_id UUID NOT NULL REFERENCES crm.brands(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
description TEXT,
sku VARCHAR(100),
category VARCHAR(100),
price DECIMAL(10,2),
currency VARCHAR(3) DEFAULT 'USD',
image_urls JSONB, -- Array of image URLs
attributes JSONB, -- Custom attributes
is_active BOOLEAN DEFAULT true,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, slug)
);
CREATE INDEX idx_products_tenant ON crm.products(tenant_id);
CREATE INDEX idx_products_brand ON crm.products(brand_id);
-- ================================================================
-- PROJECTS SCHEMA
-- ================================================================
-- Proyectos
CREATE TABLE projects.projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
client_id UUID REFERENCES crm.clients(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
description TEXT,
status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'paused', 'completed', 'archived')),
start_date DATE,
end_date DATE,
budget DECIMAL(12,2),
currency VARCHAR(3) DEFAULT 'USD',
metadata JSONB,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, slug)
);
CREATE INDEX idx_projects_tenant ON projects.projects(tenant_id);
CREATE INDEX idx_projects_client ON projects.projects(client_id);
CREATE INDEX idx_projects_status ON projects.projects(status);
-- Campanas
CREATE TABLE projects.campaigns (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE,
brand_id UUID REFERENCES crm.brands(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
description TEXT,
objective TEXT,
target_audience TEXT,
channels JSONB, -- Array of channels: instagram, facebook, etc
status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('draft', 'planning', 'in_progress', 'review', 'approved', 'published')),
start_date DATE,
end_date DATE,
brief JSONB, -- Campaign brief details
metadata JSONB,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, slug)
);
CREATE INDEX idx_campaigns_tenant ON projects.campaigns(tenant_id);
CREATE INDEX idx_campaigns_project ON projects.campaigns(project_id);
CREATE INDEX idx_campaigns_brand ON projects.campaigns(brand_id);
-- ================================================================
-- GENERATION SCHEMA
-- ================================================================
-- Workflows de generacion
CREATE TABLE generation.workflows (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
code VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
type VARCHAR(50) NOT NULL, -- image, text, composite
input_schema JSONB NOT NULL, -- JSON Schema for inputs
output_schema JSONB, -- Expected outputs
comfyui_template JSONB, -- ComfyUI workflow JSON
estimated_time_seconds INT DEFAULT 60,
is_active BOOLEAN DEFAULT true,
is_system BOOLEAN DEFAULT true, -- System vs custom workflow
tenant_id UUID REFERENCES auth.tenants(id) ON DELETE CASCADE, -- NULL for system workflows
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_workflows_code ON generation.workflows(code);
CREATE INDEX idx_workflows_tenant ON generation.workflows(tenant_id);
-- Jobs de generacion
CREATE TABLE generation.jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
workflow_id UUID NOT NULL REFERENCES generation.workflows(id),
campaign_id UUID REFERENCES projects.campaigns(id) ON DELETE SET NULL,
brand_id UUID REFERENCES crm.brands(id) ON DELETE SET NULL,
status VARCHAR(20) DEFAULT 'queued' CHECK (status IN ('queued', 'processing', 'completed', 'failed', 'cancelled')),
priority INT DEFAULT 0,
input_params JSONB NOT NULL,
output_data JSONB, -- Results after completion
error_message TEXT,
progress INT DEFAULT 0, -- 0-100
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
processing_time_ms INT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_jobs_tenant ON generation.jobs(tenant_id);
CREATE INDEX idx_jobs_user ON generation.jobs(user_id);
CREATE INDEX idx_jobs_status ON generation.jobs(status);
CREATE INDEX idx_jobs_created ON generation.jobs(created_at DESC);
-- Modelos personalizados (LoRAs)
CREATE TABLE generation.custom_models (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
brand_id UUID REFERENCES crm.brands(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL, -- lora, checkpoint, embedding
file_path VARCHAR(500) NOT NULL,
file_size_bytes BIGINT,
trigger_word VARCHAR(100),
training_images_count INT,
preview_images JSONB, -- Array of preview image URLs
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'training', 'ready', 'failed')),
training_config JSONB,
metadata JSONB,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_custom_models_tenant ON generation.custom_models(tenant_id);
CREATE INDEX idx_custom_models_brand ON generation.custom_models(brand_id);
-- ================================================================
-- ASSETS SCHEMA
-- ================================================================
-- Colecciones
CREATE TABLE assets.collections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL,
description TEXT,
is_public BOOLEAN DEFAULT false,
parent_id UUID REFERENCES assets.collections(id) ON DELETE SET NULL,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, slug)
);
CREATE INDEX idx_collections_tenant ON assets.collections(tenant_id);
CREATE INDEX idx_collections_parent ON assets.collections(parent_id);
-- Assets (archivos)
CREATE TABLE assets.assets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
collection_id UUID REFERENCES assets.collections(id) ON DELETE SET NULL,
job_id UUID REFERENCES generation.jobs(id) ON DELETE SET NULL, -- Si fue generado
name VARCHAR(255) NOT NULL,
original_filename VARCHAR(255),
mime_type VARCHAR(100) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size_bytes BIGINT NOT NULL,
width INT,
height INT,
duration_seconds DECIMAL(10,2), -- For video/audio
thumbnail_url VARCHAR(500),
metadata JSONB, -- EXIF, AI generation params, etc
tags JSONB, -- Array of tags
is_public BOOLEAN DEFAULT false,
version INT DEFAULT 1,
parent_id UUID REFERENCES assets.assets(id) ON DELETE SET NULL, -- For versions
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_assets_tenant ON assets.assets(tenant_id);
CREATE INDEX idx_assets_collection ON assets.assets(collection_id);
CREATE INDEX idx_assets_job ON assets.assets(job_id);
CREATE INDEX idx_assets_mime ON assets.assets(mime_type);
-- ================================================================
-- ROW LEVEL SECURITY (RLS)
-- ================================================================
-- Enable RLS on all tenant-scoped tables
ALTER TABLE auth.tenants ENABLE ROW LEVEL SECURITY;
ALTER TABLE auth.users ENABLE ROW LEVEL SECURITY;
ALTER TABLE auth.sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE auth.invitations ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.clients ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.brands ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.products ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects.projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects.campaigns ENABLE ROW LEVEL SECURITY;
ALTER TABLE generation.jobs ENABLE ROW LEVEL SECURITY;
ALTER TABLE generation.custom_models ENABLE ROW LEVEL SECURITY;
ALTER TABLE assets.collections ENABLE ROW LEVEL SECURITY;
ALTER TABLE assets.assets ENABLE ROW LEVEL SECURITY;
-- Function to get current tenant from session variable
CREATE OR REPLACE FUNCTION auth.current_tenant_id()
RETURNS UUID AS $$
BEGIN
RETURN NULLIF(current_setting('app.current_tenant', true), '')::UUID;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- RLS Policies (example for clients, apply similar to all tables)
CREATE POLICY tenant_isolation_policy ON crm.clients
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON crm.brands
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON crm.products
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON projects.projects
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON projects.campaigns
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON generation.jobs
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON generation.custom_models
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON assets.collections
FOR ALL
USING (tenant_id = auth.current_tenant_id());
CREATE POLICY tenant_isolation_policy ON assets.assets
FOR ALL
USING (tenant_id = auth.current_tenant_id());
-- ================================================================
-- COMMENTS
-- ================================================================
COMMENT ON SCHEMA auth IS 'Authentication and authorization: users, tenants, sessions';
COMMENT ON SCHEMA crm IS 'Customer relationship management: clients, brands, products';
COMMENT ON SCHEMA projects IS 'Project management: projects, campaigns';
COMMENT ON SCHEMA generation IS 'AI generation: workflows, jobs, models';
COMMENT ON SCHEMA assets IS 'Digital asset management: files, collections';
COMMENT ON TABLE auth.tenants IS 'Organizations/companies using the platform';
COMMENT ON TABLE auth.users IS 'Platform users, scoped to tenants';
COMMENT ON TABLE generation.workflows IS 'AI generation workflow definitions';
COMMENT ON TABLE generation.jobs IS 'AI generation job queue and history';