243 lines
11 KiB
SQL
Executable file
243 lines
11 KiB
SQL
Executable file
-- 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 $$;
|