Skip to content

Commit 3afaabf

Browse files
committed
feat: rewrite generate with Anthropic SDK (claude-3-haiku), platform guard, lifetime plan restrictions
1 parent 77e9226 commit 3afaabf

1 file changed

Lines changed: 65 additions & 32 deletions

File tree

app/api/generate/route.ts

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
import { NextRequest, NextResponse } from 'next/server';
2-
import { getServerSession } from 'next-auth';
3-
import { authOptions } from '@/lib/auth';
4-
import { ai } from '@/lib/ai';
5-
import { canUserPost, appendPost, ensureUserExists } from '@/lib/db';
6-
import { generateRequestId, trace } from '@/lib/trace';
7-
import { withRateLimit } from '@/lib/rate-limit';
8-
import { z } from 'zod';
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { getServerSession } from "next-auth";
3+
import { authOptions } from "@/lib/auth";
4+
import { prisma } from "@/lib/prisma";
5+
import { canUserPost, appendPost, ensureUserExists } from "@/lib/db";
6+
import { generateRequestId, trace } from "@/lib/trace";
7+
import { withRateLimit } from "@/lib/rate-limit";
8+
import { z } from "zod";
9+
import { Anthropic } from "@anthropic-ai/sdk";
910

10-
const generateSchema = z.object({
11-
prompt: z.string().trim().min(10).max(500),
12-
platform: z.enum(["x", "instagram", "linkedin"]),
13-
});
11+
const ALLOWED_PLATFORMS = ["x", "facebook", "instagram", "linkedin", "threads"] as const;
1412

15-
const postSchema = z.object({
16-
post: z.string(),
17-
hashtags: z.array(z.string()).min(3).max(5),
13+
const generateSchema = z.object({
14+
prompt: z.string().min(5).max(500),
15+
platform: z.enum(ALLOWED_PLATFORMS),
1816
});
1917

2018
const platformMap: Record<string, string> = {
2119
x: "X (Twitter)",
20+
facebook: "Facebook",
2221
instagram: "Instagram",
2322
linkedin: "LinkedIn",
23+
threads: "Threads",
2424
};
2525

2626
export async function POST(req: NextRequest) {
@@ -31,12 +31,12 @@ export async function POST(req: NextRequest) {
3131
try {
3232
const session = await getServerSession(authOptions);
3333
if (!session?.user?.email) {
34-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
34+
return NextResponse.json({ error: "Unauthorized. Please sign in." }, { status: 401 });
3535
}
3636

3737
const parsed = generateSchema.safeParse(await req.json());
3838
if (!parsed.success) {
39-
return NextResponse.json({ error: "Invalid request", details: parsed.error.flatten() }, { status: 400 });
39+
return NextResponse.json({ error: "Invalid platform or prompt." }, { status: 400 });
4040
}
4141

4242
const { prompt, platform } = parsed.data;
@@ -45,26 +45,59 @@ export async function POST(req: NextRequest) {
4545

4646
const check = await canUserPost(user.id, user.plan);
4747
if (!check.allowed) {
48-
return NextResponse.json({
48+
return NextResponse.json({
4949
error: check.reason,
50-
limitReached: true
50+
limitReached: true,
5151
}, { status: 429 });
5252
}
5353

54-
const result = await ai.generate({
55-
prompt: `Write a concise ${platformMap[platform]} post about: ${prompt}. Keep it sharp and useful. Include relevant hashtags.`,
56-
schema: postSchema,
57-
config: { provider: "gemini" },
58-
trace: { requestId, event: "generate.post" },
54+
if (user.plan === "lifetime") {
55+
const allowedLifetimePlatforms = ["x", "facebook", "instagram"];
56+
if (!allowedLifetimePlatforms.includes(platform)) {
57+
return NextResponse.json({
58+
error: `The Lifetime plan only supports: X, Facebook, and Instagram. Please upgrade to the Agency plan to unlock ${platform.toUpperCase()}!`,
59+
}, { status: 403 });
60+
}
61+
}
62+
63+
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
64+
if (!anthropicApiKey) {
65+
throw new Error("CRITICAL: ANTHROPIC_API_KEY is missing.");
66+
}
67+
68+
const anthropic = new Anthropic({ apiKey: anthropicApiKey });
69+
70+
const completion = await anthropic.messages.create({
71+
model: "claude-3-haiku-20240307",
72+
max_tokens: 1000,
73+
messages: [
74+
{
75+
role: "user",
76+
content: `Write a high-converting post optimized specifically for ${platformMap[platform]} based on this request: ${prompt}`,
77+
},
78+
],
5979
});
6080

61-
await appendPost({
81+
const generatedText =
82+
completion.content[0].type === "text"
83+
? completion.content[0].text
84+
: "No text generated";
85+
86+
const post = await appendPost({
6287
userId: user.id,
63-
content: result.post,
88+
content: generatedText,
6489
platform,
6590
prompt,
6691
});
6792

93+
await prisma.auditLog.create({
94+
data: {
95+
userId: user.id,
96+
action: "generation",
97+
metadata: { platform, prompt, characterCount: generatedText.length, postId: post.id } as any,
98+
},
99+
});
100+
68101
trace({
69102
requestId,
70103
event: "generate.post.completed",
@@ -75,21 +108,21 @@ export async function POST(req: NextRequest) {
75108

76109
return NextResponse.json({
77110
success: true,
78-
post: result.post,
79-
hashtags: result.hashtags,
111+
platform,
112+
content: generatedText,
80113
remaining: check.limit ? check.limit - (user.dailyPostsUsed + 1) : null,
81114
});
82-
83-
} catch (error: any) {
115+
} catch (error: unknown) {
116+
const message = error instanceof Error ? error.message : "Internal Server Error during generation.";
84117
trace({
85118
requestId,
86119
event: "generate.post.error",
87120
durationMs: Date.now() - start,
88121
status: "error",
89-
error: error.message,
122+
error: message,
90123
});
91124

92-
return NextResponse.json({ error: "Failed to generate post", detail: error.message }, { status: 500 });
125+
return NextResponse.json({ error: message }, { status: 500 });
93126
}
94127
});
95128
}

0 commit comments

Comments
 (0)