ed34b186f9
- New /refer page with share/copy/stats - Fixed modal z-index conflicts (z-50 → z-[60]) across forum, groups, souq, halal-monitor - Seed route: forum categories, marketplace listings, private groups - Mufti/Daie personas now available in Premium tier - Fixed referral share link: falahos.my/mobile/auth?ref=CODE - Fixed client-side persona access check for premium
159 lines
4.7 KiB
TypeScript
159 lines
4.7 KiB
TypeScript
import { requireAuth, type JWTContents } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { getPersona } from "@/lib/personas";
|
|
import { getUserTier, DAILY_LIMITS } from "@/lib/tiers";
|
|
import { generateAIReply } from "@/lib/ai-server";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const user = await requireAuth(request);
|
|
if (!user) {
|
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { messages, personaId } = body as {
|
|
messages?: { role: string; content: string }[];
|
|
personaId?: string;
|
|
};
|
|
|
|
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
return Response.json({ error: "messages array is required" }, { status: 400 });
|
|
}
|
|
|
|
if (!personaId || typeof personaId !== "string") {
|
|
return Response.json({ error: "personaId is required" }, { status: 400 });
|
|
}
|
|
|
|
// Verify persona exists
|
|
const persona = getPersona(personaId);
|
|
if (!persona) {
|
|
return Response.json({ error: "Invalid persona" }, { status: 400 });
|
|
}
|
|
|
|
// Get user from DB to check limits
|
|
const dbUser = await prisma.user.findUnique({
|
|
where: { id: user.userId },
|
|
});
|
|
|
|
if (!dbUser) {
|
|
return Response.json({ error: "User not found" }, { status: 404 });
|
|
}
|
|
|
|
const tier = getUserTier(dbUser.isPremium, dbUser.isPro);
|
|
const limits = DAILY_LIMITS[tier];
|
|
|
|
// Check persona access for this tier
|
|
if (!limits.personas.includes(personaId)) {
|
|
const tierName = tier.charAt(0).toUpperCase() + tier.slice(1);
|
|
return Response.json(
|
|
{
|
|
error: `This persona requires ${tier === "free" ? "Premium" : "Pro"} subscription`,
|
|
tier,
|
|
requiredTier: tier === "free" ? "premium" : "pro",
|
|
},
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
|
|
// Check daily message limit
|
|
const now = new Date();
|
|
let dailyMsgCount = dbUser.dailyMsgCount;
|
|
let dailyMsgResetAt = dbUser.dailyMsgResetAt;
|
|
|
|
// Reset count if a new day
|
|
if (!dailyMsgResetAt || now.getTime() - dailyMsgResetAt.getTime() > 24 * 60 * 60 * 1000) {
|
|
dailyMsgCount = 0;
|
|
dailyMsgResetAt = now;
|
|
}
|
|
|
|
if (limits.msgs !== Infinity && dailyMsgCount >= limits.msgs) {
|
|
return Response.json(
|
|
{
|
|
error: "Daily message limit reached",
|
|
count: dailyMsgCount,
|
|
limit: limits.msgs,
|
|
resetAt: dailyMsgResetAt.toISOString(),
|
|
},
|
|
{ status: 429 }
|
|
);
|
|
}
|
|
|
|
// Determine the user's last message for context
|
|
const lastUserMessage = messages[messages.length - 1]?.content || "";
|
|
|
|
// Get recent history from DB for context (up to 10 recent messages)
|
|
const recentHistory = await prisma.chatHistory.findMany({
|
|
where: { userId: user.userId },
|
|
orderBy: { createdAt: "desc" },
|
|
take: 10,
|
|
});
|
|
const historyContext = recentHistory
|
|
.reverse()
|
|
.map((h) => ({ role: h.role, content: h.content }));
|
|
|
|
// Save user message(s) to ChatHistory
|
|
for (const msg of messages) {
|
|
if (msg.role === "user") {
|
|
await prisma.chatHistory.create({
|
|
data: {
|
|
userId: user.userId,
|
|
role: "user",
|
|
content: msg.content,
|
|
metadata: JSON.stringify({ personaId }),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// Generate AI response (tries LLM first, falls back to keyword rules)
|
|
const aiContent = await generateAIReply(personaId, lastUserMessage, historyContext);
|
|
|
|
// Save AI response to ChatHistory
|
|
const aiMessage = await prisma.chatHistory.create({
|
|
data: {
|
|
userId: user.userId,
|
|
role: "assistant",
|
|
content: aiContent,
|
|
metadata: JSON.stringify({ personaId }),
|
|
},
|
|
});
|
|
|
|
// Update daily message count
|
|
const newCount = dailyMsgCount + messages.filter((m) => m.role === "user").length;
|
|
await prisma.user.update({
|
|
where: { id: user.userId },
|
|
data: {
|
|
dailyMsgCount: newCount,
|
|
dailyMsgResetAt: dailyMsgResetAt,
|
|
},
|
|
});
|
|
|
|
let updatedUser: any = { dailyMsgCount: newCount };
|
|
if (newCount >= limits.msgs - 2 && limits.msgs !== Infinity) {
|
|
updatedUser.limitWarning = true;
|
|
updatedUser.limitRemaining = limits.msgs - newCount;
|
|
}
|
|
|
|
return Response.json({
|
|
message: {
|
|
role: "assistant",
|
|
content: aiContent,
|
|
createdAt: aiMessage.createdAt.toISOString(),
|
|
id: aiMessage.id,
|
|
},
|
|
daily: {
|
|
count: newCount,
|
|
limit: limits.msgs,
|
|
resetAt: dailyMsgResetAt.toISOString(),
|
|
},
|
|
user: updatedUser,
|
|
});
|
|
} catch (error) {
|
|
console.error("Chat send error:", error);
|
|
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
}
|
|
}
|