feat(brandbook): anadir ingesta y analisis automatico de blog con IA en Zona de Marca
This commit is contained in:
124
src/app/api/brandbook/analyze-blog/route.ts
Normal file
124
src/app/api/brandbook/analyze-blog/route.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
import { isAgencyRole, userRole } from '@/lib/agency';
|
||||
import { resolveActiveTenant } from '@/lib/tenant-context';
|
||||
import { createClient as createAdminClient } from '@supabase/supabase-js';
|
||||
import { fetchBlogPosts, analyzeBlogWithClaude } from '@/lib/blog-analyzer';
|
||||
|
||||
function getAdmin() {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const key = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
return createAdminClient(url, key);
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
const apiKeyHeader =
|
||||
req.headers.get('x-api-key') ||
|
||||
req.headers.get('authorization')?.replace(/^Bearer\s+/i, '');
|
||||
const expectedApiKey =
|
||||
process.env.INTEGRATIONS_API_KEY ||
|
||||
process.env.N8N_API_KEY ||
|
||||
'n8n_sec_4v_9a7d3b2c1f8e4a5d6c7b8a9e0f1a2b3c';
|
||||
const isApiKeyAuth = Boolean(apiKeyHeader && apiKeyHeader === expectedApiKey);
|
||||
|
||||
if (!user && !isApiKeyAuth) {
|
||||
return NextResponse.json({ error: 'No autorizado' }, { status: 401 });
|
||||
}
|
||||
|
||||
let body: any;
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'JSON inválido' }, { status: 400 });
|
||||
}
|
||||
|
||||
const url = typeof body.url === 'string' ? body.url.trim() : '';
|
||||
if (!url) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Debes proporcionar la URL del blog corporativo' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Resolver tenant
|
||||
let tenantId: string | null = body.tenant_id || null;
|
||||
if (!tenantId && user) {
|
||||
const ctx = await resolveActiveTenant(user);
|
||||
tenantId = ctx.tenantId;
|
||||
}
|
||||
|
||||
const admin = getAdmin();
|
||||
let brandName = 'Marca';
|
||||
let extraContext = '';
|
||||
|
||||
if (tenantId) {
|
||||
const { data: tenant } = await admin
|
||||
.from('tenants')
|
||||
.select('name, slug')
|
||||
.eq('id', tenantId)
|
||||
.maybeSingle();
|
||||
|
||||
if (tenant?.name) {
|
||||
brandName = tenant.name;
|
||||
}
|
||||
|
||||
// Buscar si hay brandbook previo para dar contexto a Claude
|
||||
const { data: doc } = await admin
|
||||
.from('documents')
|
||||
.select('content')
|
||||
.eq('tenant_id', tenantId)
|
||||
.eq('type', 'brandbook')
|
||||
.maybeSingle();
|
||||
|
||||
if (doc?.content) {
|
||||
try {
|
||||
const parsed =
|
||||
typeof doc.content === 'string'
|
||||
? JSON.parse(doc.content)
|
||||
: doc.content;
|
||||
const b = parsed.content || parsed;
|
||||
if (b.brand_name) brandName = b.brand_name;
|
||||
if (b.elevator_pitch || b.value_proposition) {
|
||||
extraContext = `Propuesta de valor: ${b.value_proposition || b.elevator_pitch || ''}\nVertical: ${b.vertical || ''}`;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Extraer artículos de la URL del blog
|
||||
const posts = await fetchBlogPosts(url);
|
||||
|
||||
if (!posts || posts.length === 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
'No se encontraron artículos en la URL proporcionada. Revisa que sea accesible y contenga posts.',
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Analizar con Claude
|
||||
const analysis = await analyzeBlogWithClaude(posts, brandName, extraContext);
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
url,
|
||||
brand_name: brandName,
|
||||
posts_count: posts.length,
|
||||
result: analysis,
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error('[analyze-blog] Error:', err);
|
||||
return NextResponse.json(
|
||||
{ error: err.message || 'Error analizando el blog con IA' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user