feat: Reorganize Directus UI + add publish-to-site API for SEO engine
This commit is contained in:
198
frontend/src/pages/api/seo/publish-article.ts
Normal file
198
frontend/src/pages/api/seo/publish-article.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
// @ts-ignore - Astro types available at build time
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getDirectusClient, readItem, readItems, createItem, updateItem } from '@/lib/directus/client';
|
||||
import { generateFeaturedImage } from '@/lib/seo/image-generator';
|
||||
|
||||
/**
|
||||
* Publish Article to Site API
|
||||
*
|
||||
* Takes a generated article from the SEO engine and creates a post on the target site.
|
||||
*
|
||||
* POST /api/seo/publish-article
|
||||
*/
|
||||
export const POST: APIRoute = async ({ request }: { request: Request }) => {
|
||||
try {
|
||||
const data = await request.json();
|
||||
const { article_id, site_id, status = 'draft' } = data;
|
||||
|
||||
if (!article_id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'article_id is required' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
const directus = getDirectusClient();
|
||||
|
||||
// Get the generated article
|
||||
const article = await directus.request(
|
||||
readItem('generated_articles', article_id)
|
||||
) as any;
|
||||
|
||||
if (!article) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Article not found' }),
|
||||
{ status: 404, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if already published
|
||||
if (article.published_to_post) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Article already published',
|
||||
post_id: article.published_to_post
|
||||
}),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Use provided site_id or fall back to article's site
|
||||
const targetSiteId = site_id || article.site;
|
||||
|
||||
// Generate slug from headline
|
||||
const slug = article.headline
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.substring(0, 100);
|
||||
|
||||
// Create the post
|
||||
const post = await directus.request(
|
||||
createItem('posts', {
|
||||
site: targetSiteId,
|
||||
title: article.headline,
|
||||
slug: slug,
|
||||
status: status, // 'draft' or 'published'
|
||||
content: article.full_html_body,
|
||||
excerpt: article.meta_description,
|
||||
meta_title: article.meta_title,
|
||||
meta_description: article.meta_description,
|
||||
featured_image_alt: article.featured_image_alt,
|
||||
source: 'seo_engine',
|
||||
source_article_id: article_id,
|
||||
robots: 'index,follow',
|
||||
schema_type: 'BlogPosting',
|
||||
// Location data for local SEO
|
||||
meta_keywords: [
|
||||
article.location_city,
|
||||
article.location_state
|
||||
].filter(Boolean).join(', ')
|
||||
})
|
||||
) as any;
|
||||
|
||||
// Update the generated article with publish info
|
||||
await directus.request(
|
||||
updateItem('generated_articles', article_id, {
|
||||
publish_status: status === 'published' ? 'published' : 'ready',
|
||||
published_to_post: post.id,
|
||||
published_at: new Date().toISOString(),
|
||||
published_url: `/${slug}`
|
||||
})
|
||||
);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
post_id: post.id,
|
||||
slug: slug,
|
||||
status: status,
|
||||
message: `Article published as ${status} post`
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error publishing article:', error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to publish article' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bulk publish multiple articles
|
||||
*
|
||||
* POST /api/seo/publish-article with { article_ids: [...] }
|
||||
*/
|
||||
export const PUT: APIRoute = async ({ request }: { request: Request }) => {
|
||||
try {
|
||||
const data = await request.json();
|
||||
const { article_ids, site_id, status = 'draft' } = data;
|
||||
|
||||
if (!article_ids || !Array.isArray(article_ids) || article_ids.length === 0) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'article_ids array is required' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
const directus = getDirectusClient();
|
||||
const results: { article_id: string; post_id?: string; error?: string }[] = [];
|
||||
|
||||
for (const articleId of article_ids) {
|
||||
try {
|
||||
const article = await directus.request(
|
||||
readItem('generated_articles', articleId)
|
||||
) as any;
|
||||
|
||||
if (!article || article.published_to_post) {
|
||||
results.push({ article_id: articleId, error: 'Already published or not found' });
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetSiteId = site_id || article.site;
|
||||
const slug = article.headline
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.substring(0, 100);
|
||||
|
||||
const post = await directus.request(
|
||||
createItem('posts', {
|
||||
site: targetSiteId,
|
||||
title: article.headline,
|
||||
slug: slug,
|
||||
status: status,
|
||||
content: article.full_html_body,
|
||||
excerpt: article.meta_description,
|
||||
meta_title: article.meta_title,
|
||||
meta_description: article.meta_description,
|
||||
source: 'seo_engine',
|
||||
source_article_id: articleId
|
||||
})
|
||||
) as any;
|
||||
|
||||
await directus.request(
|
||||
updateItem('generated_articles', articleId, {
|
||||
publish_status: 'published',
|
||||
published_to_post: post.id,
|
||||
published_at: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
|
||||
results.push({ article_id: articleId, post_id: post.id });
|
||||
} catch (err) {
|
||||
results.push({ article_id: articleId, error: 'Failed to publish' });
|
||||
}
|
||||
}
|
||||
|
||||
const successCount = results.filter(r => r.post_id).length;
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
published: successCount,
|
||||
total: article_ids.length,
|
||||
results
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error bulk publishing:', error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to bulk publish' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user