feat(content): Phase 8 content generation (Faz A+B) for Sase.tr
Hybrid content automation pilot: generation + review + drafts live in the panel (reusing the insight pipeline's DeepSeek client, prompt_templates versioning, cost_ledger and budget_settings); publishing/distribution will go through n8n (Faz C, not built). Channels: blog, LinkedIn, X, Instagram. Topic sourcing is automatic (LLM-generated ideas). Approval model: drafts sit in the panel for manual review/edit/publish. Faz A (worker): - ContentTopic / ContentDraft Prisma models (content_topics, content_drafts) - content-prompts.ts: 5 seed prompts (topic ideas[pro] + blog[pro] + linkedin/x/instagram[flash]), Turkish B2B automotive tone, per-channel JSON schemas - content-budget.ts: separate budget envelope (sums only content_* spend) - content-topics job (auto idea gen, backlog-capped, title dedupe) + content-generate job (queued topic -> one draft per channel) - content-pipeline scheduler (separate BullMQ queue, topics@*/8h, generate@*/10min), wired into index.ts; seeded via seed-runtime - content budget settings (caps + content_paused kill switch); seed default content_paused=true for a safe first deploy Faz B (web): - /content (queue + auto/manual triggers + manual topic form), /content/t/[id] (per-channel draft cards: preview, JSON edits, approve/reject), /content/costs (content-only spend) - server actions (audit-logged), manual trigger API routes, contentQueue(), nav + Cmd+K entries - content caps surfaced on /insights/settings/budgets + whitelisted Both packages typecheck. Schema applies on deploy (web start runs prisma db push). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
27
apps/web/src/app/api/content/generate/route.ts
Normal file
27
apps/web/src/app/api/content/generate/route.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { headers } from "next/headers";
|
||||
import { contentQueue } from "@/lib/queue";
|
||||
import { writeAudit } from "@/lib/audit";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Manually trigger one content-generate run (queued topics → channel drafts).
|
||||
// Also scheduled every 10min; this is for on-demand kicks after queueing topics.
|
||||
export async function POST() {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session) return NextResponse.json({ ok: false, error: "unauthenticated" }, { status: 401 });
|
||||
|
||||
const job = await contentQueue().add(
|
||||
"content-generate",
|
||||
{},
|
||||
{ removeOnComplete: 50, removeOnFail: 25 },
|
||||
);
|
||||
await writeAudit({
|
||||
projectKey: "sase",
|
||||
endpoint: "/api/content/generate",
|
||||
method: "POST",
|
||||
responseStatus: 200,
|
||||
});
|
||||
return NextResponse.json({ ok: true, jobId: job.id });
|
||||
}
|
||||
27
apps/web/src/app/api/content/topics/generate/route.ts
Normal file
27
apps/web/src/app/api/content/topics/generate/route.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { headers } from "next/headers";
|
||||
import { contentQueue } from "@/lib/queue";
|
||||
import { writeAudit } from "@/lib/audit";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Manually trigger one content-topics run (auto topic-idea generation).
|
||||
// The job itself is also scheduled (every 8h) — this is for on-demand kicks.
|
||||
export async function POST() {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session) return NextResponse.json({ ok: false, error: "unauthenticated" }, { status: 401 });
|
||||
|
||||
const job = await contentQueue().add(
|
||||
"content-topics",
|
||||
{},
|
||||
{ removeOnComplete: 30, removeOnFail: 15 },
|
||||
);
|
||||
await writeAudit({
|
||||
projectKey: "sase",
|
||||
endpoint: "/api/content/topics/generate",
|
||||
method: "POST",
|
||||
responseStatus: 200,
|
||||
});
|
||||
return NextResponse.json({ ok: true, jobId: job.id });
|
||||
}
|
||||
Reference in New Issue
Block a user