feat: Add initial database schemas and seeds
- Add .gitignore - Add schemas/001_initial_schema.sql - Add seeds/001_initial_data.sql Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7e218e8613
commit
1a2df4aaec
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Backups
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
*.dump
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
472
schemas/001_initial_schema.sql
Normal file
472
schemas/001_initial_schema.sql
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
-- ================================================================
|
||||||
|
-- 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';
|
||||||
274
seeds/001_initial_data.sql
Normal file
274
seeds/001_initial_data.sql
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
-- ================================================================
|
||||||
|
-- PMC Database Seeds - Initial Data
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- Date: 2025-12-08
|
||||||
|
-- ================================================================
|
||||||
|
|
||||||
|
-- ================================================================
|
||||||
|
-- TENANT PLANS
|
||||||
|
-- ================================================================
|
||||||
|
|
||||||
|
INSERT INTO auth.tenant_plans (id, name, code, description, price_monthly, price_yearly, max_users, max_clients, max_brands, max_generations_month, max_storage_bytes, max_custom_models, max_training_month, features, sort_order) VALUES
|
||||||
|
-- Starter (Free/Trial)
|
||||||
|
(
|
||||||
|
'a0000000-0000-0000-0000-000000000001',
|
||||||
|
'Starter',
|
||||||
|
'starter',
|
||||||
|
'Plan gratuito para empezar',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
3, -- max_users
|
||||||
|
5, -- max_clients
|
||||||
|
10, -- max_brands
|
||||||
|
100, -- max_generations_month
|
||||||
|
1073741824, -- max_storage_bytes (1GB)
|
||||||
|
0, -- max_custom_models
|
||||||
|
0, -- max_training_month
|
||||||
|
'{"analytics": false, "automation": false, "api_access": false, "priority_support": false}'::JSONB,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
-- Pro
|
||||||
|
(
|
||||||
|
'a0000000-0000-0000-0000-000000000002',
|
||||||
|
'Pro',
|
||||||
|
'pro',
|
||||||
|
'Para equipos en crecimiento',
|
||||||
|
49.00,
|
||||||
|
470.00,
|
||||||
|
10, -- max_users
|
||||||
|
25, -- max_clients
|
||||||
|
50, -- max_brands
|
||||||
|
1000, -- max_generations_month
|
||||||
|
10737418240, -- max_storage_bytes (10GB)
|
||||||
|
10, -- max_custom_models
|
||||||
|
5, -- max_training_month
|
||||||
|
'{"analytics": true, "automation": true, "api_access": false, "priority_support": false}'::JSONB,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
-- Business
|
||||||
|
(
|
||||||
|
'a0000000-0000-0000-0000-000000000003',
|
||||||
|
'Business',
|
||||||
|
'business',
|
||||||
|
'Para agencias y empresas',
|
||||||
|
149.00,
|
||||||
|
1430.00,
|
||||||
|
50, -- max_users
|
||||||
|
100, -- max_clients
|
||||||
|
200, -- max_brands
|
||||||
|
10000, -- max_generations_month
|
||||||
|
107374182400, -- max_storage_bytes (100GB)
|
||||||
|
50, -- max_custom_models
|
||||||
|
20, -- max_training_month
|
||||||
|
'{"analytics": true, "automation": true, "api_access": true, "priority_support": true}'::JSONB,
|
||||||
|
3
|
||||||
|
),
|
||||||
|
-- Enterprise
|
||||||
|
(
|
||||||
|
'a0000000-0000-0000-0000-000000000004',
|
||||||
|
'Enterprise',
|
||||||
|
'enterprise',
|
||||||
|
'Plan personalizado para grandes organizaciones',
|
||||||
|
0, -- Custom pricing
|
||||||
|
0,
|
||||||
|
-1, -- unlimited (represented as -1)
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
'{"analytics": true, "automation": true, "api_access": true, "priority_support": true, "sla": true, "custom_integrations": true}'::JSONB,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ================================================================
|
||||||
|
-- SYSTEM WORKFLOWS
|
||||||
|
-- ================================================================
|
||||||
|
|
||||||
|
INSERT INTO generation.workflows (id, name, code, description, type, input_schema, output_schema, estimated_time_seconds, is_active, is_system, tenant_id) VALUES
|
||||||
|
-- Product Photo Synthetic
|
||||||
|
(
|
||||||
|
'w0000000-0000-0000-0000-000000000001',
|
||||||
|
'Product Photo Synthetic',
|
||||||
|
'product_photo_synthetic',
|
||||||
|
'Genera fotos de producto en contexto comercial',
|
||||||
|
'image',
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["product_description"],
|
||||||
|
"properties": {
|
||||||
|
"product_description": {"type": "string", "description": "Descripcion del producto"},
|
||||||
|
"reference_image": {"type": "string", "format": "uri", "description": "URL imagen referencia"},
|
||||||
|
"background": {"type": "string", "enum": ["white", "lifestyle", "custom"], "default": "white"},
|
||||||
|
"style": {"type": "string", "enum": ["minimalist", "premium", "casual"], "default": "minimalist"},
|
||||||
|
"lora_id": {"type": "string", "format": "uuid", "description": "ID del LoRA de marca"},
|
||||||
|
"seed": {"type": "integer", "description": "Seed para reproducibilidad"}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"images": {"type": "array", "items": {"type": "string", "format": "uri"}},
|
||||||
|
"count": {"type": "integer"}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
60,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
-- Social Media Post
|
||||||
|
(
|
||||||
|
'w0000000-0000-0000-0000-000000000002',
|
||||||
|
'Social Media Post',
|
||||||
|
'social_media_post',
|
||||||
|
'Genera imagen + copy para redes sociales',
|
||||||
|
'composite',
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["brief", "brand_id"],
|
||||||
|
"properties": {
|
||||||
|
"brief": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"objetivo": {"type": "string"},
|
||||||
|
"audiencia": {"type": "string"},
|
||||||
|
"tono": {"type": "string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"product_id": {"type": "string", "format": "uuid"},
|
||||||
|
"channel": {"type": "string", "enum": ["instagram", "facebook", "linkedin", "tiktok"]},
|
||||||
|
"format": {"type": "string", "enum": ["post", "story", "carousel"]},
|
||||||
|
"brand_id": {"type": "string", "format": "uuid"}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"images": {"type": "array"},
|
||||||
|
"copy": {"type": "string"},
|
||||||
|
"hashtags": {"type": "array", "items": {"type": "string"}}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
90,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
-- Ad Variations
|
||||||
|
(
|
||||||
|
'w0000000-0000-0000-0000-000000000003',
|
||||||
|
'Ad Variations',
|
||||||
|
'ad_variations',
|
||||||
|
'Genera variaciones para A/B testing',
|
||||||
|
'image',
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["base_image"],
|
||||||
|
"properties": {
|
||||||
|
"base_image": {"type": "string", "description": "Asset ID o URL de imagen base"},
|
||||||
|
"variations_count": {"type": "integer", "minimum": 2, "maximum": 10, "default": 5},
|
||||||
|
"variation_type": {"type": "string", "enum": ["color", "background", "composition"]}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"variations": {"type": "array"},
|
||||||
|
"metadata": {"type": "object"}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
120,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
NULL
|
||||||
|
),
|
||||||
|
-- Virtual Avatar
|
||||||
|
(
|
||||||
|
'w0000000-0000-0000-0000-000000000004',
|
||||||
|
'Virtual Avatar',
|
||||||
|
'virtual_avatar',
|
||||||
|
'Genera avatar/influencer virtual consistente',
|
||||||
|
'image',
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["character_lora_id"],
|
||||||
|
"properties": {
|
||||||
|
"character_lora_id": {"type": "string", "format": "uuid"},
|
||||||
|
"pose": {"type": "string", "enum": ["standing", "sitting", "walking", "custom"]},
|
||||||
|
"outfit": {"type": "string"},
|
||||||
|
"background": {"type": "string"},
|
||||||
|
"expression": {"type": "string", "enum": ["happy", "serious", "surprised", "neutral"]}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
'{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"images": {"type": "array"},
|
||||||
|
"character_consistency_score": {"type": "number"}
|
||||||
|
}
|
||||||
|
}'::JSONB,
|
||||||
|
90,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ================================================================
|
||||||
|
-- DEMO TENANT (for development)
|
||||||
|
-- ================================================================
|
||||||
|
|
||||||
|
-- Only insert demo data in development
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
-- Demo Tenant
|
||||||
|
INSERT INTO auth.tenants (id, name, slug, plan_id, status, trial_ends_at) VALUES
|
||||||
|
(
|
||||||
|
'd0000000-0000-0000-0000-000000000001',
|
||||||
|
'Demo Agency',
|
||||||
|
'demo-agency',
|
||||||
|
'a0000000-0000-0000-0000-000000000002', -- Pro plan
|
||||||
|
'active',
|
||||||
|
(CURRENT_DATE + INTERVAL '30 days')::DATE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Demo User (password: demo123456)
|
||||||
|
INSERT INTO auth.users (id, tenant_id, email, password_hash, first_name, last_name, role, status) VALUES
|
||||||
|
(
|
||||||
|
'u0000000-0000-0000-0000-000000000001',
|
||||||
|
'd0000000-0000-0000-0000-000000000001',
|
||||||
|
'demo@pmc.dev',
|
||||||
|
'$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.E5F5u5V5V5V5V5V', -- bcrypt hash of 'demo123456'
|
||||||
|
'Demo',
|
||||||
|
'User',
|
||||||
|
'owner',
|
||||||
|
'active'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Demo Client
|
||||||
|
INSERT INTO crm.clients (id, tenant_id, name, slug, type, industry) VALUES
|
||||||
|
(
|
||||||
|
'c0000000-0000-0000-0000-000000000001',
|
||||||
|
'd0000000-0000-0000-0000-000000000001',
|
||||||
|
'Acme Corporation',
|
||||||
|
'acme-corp',
|
||||||
|
'company',
|
||||||
|
'Technology'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Demo Brand
|
||||||
|
INSERT INTO crm.brands (id, tenant_id, client_id, name, slug, primary_color, secondary_color) VALUES
|
||||||
|
(
|
||||||
|
'b0000000-0000-0000-0000-000000000001',
|
||||||
|
'd0000000-0000-0000-0000-000000000001',
|
||||||
|
'c0000000-0000-0000-0000-000000000001',
|
||||||
|
'Acme Brand',
|
||||||
|
'acme-brand',
|
||||||
|
'#3B82F6',
|
||||||
|
'#10B981'
|
||||||
|
);
|
||||||
|
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RAISE NOTICE 'Demo data already exists or error: %', SQLERRM;
|
||||||
|
END $$;
|
||||||
Loading…
Reference in New Issue
Block a user