-
Notifications
You must be signed in to change notification settings - Fork 4
Refactor: Security + Performance optimisation #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
fa7b12c
Refactor: Security + Performance optimisation
Priyans00 74e25c1
Fix: Fix copilot suggestions
Priyans00 60c6338
Fix: Add centralised rate limiter
Priyans00 213f8cb
Fix: Fix copilot suggestion - 2
Priyans00 07577db
csp i did something
Priyans00 a3eb960
Fix: Fix Copilot suggestion-3
Priyans00 3ac7d55
Fix: Fix login
Priyans00 8126f8c
Fix: Fix login - 2
Priyans00 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,60 +1,129 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { createClient } from "@/lib/supabase/server"; | ||
|
|
||
| // Cache for active chats | ||
| const chatCache = new Map<string, { data: unknown[]; timestamp: number }>(); | ||
| const CACHE_TTL = 5 * 60 * 1000; // 5 minutes | ||
|
|
||
| function validateUserID(id: string): boolean { | ||
| // Accept both UUID format and SRN format (PES2UG24CS453) | ||
| const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; | ||
| const srnRegex = /^PES[0-9]{1}UG[0-9]{2}[A-Z]{2}[0-9]{3}$/; | ||
| return uuidRegex.test(id) || srnRegex.test(id); | ||
| } | ||
|
|
||
| export async function GET(req: NextRequest) { | ||
| const supabase = await createClient(); | ||
|
|
||
| // Authenticate user | ||
| const { data: { user } } = await supabase.auth.getUser(); | ||
| if (!user) { | ||
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||
| } | ||
|
|
||
| const { searchParams } = new URL(req.url); | ||
| const userId = searchParams.get("userId"); | ||
|
|
||
| if (!userId) { | ||
| return NextResponse.json({ error: "Missing userId" }, { status: 400 }); | ||
| } | ||
|
|
||
| try { | ||
| type Message = { sender_id: string; receiver_id: string }; | ||
| if (!validateUserID(userId)) { | ||
| return NextResponse.json({ error: "Invalid userId format" }, { status: 400 }); | ||
| } | ||
|
|
||
| // Authorization check | ||
| if (user.id !== userId) { | ||
| return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | ||
| } | ||
|
|
||
| // Check cache first | ||
| const cacheKey = `active_chats_${userId}`; | ||
| const cached = chatCache.get(cacheKey); | ||
| if (cached && Date.now() - cached.timestamp < CACHE_TTL) { | ||
| return NextResponse.json(cached.data); | ||
| } | ||
|
|
||
| // Find all users that have exchanged messages with this user | ||
| const { data: messages, error } = await supabase | ||
| try { | ||
| // Optimized query: get unique conversation partners with last message time :> | ||
| const { data: conversations, error } = await supabase | ||
| .from("messages") | ||
| .select("sender_id, receiver_id") | ||
| .or(`sender_id.eq.${userId},receiver_id.eq.${userId}`); | ||
| .select(` | ||
| sender_id, | ||
| receiver_id, | ||
| created_at, | ||
| message | ||
| `) | ||
| .or(`sender_id.eq.${userId},receiver_id.eq.${userId}`) | ||
| .order("created_at", { ascending: false }) | ||
| .limit(200); // Reasonable limit for performance | ||
|
|
||
| if (error) { | ||
| console.error("Database error in active-chats:", error); | ||
| // Return empty array if there's a database error (e.g., migration not run) | ||
| return NextResponse.json([]); | ||
| return NextResponse.json({ error: "Failed to fetch conversations" }, { status: 500 }); | ||
| } | ||
|
|
||
| // Handle case where messages is null or undefined | ||
| if (!messages || !Array.isArray(messages)) { | ||
| if (!conversations || !Array.isArray(conversations)) { | ||
| return NextResponse.json([]); | ||
| } | ||
|
|
||
| // Get unique other user IDs | ||
| const otherUserIds = new Set<string>(); | ||
| messages.forEach((m: Message) => { | ||
| if (m.sender_id !== userId) otherUserIds.add(m.sender_id); | ||
| if (m.receiver_id !== userId) otherUserIds.add(m.receiver_id); | ||
| // Process conversations more efficiently | ||
| const conversationMap = new Map<string, { | ||
| userId: string; | ||
| lastMessage: string; | ||
| lastMessageTime: string | ||
| }>(); | ||
|
|
||
| conversations.forEach((msg: { sender_id: string; receiver_id: string; message: string; created_at: string }) => { | ||
| const otherUserId = msg.sender_id === userId ? msg.receiver_id : msg.sender_id; | ||
|
|
||
| // Keep only the most recent message per conversation | ||
| if (!conversationMap.has(otherUserId)) { | ||
| conversationMap.set(otherUserId, { | ||
| userId: otherUserId, | ||
| lastMessage: msg.message, | ||
| lastMessageTime: msg.created_at | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| if (otherUserIds.size === 0) return NextResponse.json([]); | ||
| if (conversationMap.size === 0) { | ||
| chatCache.set(cacheKey, { data: [], timestamp: Date.now() }); | ||
| return NextResponse.json([]); | ||
| } | ||
|
|
||
| // Fetch user details for these IDs | ||
| // Fetch user details in batch | ||
| const otherUserIds = Array.from(conversationMap.keys()); | ||
| const { data: users, error: usersError } = await supabase | ||
| .from("user_profiles") | ||
| .select("id, name") | ||
| .in("id", Array.from(otherUserIds)); | ||
| .select("id, name, srn") | ||
| .in("id", otherUserIds); | ||
|
|
||
| if (usersError) { | ||
| console.error("Database error fetching user profiles:", usersError); | ||
| // Return empty array if user_profiles table has issues | ||
| return NextResponse.json([]); | ||
| return NextResponse.json({ error: "Failed to fetch user profiles" }, { status: 500 }); | ||
| } | ||
|
|
||
| return NextResponse.json(users || []); | ||
| } catch (err) { | ||
| console.error("Unexpected error in active-chats:", err); | ||
| const message = err instanceof Error ? err.message : "Failed to fetch chats"; | ||
| return NextResponse.json({ error: message }, { status: 500 }); | ||
| // Combine user data with conversation metadata | ||
| const result = (users || []).map(user => { | ||
| const conversation = conversationMap.get(user.id); | ||
| return { | ||
| id: user.id, | ||
| name: user.name || user.srn || 'Unknown User', | ||
| lastMessage: conversation?.lastMessage?.substring(0, 100), | ||
| lastMessageTime: conversation?.lastMessageTime | ||
| }; | ||
| }).sort((a, b) => | ||
| new Date(b.lastMessageTime || 0).getTime() - new Date(a.lastMessageTime || 0).getTime() | ||
| ); | ||
|
|
||
| // Cache the result | ||
| chatCache.set(cacheKey, { data: result, timestamp: Date.now() }); | ||
|
|
||
| return NextResponse.json(result); | ||
|
|
||
| } catch (error) { | ||
| console.error("Unexpected error in active-chats:", error); | ||
| return NextResponse.json({ error: "Internal server error" }, { status: 500 }); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the fetch request completes successfully before the timeout, the timeout is cleared but the AbortController signal remains aborted for subsequent requests. Consider creating a new AbortController for each request.