Refactor Directus types: rename to schemas.ts, fix imports, and resolve type errors

This commit is contained in:
cawcenter
2025-12-14 12:48:08 -05:00
parent 99f406e998
commit a74a4e946d
15 changed files with 716 additions and 915 deletions

View File

@@ -21,7 +21,7 @@ export default function JobLaunchpad() {
const client = getDirectusClient();
try {
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'));
setSites(s);
@@ -59,7 +59,7 @@ export default function JobLaunchpad() {
const job = await client.request(createItem('generation_jobs', {
site_id: selectedSite,
target_quantity: targetQuantity,
status: 'Pending',
status: 'pending',
filters: {
avatars: selectedAvatars,
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)}
>
<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>
</div>

View File

@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
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() {
const [stats, setStats] = useState({ total: 0, published: 0, processing: 0 });
@@ -58,21 +58,21 @@ export default function ContentFactoryDashboard() {
sort: ['-date_created'],
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)
const recentJobs = await client.request(readItems('generation_jobs', {
limit: 5,
sort: ['-date_created']
}));
setJobs(recentJobs as GenerationJob[]);
setJobs(recentJobs as unknown as GenerationJob[]);
// 4. Fetch Work Log
const recentLogs = await client.request(readItems('work_log', {
limit: 20,
sort: ['-date_created']
}));
setLogs(recentLogs as WorkLog[]);
setLogs(recentLogs as unknown as WorkLog[]);
setLoading(false);
} catch (error) {

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { getDirectusClient, readItems } from '@/lib/directus/client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Page } from '@/types/schema'; // Ensure exported
import { Pages as Page } from '@/lib/schemas';
export default function PageList() {
const [pages, setPages] = useState<Page[]>([]);
@@ -30,7 +30,7 @@ export default function PageList() {
<CardHeader className="p-4 flex flex-row items-center justify-between">
<div>
<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 className="flex items-center gap-3">
<Badge variant="outline" className="text-slate-400 border-slate-600">

View File

@@ -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.
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Post } from '@/types/schema';
import { Posts as Post } from '@/lib/schemas';
export default function PostList() {
const [posts, setPosts] = useState<Post[]>([]);
@@ -52,14 +52,11 @@ export default function PostList() {
{post.status}
</Badge>
</td>
<td className="px-6 py-4">
{new Date(post.date_created || '').toLocaleDateString()}
</td>
</tr>
))}
{posts.length === 0 && (
<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.
</td>
</tr>

View File

@@ -80,7 +80,7 @@ export default function CampaignWizard({ onComplete, onCancel }: CampaignWizardP
onChange={e => setFormData({ ...formData, site: e.target.value })}
>
<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>
</div>
<div className="grid grid-cols-2 gap-4 pt-2">

View File

@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Badge } from '@/components/ui/badge';
import { Site } from '@/types/schema';
import { Sites as Site } from '@/lib/schemas';
import DomainSetupGuide from '@/components/admin/DomainSetupGuide';
interface SiteEditorProps {
@@ -33,12 +33,12 @@ export default function SiteEditor({ id }: SiteEditorProps) {
try {
const client = getDirectusClient();
// @ts-ignore
const s = await client.request(readItem('sites', id));
setSite(s as Site);
const result = await client.request(readItem('sites', id));
setSite(result as unknown as Site);
// Merge settings into defaults
if (s.settings) {
setFeatures(prev => ({ ...prev, ...s.settings }));
if (result.settings) {
setFeatures(prev => ({ ...prev, ...(result.settings as Record<string, any>) }));
}
} catch (e) {
console.error(e);
@@ -57,7 +57,7 @@ export default function SiteEditor({ id }: SiteEditorProps) {
// @ts-ignore
await client.request(updateItem('sites', id, {
name: site.name,
domain: site.domain,
url: site.url,
status: site.status,
settings: features
}));
@@ -97,8 +97,8 @@ export default function SiteEditor({ id }: SiteEditorProps) {
<div className="space-y-2">
<Label>Domain</Label>
<Input
value={site.domain}
onChange={(e) => setSite({ ...site, domain: e.target.value })}
value={site.url || ''}
onChange={(e) => setSite({ ...site, url: e.target.value })}
className="bg-slate-900 border-slate-700 font-mono text-blue-400"
placeholder="example.com"
/>
@@ -206,7 +206,7 @@ export default function SiteEditor({ id }: SiteEditorProps) {
</Card>
{/* Domain Setup Guide */}
<DomainSetupGuide siteDomain={site.domain} />
<DomainSetupGuide siteDomain={site.url} />
<div className="flex justify-end gap-4">
<Button variant="outline" onClick={() => window.history.back()}>

View File

@@ -3,7 +3,7 @@ import { getDirectusClient, readItems } from '@/lib/directus/client';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Site } from '@/types/schema';
import { Sites as Site } from '@/lib/schemas';
export default function SiteList() {
const [sites, setSites] = useState<Site[]>([]);
@@ -15,7 +15,7 @@ export default function SiteList() {
const client = getDirectusClient();
// @ts-ignore
const s = await client.request(readItems('sites'));
setSites(s as Site[]);
setSites(s as unknown as Site[]);
} catch (e) {
console.error(e);
} finally {
@@ -40,9 +40,9 @@ export default function SiteList() {
</Badge>
</CardHeader>
<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">
{site.domain ? '🟢 Domain configured' : '⚠️ Set up domain'}
{site.url ? '🟢 Site configured' : '⚠️ Set up site URL'}
</p>
<div className="mt-4 flex gap-2">
<Button
@@ -62,8 +62,8 @@ export default function SiteList() {
className="flex-1"
onClick={(e) => {
e.stopPropagation();
if (site.domain) {
window.open(`https://${site.domain}`, '_blank');
if (site.url) {
window.open(`https://${site.url || 'No URL'}`, '_blank');
} else {
alert('Set up a domain first in site settings');
}

View File

@@ -16,7 +16,7 @@ const client = getDirectusClient();
interface Site {
id: string;
name: string;
domain: string;
url: string;
status: 'active' | 'inactive';
settings?: any;
}
@@ -89,14 +89,14 @@ export default function SitesManager() {
</Badge>
</CardHeader>
<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">
<Globe className="h-3 w-3 mr-1" />
deployed via Launchpad
</p>
</CardContent>
<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
</Button>
<div className="flex gap-2">
@@ -148,8 +148,8 @@ export default function SitesManager() {
<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>
<Input
value={editingSite.domain || ''}
onChange={e => setEditingSite({ ...editingSite, domain: e.target.value })}
value={editingSite.url || ''}
onChange={e => setEditingSite({ ...editingSite, url: e.target.value })}
placeholder="example.com"
className="rounded-l-none bg-zinc-950 border-zinc-800"
/>

View File

@@ -1,10 +1,10 @@
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';
export const directus = createDirectus<SparkSchema>(DIRECTUS_URL)
.with(authentication('cookie', { autoRefresh: true, mode: 'json' }))
export const directus = createDirectus<DirectusSchema>(DIRECTUS_URL)
.with(authentication('cookie', { autoRefresh: true }))
.with(rest())
.with(realtime());

View File

@@ -10,8 +10,10 @@ import {
deleteItem,
aggregate
} 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';
// 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
: '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';
// 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
*/
export function getDirectusClient(token?: string) {
const client = createDirectus<SparkSchema>(DIRECTUS_URL).with(rest());
export function getDirectusClient(token?: string): DirectusClient<DirectusSchema> & RestClient<DirectusSchema> {
const client = createDirectus<DirectusSchema>(DIRECTUS_URL).with(rest());
if (token || DIRECTUS_TOKEN) {
return client.with(staticToken(token || DIRECTUS_TOKEN));
}
return client;
}

View File

@@ -1,5 +1,6 @@
import { getDirectusClient, readItems, readItem, readSingleton, aggregate } from './client';
import type { Page, Post, Site, Globals, Navigation } from '@/types/schema';
import { getDirectusClient } from './client';
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();
@@ -13,7 +14,7 @@ export async function fetchPageByPermalink(
): Promise<Page | null> {
const filter: Record<string, any> = {
permalink: { _eq: permalink },
site: { _eq: siteId }
site_id: { _eq: siteId }
};
if (!options?.preview) {
@@ -29,7 +30,7 @@ export async function fetchPageByPermalink(
'id',
'title',
'permalink',
'site',
'site_id',
'status',
'seo_title',
'seo_description',
@@ -54,12 +55,12 @@ export async function fetchSiteGlobals(siteId: string): Promise<Globals | null>
try {
const globals = await directus.request(
readItems('globals', {
filter: { site: { _eq: siteId } },
filter: { site_id: { _eq: siteId } },
limit: 1,
fields: ['*']
})
);
return globals?.[0] || null;
return (globals as unknown as Globals[])?.[0] || null;
} catch (err) {
console.error('Error fetching globals:', err);
return null;
@@ -73,12 +74,12 @@ export async function fetchNavigation(siteId: string): Promise<Partial<Navigatio
try {
const nav = await directus.request(
readItems('navigation', {
filter: { site: { _eq: siteId } },
filter: { site_id: { _eq: siteId } },
sort: ['sort'],
fields: ['id', 'label', 'url', 'parent', 'target', 'sort']
})
);
return nav || [];
return (nav as unknown as Navigation[]) || [];
} catch (err) {
console.error('Error fetching navigation:', err);
return [];
@@ -97,7 +98,7 @@ export async function fetchPosts(
const offset = (page - 1) * limit;
const filter: Record<string, any> = {
site: { _eq: siteId }, // siteId is UUID string
site_id: { _eq: siteId }, // siteId is UUID string
status: { _eq: 'published' }
};
@@ -122,7 +123,7 @@ export async function fetchPosts(
'published_at',
'category',
'author',
'site',
'site_id',
'status',
'content'
]
@@ -158,7 +159,7 @@ export async function fetchPostBySlug(
readItems('posts', {
filter: {
slug: { _eq: slug },
site: { _eq: siteId },
site_id: { _eq: siteId },
status: { _eq: 'published' }
},
limit: 1,
@@ -247,8 +248,8 @@ export async function fetchCampaigns(siteId?: string) {
const filter: Record<string, any> = {};
if (siteId) {
filter._or = [
{ site: { _eq: siteId } },
{ site: { _null: true } }
{ site_id: { _eq: siteId } },
{ site_id: { _null: true } }
];
}

386
frontend/src/lib/schemas.ts Normal file
View 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>>;

View File

@@ -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;
}