Skip to content

Commit eeba28e

Browse files
committed
feat: comprehensive platform optimizations and game leaderboards
- refactor: split connection.ts into modular feature-based database adapters - perf: implement lazy-loading for heavy libraries (Mermaid) and enable image optimizations - feat: implement onboarding flow trigger for new users via profiles flag - feat: replace localStorage polling with Supabase Realtime for notifications - feat: implement global game leaderboard and integrate all entertainment modules - security: remove localStorage fallbacks and add server-only guards for bcryptjs - fix: resolve linting errors in auth routes and dynamic imports
1 parent 97889c9 commit eeba28e

26 files changed

Lines changed: 882 additions & 654 deletions

File tree

app/api/auth/login/route.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'server-only'
12
import { NextRequest, NextResponse } from 'next/server'
23
import { loginRateLimit, clearRateLimit } from '@/lib/utils/rate-limiter'
34
import { verifyMFAToken } from '@/lib/utils/mfa'
@@ -15,7 +16,7 @@ export async function POST(request: NextRequest) {
1516

1617
// 1. Rate limiting - Prevent brute force attacks
1718
const rateLimitResult = await loginRateLimit(request)
18-
19+
1920
if (!rateLimitResult.allowed) {
2021
return NextResponse.json(
2122
{
@@ -68,7 +69,7 @@ export async function POST(request: NextRequest) {
6869

6970
// 5. Verify password
7071
const isValidPassword = await bcrypt.compare(password, user.password)
71-
72+
7273
if (!isValidPassword) {
7374
// Send notification after multiple failed attempts
7475
if (rateLimitResult.remaining <= 2) {
@@ -106,7 +107,7 @@ export async function POST(request: NextRequest) {
106107

107108
// Verify MFA token
108109
const mfaValid = verifyMFAToken(user.mfaSecret!, mfaToken)
109-
110+
110111
if (!mfaValid.valid) {
111112
return NextResponse.json(
112113
{ error: 'Invalid MFA code. Please try again.' },
@@ -128,9 +129,9 @@ export async function POST(request: NextRequest) {
128129
// const allUserSessions = await db.sessions.findMany({
129130
// where: { userId: user.id, isActive: true }
130131
// })
131-
132+
132133
const allUserSessions: UserSession[] = [] // Mock empty sessions
133-
134+
134135
// Only check for suspicious activity if there's a previous session
135136
if (allUserSessions.length > 0) {
136137
const previousSession = allUserSessions[0]
@@ -145,9 +146,9 @@ export async function POST(request: NextRequest) {
145146
isActive: true,
146147
isCurrent: true
147148
}
148-
149+
149150
const suspiciousFlags = detectSuspiciousActivity(sessionWithLocation, previousSession)
150-
151+
151152
if (suspiciousFlags.suspicious && suspiciousFlags.reasons.length > 0) {
152153
await sendSecurityNotification({
153154
type: 'suspicious_activity',

app/api/auth/password/change/route.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'server-only'
12
import { NextRequest, NextResponse } from 'next/server'
23
import { validatePasswordStrength } from '@/lib/utils/password-validator'
34
import { sendSecurityNotification } from '@/lib/utils/security-notifications'
@@ -10,7 +11,7 @@ export async function POST(request: NextRequest) {
1011
try {
1112
// TODO: Get authenticated user from session/JWT
1213
const userId = request.headers.get('x-user-id')
13-
14+
1415
if (!userId) {
1516
return NextResponse.json(
1617
{ error: 'Unauthorized. Please log in.' },
@@ -51,7 +52,7 @@ export async function POST(request: NextRequest) {
5152

5253
// 3. Verify current password
5354
const isValidPassword = await bcrypt.compare(currentPassword, user.password)
54-
55+
5556
if (!isValidPassword) {
5657
return NextResponse.json(
5758
{ error: 'Current password is incorrect' },
@@ -61,7 +62,7 @@ export async function POST(request: NextRequest) {
6162

6263
// 4. Check if new password is same as current password
6364
const isSamePassword = await bcrypt.compare(newPassword, user.password)
64-
65+
6566
if (isSamePassword) {
6667
return NextResponse.json(
6768
{ error: 'New password must be different from current password' },
@@ -71,7 +72,7 @@ export async function POST(request: NextRequest) {
7172

7273
// 5. Validate new password strength
7374
const passwordValidation = await validatePasswordStrength(newPassword, [user.email, user.name])
74-
75+
7576
if (passwordValidation.score < 3) {
7677
return NextResponse.json(
7778
{

app/api/auth/signup/route.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'server-only'
12
import { NextRequest, NextResponse } from 'next/server'
23
import { signupRateLimit, clearRateLimit } from '@/lib/utils/rate-limiter'
34
import { validatePasswordStrength } from '@/lib/utils/password-validator'
@@ -11,7 +12,7 @@ export async function POST(request: NextRequest) {
1112
try {
1213
// 1. Rate limiting - Prevent brute force signup attempts
1314
const rateLimitResult = await signupRateLimit(request)
14-
15+
1516
if (!rateLimitResult.allowed) {
1617
return NextResponse.json(
1718
{
@@ -46,7 +47,7 @@ export async function POST(request: NextRequest) {
4647

4748
// 5. Validate password strength
4849
const passwordValidation = await validatePasswordStrength(password, [email, name])
49-
50+
5051
if (passwordValidation.score < 3) {
5152
return NextResponse.json(
5253
{
@@ -67,7 +68,7 @@ export async function POST(request: NextRequest) {
6768
// const existingUser = await db.users.findUnique({
6869
// where: { email: email.toLowerCase() }
6970
// })
70-
71+
7172
// if (existingUser) {
7273
// return NextResponse.json(
7374
// { error: 'An account with this email already exists' },
@@ -107,7 +108,7 @@ export async function POST(request: NextRequest) {
107108

108109
// 10. Send welcome email (optional)
109110
const userAgent = request.headers.get('user-agent') || 'Unknown'
110-
111+
111112
await sendSecurityNotification({
112113
type: 'login',
113114
userEmail: user.email,

app/api/chat/route.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,40 @@
1-
import { gateway } from "ai"
1+
import { createGroq } from "@ai-sdk/groq"
22
import { streamText } from "ai"
33
import { NextResponse } from "next/server"
44

5+
const GROQ_MODELS: Record<string, string> = {
6+
"llama-3.3-70b-versatile": "llama-3.3-70b-versatile",
7+
"llama-3.1-8b-instant": "llama-3.1-8b-instant",
8+
"gemma2-9b-it": "gemma2-9b-it",
9+
"mixtral-8x7b-32768": "mixtral-8x7b-32768",
10+
}
11+
512
export async function POST(req: Request) {
613
try {
714
const { messages, model } = await req.json()
815

9-
// Ensure API key is configured
10-
if (!process.env.AI_GATEWAY_API_KEY) {
16+
if (!process.env.GROQ_API_KEY) {
1117
return NextResponse.json(
12-
{ error: "AI Gateway API Key not found. Please set AI_GATEWAY_API_KEY in your env." },
18+
{ error: "GROQ_API_KEY is not configured. Add it to your .env.local file." },
1319
{ status: 500 }
1420
)
1521
}
1622

23+
const groq = createGroq({ apiKey: process.env.GROQ_API_KEY })
24+
const modelId = GROQ_MODELS[model] ?? "llama-3.3-70b-versatile"
25+
1726
const result = streamText({
18-
model: gateway(model || "google:gemini-1.5-pro"),
27+
model: groq(modelId),
1928
messages,
2029
})
2130

2231
return result.toTextStreamResponse()
23-
} catch (error: any) {
32+
} catch (error: unknown) {
33+
const message = error instanceof Error ? error.message : "Failed to process request"
2434
console.error("Error in AI chat:", error)
2535
return NextResponse.json(
26-
{ error: error.message || "Failed to process chat request" },
36+
{ error: message },
2737
{ status: 500 }
2838
)
2939
}
3040
}
31-

app/dashboard/ai-tools/page.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function AIToolsPage() {
2020

2121
// Manual input state management for @ai-sdk/react v3+
2222
const [input, setInput] = useState("")
23-
const [selectedModel, setSelectedModel] = useState("google:gemini-1.5-pro")
23+
const [selectedModel, setSelectedModel] = useState("llama-3.3-70b-versatile")
2424

2525
const { messages, sendMessage, status, setMessages } = useChat({
2626
// Cast to any to bypass strict UIMessage type vs helper type conflicts if necessary
@@ -99,7 +99,7 @@ export default function AIToolsPage() {
9999
<div className="flex items-center gap-2 mt-1">
100100
<div className="h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
101101
<p className="text-sm text-muted-foreground">
102-
Powered by Vercel AI Gateway
102+
Powered by Groq
103103
</p>
104104
</div>
105105
</div>
@@ -112,9 +112,10 @@ export default function AIToolsPage() {
112112
<SelectValue placeholder="Select Model" />
113113
</SelectTrigger>
114114
<SelectContent>
115-
<SelectItem value="google:gemini-1.5-pro">Gemini 1.5 Pro</SelectItem>
116-
<SelectItem value="anthropic:claude-3-5-sonnet">Claude 3.5 Sonnet</SelectItem>
117-
<SelectItem value="openai:gpt-4o">GPT-4o</SelectItem>
115+
<SelectItem value="llama-3.3-70b-versatile">Llama 3.3 70B</SelectItem>
116+
<SelectItem value="llama-3.1-8b-instant">Llama 3.1 8B (Fast)</SelectItem>
117+
<SelectItem value="gemma2-9b-it">Gemma 2 9B</SelectItem>
118+
<SelectItem value="mixtral-8x7b-32768">Mixtral 8x7B</SelectItem>
118119
</SelectContent>
119120
</Select>
120121
</div>
@@ -160,7 +161,7 @@ export default function AIToolsPage() {
160161
<div className="flex-1 space-y-2">
161162
<div className="flex items-center gap-2">
162163
<span className="text-xs font-semibold text-foreground">
163-
{message.role === "user" ? "You" : selectedModel.split(':')[1] || selectedModel}
164+
{message.role === "user" ? "You" : selectedModel}
164165
</span>
165166
</div>
166167
<div className={`group relative rounded-2xl p-4 shadow-sm ${
@@ -199,7 +200,7 @@ export default function AIToolsPage() {
199200
</div>
200201
<div className="flex-1 space-y-2">
201202
<div className="flex items-center gap-2">
202-
<span className="text-xs font-semibold text-foreground">{selectedModel.split(':')[1] || selectedModel}</span>
203+
<span className="text-xs font-semibold text-foreground">{selectedModel}</span>
203204
</div>
204205
<div className="rounded-2xl p-4 bg-card border border-border shadow-sm">
205206
<div className="flex items-center gap-2">
@@ -228,7 +229,7 @@ export default function AIToolsPage() {
228229
type="text"
229230
value={input}
230231
onChange={handleInputChange}
231-
placeholder={`Ask ${selectedModel.split(':')[1] || "AI"} anything...`}
232+
placeholder={`Ask ${selectedModel} anything...`}
232233
className="w-full bg-background border border-border rounded-xl px-4 py-3 pr-12 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all placeholder:text-muted-foreground/60"
233234
disabled={isLoading}
234235
/>

app/dashboard/diagrams/text/[id]/page.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { useRouter, useParams } from "next/navigation"
55
import { getCurrentUser } from "@/lib/features/auth"
66
import { useLanguage } from "@/lib/config"
77
import { Save, Download, ArrowLeft, Copy, Maximize2, Minimize2, BookOpen } from "lucide-react"
8-
import mermaid from "mermaid"
8+
// Removed static import for mermaid to enable lazy-loading
9+
// import mermaid from "mermaid"
910

1011
interface Diagram {
1112
id: string
@@ -33,7 +34,8 @@ export default function TextDiagramEditorPage() {
3334
const [showDocumentation, setShowDocumentation] = useState(false)
3435

3536
useEffect(() => {
36-
if (typeof window !== "undefined") {
37+
const initMermaid = async () => {
38+
const { default: mermaid } = await import("mermaid")
3739
mermaid.initialize({
3840
startOnLoad: false,
3941
theme: "dark",
@@ -51,6 +53,10 @@ export default function TextDiagramEditorPage() {
5153
},
5254
})
5355
}
56+
57+
if (typeof window !== "undefined") {
58+
initMermaid()
59+
}
5460
}, [])
5561

5662
useEffect(() => {
@@ -85,6 +91,7 @@ export default function TextDiagramEditorPage() {
8591
setError(null)
8692
previewRef.current.innerHTML = ""
8793

94+
const { default: mermaid } = await import("mermaid")
8895
const id = `mermaid-${Date.now()}`
8996
const { svg } = await mermaid.render(id, textContent)
9097
previewRef.current.innerHTML = svg
@@ -118,6 +125,7 @@ export default function TextDiagramEditorPage() {
118125
if (!textContent) return
119126

120127
try {
128+
const { default: mermaid } = await import("mermaid")
121129
const id = `mermaid-export-${Date.now()}`
122130
const { svg } = await mermaid.render(id, textContent)
123131

0 commit comments

Comments
 (0)