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