Add direct SQL deploy handler to God Mode API
This commit is contained in:
@@ -96,7 +96,7 @@ export const GET: APIRoute = async ({ request, url }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// POST /api/god/sql - Execute raw SQL
|
// POST handlers for /api/god/* endpoints
|
||||||
export const POST: APIRoute = async ({ request, url }) => {
|
export const POST: APIRoute = async ({ request, url }) => {
|
||||||
if (!validateGodToken(request)) {
|
if (!validateGodToken(request)) {
|
||||||
return json({ error: 'Unauthorized - Invalid God Mode Token' }, 401);
|
return json({ error: 'Unauthorized - Invalid God Mode Token' }, 401);
|
||||||
@@ -104,32 +104,172 @@ export const POST: APIRoute = async ({ request, url }) => {
|
|||||||
|
|
||||||
const action = url.pathname.split('/').pop();
|
const action = url.pathname.split('/').pop();
|
||||||
|
|
||||||
if (action !== 'sql') {
|
|
||||||
return json({ error: 'POST only supported for /api/god/sql' }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { query } = body;
|
|
||||||
|
|
||||||
if (!query) {
|
switch (action) {
|
||||||
return json({ error: 'Missing query in request body' }, 400);
|
case 'sql':
|
||||||
|
return await handleSqlQuery(body);
|
||||||
|
case 'deploy':
|
||||||
|
return await handleDeploy(body);
|
||||||
|
default:
|
||||||
|
return json({
|
||||||
|
error: `POST not supported for /api/god/${action}`,
|
||||||
|
available_post_actions: ['sql', 'deploy']
|
||||||
|
}, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await pool.query(query);
|
|
||||||
|
|
||||||
return json({
|
|
||||||
success: true,
|
|
||||||
command: result.command,
|
|
||||||
rowCount: result.rowCount,
|
|
||||||
rows: result.rows,
|
|
||||||
fields: result.fields?.map(f => f.name)
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return json({ error: error.message, code: error.code }, 500);
|
return json({ error: error.message, code: error.code }, 500);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle raw SQL queries
|
||||||
|
async function handleSqlQuery(body: any) {
|
||||||
|
const { query } = body;
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
return json({ error: 'Missing query in request body' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await pool.query(query);
|
||||||
|
|
||||||
|
return json({
|
||||||
|
success: true,
|
||||||
|
command: result.command,
|
||||||
|
rowCount: result.rowCount,
|
||||||
|
rows: result.rows,
|
||||||
|
fields: result.fields?.map(f => f.name)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle campaign deployment - Direct SQL, bypasses Directus
|
||||||
|
async function handleDeploy(payload: any) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
const results: any = {
|
||||||
|
success: false,
|
||||||
|
workflow: { steps_completed: 0, steps_total: 5 },
|
||||||
|
created: {},
|
||||||
|
errors: []
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { deployment_instruction, deployment_config, deployment_data } = payload;
|
||||||
|
|
||||||
|
if (!deployment_data) {
|
||||||
|
return json({ error: 'Missing deployment_data in payload' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate UUIDs for new records
|
||||||
|
const crypto = await import('crypto');
|
||||||
|
const siteId = crypto.randomUUID();
|
||||||
|
const templateId = crypto.randomUUID();
|
||||||
|
const campaignId = crypto.randomUUID();
|
||||||
|
|
||||||
|
// Step 1: Create Site
|
||||||
|
if (deployment_data.site_setup) {
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO sites (id, name, url, status, date_created)
|
||||||
|
VALUES ($1, $2, $3, $4, NOW())
|
||||||
|
ON CONFLICT (id) DO UPDATE SET name = $2, url = $3, status = $4
|
||||||
|
`, [
|
||||||
|
siteId,
|
||||||
|
deployment_data.site_setup.name,
|
||||||
|
deployment_data.site_setup.url,
|
||||||
|
deployment_data.site_setup.status || 'active'
|
||||||
|
]);
|
||||||
|
results.created.site_id = siteId;
|
||||||
|
results.workflow.steps_completed = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Create Article Template
|
||||||
|
if (deployment_data.article_template) {
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO article_templates (id, name, structure_json, date_created)
|
||||||
|
VALUES ($1, $2, $3, NOW())
|
||||||
|
ON CONFLICT (id) DO UPDATE SET name = $2, structure_json = $3
|
||||||
|
`, [
|
||||||
|
templateId,
|
||||||
|
deployment_data.article_template.name,
|
||||||
|
JSON.stringify(deployment_data.article_template.structure_json)
|
||||||
|
]);
|
||||||
|
results.created.template_id = templateId;
|
||||||
|
}
|
||||||
|
results.workflow.steps_completed = 2;
|
||||||
|
|
||||||
|
// Step 3: Create Campaign Master
|
||||||
|
if (deployment_data.campaign_master) {
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO campaign_masters (id, site_id, name, target_word_count, location_mode, niche_variables, article_template, status, date_created)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
|
||||||
|
`, [
|
||||||
|
campaignId,
|
||||||
|
siteId,
|
||||||
|
deployment_data.campaign_master.name,
|
||||||
|
deployment_data.campaign_master.target_word_count || 2200,
|
||||||
|
deployment_data.campaign_master.location_mode || 'city',
|
||||||
|
JSON.stringify(deployment_data.campaign_master.niche_variables),
|
||||||
|
templateId,
|
||||||
|
'active'
|
||||||
|
]);
|
||||||
|
results.created.campaign_id = campaignId;
|
||||||
|
}
|
||||||
|
results.workflow.steps_completed = 3;
|
||||||
|
|
||||||
|
// Step 4: Import Headlines
|
||||||
|
if (deployment_data.headline_inventory?.length > 0) {
|
||||||
|
let headlinesCreated = 0;
|
||||||
|
for (const headline of deployment_data.headline_inventory) {
|
||||||
|
const headlineId = crypto.randomUUID();
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO headline_inventory (id, campaign_id, headline_text, status, location_data, date_created)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
||||||
|
`, [
|
||||||
|
headlineId,
|
||||||
|
campaignId,
|
||||||
|
headline.headline_text,
|
||||||
|
headline.status || 'available',
|
||||||
|
JSON.stringify(headline.location_data)
|
||||||
|
]);
|
||||||
|
headlinesCreated++;
|
||||||
|
}
|
||||||
|
results.created.headlines_created = headlinesCreated;
|
||||||
|
}
|
||||||
|
results.workflow.steps_completed = 4;
|
||||||
|
|
||||||
|
// Step 5: Import Content Fragments
|
||||||
|
if (deployment_data.content_fragments?.length > 0) {
|
||||||
|
let fragmentsCreated = 0;
|
||||||
|
for (const fragment of deployment_data.content_fragments) {
|
||||||
|
const fragmentId = crypto.randomUUID();
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO content_fragments (id, campaign_id, fragment_type, content_body, word_count, status, date_created)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
||||||
|
`, [
|
||||||
|
fragmentId,
|
||||||
|
campaignId,
|
||||||
|
fragment.type,
|
||||||
|
fragment.content,
|
||||||
|
fragment.word_count || 0,
|
||||||
|
'active'
|
||||||
|
]);
|
||||||
|
fragmentsCreated++;
|
||||||
|
}
|
||||||
|
results.created.fragments_imported = fragmentsCreated;
|
||||||
|
}
|
||||||
|
results.workflow.steps_completed = 5;
|
||||||
|
|
||||||
|
results.success = true;
|
||||||
|
results.execution_time = `${((Date.now() - startTime) / 1000).toFixed(2)}s`;
|
||||||
|
results.message = `Campaign "${deployment_data.campaign_master?.name}" deployed successfully via direct SQL`;
|
||||||
|
|
||||||
|
return json(results);
|
||||||
|
} catch (error: any) {
|
||||||
|
results.error = error.message;
|
||||||
|
results.code = error.code;
|
||||||
|
return json(results, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Quick service status check
|
// Quick service status check
|
||||||
async function getServices() {
|
async function getServices() {
|
||||||
const services: Record<string, any> = {
|
const services: Record<string, any> = {
|
||||||
|
|||||||
Reference in New Issue
Block a user