Add in-app editorial calendar creator with month zoom and preview.
Agency can pick a month, generate a brand/seasonality preview, and add/edit/delete pieces with the same fields as the creative pipeline, optionally syncing drafts there. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
73
src/app/api/calendar/preview/route.ts
Normal file
73
src/app/api/calendar/preview/route.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* POST { month: 'YYYY-MM' } → editorial preview for active tenant.
|
||||
*/
|
||||
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 { asPlanObject } from '@/lib/calendar-documents';
|
||||
import { generateMonthPreview } from '@/lib/calendar-generate';
|
||||
import type { RrssMonthPlan } from '@/components/RrssMonthView';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const maxDuration = 60;
|
||||
|
||||
export async function POST(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 body = (await req.json().catch(() => ({}))) as { month?: string };
|
||||
const month = String(body.month || '').slice(0, 7);
|
||||
if (!/^\d{4}-\d{2}$/.test(month)) {
|
||||
return NextResponse.json({ error: 'month YYYY-MM requerido' }, { status: 400 });
|
||||
}
|
||||
|
||||
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 docs = await restGet<{ content?: unknown }[]>(
|
||||
`/rest/v1/documents?tenant_id=eq.${encodeURIComponent(
|
||||
ctx.tenantId
|
||||
)}&type=eq.calendar&select=content&order=created_at.desc&limit=24`
|
||||
);
|
||||
const history: RrssMonthPlan[] = [];
|
||||
for (const d of docs.data || []) {
|
||||
const plan = asPlanObject(d.content) as RrssMonthPlan | null;
|
||||
if (plan?.month && plan.month !== month && Array.isArray(plan.pieces)) {
|
||||
history.push(plan as unknown as RrssMonthPlan);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await generateMonthPreview({
|
||||
ym: month,
|
||||
slug,
|
||||
label: ctx.label,
|
||||
history,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
plan: result.plan,
|
||||
rationale: result.rationale,
|
||||
source: result.source,
|
||||
brand: slug,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user