Phase 1: Foundation & Stability Infrastructure

 BullMQ job queue system installed and configured
 Zod validation schemas for all collections
 Spintax validator with integrity checks
 Work log helper for centralized logging
 Transaction wrapper for safe database operations
 Batch operation utilities with rate limiting
 Circuit breaker for WordPress/Directus resilience
 Dry-run mode for preview generation
 Version management system
 Environment configuration

This establishes the bulletproof infrastructure for Spark Alpha.
This commit is contained in:
cawcenter
2025-12-13 12:12:17 -05:00
parent 3e5eba4a1f
commit fd9f428dcd
50 changed files with 22559 additions and 3 deletions

334
scripts/improve_ux.js Normal file
View File

@@ -0,0 +1,334 @@
#!/usr/bin/env node
/**
* Directus UX Improvement Script
* Fixes field interfaces to make admin UI more user-friendly
*/
const DIRECTUS_URL = 'https://spark.jumpstartscaling.com';
const ADMIN_TOKEN = 'SufWLAbsqmbbqF_gg5I70ng8wE1zXt-a';
async function makeRequest(endpoint, method = 'GET', body = null) {
const options = {
method,
headers: {
'Authorization': `Bearer ${ADMIN_TOKEN}`,
'Content-Type': 'application/json'
}
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${DIRECTUS_URL}${endpoint}`, options);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API Error: ${response.status} - ${errorText}`);
}
return response.json();
}
async function updateField(collection, field, updates) {
try {
await makeRequest(`/fields/${collection}/${field}`, 'PATCH', updates);
console.log(` ✅ Updated ${collection}.${field}`);
return true;
} catch (err) {
console.log(` ❌ Failed to update ${collection}.${field}: ${err.message}`);
return false;
}
}
async function improveUX() {
console.log('🎨 DIRECTUS UX IMPROVEMENTS\n');
console.log('═'.repeat(60));
let successCount = 0;
let failCount = 0;
// Fix 1: Make all site_id fields use select-dropdown-m2o
console.log('\n1⃣ Fixing site_id relationships...\n');
const siteIdFields = [
{ collection: 'posts', field: 'site_id' },
{ collection: 'campaign_masters', field: 'site_id' },
{ collection: 'leads', field: 'site_id' }
];
for (const { collection, field } of siteIdFields) {
const success = await updateField(collection, field, {
meta: {
interface: 'select-dropdown-m2o',
options: {
template: '{{name}}'
},
display: 'related-values',
display_options: {
template: '{{name}}'
}
}
});
success ? successCount++ : failCount++;
}
// Fix 2: Make campaign_id fields use select-dropdown-m2o
console.log('\n2⃣ Fixing campaign_id relationships...\n');
const campaignIdFields = [
{ collection: 'content_fragments', field: 'campaign_id' },
{ collection: 'generated_articles', field: 'campaign_id' },
{ collection: 'headline_inventory', field: 'campaign_id' }
];
for (const { collection, field } of campaignIdFields) {
const success = await updateField(collection, field, {
meta: {
interface: 'select-dropdown-m2o',
options: {
template: '{{campaign_name}}'
},
display: 'related-values',
display_options: {
template: '{{campaign_name}}'
}
}
});
success ? successCount++ : failCount++;
}
// Fix 3: Make status fields use select-dropdown
console.log('\n3⃣ Fixing status fields...\n');
const statusFields = [
{
collection: 'sites',
field: 'status',
choices: {
active: 'Active',
inactive: 'Inactive',
testing: 'Testing'
}
},
{
collection: 'campaign_masters',
field: 'status',
choices: {
active: 'Active',
paused: 'Paused',
completed: 'Completed',
draft: 'Draft'
}
},
{
collection: 'generation_jobs',
field: 'status',
choices: {
pending: 'Pending',
running: 'Running',
completed: 'Completed',
failed: 'Failed',
paused: 'Paused'
}
},
{
collection: 'headline_inventory',
field: 'status',
choices: {
active: 'Active',
archived: 'Archived'
}
}
];
for (const { collection, field, choices } of statusFields) {
const success = await updateField(collection, field, {
meta: {
interface: 'select-dropdown',
options: {
choices
},
display: 'labels',
display_options: {
choices,
showAsDot: true
}
}
});
success ? successCount++ : failCount++;
}
// Enhancement 1: Improve avatar_key fields
console.log('\n4⃣ Improving avatar selection fields...\n');
const avatarFields = [
{ collection: 'posts', field: 'avatar_key' },
{ collection: 'offer_blocks', field: 'avatar_key' }
];
for (const { collection, field } of avatarFields) {
const success = await updateField(collection, field, {
meta: {
interface: 'select-dropdown',
width: 'half',
note: 'Select avatar persona for content generation',
options: {
allowNone: true,
placeholder: 'Choose avatar...'
}
}
});
success ? successCount++ : failCount++;
}
// Enhancement 2: Improve JSON fields
console.log('\n5⃣ Improving JSON editor fields...\n');
const jsonFields = [
{ collection: 'posts', field: 'schema_json' },
{ collection: 'pages', field: 'schema_json' },
{ collection: 'article_templates', field: 'structure_json' },
{ collection: 'link_targets', field: 'anchor_variations' },
{ collection: 'spintax_dictionaries', field: 'data' },
{ collection: 'offer_blocks', field: 'data' },
{ collection: 'cartesian_patterns', field: 'pattern_json' }
];
for (const { collection, field } of jsonFields) {
const success = await updateField(collection, field, {
meta: {
interface: 'input-code',
options: {
language: 'json',
lineNumber: true,
template: '{}'
}
}
});
success ? successCount++ : failCount++;
}
// Enhancement 3: Improve text areas
console.log('\n6⃣ Improving text content fields...\n');
const textFields = [
{ collection: 'posts', field: 'content' },
{ collection: 'posts', field: 'excerpt' },
{ collection: 'pages', field: 'content' },
{ collection: 'generated_articles', field: 'html_content' }
];
for (const { collection, field } of textFields) {
const success = await updateField(collection, field, {
meta: {
interface: 'input-rich-text-html',
options: {
toolbar: [
'bold',
'italic',
'underline',
'h1',
'h2',
'h3',
'numlist',
'bullist',
'link',
'code',
'removeformat'
]
}
}
});
success ? successCount++ : failCount++;
}
// Enhancement 4: Improve date fields
console.log('\n7⃣ Improving date/time fields...\n');
const dateFields = [
{ collection: 'posts', field: 'created_at' },
{ collection: 'posts', field: 'published_at' },
{ collection: 'pages', field: 'created_at' },
{ collection: 'sites', field: 'created_at' },
{ collection: 'sites', field: 'updated_at' }
];
for (const { collection, field } of dateFields) {
const success = await updateField(collection, field, {
meta: {
interface: 'datetime',
display: 'datetime',
display_options: {
relative: true
},
readonly: field.includes('created') || field.includes('updated')
}
});
success ? successCount++ : failCount++;
}
// Enhancement 5: Add helpful notes and placeholders
console.log('\n8⃣ Adding field descriptions and placeholders...\n');
const fieldNotes = [
{
collection: 'sites',
field: 'wp_username',
note: 'WordPress admin username for API access',
placeholder: 'admin'
},
{
collection: 'sites',
field: 'wp_app_password',
note: 'WordPress Application Password (not regular password)',
placeholder: 'xxxx xxxx xxxx xxxx'
},
{
collection: 'generation_jobs',
field: 'target_quantity',
note: 'Number of articles to generate in this job'
},
{
collection: 'generated_articles',
field: 'meta_desc',
note: 'SEO meta description (150-160 characters)',
placeholder: 'Compelling description for search results...'
}
];
for (const { collection, field, note, placeholder } of fieldNotes) {
const updates = { meta: {} };
if (note) updates.meta.note = note;
if (placeholder) updates.meta.options = { placeholder };
const success = await updateField(collection, field, updates);
success ? successCount++ : failCount++;
}
// Summary
console.log('\n\n═'.repeat(60));
console.log('📊 IMPROVEMENT SUMMARY');
console.log('═'.repeat(60));
console.log(`✅ Successful updates: ${successCount}`);
console.log(`❌ Failed updates: ${failCount}`);
console.log(`📈 Success rate: ${Math.round((successCount / (successCount + failCount)) * 100)}%`);
console.log('═'.repeat(60) + '\n');
return { successCount, failCount };
}
// Run improvements
improveUX()
.then(({ successCount, failCount }) => {
if (failCount === 0) {
console.log('🎉 All improvements applied successfully!\n');
process.exit(0);
} else {
console.log('⚠️ Some improvements failed. Check output above.\n');
process.exit(1);
}
})
.catch(err => {
console.error('❌ Improvement script failed:', err.message);
process.exit(1);
});