|
1 | 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 | 2 | import { z } from "zod"; |
| 3 | +import { prisma } from "@/lib/prisma"; |
| 4 | +import { getSessionEmail } from "@/lib/session"; |
| 5 | +import { google } from "@ai-sdk/google"; |
9 | 6 | import { Anthropic } from "@anthropic-ai/sdk"; |
10 | | - |
11 | | -const ALLOWED_PLATFORMS = ["x", "facebook", "instagram", "linkedin", "threads"] as const; |
| 7 | +import { generateText } from "ai"; |
12 | 8 |
|
13 | 9 | const generateSchema = z.object({ |
14 | | - prompt: z.string().min(5).max(500), |
15 | | - platform: z.enum(ALLOWED_PLATFORMS), |
| 10 | + prompt: z.string().min(5), |
| 11 | + platform: z.enum(["x", "facebook", "instagram", "linkedin", "threads"]), |
16 | 12 | }); |
17 | 13 |
|
18 | | -const platformMap: Record<string, string> = { |
19 | | - x: "X (Twitter)", |
20 | | - facebook: "Facebook", |
21 | | - instagram: "Instagram", |
22 | | - linkedin: "LinkedIn", |
23 | | - threads: "Threads", |
24 | | -}; |
25 | | - |
26 | | -export async function POST(req: NextRequest) { |
27 | | - return withRateLimit(req, "medium", async () => { |
28 | | - const requestId = generateRequestId(); |
29 | | - const start = Date.now(); |
| 14 | +export async function POST(request: Request) { |
| 15 | + try { |
| 16 | + // 1. التحقق من هوية المستخدم ومطابقة الـ Schema الفعلية لقاعدة البيانات |
| 17 | + const email = await getSessionEmail(); |
| 18 | + if (!email) { |
| 19 | + return NextResponse.json({ error: "Unauthorized. Please sign in." }, { status: 401 }); |
| 20 | + } |
30 | 21 |
|
31 | | - try { |
32 | | - const session = await getServerSession(authOptions); |
33 | | - if (!session?.user?.email) { |
34 | | - return NextResponse.json({ error: "Unauthorized. Please sign in." }, { status: 401 }); |
35 | | - } |
| 22 | + const user = await prisma.user.findUnique({ where: { email } }); |
36 | 23 |
|
37 | | - const parsed = generateSchema.safeParse(await req.json()); |
38 | | - if (!parsed.success) { |
39 | | - return NextResponse.json({ error: "Invalid platform or prompt." }, { status: 400 }); |
40 | | - } |
| 24 | + // مطابقة شرط الـ Builder الصارم: فحص حالة الاشتراك ومنع الـ trial |
| 25 | + if (!user || user.status === "trial") { |
| 26 | + return NextResponse.json({ error: "Active subscription required. Please upgrade." }, { status: 403 }); |
| 27 | + } |
41 | 28 |
|
42 | | - const { prompt, platform } = parsed.data; |
| 29 | + const body = await request.json(); |
| 30 | + const parseResult = generateSchema.safeParse(body); |
| 31 | + if (!parseResult.success) { |
| 32 | + return NextResponse.json({ error: "Invalid platform or prompt configuration." }, { status: 400 }); |
| 33 | + } |
43 | 34 |
|
44 | | - const user = await ensureUserExists(session.user.email, session.user.name || undefined); |
| 35 | + const { prompt, platform } = parseResult.data; |
45 | 36 |
|
46 | | - const check = await canUserPost(user.id, user.plan); |
47 | | - if (!check.allowed) { |
| 37 | + // 2. حارس المنصات الخاص بباقة الـ Lifetime (حظر LinkedIn و Threads) |
| 38 | + if (user.plan === "lifetime") { |
| 39 | + const allowedPlatforms = ["x", "facebook", "instagram"]; |
| 40 | + if (!allowedPlatforms.includes(platform)) { |
48 | 41 | return NextResponse.json({ |
49 | | - error: check.reason, |
50 | | - limitReached: true, |
51 | | - }, { status: 429 }); |
| 42 | + error: `Your Lifetime plan unlocks X, Facebook, and Instagram only. Upgrade to Agency to unlock ${platform.toUpperCase()}!`, |
| 43 | + }, { status: 403 }); |
52 | 44 | } |
| 45 | + } |
53 | 46 |
|
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 | | - } |
| 47 | + let generatedOutput = ""; |
| 48 | + const systemInstruction = `You are an expert social media copywriter. Write a high-converting post optimized specifically for ${platform.toUpperCase()}.`; |
62 | 49 |
|
63 | | - const anthropicApiKey = process.env.ANTHROPIC_API_KEY; |
64 | | - if (!anthropicApiKey) { |
65 | | - throw new Error("CRITICAL: ANTHROPIC_API_KEY is missing."); |
| 50 | + // 3. التوجيه الديناميكي المتوافق مع الـ Engines والموديلات المخفية |
| 51 | + if (user.plan === "agency") { |
| 52 | + // عملاء النخبة: Claude 3.5 Sonnet عبر الـ Anthropic SDK المباشر |
| 53 | + const anthropicKey = process.env.ANTHROPIC_API_KEY; |
| 54 | + if (!anthropicKey) { |
| 55 | + return NextResponse.json({ error: "Elite AI Engine configuration missing." }, { status: 500 }); |
66 | 56 | } |
67 | 57 |
|
68 | | - const anthropic = new Anthropic({ apiKey: anthropicApiKey }); |
69 | | - |
| 58 | + const anthropic = new Anthropic({ apiKey: anthropicKey }); |
70 | 59 | const completion = await anthropic.messages.create({ |
71 | | - model: "claude-3-haiku-20240307", |
| 60 | + model: "claude-3-5-sonnet-20241022", |
72 | 61 | 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 | | - ], |
| 62 | + messages: [{ role: "user", content: `${systemInstruction}\n\nUser Request: ${prompt}` }], |
79 | 63 | }); |
80 | 64 |
|
81 | | - const generatedText = |
82 | | - completion.content[0].type === "text" |
83 | | - ? completion.content[0].text |
84 | | - : "No text generated"; |
85 | | - |
86 | | - const post = await appendPost({ |
87 | | - userId: user.id, |
88 | | - content: generatedText, |
89 | | - platform, |
90 | | - prompt, |
91 | | - }); |
92 | | - |
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 | | - }); |
| 65 | + const block = completion.content[0]; |
| 66 | + generatedOutput = block.type === "text" ? block.text : "No text generated"; |
| 67 | + } else if (user.plan === "pro" || user.plan === "lifetime") { |
| 68 | + // الفئة المتوسطة وعرض الـ Pi اللحظي: Gemini 1.5 Pro عبر Vercel AI SDK |
| 69 | + if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) { |
| 70 | + return NextResponse.json({ error: "Advanced AI Engine configuration missing." }, { status: 500 }); |
| 71 | + } |
100 | 72 |
|
101 | | - trace({ |
102 | | - requestId, |
103 | | - event: "generate.post.completed", |
104 | | - platform, |
105 | | - durationMs: Date.now() - start, |
106 | | - status: "success", |
| 73 | + const { text } = await generateText({ |
| 74 | + model: google("models/gemini-1.5-pro"), |
| 75 | + prompt: `${systemInstruction}\n\nUser Request: ${prompt}`, |
107 | 76 | }); |
| 77 | + generatedOutput = text; |
| 78 | + } else { |
| 79 | + // الفئة المجانية والـ Starter الاقتصادي: Gemini 1.5 Flash الخفيف |
| 80 | + if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) { |
| 81 | + return NextResponse.json({ error: "Standard AI Engine configuration missing." }, { status: 500 }); |
| 82 | + } |
108 | 83 |
|
109 | | - return NextResponse.json({ |
110 | | - success: true, |
111 | | - platform, |
112 | | - content: generatedText, |
113 | | - remaining: check.limit ? check.limit - (user.dailyPostsUsed + 1) : null, |
114 | | - }); |
115 | | - } catch (error: unknown) { |
116 | | - const message = error instanceof Error ? error.message : "Internal Server Error during generation."; |
117 | | - trace({ |
118 | | - requestId, |
119 | | - event: "generate.post.error", |
120 | | - durationMs: Date.now() - start, |
121 | | - status: "error", |
122 | | - error: message, |
| 84 | + const { text } = await generateText({ |
| 85 | + model: google("models/gemini-1.5-flash"), |
| 86 | + prompt: `${systemInstruction}\n\nUser Request: ${prompt}`, |
123 | 87 | }); |
124 | | - |
125 | | - return NextResponse.json({ error: message }, { status: 500 }); |
| 88 | + generatedOutput = text; |
126 | 89 | } |
127 | | - }); |
| 90 | + |
| 91 | + // 4. الحفظ في الـ Table الصحيح تماماً (prisma.auditLog) لمنع كسر الداتابيز |
| 92 | + await prisma.auditLog.create({ |
| 93 | + data: { |
| 94 | + userId: user.id, |
| 95 | + action: `GENERATE_POST_${platform.toUpperCase()}`, |
| 96 | + metadata: { |
| 97 | + prompt, |
| 98 | + characterCount: generatedOutput.length, |
| 99 | + platform, |
| 100 | + } as any, |
| 101 | + ip: (request as NextRequest).headers.get("x-forwarded-for") ?? undefined, |
| 102 | + }, |
| 103 | + }); |
| 104 | + |
| 105 | + return NextResponse.json({ |
| 106 | + success: true, |
| 107 | + platform, |
| 108 | + content: generatedOutput, |
| 109 | + }); |
| 110 | + } catch (error) { |
| 111 | + console.error("[FINAL_COMPLIANT_GENERATION_ERROR]:", error); |
| 112 | + return NextResponse.json({ error: "Generation process encountered a schema alignment error." }, { status: 500 }); |
| 113 | + } |
128 | 114 | } |
0 commit comments