Integra estrategias LinkedIn en calendarios (backlog publicado vs pendiente), unifica roles/cookies de vista agencia y permite deshacer cambios de brandbook dejando constancia en el historial. Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
3.4 KiB
TypeScript
117 lines
3.4 KiB
TypeScript
/**
|
|
* GET — resolve LinkedIn strategy URL + summary for active tenant
|
|
* (brandbook URL, known slug map, optional ?url= override).
|
|
* Used by CalendarCreator / zoom to show associated strategy content.
|
|
*/
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { createClient } from '@/lib/supabase/server';
|
|
import { resolveActiveTenant, restGet } from '@/lib/tenant-context';
|
|
import { isAgencyRole, userRole } from '@/lib/agency';
|
|
import {
|
|
BRANDBOOK_DOC_TYPE,
|
|
parseBrandbook,
|
|
} from '@/lib/brandbook';
|
|
import {
|
|
loadLinkedInStrategyContext,
|
|
resolveLinkedInStrategyUrl,
|
|
} from '@/lib/linkedin-strategy';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
export const maxDuration = 45;
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const supabase = await createClient();
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
if (!isAgencyRole(userRole(user))) {
|
|
return NextResponse.json({ error: 'Solo agencia' }, { status: 403 });
|
|
}
|
|
const ctx = await resolveActiveTenant(user);
|
|
if (!ctx.tenantId || ctx.portfolioWide) {
|
|
return NextResponse.json(
|
|
{ error: 'Selecciona un cliente en Contexto.' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const override = req.nextUrl.searchParams.get('url') || '';
|
|
const postId = req.nextUrl.searchParams.get('post_id') || '';
|
|
|
|
let slug = 'cliente';
|
|
const t = await restGet<{ slug?: string }[]>(
|
|
`/rest/v1/tenants?id=eq.${encodeURIComponent(ctx.tenantId)}&select=slug`
|
|
);
|
|
slug = t.data?.[0]?.slug || ctx.label.toLowerCase().replace(/\s+/g, '-');
|
|
|
|
const bb = await restGet<{ content?: unknown }[]>(
|
|
`/rest/v1/documents?tenant_id=eq.${encodeURIComponent(
|
|
ctx.tenantId
|
|
)}&type=eq.${BRANDBOOK_DOC_TYPE}&select=content&order=updated_at.desc&limit=1`
|
|
);
|
|
const brandbook = parseBrandbook(bb.data?.[0]?.content);
|
|
|
|
const url = resolveLinkedInStrategyUrl({
|
|
brandbook,
|
|
slug,
|
|
overrideUrl: override,
|
|
});
|
|
|
|
if (!url) {
|
|
return NextResponse.json({
|
|
ok: true,
|
|
url: null,
|
|
brandbook_url: brandbook?.linkedin_strategy_url || null,
|
|
posts: [],
|
|
themes: brandbook?.linkedin_themes || [],
|
|
formats: brandbook?.linkedin_formats || [],
|
|
ctas: brandbook?.linkedin_cta || [],
|
|
message: 'Sin URL de estrategia LinkedIn en brandbook ni mapa conocido.',
|
|
});
|
|
}
|
|
|
|
const ctxLi = await loadLinkedInStrategyContext({
|
|
url,
|
|
brandbook,
|
|
mode: 'from_strategy',
|
|
});
|
|
|
|
const posts = (ctxLi?.posts || []).map((p) => ({
|
|
id: p.id,
|
|
n: p.n,
|
|
title: p.title,
|
|
meta: p.meta,
|
|
angle: p.angle,
|
|
body_preview: (p.body || '').slice(0, 500),
|
|
body: postId && p.id === postId ? p.body : undefined,
|
|
}));
|
|
|
|
let selected = postId
|
|
? ctxLi?.posts.find((p) => p.id === postId) || null
|
|
: null;
|
|
|
|
return NextResponse.json({
|
|
ok: true,
|
|
url,
|
|
brandbook_url: brandbook?.linkedin_strategy_url || null,
|
|
posts_count: ctxLi?.posts.length || 0,
|
|
posts: posts.slice(0, 80),
|
|
selected: selected
|
|
? {
|
|
id: selected.id,
|
|
title: selected.title,
|
|
meta: selected.meta,
|
|
angle: selected.angle,
|
|
body: selected.body,
|
|
}
|
|
: null,
|
|
themes: brandbook?.linkedin_themes || [],
|
|
formats: brandbook?.linkedin_formats || [],
|
|
ctas: brandbook?.linkedin_cta || [],
|
|
thought_leadership: brandbook?.thought_leadership || [],
|
|
});
|
|
}
|