Add Zona de marca brandbooks and strategist calendar generation.
Per-tenant brandbooks power ads, RRSS calendars, LinkedIn B2B and blog; Nov calendars now create from trends + client DNA instead of copying history. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
143
scripts/seed_brandbooks_regen_nov.ts
Normal file
143
scripts/seed_brandbooks_regen_nov.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Seed brandbook for every tenant + regenerate Nov 2026 editorial strategy.
|
||||
* Usage: npx tsx scripts/seed_brandbooks_regen_nov.ts
|
||||
* Optional: MONTH=2026-11 FORCE_BRANDBOOK=1
|
||||
*/
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
config({ path: resolve(process.cwd(), '.env.local') });
|
||||
|
||||
import type { RrssMonthPlan } from '../src/components/RrssMonthView';
|
||||
import { generateMonthPreview } from '../src/lib/calendar-generate';
|
||||
import {
|
||||
BRANDBOOK_DOC_TITLE,
|
||||
BRANDBOOK_DOC_TYPE,
|
||||
parseBrandbook,
|
||||
seedBrandbook,
|
||||
} from '../src/lib/brandbook';
|
||||
import { monthLabel } from '../src/lib/calendar-import';
|
||||
|
||||
const ym = process.env.MONTH || '2026-11';
|
||||
const forceBook = process.env.FORCE_BRANDBOOK === '1';
|
||||
|
||||
async function main() {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const key = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
const h = {
|
||||
apikey: key,
|
||||
Authorization: `Bearer ${key}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
const tenants = (await (
|
||||
await fetch(
|
||||
`${url}/rest/v1/tenants?select=id,slug,name&order=name.asc`,
|
||||
{ headers: h }
|
||||
)
|
||||
).json()) as { id: string; slug: string; name: string }[];
|
||||
|
||||
console.log(`Tenants: ${tenants.length} · month ${ym} · forceBook=${forceBook}`);
|
||||
|
||||
for (const t of tenants) {
|
||||
// --- brandbook ---
|
||||
const bbRes = await fetch(
|
||||
`${url}/rest/v1/documents?tenant_id=eq.${t.id}&type=eq.${BRANDBOOK_DOC_TYPE}&select=id,content&order=updated_at.desc&limit=1`,
|
||||
{ headers: h }
|
||||
);
|
||||
const bbRows = (await bbRes.json()) as { id: string; content: unknown }[];
|
||||
const existing = parseBrandbook(bbRows[0]?.content);
|
||||
let book = existing;
|
||||
if (!existing || forceBook) {
|
||||
book = seedBrandbook(t.slug, t.name);
|
||||
if (bbRows[0]?.id) {
|
||||
await fetch(`${url}/rest/v1/documents?id=eq.${bbRows[0].id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { ...h, Prefer: 'return=minimal' },
|
||||
body: JSON.stringify({
|
||||
content: book,
|
||||
title: BRANDBOOK_DOC_TITLE,
|
||||
updated_at: new Date().toISOString(),
|
||||
}),
|
||||
});
|
||||
console.log(`[${t.slug}] brandbook updated`);
|
||||
} else {
|
||||
await fetch(`${url}/rest/v1/documents`, {
|
||||
method: 'POST',
|
||||
headers: { ...h, Prefer: 'return=minimal' },
|
||||
body: JSON.stringify({
|
||||
tenant_id: t.id,
|
||||
type: BRANDBOOK_DOC_TYPE,
|
||||
title: BRANDBOOK_DOC_TITLE,
|
||||
status: 'approved',
|
||||
content: book,
|
||||
}),
|
||||
});
|
||||
console.log(`[${t.slug}] brandbook created`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[${t.slug}] brandbook exists (skip)`);
|
||||
}
|
||||
|
||||
// --- calendars ---
|
||||
const calRes = await fetch(
|
||||
`${url}/rest/v1/documents?tenant_id=eq.${t.id}&type=eq.calendar&select=id,content&order=created_at.desc&limit=48`,
|
||||
{ headers: h }
|
||||
);
|
||||
const docs = (await calRes.json()) as { id: string; content: unknown }[];
|
||||
const history: RrssMonthPlan[] = [];
|
||||
let novId: string | null = null;
|
||||
for (const d of docs) {
|
||||
const c =
|
||||
typeof d.content === 'string' ? JSON.parse(d.content) : d.content;
|
||||
const plan = c as RrssMonthPlan;
|
||||
if (plan?.month === ym) novId = d.id;
|
||||
else if (plan?.month && Array.isArray(plan.pieces)) history.push(plan);
|
||||
}
|
||||
|
||||
const result = await generateMonthPreview({
|
||||
ym,
|
||||
slug: t.slug,
|
||||
label: t.name,
|
||||
history,
|
||||
brandbook: book,
|
||||
});
|
||||
|
||||
const title = `RRSS ${monthLabel(ym)}`;
|
||||
if (novId) {
|
||||
const patch = await fetch(`${url}/rest/v1/documents?id=eq.${novId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { ...h, Prefer: 'return=minimal' },
|
||||
body: JSON.stringify({
|
||||
content: result.plan,
|
||||
title,
|
||||
updated_at: new Date().toISOString(),
|
||||
}),
|
||||
});
|
||||
console.log(
|
||||
`[${t.slug}] ${ym} patched ${patch.status} · ${result.plan.pieces.length} pcs · ${result.source}`
|
||||
);
|
||||
} else {
|
||||
const ins = await fetch(`${url}/rest/v1/documents`, {
|
||||
method: 'POST',
|
||||
headers: { ...h, Prefer: 'return=minimal' },
|
||||
body: JSON.stringify({
|
||||
tenant_id: t.id,
|
||||
type: 'calendar',
|
||||
title,
|
||||
status: 'draft',
|
||||
content: result.plan,
|
||||
}),
|
||||
});
|
||||
console.log(
|
||||
`[${t.slug}] ${ym} created ${ins.status} · ${result.plan.pieces.length} pcs · ${result.source}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Done.');
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user