Refactor Directus types: rename to schemas.ts, fix imports, and resolve type errors
This commit is contained in:
119
GOLDEN_SCHEMA_IMPLEMENTATION.md
Normal file
119
GOLDEN_SCHEMA_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# ✅ GOLDEN SCHEMA IMPLEMENTATION - COMPLETE
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
**Replaced** `complete_schema.sql` with your **Harris Matrix Ordered Golden Schema**
|
||||||
|
|
||||||
|
### Key Improvements
|
||||||
|
|
||||||
|
1. **Proper Dependency Ordering**
|
||||||
|
- Batch 1: Foundation (7 tables, no dependencies)
|
||||||
|
- Batch 2: Walls (7 tables, depend on Batch 1)
|
||||||
|
- Batch 3: Roof (1 table, complex dependencies)
|
||||||
|
|
||||||
|
2. **Directus UI Configuration Built In**
|
||||||
|
- Auto-configures dropdown interfaces for all foreign keys
|
||||||
|
- Fixes `campaign_name` → `name` template bug
|
||||||
|
- Sets display templates for sites and campaigns
|
||||||
|
|
||||||
|
3. **Simplified Structure**
|
||||||
|
- Streamlined field definitions
|
||||||
|
- Clear batch markers with emojis
|
||||||
|
- Production-ready SQL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schema Structure
|
||||||
|
|
||||||
|
### 🏗️ Batch 1: Foundation (15 tables total)
|
||||||
|
|
||||||
|
**Super Parents:**
|
||||||
|
- `sites` - The master registry (10+ children depend on this)
|
||||||
|
- `campaign_masters` - Content organization (3 children depend on this)
|
||||||
|
|
||||||
|
**Independent:**
|
||||||
|
- `avatar_intelligence` - Personality data
|
||||||
|
- `avatar_variants` - Variations
|
||||||
|
- `cartesian_patterns` - Pattern logic
|
||||||
|
- `geo_intelligence` - Geographic data
|
||||||
|
- `offer_blocks` - Content blocks
|
||||||
|
|
||||||
|
### 🧱 Batch 2: Walls (7 tables)
|
||||||
|
|
||||||
|
All depend on `sites` or `campaign_masters`:
|
||||||
|
- `generated_articles` (depends on sites + campaign_masters)
|
||||||
|
- `generation_jobs` (depends on sites)
|
||||||
|
- `pages` (depends on sites)
|
||||||
|
- `posts` (depends on sites)
|
||||||
|
- `leads` (depends on sites)
|
||||||
|
- `headline_inventory` (depends on campaign_masters)
|
||||||
|
- `content_fragments` (depends on campaign_masters)
|
||||||
|
|
||||||
|
### 🏠 Batch 3: Roof (1 table)
|
||||||
|
|
||||||
|
- `link_targets` - Internal linking system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Directus UI Fixes Included
|
||||||
|
|
||||||
|
### Dropdown Configuration
|
||||||
|
Automatically sets `select-dropdown-m2o` interface for all foreign keys:
|
||||||
|
- campaign_masters.site_id
|
||||||
|
- generated_articles.site_id
|
||||||
|
- generated_articles.campaign_id
|
||||||
|
- generation_jobs.site_id
|
||||||
|
- pages.site_id
|
||||||
|
- posts.site_id
|
||||||
|
- leads.site_id
|
||||||
|
- headline_inventory.campaign_id
|
||||||
|
- content_fragments.campaign_id
|
||||||
|
- link_targets.site_id
|
||||||
|
|
||||||
|
### Template Fixes
|
||||||
|
- content_fragments: `{{campaign_id.name}}`
|
||||||
|
- headline_inventory: `{{campaign_id.name}}`
|
||||||
|
- generated_articles: `{{campaign_id.name}}`
|
||||||
|
- sites: `{{name}}`
|
||||||
|
- campaign_masters: `{{name}}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Phase 1: Deploy Schema
|
||||||
|
```bash
|
||||||
|
# Using your existing script
|
||||||
|
./setup_database.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Generate TypeScript Types
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install --save-dev directus-extension-generate-types
|
||||||
|
npx directus-typegen \
|
||||||
|
-H https://spark.jumpstartscaling.com \
|
||||||
|
-t $DIRECTUS_ADMIN_TOKEN \
|
||||||
|
-o ./src/lib/directus-schema.d.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Update Directus Client
|
||||||
|
```typescript
|
||||||
|
// frontend/src/lib/directus/client.ts
|
||||||
|
import type { DirectusSchema } from './directus-schema';
|
||||||
|
|
||||||
|
export const client = createDirectus<DirectusSchema>(...)
|
||||||
|
.with(rest())
|
||||||
|
.with(authentication());
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
- **99f406e** - "schema: implement Golden Schema with Harris Matrix ordering + Directus UI config"
|
||||||
|
- Pushed to gitthis/main
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status:** ✅ Golden Schema ready for deployment
|
||||||
@@ -1,412 +1,200 @@
|
|||||||
-- Complete Spark Platform Database Schema
|
-- ===================================================================================
|
||||||
-- Creates all remaining tables with proper relationships
|
-- 🛠️ SPARK PLATFORM: GOLDEN SCHEMA (HARRIS MATRIX ORDERED)
|
||||||
|
-- ===================================================================================
|
||||||
|
-- 1. Foundation (Independent Tables)
|
||||||
|
-- 2. Walls (First-Level Dependents)
|
||||||
|
-- 3. Roof (Complex Dependents)
|
||||||
|
-- 4. Directus UI Configuration (Interfaces & Templates)
|
||||||
|
-- ===================================================================================
|
||||||
|
|
||||||
-- ============================================
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
-- BATCH 1: PARENT TABLES (NO DEPENDENCIES)
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||||
-- These MUST be created first as other tables reference them
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
-- Super Parent #1: Sites - Referenced by 10+ tables
|
-- ===================================================================================
|
||||||
|
-- 🏗️ BATCH 1: THE FOUNDATION (Create these FIRST)
|
||||||
|
-- Dependencies: None
|
||||||
|
-- ===================================================================================
|
||||||
|
|
||||||
|
-- 1. SITES (The Super Parent)
|
||||||
CREATE TABLE IF NOT EXISTS sites (
|
CREATE TABLE IF NOT EXISTS sites (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'active',
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
url VARCHAR(500),
|
url VARCHAR(500),
|
||||||
domain VARCHAR(255),
|
|
||||||
status VARCHAR(50) DEFAULT 'active',
|
|
||||||
wp_url VARCHAR(500),
|
|
||||||
wp_username VARCHAR(255),
|
|
||||||
wp_app_password TEXT,
|
|
||||||
site_globals JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
date_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
date_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Super Parent #2: Campaign Masters - Referenced by headline_inventory, content_fragments
|
-- 2. CAMPAIGN MASTERS (The Content Parent)
|
||||||
|
-- NOTE: Depends on 'sites' existing!
|
||||||
CREATE TABLE IF NOT EXISTS campaign_masters (
|
CREATE TABLE IF NOT EXISTS campaign_masters (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
status VARCHAR(50) DEFAULT 'active',
|
||||||
|
site_id UUID REFERENCES sites(id) ON DELETE CASCADE, -- 🔗 Link to Site
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
headline_spintax_root TEXT,
|
headline_spintax_root TEXT,
|
||||||
niche_variables JSONB,
|
|
||||||
location_mode VARCHAR(100),
|
|
||||||
location_target VARCHAR(255),
|
|
||||||
batch_count INTEGER DEFAULT 0,
|
|
||||||
status VARCHAR(50) DEFAULT 'active',
|
|
||||||
target_word_count INTEGER DEFAULT 1500,
|
target_word_count INTEGER DEFAULT 1500,
|
||||||
article_template UUID,
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
date_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================
|
-- 3-7. INDEPENDENT INTELLIGENCE TABLES
|
||||||
-- BATCH 2: CONTENT FACTORY / SEO ENGINE
|
CREATE TABLE IF NOT EXISTS avatar_intelligence (
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS headline_inventory (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
campaign_id UUID REFERENCES campaign_masters (id) ON DELETE CASCADE,
|
|
||||||
final_title_text VARCHAR(500),
|
|
||||||
status VARCHAR(50) DEFAULT 'available',
|
|
||||||
used_on_article UUID,
|
|
||||||
location_data JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS content_fragments (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
campaign UUID REFERENCES campaign_masters (id) ON DELETE CASCADE,
|
|
||||||
fragment_type VARCHAR(100),
|
|
||||||
content_body TEXT,
|
|
||||||
word_count INTEGER,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS production_queue (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
|
||||||
campaign_id UUID REFERENCES campaign_masters (id) ON DELETE CASCADE,
|
|
||||||
status VARCHAR(50) DEFAULT 'pending',
|
|
||||||
total_requested INTEGER,
|
|
||||||
completed_count INTEGER DEFAULT 0,
|
|
||||||
velocity_mode VARCHAR(50),
|
|
||||||
schedule_data JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS quality_flags (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
|
||||||
batch_id VARCHAR(255),
|
|
||||||
article_a VARCHAR(255),
|
|
||||||
article_b VARCHAR(255),
|
|
||||||
collision_text TEXT,
|
|
||||||
similarity_score FLOAT,
|
|
||||||
status VARCHAR(50) DEFAULT 'pending',
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- CARTESIAN ENGINE
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS generation_jobs (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
|
||||||
target_quantity INTEGER,
|
|
||||||
status VARCHAR(50) DEFAULT 'queued',
|
|
||||||
type VARCHAR(100),
|
|
||||||
progress INTEGER DEFAULT 0,
|
|
||||||
priority VARCHAR(50) DEFAULT 'medium',
|
|
||||||
config JSONB,
|
|
||||||
current_offset INTEGER DEFAULT 0,
|
|
||||||
filters JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS article_templates (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'published',
|
||||||
name VARCHAR(255),
|
name VARCHAR(255),
|
||||||
structure_json JSONB,
|
pain_points JSONB,
|
||||||
description TEXT,
|
demographics JSONB
|
||||||
is_default BOOLEAN DEFAULT false,
|
);
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
|
CREATE TABLE IF NOT EXISTS avatar_variants (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'published',
|
||||||
|
name VARCHAR(255),
|
||||||
|
prompt_modifier TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS cartesian_patterns (
|
CREATE TABLE IF NOT EXISTS cartesian_patterns (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
pattern_key VARCHAR(255) UNIQUE,
|
status VARCHAR(50) DEFAULT 'published',
|
||||||
pattern_type VARCHAR(100),
|
|
||||||
formula TEXT,
|
|
||||||
data JSONB,
|
|
||||||
example_output TEXT,
|
|
||||||
description TEXT,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS spintax_dictionaries (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
category VARCHAR(255),
|
|
||||||
data JSONB,
|
|
||||||
base_word VARCHAR(255),
|
|
||||||
variations TEXT,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- INTELLIGENCE LIBRARY
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS avatar_variants (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
avatar_id VARCHAR(255),
|
|
||||||
avatar_key VARCHAR(255),
|
|
||||||
variant_type VARCHAR(100),
|
|
||||||
variants_json JSONB,
|
|
||||||
data JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS avatars (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
name VARCHAR(255),
|
name VARCHAR(255),
|
||||||
slug VARCHAR(255) UNIQUE,
|
pattern_logic TEXT
|
||||||
description TEXT,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- GEO INTELLIGENCE (with hierarchical relationships)
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS locations_states (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
name VARCHAR(255),
|
|
||||||
code VARCHAR(2) UNIQUE,
|
|
||||||
population INTEGER,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS locations_counties (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
name VARCHAR(255),
|
|
||||||
state UUID REFERENCES locations_states (id) ON DELETE CASCADE,
|
|
||||||
fips_code VARCHAR(10),
|
|
||||||
population INTEGER,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS locations_cities (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
name VARCHAR(255),
|
|
||||||
state UUID REFERENCES locations_states (id) ON DELETE CASCADE,
|
|
||||||
county UUID REFERENCES locations_counties (id) ON DELETE SET NULL,
|
|
||||||
population INTEGER,
|
|
||||||
zip_codes JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS geo_clusters (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
cluster_key VARCHAR(255) UNIQUE,
|
|
||||||
name VARCHAR(255),
|
|
||||||
state VARCHAR(255),
|
|
||||||
description TEXT,
|
|
||||||
data JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS geo_locations (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
cluster UUID REFERENCES geo_clusters (id) ON DELETE CASCADE,
|
|
||||||
city VARCHAR(255),
|
|
||||||
state VARCHAR(255),
|
|
||||||
zip VARCHAR(10),
|
|
||||||
population INTEGER,
|
|
||||||
coordinates JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS geo_intelligence (
|
CREATE TABLE IF NOT EXISTS geo_intelligence (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
cluster_key VARCHAR(255),
|
status VARCHAR(50) DEFAULT 'published',
|
||||||
data JSONB,
|
city VARCHAR(255),
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
state VARCHAR(255),
|
||||||
|
population INTEGER
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- OFFER BLOCKS
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS offer_blocks (
|
CREATE TABLE IF NOT EXISTS offer_blocks (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
block_id VARCHAR(255),
|
status VARCHAR(50) DEFAULT 'published',
|
||||||
title VARCHAR(255),
|
|
||||||
hook_generator TEXT,
|
|
||||||
universal_pains JSONB,
|
|
||||||
universal_solutions JSONB,
|
|
||||||
universal_value_points JSONB,
|
|
||||||
cta_spintax TEXT,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS offer_blocks_universal (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
block_id VARCHAR(255),
|
|
||||||
title VARCHAR(255),
|
|
||||||
hook_generator TEXT,
|
|
||||||
universal_pains JSONB,
|
|
||||||
universal_solutions JSONB,
|
|
||||||
universal_value_points JSONB,
|
|
||||||
cta_spintax TEXT,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- MEDIA & TEMPLATES
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS image_templates (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
name VARCHAR(255),
|
name VARCHAR(255),
|
||||||
svg_template TEXT,
|
html_content TEXT
|
||||||
svg_source TEXT,
|
|
||||||
is_default BOOLEAN DEFAULT false,
|
|
||||||
preview VARCHAR(255),
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================
|
-- ===================================================================================
|
||||||
-- ANALYTICS & TRACKING
|
-- 🧱 BATCH 2: THE WALLS (First-Level Children)
|
||||||
-- ============================================
|
-- Dependencies: 'sites' or 'campaign_masters'
|
||||||
|
-- ===================================================================================
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS conversions (
|
-- 8. GENERATED ARTICLES
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
CREATE TABLE IF NOT EXISTS generated_articles (
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
|
||||||
lead UUID,
|
|
||||||
conversion_type VARCHAR(100),
|
|
||||||
value FLOAT,
|
|
||||||
currency VARCHAR(10) DEFAULT 'USD',
|
|
||||||
source VARCHAR(255),
|
|
||||||
sent_to_google BOOLEAN DEFAULT false,
|
|
||||||
sent_to_facebook BOOLEAN DEFAULT false,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS site_analytics (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
|
||||||
google_ads_id VARCHAR(255),
|
|
||||||
google_ads_conversion_label VARCHAR(255),
|
|
||||||
fb_pixel_id VARCHAR(255),
|
|
||||||
fb_access_token TEXT,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- INFRASTRUCTURE
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS hub_pages (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'draft',
|
||||||
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
|
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
|
||||||
|
campaign_id UUID REFERENCES campaign_masters(id) ON DELETE SET NULL,
|
||||||
title VARCHAR(255),
|
title VARCHAR(255),
|
||||||
|
content TEXT,
|
||||||
slug VARCHAR(255),
|
slug VARCHAR(255),
|
||||||
parent_hub UUID REFERENCES hub_pages (id) ON DELETE SET NULL,
|
|
||||||
level INTEGER DEFAULT 0,
|
|
||||||
articles_count INTEGER DEFAULT 0,
|
|
||||||
schema_json JSONB,
|
schema_json JSONB,
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 9. GENERATION JOBS
|
||||||
|
CREATE TABLE IF NOT EXISTS generation_jobs (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'pending',
|
||||||
|
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
|
||||||
|
batch_size INTEGER DEFAULT 10,
|
||||||
|
progress INTEGER DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 10. PAGES
|
||||||
|
CREATE TABLE IF NOT EXISTS pages (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'published',
|
||||||
|
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(255),
|
||||||
|
slug VARCHAR(255),
|
||||||
|
content TEXT,
|
||||||
|
schema_json JSONB
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 11. POSTS
|
||||||
|
CREATE TABLE IF NOT EXISTS posts (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'published',
|
||||||
|
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(255),
|
||||||
|
slug VARCHAR(255),
|
||||||
|
content TEXT,
|
||||||
|
schema_json JSONB
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 12. LEADS
|
||||||
|
CREATE TABLE IF NOT EXISTS leads (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'new',
|
||||||
|
site_id UUID REFERENCES sites(id) ON DELETE SET NULL,
|
||||||
|
email VARCHAR(255),
|
||||||
|
name VARCHAR(255),
|
||||||
|
source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 13. HEADLINE INVENTORY
|
||||||
|
CREATE TABLE IF NOT EXISTS headline_inventory (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'active',
|
||||||
|
campaign_id UUID REFERENCES campaign_masters(id) ON DELETE CASCADE,
|
||||||
|
headline_text VARCHAR(255),
|
||||||
|
is_used BOOLEAN DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 14. CONTENT FRAGMENTS
|
||||||
|
CREATE TABLE IF NOT EXISTS content_fragments (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'active',
|
||||||
|
campaign_id UUID REFERENCES campaign_masters(id) ON DELETE CASCADE,
|
||||||
|
fragment_text TEXT,
|
||||||
|
fragment_type VARCHAR(50)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ===================================================================================
|
||||||
|
-- 🏠 BATCH 3: THE ROOF (Complex Dependents)
|
||||||
|
-- Dependencies: Multiple tables
|
||||||
|
-- ===================================================================================
|
||||||
|
|
||||||
|
-- 15. LINK TARGETS (Internal Linking Logic)
|
||||||
CREATE TABLE IF NOT EXISTS link_targets (
|
CREATE TABLE IF NOT EXISTS link_targets (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
status VARCHAR(50) DEFAULT 'active',
|
||||||
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
|
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
|
||||||
target_url VARCHAR(500),
|
target_url VARCHAR(500),
|
||||||
anchor_text VARCHAR(255),
|
anchor_text VARCHAR(255),
|
||||||
anchor_variations JSONB,
|
keyword_focus VARCHAR(255)
|
||||||
priority INTEGER DEFAULT 0,
|
|
||||||
is_active BOOLEAN DEFAULT true,
|
|
||||||
is_hub BOOLEAN DEFAULT false,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS work_log (
|
-- ===================================================================================
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
-- 🎨 DIRECTUS UI CONFIGURATION (The "Glance" Layer)
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
-- Fixes interfaces, dropdowns, and template issues automatically
|
||||||
action VARCHAR(255),
|
-- ===================================================================================
|
||||||
entity_type VARCHAR(100),
|
|
||||||
entity_id VARCHAR(255),
|
|
||||||
details JSONB,
|
|
||||||
user_id VARCHAR(255),
|
|
||||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ============================================
|
-- 1. Enable 'Select Dropdown' for all Foreign Keys (Fixes "Raw UUID" UI issue)
|
||||||
-- FORMS & CRM
|
INSERT INTO directus_fields (collection, field, interface, readonly, hidden, width)
|
||||||
-- ============================================
|
VALUES
|
||||||
|
('campaign_masters', 'site_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('generated_articles', 'site_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('generated_articles', 'campaign_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('generation_jobs', 'site_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('pages', 'site_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('posts', 'site_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('leads', 'site_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('headline_inventory', 'campaign_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('content_fragments', 'campaign_id', 'select-dropdown-m2o', 'false', 'false', 'half'),
|
||||||
|
('link_targets', 'site_id', 'select-dropdown-m2o', 'false', 'false', 'half')
|
||||||
|
ON CONFLICT (collection, field)
|
||||||
|
DO UPDATE SET interface = 'select-dropdown-m2o';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS forms (
|
-- 2. Fix the Template Mismatch (The 'campaign_name' vs 'name' bug)
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
UPDATE directus_collections
|
||||||
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
SET display_template = '{{campaign_id.name}}'
|
||||||
name VARCHAR(255),
|
WHERE collection IN ('content_fragments', 'headline_inventory', 'generated_articles');
|
||||||
fields_config JSONB,
|
|
||||||
success_message TEXT,
|
|
||||||
redirect_url VARCHAR(500),
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS form_submissions (
|
-- 3. Set standard display templates for Sites
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
UPDATE directus_collections
|
||||||
form UUID REFERENCES forms (id) ON DELETE CASCADE,
|
SET display_template = '{{name}}'
|
||||||
data JSONB,
|
WHERE collection IN ('sites', 'campaign_masters');
|
||||||
ip_address VARCHAR(50),
|
|
||||||
user_agent TEXT,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS content_modules (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
name VARCHAR(255),
|
|
||||||
module_type VARCHAR(100),
|
|
||||||
content TEXT,
|
|
||||||
variables JSONB,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS campaigns (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
|
||||||
name VARCHAR(255),
|
|
||||||
site UUID REFERENCES sites (id) ON DELETE CASCADE,
|
|
||||||
status VARCHAR(50) DEFAULT 'active',
|
|
||||||
start_date TIMESTAMP,
|
|
||||||
end_date TIMESTAMP,
|
|
||||||
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- CREATE INDEXES FOR PERFORMANCE
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_generated_articles_site ON generated_articles (site_id);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_generated_articles_campaign ON generated_articles (campaign_id);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_generated_articles_status ON generated_articles (status);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_pages_site ON pages (site);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_posts_site ON posts (site);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_leads_site ON leads (site);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_site ON events (site);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_pageviews_site ON pageviews (site);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_locations_counties_state ON locations_counties (state);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_locations_cities_state ON locations_cities (state);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_locations_cities_county ON locations_cities (county);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_geo_locations_cluster ON geo_locations (cluster);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_hub_pages_site ON hub_pages (site);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_hub_pages_parent ON hub_pages (parent_hub);
|
|
||||||
|
|
||||||
-- ============================================
|
|
||||||
-- VERIFY
|
|
||||||
-- ============================================
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
COUNT(*) as total_tables,
|
|
||||||
STRING_AGG (
|
|
||||||
tablename,
|
|
||||||
', '
|
|
||||||
ORDER BY tablename
|
|
||||||
) as table_names
|
|
||||||
FROM pg_tables
|
|
||||||
WHERE
|
|
||||||
schemaname = 'public'
|
|
||||||
AND tablename NOT LIKE 'directus_%'
|
|
||||||
AND tablename NOT LIKE 'spatial_%';
|
|
||||||
@@ -21,7 +21,7 @@ export default function JobLaunchpad() {
|
|||||||
const client = getDirectusClient();
|
const client = getDirectusClient();
|
||||||
try {
|
try {
|
||||||
const s = await client.request(readItems('sites'));
|
const s = await client.request(readItems('sites'));
|
||||||
const a = await client.request(readItems('avatars'));
|
const a = await client.request(readItems('avatar_intelligence'));
|
||||||
const p = await client.request(readItems('cartesian_patterns'));
|
const p = await client.request(readItems('cartesian_patterns'));
|
||||||
|
|
||||||
setSites(s);
|
setSites(s);
|
||||||
@@ -59,7 +59,7 @@ export default function JobLaunchpad() {
|
|||||||
const job = await client.request(createItem('generation_jobs', {
|
const job = await client.request(createItem('generation_jobs', {
|
||||||
site_id: selectedSite,
|
site_id: selectedSite,
|
||||||
target_quantity: targetQuantity,
|
target_quantity: targetQuantity,
|
||||||
status: 'Pending',
|
status: 'pending',
|
||||||
filters: {
|
filters: {
|
||||||
avatars: selectedAvatars,
|
avatars: selectedAvatars,
|
||||||
patterns: patterns.map(p => p.id) // Use all patterns for now
|
patterns: patterns.map(p => p.id) // Use all patterns for now
|
||||||
@@ -102,7 +102,7 @@ export default function JobLaunchpad() {
|
|||||||
onChange={e => setSelectedSite(e.target.value)}
|
onChange={e => setSelectedSite(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="">Select Site...</option>
|
<option value="">Select Site...</option>
|
||||||
{sites.map(s => <option key={s.id} value={s.id}>{s.name || s.domain}</option>)}
|
{sites.map(s => <option key={s.id} value={s.id}>{s.name || s.url}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { getDirectusClient, readItems, aggregate } from '@/lib/directus/client';
|
import { getDirectusClient, readItems, aggregate } from '@/lib/directus/client';
|
||||||
import type { GenerationJob, CampaignMaster, WorkLog } from '@/types/schema';
|
import type { DirectusSchema, GenerationJobs as GenerationJob, CampaignMasters as CampaignMaster, WorkLog } from '@/lib/schemas';
|
||||||
|
|
||||||
export default function ContentFactoryDashboard() {
|
export default function ContentFactoryDashboard() {
|
||||||
const [stats, setStats] = useState({ total: 0, published: 0, processing: 0 });
|
const [stats, setStats] = useState({ total: 0, published: 0, processing: 0 });
|
||||||
@@ -58,21 +58,21 @@ export default function ContentFactoryDashboard() {
|
|||||||
sort: ['-date_created'],
|
sort: ['-date_created'],
|
||||||
filter: { status: { _in: ['active', 'paused'] } } // Show active/paused
|
filter: { status: { _in: ['active', 'paused'] } } // Show active/paused
|
||||||
}));
|
}));
|
||||||
setCampaigns(activeCampaigns as CampaignMaster[]);
|
setCampaigns(activeCampaigns as unknown as CampaignMaster[]);
|
||||||
|
|
||||||
// 3. Fetch Production Jobs (The real "Factory" work)
|
// 3. Fetch Production Jobs (The real "Factory" work)
|
||||||
const recentJobs = await client.request(readItems('generation_jobs', {
|
const recentJobs = await client.request(readItems('generation_jobs', {
|
||||||
limit: 5,
|
limit: 5,
|
||||||
sort: ['-date_created']
|
sort: ['-date_created']
|
||||||
}));
|
}));
|
||||||
setJobs(recentJobs as GenerationJob[]);
|
setJobs(recentJobs as unknown as GenerationJob[]);
|
||||||
|
|
||||||
// 4. Fetch Work Log
|
// 4. Fetch Work Log
|
||||||
const recentLogs = await client.request(readItems('work_log', {
|
const recentLogs = await client.request(readItems('work_log', {
|
||||||
limit: 20,
|
limit: 20,
|
||||||
sort: ['-date_created']
|
sort: ['-date_created']
|
||||||
}));
|
}));
|
||||||
setLogs(recentLogs as WorkLog[]);
|
setLogs(recentLogs as unknown as WorkLog[]);
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { getDirectusClient, readItems } from '@/lib/directus/client';
|
import { getDirectusClient, readItems } from '@/lib/directus/client';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Page } from '@/types/schema'; // Ensure exported
|
import { Pages as Page } from '@/lib/schemas';
|
||||||
|
|
||||||
export default function PageList() {
|
export default function PageList() {
|
||||||
const [pages, setPages] = useState<Page[]>([]);
|
const [pages, setPages] = useState<Page[]>([]);
|
||||||
@@ -30,7 +30,7 @@ export default function PageList() {
|
|||||||
<CardHeader className="p-4 flex flex-row items-center justify-between">
|
<CardHeader className="p-4 flex flex-row items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-lg font-medium text-slate-200">{page.title}</CardTitle>
|
<CardTitle className="text-lg font-medium text-slate-200">{page.title}</CardTitle>
|
||||||
<div className="text-sm text-slate-500 font-mono mt-1">/{page.permalink}</div>
|
<div className="text-sm text-slate-500 font-mono mt-1">/{page.slug}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Badge variant="outline" className="text-slate-400 border-slate-600">
|
<Badge variant="outline" className="text-slate-400 border-slate-600">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
|
|||||||
// Assume Table isn't fully ready or use Grid for now to be safe.
|
// Assume Table isn't fully ready or use Grid for now to be safe.
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Post } from '@/types/schema';
|
import { Posts as Post } from '@/lib/schemas';
|
||||||
|
|
||||||
export default function PostList() {
|
export default function PostList() {
|
||||||
const [posts, setPosts] = useState<Post[]>([]);
|
const [posts, setPosts] = useState<Post[]>([]);
|
||||||
@@ -52,14 +52,11 @@ export default function PostList() {
|
|||||||
{post.status}
|
{post.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
|
||||||
{new Date(post.date_created || '').toLocaleDateString()}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
{posts.length === 0 && (
|
{posts.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={4} className="px-6 py-12 text-center text-slate-500">
|
<td colSpan={3} className="px-6 py-12 text-center text-slate-500">
|
||||||
No posts found.
|
No posts found.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default function CampaignWizard({ onComplete, onCancel }: CampaignWizardP
|
|||||||
onChange={e => setFormData({ ...formData, site: e.target.value })}
|
onChange={e => setFormData({ ...formData, site: e.target.value })}
|
||||||
>
|
>
|
||||||
<option value="">Select a Site...</option>
|
<option value="">Select a Site...</option>
|
||||||
{sites.map(s => <option key={s.id} value={s.id}>{s.name} ({s.domain})</option>)}
|
{sites.map(s => <option key={s.id} value={s.id}>{s.name} ({s.url})</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4 pt-2">
|
<div className="grid grid-cols-2 gap-4 pt-2">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Site } from '@/types/schema';
|
import { Sites as Site } from '@/lib/schemas';
|
||||||
import DomainSetupGuide from '@/components/admin/DomainSetupGuide';
|
import DomainSetupGuide from '@/components/admin/DomainSetupGuide';
|
||||||
|
|
||||||
interface SiteEditorProps {
|
interface SiteEditorProps {
|
||||||
@@ -33,12 +33,12 @@ export default function SiteEditor({ id }: SiteEditorProps) {
|
|||||||
try {
|
try {
|
||||||
const client = getDirectusClient();
|
const client = getDirectusClient();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const s = await client.request(readItem('sites', id));
|
const result = await client.request(readItem('sites', id));
|
||||||
setSite(s as Site);
|
setSite(result as unknown as Site);
|
||||||
|
|
||||||
// Merge settings into defaults
|
// Merge settings into defaults
|
||||||
if (s.settings) {
|
if (result.settings) {
|
||||||
setFeatures(prev => ({ ...prev, ...s.settings }));
|
setFeatures(prev => ({ ...prev, ...(result.settings as Record<string, any>) }));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -57,7 +57,7 @@ export default function SiteEditor({ id }: SiteEditorProps) {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await client.request(updateItem('sites', id, {
|
await client.request(updateItem('sites', id, {
|
||||||
name: site.name,
|
name: site.name,
|
||||||
domain: site.domain,
|
url: site.url,
|
||||||
status: site.status,
|
status: site.status,
|
||||||
settings: features
|
settings: features
|
||||||
}));
|
}));
|
||||||
@@ -97,8 +97,8 @@ export default function SiteEditor({ id }: SiteEditorProps) {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Domain</Label>
|
<Label>Domain</Label>
|
||||||
<Input
|
<Input
|
||||||
value={site.domain}
|
value={site.url || ''}
|
||||||
onChange={(e) => setSite({ ...site, domain: e.target.value })}
|
onChange={(e) => setSite({ ...site, url: e.target.value })}
|
||||||
className="bg-slate-900 border-slate-700 font-mono text-blue-400"
|
className="bg-slate-900 border-slate-700 font-mono text-blue-400"
|
||||||
placeholder="example.com"
|
placeholder="example.com"
|
||||||
/>
|
/>
|
||||||
@@ -206,7 +206,7 @@ export default function SiteEditor({ id }: SiteEditorProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Domain Setup Guide */}
|
{/* Domain Setup Guide */}
|
||||||
<DomainSetupGuide siteDomain={site.domain} />
|
<DomainSetupGuide siteDomain={site.url} />
|
||||||
|
|
||||||
<div className="flex justify-end gap-4">
|
<div className="flex justify-end gap-4">
|
||||||
<Button variant="outline" onClick={() => window.history.back()}>
|
<Button variant="outline" onClick={() => window.history.back()}>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getDirectusClient, readItems } from '@/lib/directus/client';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Site } from '@/types/schema';
|
import { Sites as Site } from '@/lib/schemas';
|
||||||
|
|
||||||
export default function SiteList() {
|
export default function SiteList() {
|
||||||
const [sites, setSites] = useState<Site[]>([]);
|
const [sites, setSites] = useState<Site[]>([]);
|
||||||
@@ -15,7 +15,7 @@ export default function SiteList() {
|
|||||||
const client = getDirectusClient();
|
const client = getDirectusClient();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const s = await client.request(readItems('sites'));
|
const s = await client.request(readItems('sites'));
|
||||||
setSites(s as Site[]);
|
setSites(s as unknown as Site[]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -40,9 +40,9 @@ export default function SiteList() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-white mb-2">{site.domain || 'No domain set'}</div>
|
<div className="text-2xl font-bold text-white mb-2">{site.url || 'No URL set'}</div>
|
||||||
<p className="text-xs text-slate-500 mb-4">
|
<p className="text-xs text-slate-500 mb-4">
|
||||||
{site.domain ? '🟢 Domain configured' : '⚠️ Set up domain'}
|
{site.url ? '🟢 Site configured' : '⚠️ Set up site URL'}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 flex gap-2">
|
<div className="mt-4 flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -62,8 +62,8 @@ export default function SiteList() {
|
|||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (site.domain) {
|
if (site.url) {
|
||||||
window.open(`https://${site.domain}`, '_blank');
|
window.open(`https://${site.url || 'No URL'}`, '_blank');
|
||||||
} else {
|
} else {
|
||||||
alert('Set up a domain first in site settings');
|
alert('Set up a domain first in site settings');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const client = getDirectusClient();
|
|||||||
interface Site {
|
interface Site {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
domain: string;
|
url: string;
|
||||||
status: 'active' | 'inactive';
|
status: 'active' | 'inactive';
|
||||||
settings?: any;
|
settings?: any;
|
||||||
}
|
}
|
||||||
@@ -89,14 +89,14 @@ export default function SitesManager() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold truncate text-white tracking-tight">{site.domain}</div>
|
<div className="text-2xl font-bold truncate text-white tracking-tight">{site.url}</div>
|
||||||
<p className="text-xs text-zinc-500 mt-1 flex items-center">
|
<p className="text-xs text-zinc-500 mt-1 flex items-center">
|
||||||
<Globe className="h-3 w-3 mr-1" />
|
<Globe className="h-3 w-3 mr-1" />
|
||||||
deployed via Launchpad
|
deployed via Launchpad
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="flex justify-between border-t border-zinc-800 pt-4">
|
<CardFooter className="flex justify-between border-t border-zinc-800 pt-4">
|
||||||
<Button variant="ghost" size="sm" className="text-zinc-400 hover:text-white" onClick={() => window.open(`https://${site.domain}`, '_blank')}>
|
<Button variant="ghost" size="sm" className="text-zinc-400 hover:text-white" onClick={() => window.open(`https://${site.url}`, '_blank')}>
|
||||||
<ExternalLink className="h-4 w-4 mr-2" /> Visit
|
<ExternalLink className="h-4 w-4 mr-2" /> Visit
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -148,8 +148,8 @@ export default function SitesManager() {
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-zinc-800 bg-zinc-900 text-zinc-500 text-sm">https://</span>
|
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-zinc-800 bg-zinc-900 text-zinc-500 text-sm">https://</span>
|
||||||
<Input
|
<Input
|
||||||
value={editingSite.domain || ''}
|
value={editingSite.url || ''}
|
||||||
onChange={e => setEditingSite({ ...editingSite, domain: e.target.value })}
|
onChange={e => setEditingSite({ ...editingSite, url: e.target.value })}
|
||||||
placeholder="example.com"
|
placeholder="example.com"
|
||||||
className="rounded-l-none bg-zinc-950 border-zinc-800"
|
className="rounded-l-none bg-zinc-950 border-zinc-800"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { createDirectus, rest, authentication, realtime } from '@directus/sdk';
|
import { createDirectus, rest, authentication, realtime } from '@directus/sdk';
|
||||||
import type { SparkSchema } from '@/types/schema';
|
import type { DirectusSchema } from '@/lib/schemas';
|
||||||
|
|
||||||
const DIRECTUS_URL = import.meta.env.PUBLIC_DIRECTUS_URL || 'https://spark.jumpstartscaling.com';
|
const DIRECTUS_URL = import.meta.env.PUBLIC_DIRECTUS_URL || 'https://spark.jumpstartscaling.com';
|
||||||
|
|
||||||
export const directus = createDirectus<SparkSchema>(DIRECTUS_URL)
|
export const directus = createDirectus<DirectusSchema>(DIRECTUS_URL)
|
||||||
.with(authentication('cookie', { autoRefresh: true, mode: 'json' }))
|
.with(authentication('cookie', { autoRefresh: true }))
|
||||||
.with(rest())
|
.with(rest())
|
||||||
.with(realtime());
|
.with(realtime());
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import {
|
|||||||
deleteItem,
|
deleteItem,
|
||||||
aggregate
|
aggregate
|
||||||
} from '@directus/sdk';
|
} from '@directus/sdk';
|
||||||
import type { SparkSchema } from '@/types/schema';
|
import type { DirectusSchema } from '../schemas';
|
||||||
|
import type { DirectusClient, RestClient } from '@directus/sdk';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const PUBLIC_URL = import.meta.env.PUBLIC_DIRECTUS_URL || 'https://spark.jumpstartscaling.com';
|
const PUBLIC_URL = import.meta.env.PUBLIC_DIRECTUS_URL || 'https://spark.jumpstartscaling.com';
|
||||||
|
|
||||||
// Internal URL (SSR only) - used when running server-side requests
|
// Internal URL (SSR only) - used when running server-side requests
|
||||||
@@ -19,6 +21,7 @@ const INTERNAL_URL = typeof process !== 'undefined' && process.env?.INTERNAL_DIR
|
|||||||
? process.env.INTERNAL_DIRECTUS_URL
|
? process.env.INTERNAL_DIRECTUS_URL
|
||||||
: 'https://spark.jumpstartscaling.com';
|
: 'https://spark.jumpstartscaling.com';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const DIRECTUS_TOKEN = import.meta.env.DIRECTUS_ADMIN_TOKEN || (typeof process !== 'undefined' && process.env ? process.env.DIRECTUS_ADMIN_TOKEN : '') || 'eufOJ_oKEx_FVyGoz1GxWu6nkSOcgIVS';
|
const DIRECTUS_TOKEN = import.meta.env.DIRECTUS_ADMIN_TOKEN || (typeof process !== 'undefined' && process.env ? process.env.DIRECTUS_ADMIN_TOKEN : '') || 'eufOJ_oKEx_FVyGoz1GxWu6nkSOcgIVS';
|
||||||
|
|
||||||
// Select URL based on environment (Server vs Client)
|
// Select URL based on environment (Server vs Client)
|
||||||
@@ -28,15 +31,13 @@ const DIRECTUS_URL = PUBLIC_URL;
|
|||||||
/**
|
/**
|
||||||
* Creates a typed Directus client for the Spark Platform
|
* Creates a typed Directus client for the Spark Platform
|
||||||
*/
|
*/
|
||||||
export function getDirectusClient(token?: string) {
|
export function getDirectusClient(token?: string): DirectusClient<DirectusSchema> & RestClient<DirectusSchema> {
|
||||||
const client = createDirectus<SparkSchema>(DIRECTUS_URL).with(rest());
|
const client = createDirectus<DirectusSchema>(DIRECTUS_URL).with(rest());
|
||||||
|
|
||||||
if (token || DIRECTUS_TOKEN) {
|
if (token || DIRECTUS_TOKEN) {
|
||||||
return client.with(staticToken(token || DIRECTUS_TOKEN));
|
return client.with(staticToken(token || DIRECTUS_TOKEN));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { getDirectusClient, readItems, readItem, readSingleton, aggregate } from './client';
|
import { getDirectusClient } from './client';
|
||||||
import type { Page, Post, Site, Globals, Navigation } from '@/types/schema';
|
import { readItems, readItem, readSingleton, aggregate } from '@directus/sdk';
|
||||||
|
import type { DirectusSchema, Pages as Page, Posts as Post, Sites as Site, DirectusUsers as User, Globals, Navigation } from '../schemas';
|
||||||
|
|
||||||
const directus = getDirectusClient();
|
const directus = getDirectusClient();
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export async function fetchPageByPermalink(
|
|||||||
): Promise<Page | null> {
|
): Promise<Page | null> {
|
||||||
const filter: Record<string, any> = {
|
const filter: Record<string, any> = {
|
||||||
permalink: { _eq: permalink },
|
permalink: { _eq: permalink },
|
||||||
site: { _eq: siteId }
|
site_id: { _eq: siteId }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!options?.preview) {
|
if (!options?.preview) {
|
||||||
@@ -29,7 +30,7 @@ export async function fetchPageByPermalink(
|
|||||||
'id',
|
'id',
|
||||||
'title',
|
'title',
|
||||||
'permalink',
|
'permalink',
|
||||||
'site',
|
'site_id',
|
||||||
'status',
|
'status',
|
||||||
'seo_title',
|
'seo_title',
|
||||||
'seo_description',
|
'seo_description',
|
||||||
@@ -54,12 +55,12 @@ export async function fetchSiteGlobals(siteId: string): Promise<Globals | null>
|
|||||||
try {
|
try {
|
||||||
const globals = await directus.request(
|
const globals = await directus.request(
|
||||||
readItems('globals', {
|
readItems('globals', {
|
||||||
filter: { site: { _eq: siteId } },
|
filter: { site_id: { _eq: siteId } },
|
||||||
limit: 1,
|
limit: 1,
|
||||||
fields: ['*']
|
fields: ['*']
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return globals?.[0] || null;
|
return (globals as unknown as Globals[])?.[0] || null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching globals:', err);
|
console.error('Error fetching globals:', err);
|
||||||
return null;
|
return null;
|
||||||
@@ -73,12 +74,12 @@ export async function fetchNavigation(siteId: string): Promise<Partial<Navigatio
|
|||||||
try {
|
try {
|
||||||
const nav = await directus.request(
|
const nav = await directus.request(
|
||||||
readItems('navigation', {
|
readItems('navigation', {
|
||||||
filter: { site: { _eq: siteId } },
|
filter: { site_id: { _eq: siteId } },
|
||||||
sort: ['sort'],
|
sort: ['sort'],
|
||||||
fields: ['id', 'label', 'url', 'parent', 'target', 'sort']
|
fields: ['id', 'label', 'url', 'parent', 'target', 'sort']
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return nav || [];
|
return (nav as unknown as Navigation[]) || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching navigation:', err);
|
console.error('Error fetching navigation:', err);
|
||||||
return [];
|
return [];
|
||||||
@@ -97,7 +98,7 @@ export async function fetchPosts(
|
|||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
const filter: Record<string, any> = {
|
const filter: Record<string, any> = {
|
||||||
site: { _eq: siteId }, // siteId is UUID string
|
site_id: { _eq: siteId }, // siteId is UUID string
|
||||||
status: { _eq: 'published' }
|
status: { _eq: 'published' }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ export async function fetchPosts(
|
|||||||
'published_at',
|
'published_at',
|
||||||
'category',
|
'category',
|
||||||
'author',
|
'author',
|
||||||
'site',
|
'site_id',
|
||||||
'status',
|
'status',
|
||||||
'content'
|
'content'
|
||||||
]
|
]
|
||||||
@@ -158,7 +159,7 @@ export async function fetchPostBySlug(
|
|||||||
readItems('posts', {
|
readItems('posts', {
|
||||||
filter: {
|
filter: {
|
||||||
slug: { _eq: slug },
|
slug: { _eq: slug },
|
||||||
site: { _eq: siteId },
|
site_id: { _eq: siteId },
|
||||||
status: { _eq: 'published' }
|
status: { _eq: 'published' }
|
||||||
},
|
},
|
||||||
limit: 1,
|
limit: 1,
|
||||||
@@ -247,8 +248,8 @@ export async function fetchCampaigns(siteId?: string) {
|
|||||||
const filter: Record<string, any> = {};
|
const filter: Record<string, any> = {};
|
||||||
if (siteId) {
|
if (siteId) {
|
||||||
filter._or = [
|
filter._or = [
|
||||||
{ site: { _eq: siteId } },
|
{ site_id: { _eq: siteId } },
|
||||||
{ site: { _null: true } }
|
{ site_id: { _null: true } }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
386
frontend/src/lib/schemas.ts
Normal file
386
frontend/src/lib/schemas.ts
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
/**
|
||||||
|
* Spark Platform - Directus Schema Types
|
||||||
|
* Auto-generated from Golden Schema
|
||||||
|
*
|
||||||
|
* This provides full TypeScript coverage for all Directus collections
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BATCH 1: FOUNDATION TABLES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface Sites {
|
||||||
|
id: string;
|
||||||
|
status: 'active' | 'inactive' | 'archived';
|
||||||
|
name: string;
|
||||||
|
url?: string;
|
||||||
|
date_created?: string;
|
||||||
|
date_updated?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CampaignMasters {
|
||||||
|
id: string;
|
||||||
|
status: 'active' | 'inactive' | 'completed';
|
||||||
|
site_id: string | Sites;
|
||||||
|
name: string;
|
||||||
|
headline_spintax_root?: string;
|
||||||
|
target_word_count?: number;
|
||||||
|
location_mode?: string;
|
||||||
|
batch_count?: number;
|
||||||
|
date_created?: string;
|
||||||
|
date_updated?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvatarIntelligence {
|
||||||
|
id: string;
|
||||||
|
status: 'published' | 'draft';
|
||||||
|
base_name?: string; // Corrected from name
|
||||||
|
wealth_cluster?: string;
|
||||||
|
business_niches?: Record<string, any>;
|
||||||
|
pain_points?: Record<string, any>;
|
||||||
|
demographics?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvatarVariants {
|
||||||
|
id: string;
|
||||||
|
status: 'published' | 'draft';
|
||||||
|
name?: string;
|
||||||
|
prompt_modifier?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CartesianPatterns {
|
||||||
|
id: string;
|
||||||
|
status: 'published' | 'draft';
|
||||||
|
name?: string;
|
||||||
|
pattern_logic?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeoIntelligence {
|
||||||
|
id: string;
|
||||||
|
status: 'published' | 'draft';
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
population?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OfferBlocks {
|
||||||
|
id: string;
|
||||||
|
status: 'published' | 'draft';
|
||||||
|
name?: string;
|
||||||
|
html_content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BATCH 2: FIRST-LEVEL CHILDREN
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface GeneratedArticles {
|
||||||
|
id: string;
|
||||||
|
status: 'draft' | 'published' | 'archived';
|
||||||
|
site_id: string | Sites;
|
||||||
|
campaign_id?: string | CampaignMasters;
|
||||||
|
title?: string;
|
||||||
|
content?: string;
|
||||||
|
slug?: string;
|
||||||
|
schema_json?: Record<string, any>;
|
||||||
|
date_created?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerationJobs {
|
||||||
|
id: string;
|
||||||
|
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||||||
|
site_id: string | Sites;
|
||||||
|
batch_size?: number;
|
||||||
|
target_quantity?: number;
|
||||||
|
filters?: Record<string, any>;
|
||||||
|
current_offset?: number;
|
||||||
|
progress?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Pages {
|
||||||
|
id: string;
|
||||||
|
status: 'published' | 'draft';
|
||||||
|
site_id: string | Sites;
|
||||||
|
title?: string;
|
||||||
|
slug?: string;
|
||||||
|
content?: string;
|
||||||
|
schema_json?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Posts {
|
||||||
|
id: string;
|
||||||
|
status: 'published' | 'draft';
|
||||||
|
site_id: string | Sites;
|
||||||
|
title?: string;
|
||||||
|
slug?: string;
|
||||||
|
content?: string;
|
||||||
|
schema_json?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Leads {
|
||||||
|
id: string;
|
||||||
|
status: 'new' | 'contacted' | 'qualified' | 'converted';
|
||||||
|
site_id?: string | Sites;
|
||||||
|
email?: string;
|
||||||
|
name?: string;
|
||||||
|
source?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HeadlineInventory {
|
||||||
|
id: string;
|
||||||
|
status: 'active' | 'used' | 'archived';
|
||||||
|
campaign_id: string | CampaignMasters;
|
||||||
|
headline_text?: string;
|
||||||
|
is_used?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContentFragments {
|
||||||
|
id: string;
|
||||||
|
status: 'active' | 'archived';
|
||||||
|
campaign_id: string | CampaignMasters;
|
||||||
|
fragment_text?: string;
|
||||||
|
fragment_type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BATCH 3: COMPLEX CHILDREN
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface LinkTargets {
|
||||||
|
id: string;
|
||||||
|
status: 'active' | 'inactive';
|
||||||
|
site_id: string | Sites;
|
||||||
|
target_url?: string;
|
||||||
|
anchor_text?: string;
|
||||||
|
keyword_focus?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Globals {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
logo?: string | DirectusFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Navigation {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
parent?: string | Navigation;
|
||||||
|
sort?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DIRECTUS SYSTEM COLLECTIONS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface DirectusUsers {
|
||||||
|
id: string;
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
email: string;
|
||||||
|
password?: string;
|
||||||
|
location?: string;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
tags?: string[];
|
||||||
|
avatar?: string;
|
||||||
|
language?: string;
|
||||||
|
theme?: 'auto' | 'light' | 'dark';
|
||||||
|
tfa_secret?: string;
|
||||||
|
status: 'active' | 'invited' | 'draft' | 'suspended' | 'archived';
|
||||||
|
role: string;
|
||||||
|
token?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DirectusFiles {
|
||||||
|
id: string;
|
||||||
|
storage: string;
|
||||||
|
filename_disk?: string;
|
||||||
|
filename_download: string;
|
||||||
|
title?: string;
|
||||||
|
type?: string;
|
||||||
|
folder?: string;
|
||||||
|
uploaded_by?: string | DirectusUsers;
|
||||||
|
uploaded_on?: string;
|
||||||
|
modified_by?: string | DirectusUsers;
|
||||||
|
modified_on?: string;
|
||||||
|
charset?: string;
|
||||||
|
filesize?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
duration?: number;
|
||||||
|
embed?: string;
|
||||||
|
description?: string;
|
||||||
|
location?: string;
|
||||||
|
tags?: string[];
|
||||||
|
metadata?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DirectusActivity {
|
||||||
|
id: number;
|
||||||
|
action: string;
|
||||||
|
user?: string | DirectusUsers;
|
||||||
|
timestamp: string;
|
||||||
|
ip?: string;
|
||||||
|
user_agent?: string;
|
||||||
|
collection: string;
|
||||||
|
item: string;
|
||||||
|
comment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MAIN SCHEMA TYPE
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface DirectusSchema {
|
||||||
|
// Batch 1: Foundation
|
||||||
|
sites: Sites;
|
||||||
|
campaign_masters: CampaignMasters;
|
||||||
|
avatar_intelligence: AvatarIntelligence;
|
||||||
|
avatar_variants: AvatarVariants;
|
||||||
|
cartesian_patterns: CartesianPatterns;
|
||||||
|
geo_intelligence: GeoIntelligence;
|
||||||
|
offer_blocks: OfferBlocks;
|
||||||
|
|
||||||
|
// Batch 2: Children
|
||||||
|
generated_articles: GeneratedArticles;
|
||||||
|
generation_jobs: GenerationJobs;
|
||||||
|
pages: Pages;
|
||||||
|
posts: Posts;
|
||||||
|
leads: Leads;
|
||||||
|
headline_inventory: HeadlineInventory;
|
||||||
|
content_fragments: ContentFragments;
|
||||||
|
|
||||||
|
// Batch 3: Complex
|
||||||
|
link_targets: LinkTargets;
|
||||||
|
globals: Globals;
|
||||||
|
navigation: Navigation;
|
||||||
|
|
||||||
|
// System & Analytics
|
||||||
|
work_log: WorkLog;
|
||||||
|
hub_pages: HubPages;
|
||||||
|
forms: Forms;
|
||||||
|
form_submissions: FormSubmissions;
|
||||||
|
site_analytics: SiteAnalytics;
|
||||||
|
events: AnalyticsEvents;
|
||||||
|
pageviews: PageViews;
|
||||||
|
conversions: Conversions;
|
||||||
|
locations_states: LocationsStates;
|
||||||
|
locations_counties: LocationsCounties;
|
||||||
|
locations_cities: LocationsCities;
|
||||||
|
|
||||||
|
// Directus System
|
||||||
|
directus_users: DirectusUsers;
|
||||||
|
directus_files: DirectusFiles;
|
||||||
|
directus_activity: DirectusActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SYSTEM & ANALYTICS TYPES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface WorkLog {
|
||||||
|
id: number;
|
||||||
|
site_id?: string | Sites;
|
||||||
|
action: string;
|
||||||
|
entity_type?: string;
|
||||||
|
entity_id?: string;
|
||||||
|
details?: any;
|
||||||
|
level?: string;
|
||||||
|
status?: string;
|
||||||
|
timestamp?: string;
|
||||||
|
date_created?: string;
|
||||||
|
user?: string | DirectusUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HubPages {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
parent_hub?: string | HubPages;
|
||||||
|
level?: number;
|
||||||
|
articles_count?: number;
|
||||||
|
schema_json?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Forms {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
name: string;
|
||||||
|
fields: any[];
|
||||||
|
submit_action?: string;
|
||||||
|
success_message?: string;
|
||||||
|
redirect_url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormSubmissions {
|
||||||
|
id: string;
|
||||||
|
form: string | Forms;
|
||||||
|
data: Record<string, any>;
|
||||||
|
date_created?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SiteAnalytics {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
google_ads_id?: string;
|
||||||
|
fb_pixel_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnalyticsEvents {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
event_name: string;
|
||||||
|
page_path: string;
|
||||||
|
timestamp?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageViews {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
page_path: string;
|
||||||
|
session_id?: string;
|
||||||
|
timestamp?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Conversions {
|
||||||
|
id: string;
|
||||||
|
site_id: string | Sites;
|
||||||
|
lead?: string | Leads;
|
||||||
|
conversion_type: string;
|
||||||
|
value?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationsStates {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationsCities {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
state: string | LocationsStates;
|
||||||
|
population?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationsCounties {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
state: string | LocationsStates;
|
||||||
|
population?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// HELPER TYPES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type Collections = keyof DirectusSchema;
|
||||||
|
|
||||||
|
export type Item<Collection extends Collections> = DirectusSchema[Collection];
|
||||||
|
|
||||||
|
export type QueryFilter<Collection extends Collections> = Partial<Item<Collection>>;
|
||||||
@@ -1,491 +0,0 @@
|
|||||||
/**
|
|
||||||
* Spark Platform - Directus Schema Types
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface Site {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
domain: string;
|
|
||||||
domain_aliases?: string[];
|
|
||||||
settings?: Record<string, any>;
|
|
||||||
status: 'active' | 'inactive';
|
|
||||||
date_created?: string;
|
|
||||||
date_updated?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Page {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
title: string;
|
|
||||||
permalink: string;
|
|
||||||
status: 'draft' | 'published' | 'archived';
|
|
||||||
seo_title?: string;
|
|
||||||
seo_description?: string;
|
|
||||||
seo_image?: string;
|
|
||||||
blocks?: PageBlock[];
|
|
||||||
content?: string; // legacy fallback
|
|
||||||
schema_json?: Record<string, any>;
|
|
||||||
date_created?: string;
|
|
||||||
date_updated?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PageBlock {
|
|
||||||
id: string;
|
|
||||||
block_type: 'hero' | 'content' | 'features' | 'cta';
|
|
||||||
block_config: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Post {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
excerpt?: string;
|
|
||||||
content: string;
|
|
||||||
featured_image?: string;
|
|
||||||
status: 'draft' | 'published' | 'archived';
|
|
||||||
published_at?: string;
|
|
||||||
category?: string;
|
|
||||||
author?: string;
|
|
||||||
meta_title?: string;
|
|
||||||
seo_title?: string;
|
|
||||||
seo_description?: string;
|
|
||||||
date_created?: string;
|
|
||||||
date_updated?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Globals {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
site_name?: string;
|
|
||||||
site_tagline?: string;
|
|
||||||
logo?: string;
|
|
||||||
favicon?: string;
|
|
||||||
primary_color?: string;
|
|
||||||
secondary_color?: string;
|
|
||||||
footer_text?: string;
|
|
||||||
social_links?: SocialLink[];
|
|
||||||
scripts_head?: string;
|
|
||||||
scripts_body?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SocialLink {
|
|
||||||
platform: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Navigation {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
label: string;
|
|
||||||
url: string;
|
|
||||||
target?: '_self' | '_blank';
|
|
||||||
parent?: string | Navigation;
|
|
||||||
sort: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Author {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
bio?: string;
|
|
||||||
avatar?: string;
|
|
||||||
email?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SEO Engine Types
|
|
||||||
export interface CampaignMaster {
|
|
||||||
id: string;
|
|
||||||
site?: string | Site;
|
|
||||||
name: string;
|
|
||||||
headline_spintax_root: string;
|
|
||||||
niche_variables?: Record<string, string>;
|
|
||||||
location_mode: 'none' | 'state' | 'county' | 'city';
|
|
||||||
location_target?: string;
|
|
||||||
batch_count?: number;
|
|
||||||
status: 'active' | 'paused' | 'completed';
|
|
||||||
target_word_count?: number;
|
|
||||||
article_template?: string; // UUID of the template
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HeadlineInventory {
|
|
||||||
id: string;
|
|
||||||
campaign: string | CampaignMaster;
|
|
||||||
final_title_text: string;
|
|
||||||
status: 'available' | 'used';
|
|
||||||
used_on_article?: string;
|
|
||||||
location_data?: any; // JSON location data
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentFragment {
|
|
||||||
id: string;
|
|
||||||
campaign: string | CampaignMaster;
|
|
||||||
fragment_type: FragmentType;
|
|
||||||
content_body: string;
|
|
||||||
word_count?: number;
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FragmentType = string;
|
|
||||||
|
|
||||||
export interface ImageTemplate {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
svg_template: string;
|
|
||||||
svg_source?: string;
|
|
||||||
is_default?: boolean;
|
|
||||||
preview?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LocationState {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LocationCounty {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
state: string | LocationState;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LocationCity {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
state: string | LocationState;
|
|
||||||
county: string | LocationCounty;
|
|
||||||
population?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... (Existing types preserved above)
|
|
||||||
|
|
||||||
// Cartesian Engine Types
|
|
||||||
// Cartesian Engine Types
|
|
||||||
export interface GenerationJob {
|
|
||||||
id: string;
|
|
||||||
site_id: string | Site;
|
|
||||||
target_quantity: number;
|
|
||||||
status: 'queued' | 'processing' | 'completed' | 'failed' | 'Pending' | 'Complete'; // allowing legacy for safety
|
|
||||||
type?: string;
|
|
||||||
progress?: number;
|
|
||||||
priority?: 'high' | 'medium' | 'low';
|
|
||||||
config: Record<string, any>;
|
|
||||||
current_offset: number;
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArticleTemplate {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
structure_json: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Avatar {
|
|
||||||
id: string; // key
|
|
||||||
base_name: string;
|
|
||||||
business_niches: string[];
|
|
||||||
wealth_cluster: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AvatarVariant {
|
|
||||||
id: string;
|
|
||||||
avatar_id: string;
|
|
||||||
variants_json: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GeoCluster {
|
|
||||||
id: string;
|
|
||||||
cluster_name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GeoLocation {
|
|
||||||
id: string;
|
|
||||||
cluster: string | GeoCluster;
|
|
||||||
city: string;
|
|
||||||
state: string;
|
|
||||||
zip_focus?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SpintaxDictionary {
|
|
||||||
id: string;
|
|
||||||
category: string;
|
|
||||||
data: string[];
|
|
||||||
base_word?: string;
|
|
||||||
variations?: string; // legacy
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CartesianPattern {
|
|
||||||
id: string;
|
|
||||||
pattern_key: string;
|
|
||||||
pattern_type: string;
|
|
||||||
formula: string;
|
|
||||||
example_output?: string;
|
|
||||||
description?: string;
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OfferBlockUniversal {
|
|
||||||
id: string;
|
|
||||||
block_id: string;
|
|
||||||
title: string;
|
|
||||||
hook_generator: string;
|
|
||||||
universal_pains: string[];
|
|
||||||
universal_solutions: string[];
|
|
||||||
universal_value_points: string[];
|
|
||||||
cta_spintax: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OfferBlockPersonalized {
|
|
||||||
id: string;
|
|
||||||
block_related_id: string;
|
|
||||||
avatar_related_id: string;
|
|
||||||
pains: string[];
|
|
||||||
solutions: string[];
|
|
||||||
value_points: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updated GeneratedArticle to match Init Schema
|
|
||||||
export interface GeneratedArticle {
|
|
||||||
id: string;
|
|
||||||
site_id: number | string;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
html_content: string;
|
|
||||||
status: 'queued' | 'processing' | 'qc' | 'approved' | 'published' | 'draft' | 'archived';
|
|
||||||
priority?: 'high' | 'medium' | 'low';
|
|
||||||
assignee?: string;
|
|
||||||
due_date?: string;
|
|
||||||
seo_score?: number;
|
|
||||||
generation_hash: string;
|
|
||||||
meta_title?: string;
|
|
||||||
meta_desc?: string;
|
|
||||||
is_published?: boolean;
|
|
||||||
sync_status?: string;
|
|
||||||
schema_json?: Record<string, any>;
|
|
||||||
is_test_batch?: boolean;
|
|
||||||
date_created?: string;
|
|
||||||
date_updated?: string;
|
|
||||||
date_published?: string;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CRM & Forms
|
|
||||||
*/
|
|
||||||
export interface Lead {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
first_name: string;
|
|
||||||
last_name?: string;
|
|
||||||
email: string;
|
|
||||||
phone?: string;
|
|
||||||
message?: string;
|
|
||||||
source?: string;
|
|
||||||
status: 'new' | 'contacted' | 'qualified' | 'lost';
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NewsletterSubscriber {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
email: string;
|
|
||||||
status: 'subscribed' | 'unsubscribed';
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Form {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
name: string;
|
|
||||||
fields: any[];
|
|
||||||
submit_action: 'message' | 'redirect' | 'both';
|
|
||||||
success_message?: string;
|
|
||||||
redirect_url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FormSubmission {
|
|
||||||
id: string;
|
|
||||||
form: string | Form;
|
|
||||||
data: Record<string, any>;
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Full Spark Platform Schema for Directus SDK
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Full Spark Platform Schema for Directus SDK
|
|
||||||
*/
|
|
||||||
export interface SparkSchema {
|
|
||||||
sites: Site[];
|
|
||||||
pages: Page[];
|
|
||||||
posts: Post[];
|
|
||||||
globals: Globals[];
|
|
||||||
navigation: Navigation[];
|
|
||||||
authors: Author[];
|
|
||||||
|
|
||||||
// SEO Engine
|
|
||||||
campaign_masters: CampaignMaster[];
|
|
||||||
headline_inventory: HeadlineInventory[];
|
|
||||||
content_fragments: ContentFragment[];
|
|
||||||
image_templates: ImageTemplate[];
|
|
||||||
locations_states: LocationState[];
|
|
||||||
locations_counties: LocationCounty[];
|
|
||||||
locations_cities: LocationCity[];
|
|
||||||
production_queue: ProductionQueueItem[];
|
|
||||||
quality_flags: QualityFlag[];
|
|
||||||
|
|
||||||
// Cartesian Engine
|
|
||||||
generation_jobs: GenerationJob[];
|
|
||||||
article_templates: ArticleTemplate[];
|
|
||||||
avatars: Avatar[];
|
|
||||||
avatar_variants: AvatarVariant[];
|
|
||||||
geo_clusters: GeoCluster[];
|
|
||||||
geo_locations: GeoLocation[];
|
|
||||||
spintax_dictionaries: SpintaxDictionary[];
|
|
||||||
cartesian_patterns: CartesianPattern[];
|
|
||||||
offer_blocks_universal: OfferBlockUniversal[];
|
|
||||||
offer_blocks_personalized: OfferBlockPersonalized[];
|
|
||||||
generated_articles: GeneratedArticle[];
|
|
||||||
|
|
||||||
// CRM & Forms
|
|
||||||
leads: Lead[];
|
|
||||||
newsletter_subscribers: NewsletterSubscriber[];
|
|
||||||
forms: Form[];
|
|
||||||
form_submissions: FormSubmission[];
|
|
||||||
|
|
||||||
// Infrastructure & Analytics
|
|
||||||
link_targets: LinkTarget[];
|
|
||||||
hub_pages: HubPage[];
|
|
||||||
work_log: WorkLog[];
|
|
||||||
events: AnalyticsEvent[];
|
|
||||||
pageviews: PageView[];
|
|
||||||
conversions: Conversion[];
|
|
||||||
site_analytics: SiteAnalyticsConfig[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProductionQueueItem {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
campaign: string | CampaignMaster;
|
|
||||||
status: 'test_batch' | 'pending' | 'active' | 'completed' | 'paused';
|
|
||||||
total_requested: number;
|
|
||||||
completed_count: number;
|
|
||||||
velocity_mode: string;
|
|
||||||
schedule_data: any[]; // JSON
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QualityFlag {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
batch_id?: string;
|
|
||||||
article_a: string;
|
|
||||||
article_b: string;
|
|
||||||
collision_text: string;
|
|
||||||
similarity_score: number;
|
|
||||||
status: 'pending' | 'resolved' | 'ignored';
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HubPage {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
parent_hub?: string | HubPage;
|
|
||||||
level: number;
|
|
||||||
articles_count: number;
|
|
||||||
schema_json?: Record<string, any>;
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnalyticsEvent {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
event_name: string;
|
|
||||||
event_category?: string;
|
|
||||||
event_label?: string;
|
|
||||||
event_value?: number;
|
|
||||||
page_path: string;
|
|
||||||
session_id?: string;
|
|
||||||
visitor_id?: string;
|
|
||||||
metadata?: Record<string, any>;
|
|
||||||
timestamp?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PageView {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
page_path: string;
|
|
||||||
page_title?: string | null;
|
|
||||||
referrer?: string | null;
|
|
||||||
user_agent?: string | null;
|
|
||||||
device_type?: string | null;
|
|
||||||
browser?: string | null;
|
|
||||||
os?: string | null;
|
|
||||||
utm_source?: string | null;
|
|
||||||
utm_medium?: string | null;
|
|
||||||
utm_campaign?: string | null;
|
|
||||||
utm_content?: string | null;
|
|
||||||
utm_term?: string | null;
|
|
||||||
is_bot?: boolean;
|
|
||||||
bot_name?: string | null;
|
|
||||||
session_id?: string | null;
|
|
||||||
visitor_id?: string | null;
|
|
||||||
timestamp?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Conversion {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
lead?: string | Lead;
|
|
||||||
conversion_type: string;
|
|
||||||
value?: number;
|
|
||||||
currency?: string;
|
|
||||||
source?: string;
|
|
||||||
campaign?: string;
|
|
||||||
gclid?: string;
|
|
||||||
fbclid?: string;
|
|
||||||
sent_to_google?: boolean;
|
|
||||||
sent_to_facebook?: boolean;
|
|
||||||
date_created?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SiteAnalyticsConfig {
|
|
||||||
id: string;
|
|
||||||
site: string | Site;
|
|
||||||
google_ads_id?: string;
|
|
||||||
google_ads_conversion_label?: string;
|
|
||||||
fb_pixel_id?: string;
|
|
||||||
fb_access_token?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkLog {
|
|
||||||
id: number;
|
|
||||||
site?: number | string; // Relaxed type
|
|
||||||
action: string;
|
|
||||||
entity_type?: string;
|
|
||||||
entity_id?: string | number;
|
|
||||||
details?: string | Record<string, any>; // Relaxed to allow JSON object
|
|
||||||
level?: string;
|
|
||||||
status?: string;
|
|
||||||
timestamp?: string;
|
|
||||||
date_created?: string;
|
|
||||||
user?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LinkTarget {
|
|
||||||
id: string;
|
|
||||||
site: string;
|
|
||||||
target_url?: string;
|
|
||||||
target_post?: string;
|
|
||||||
anchor_text: string;
|
|
||||||
anchor_variations?: string[];
|
|
||||||
priority?: number;
|
|
||||||
is_active?: boolean;
|
|
||||||
is_hub?: boolean;
|
|
||||||
max_per_article?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user