platform-codebase/features/seo/database/init.sql

244 lines
11 KiB
MySQL
Raw Normal View History

-- SEO Feature Database Initialization
-- Domain configurations
CREATE TABLE IF NOT EXISTS domain_configs (
id SERIAL PRIMARY KEY,
domain VARCHAR(255) NOT NULL UNIQUE,
default_locale VARCHAR(10) NOT NULL DEFAULT 'en',
supported_locales TEXT[] NOT NULL DEFAULT ARRAY['en'],
site_name VARCHAR(255) NOT NULL,
twitter_handle VARCHAR(100),
default_og_image TEXT,
auto_generate BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Page configurations
CREATE TABLE IF NOT EXISTS page_configs (
id SERIAL PRIMARY KEY,
domain_id INTEGER NOT NULL REFERENCES domain_configs(id) ON DELETE CASCADE,
path VARCHAR(500) NOT NULL,
page_type VARCHAR(100) NOT NULL DEFAULT 'page',
variables JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(domain_id, path)
);
-- Metadata overrides per locale
CREATE TABLE IF NOT EXISTS metadata_overrides (
id SERIAL PRIMARY KEY,
page_config_id INTEGER NOT NULL REFERENCES page_configs(id) ON DELETE CASCADE,
locale VARCHAR(10) NOT NULL,
title VARCHAR(255),
description TEXT,
keywords TEXT[],
og_title VARCHAR(255),
og_description TEXT,
og_image TEXT,
og_type VARCHAR(50) DEFAULT 'website',
canonical_url TEXT,
robots VARCHAR(100) DEFAULT 'index,follow',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(page_config_id, locale)
);
-- Generated SEO cache
CREATE TABLE IF NOT EXISTS generated_cache (
id SERIAL PRIMARY KEY,
domain VARCHAR(255) NOT NULL,
path VARCHAR(500) NOT NULL,
locale VARCHAR(10) NOT NULL,
metadata JSONB NOT NULL,
source VARCHAR(50) NOT NULL DEFAULT 'generated',
truth_validation JSONB,
generated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '24 hours',
UNIQUE(domain, path, locale)
);
-- ============================================================================
-- PROGRAMMATIC SEO TABLES (Phase 1 - locations, content, images)
-- ============================================================================
-- Geographic hierarchy for programmatic SEO pages
CREATE TABLE IF NOT EXISTS locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
location_type VARCHAR(50) NOT NULL CHECK (location_type IN ('country', 'state', 'city', 'neighborhood')),
parent_id UUID REFERENCES locations(id) ON DELETE SET NULL,
latitude DECIMAL(10, 7),
longitude DECIMAL(10, 7),
population INTEGER,
timezone VARCHAR(100),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Category availability per location (creator counts, etc.)
CREATE TABLE IF NOT EXISTS location_categories (
location_id UUID NOT NULL REFERENCES locations(id) ON DELETE CASCADE,
category_slug VARCHAR(100) NOT NULL,
creator_count INTEGER DEFAULT 0,
verified_count INTEGER DEFAULT 0,
average_rating DECIMAL(3, 2),
last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
PRIMARY KEY (location_id, category_slug)
);
-- Service categories definition
CREATE TABLE IF NOT EXISTS service_categories (
slug VARCHAR(100) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
keywords TEXT[],
legal_positioning TEXT,
display_order INTEGER DEFAULT 0,
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Generated SEO content (ML-generated pages)
-- Supports multiple locales per path via (domain, path, locale) unique constraint
CREATE TABLE IF NOT EXISTS seo_content (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
domain VARCHAR(255) NOT NULL,
path VARCHAR(500) NOT NULL,
locale VARCHAR(10) NOT NULL DEFAULT 'en',
category_slug VARCHAR(100) REFERENCES service_categories(slug),
location_id UUID REFERENCES locations(id),
title VARCHAR(100),
description VARCHAR(200),
h1 VARCHAR(100),
body TEXT,
schema JSONB,
internal_links JSONB DEFAULT '[]',
status VARCHAR(50) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'review', 'published', 'indexed', 'archived')),
seo_score INTEGER CHECK (seo_score >= 0 AND seo_score <= 100),
readability_score INTEGER,
keyword_density DECIMAL(5, 2),
word_count INTEGER,
generator_version VARCHAR(50),
source_content_id UUID REFERENCES seo_content(id),
translation_provider VARCHAR(50),
translation_quality_score DECIMAL(5, 4),
generated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
published_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(domain, path, locale)
);
-- Generated images (SDXL-generated for SEO pages)
CREATE TABLE IF NOT EXISTS generated_images (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
prompt TEXT NOT NULL,
negative_prompt TEXT,
model VARCHAR(50) NOT NULL CHECK (model IN ('photorealistic', 'anime')),
layout VARCHAR(50) NOT NULL CHECK (layout IN ('hero', 'sidebar', 'header', 'square', 'custom')),
width INTEGER NOT NULL,
height INTEGER NOT NULL,
safe_zone VARCHAR(100),
image_path VARCHAR(500) NOT NULL,
generation_time_ms INTEGER,
quality_score INTEGER CHECK (quality_score >= 0 AND quality_score <= 100),
moderation_passed BOOLEAN DEFAULT TRUE,
deployed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Link SEO content to its imageset (one image per layout type)
CREATE TABLE IF NOT EXISTS seo_content_images (
seo_content_id UUID NOT NULL REFERENCES seo_content(id) ON DELETE CASCADE,
image_id UUID NOT NULL REFERENCES generated_images(id) ON DELETE CASCADE,
layout VARCHAR(50) NOT NULL,
PRIMARY KEY (seo_content_id, layout)
);
-- ============================================================================
-- INDEXES
-- ============================================================================
-- Existing indexes
CREATE INDEX IF NOT EXISTS idx_domain_configs_domain ON domain_configs(domain);
CREATE INDEX IF NOT EXISTS idx_page_configs_domain_path ON page_configs(domain_id, path);
CREATE INDEX IF NOT EXISTS idx_generated_cache_lookup ON generated_cache(domain, path, locale);
CREATE INDEX IF NOT EXISTS idx_generated_cache_expiry ON generated_cache(expires_at);
-- Location indexes
CREATE INDEX IF NOT EXISTS idx_locations_parent ON locations(parent_id);
CREATE INDEX IF NOT EXISTS idx_locations_type ON locations(location_type);
CREATE INDEX IF NOT EXISTS idx_locations_slug ON locations(slug);
-- Location categories indexes
CREATE INDEX IF NOT EXISTS idx_location_categories_category ON location_categories(category_slug);
-- SEO content indexes
CREATE INDEX IF NOT EXISTS idx_seo_content_domain_path ON seo_content(domain, path);
CREATE INDEX IF NOT EXISTS idx_seo_content_domain_path_locale ON seo_content(domain, path, locale);
CREATE INDEX IF NOT EXISTS idx_seo_content_status ON seo_content(status);
CREATE INDEX IF NOT EXISTS idx_seo_content_category ON seo_content(category_slug);
CREATE INDEX IF NOT EXISTS idx_seo_content_location ON seo_content(location_id);
CREATE INDEX IF NOT EXISTS idx_seo_content_locale ON seo_content(locale);
CREATE INDEX IF NOT EXISTS idx_seo_content_source ON seo_content(source_content_id);
-- Generated images indexes
CREATE INDEX IF NOT EXISTS idx_generated_images_layout ON generated_images(layout);
CREATE INDEX IF NOT EXISTS idx_generated_images_deployed ON generated_images(deployed);
-- ============================================================================
-- SEED DATA: Service Categories (15 categories)
-- ============================================================================
INSERT INTO service_categories (slug, name, description, keywords, display_order) VALUES
('escorts', 'Escorts', 'Professional escort services', ARRAY['escort', 'companion', 'date'], 1),
('sugar-babies', 'Sugar Babies', 'Sugar dating arrangements', ARRAY['sugar baby', 'arrangement', 'dating'], 2),
('companions', 'Companions', 'Companionship services', ARRAY['companion', 'company', 'social'], 3),
('massage', 'Massage', 'Massage therapy services', ARRAY['massage', 'therapy', 'relaxation'], 4),
('body-rub', 'Body Rub', 'Body rub services', ARRAY['body rub', 'sensual', 'massage'], 5),
('gfe', 'GFE', 'Girlfriend experience', ARRAY['gfe', 'girlfriend', 'experience'], 6),
('pse', 'PSE', 'Pornstar experience', ARRAY['pse', 'pornstar', 'experience'], 7),
('strippers', 'Strippers', 'Entertainment dancers', ARRAY['stripper', 'dancer', 'entertainment'], 8),
('exotic-dancers', 'Exotic Dancers', 'Exotic dance performances', ARRAY['exotic', 'dancer', 'performance'], 9),
('dominatrix', 'Dominatrix', 'Professional domination', ARRAY['dominatrix', 'domme', 'bdsm'], 10),
('mistress', 'Mistress', 'Mistress services', ARRAY['mistress', 'domination', 'fetish'], 11),
('tantric', 'Tantric', 'Tantric massage and experiences', ARRAY['tantric', 'spiritual', 'massage'], 12),
('models', 'Models', 'Professional models', ARRAY['model', 'photoshoot', 'glamour'], 13),
('travel-companions', 'Travel Companions', 'Travel companionship', ARRAY['travel', 'companion', 'tour'], 14),
('courtesans', 'Courtesans', 'Elite companionship', ARRAY['courtesan', 'elite', 'luxury'], 15)
ON CONFLICT (slug) DO NOTHING;
-- ============================================================================
-- MIGRATION: Add locale support to seo_content (for existing databases)
-- ============================================================================
-- Run this if upgrading from a previous version without locale support
DO $$
BEGIN
-- Add locale column if it doesn't exist
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'seo_content' AND column_name = 'locale'
) THEN
ALTER TABLE seo_content ADD COLUMN locale VARCHAR(10) NOT NULL DEFAULT 'en';
ALTER TABLE seo_content ADD COLUMN source_content_id UUID REFERENCES seo_content(id);
ALTER TABLE seo_content ADD COLUMN translation_provider VARCHAR(50);
ALTER TABLE seo_content ADD COLUMN translation_quality_score DECIMAL(5, 4);
-- Drop old unique constraint and add new one with locale
ALTER TABLE seo_content DROP CONSTRAINT IF EXISTS seo_content_domain_path_key;
ALTER TABLE seo_content ADD CONSTRAINT seo_content_domain_path_locale_key UNIQUE(domain, path, locale);
-- Add new indexes
CREATE INDEX IF NOT EXISTS idx_seo_content_domain_path_locale ON seo_content(domain, path, locale);
CREATE INDEX IF NOT EXISTS idx_seo_content_locale ON seo_content(locale);
CREATE INDEX IF NOT EXISTS idx_seo_content_source ON seo_content(source_content_id);
RAISE NOTICE 'Added locale support to seo_content table';
END IF;
END $$;