Skip to content

make chat work with python server#38

Open
Tobshub wants to merge 1 commit intomainfrom
feat/chat-ws
Open

make chat work with python server#38
Tobshub wants to merge 1 commit intomainfrom
feat/chat-ws

Conversation

@Tobshub
Copy link
Copy Markdown
Owner

@Tobshub Tobshub commented Oct 25, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced real-time messaging with improved data synchronization.
    • Press Enter to send messages for faster interactions.
  • UI/UX Improvements

    • Display chat recipient's name with intelligent fallback.
    • Improved chat area layout with better overflow handling.
    • Added message input placeholder text and avatar support.
    • Call button intelligently disabled when unavailable.

@vercel
Copy link
Copy Markdown

vercel bot commented Oct 25, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
tutorlink Ready Ready Preview Comment Oct 25, 2025 0:12am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 25, 2025

Walkthrough

The PR replaces Socket.IO with native WebSocket for real-time messaging, updates tRPC chat endpoints to resolve users from the database before operations, and enhances the messages page with conditional query enabling, user derivation, and UI improvements including message input handling and call button states.

Changes

Cohort / File(s) Summary
Frontend messaging real-time layer
src/app/dashboard/messages/[conversationId]/page.tsx
Replaces Socket.IO with native WebSocket connection to Python server; manages lifecycle via useRef with open/message/error/close handlers. Sends messages via tRPC first, then WebSocket; refetches data on incoming messages. Derives currentUser from message history; conditionally enables getMessages query; updates UI with otherUser name fallback, disabled call button when no otherUser, vertical overflow, and enter-to-send input.
Backend chat router user resolution
src/server/api/routers/chat.ts
Adds database user lookup in getConversations, sendMessage, and createConversation endpoints using clerkUid; validates user existence with NOT_FOUND error; replaces ctx.user.id with resolved user.id in conversation filtering and message senderId. Updates createConversation join/connect logic to use user.id.

Sequence Diagram

sequenceDiagram
    participant User
    participant App as Client App
    participant tRPC
    participant DB
    participant WS as WebSocket<br/>(Python)
    participant Recipient

    User->>App: Type & send message
    App->>tRPC: sendMessage(text, conversationId)
    tRPC->>DB: Resolve current user by clerkUid
    DB-->>tRPC: Return user record
    tRPC->>DB: Persist message with user.id
    DB-->>tRPC: Message saved
    tRPC-->>App: Success
    
    rect rgb(100, 150, 200)
        Note over App,WS: Real-time notification
        App->>WS: Send message payload via WebSocket
        WS->>Recipient: Deliver message
    end
    
    Recipient->>Recipient: Receives WebSocket message
    Recipient->>tRPC: Refetch messages
    tRPC->>DB: Query latest messages
    DB-->>tRPC: Return updated messages
    tRPC-->>Recipient: Display new message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • WebSocket lifecycle & cleanup: Verify useRef management, connection initialization, error handling, and unmount cleanup to prevent memory leaks or dangling connections.
  • tRPC + WebSocket integration: Confirm message persists successfully before WebSocket send; verify refetch logic is triggered correctly on incoming messages.
  • User resolution consistency: Ensure database user lookup (clerkUid → user.id) is consistently applied across all three endpoints; validate NOT_FOUND error scenarios.
  • currentUser derivation: Review logic that derives currentUser from message history to ensure accuracy and edge cases (empty history, new conversations).
  • UI state synchronization: Verify otherUser filtering, call button disable condition, and input clearing logic work correctly across different conversation states.

Possibly related PRs

  • Chat server done #16: Implements FastAPI WebSocket server and message persistence endpoints—direct server-side infrastructure counterpart to this PR's native WebSocket client.
  • start chat flow #37: Modifies createConversation to use clerkUid in User relation connect—potential conflict or consolidation point with this PR's user.id resolution logic.
  • custom chat #29: Implements Socket.IO-based messaging—directly replaced by this PR's native WebSocket refactor.

Poem

🐰 With native WebSockets, sleek and fine,
Where Socket.IO once held the line,
This fluffy coder hops with glee—
Real-time chat, now swift and free! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "make chat work with python server" directly addresses the main objective of this pull request, which is to integrate WebSocket support for a Python server backend. The changes across both files focus on replacing the previous Socket.IO implementation with native WebSocket connections and updating the backend to support this new integration. The title is concise, clear, and free of vague language or noise, making it immediately understandable to teammates reviewing the commit history. This accurately represents the primary change without being misleading or off-topic.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/chat-ws

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (2)
src/server/api/routers/chat.ts (2)

55-62: Duplicate pattern - see middleware suggestion for getConversations.

This endpoint repeats the same user lookup pattern. The middleware approach suggested for getConversations would eliminate this redundancy.


70-101: Duplicate pattern - see middleware suggestion for getConversations.

This endpoint also repeats the user lookup pattern. Implementing the suggested middleware would eliminate this redundancy and improve performance across all chat endpoints.

🧹 Nitpick comments (1)
src/app/dashboard/messages/[conversationId]/page.tsx (1)

126-127: Consider Shift+Enter for multiline messages.

The Enter key triggering send is good UX, but users might want to send multiline messages. Consider allowing Shift+Enter to insert a newline.

-            onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
+            onKeyDown={(e) => {
+              if (e.key === 'Enter' && !e.shiftKey) {
+                e.preventDefault();
+                handleSendMessage();
+              }
+            }}

If you implement this, also change the input to a <textarea> to better support multiline content.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8edfe11 and 20fd308.

📒 Files selected for processing (2)
  • src/app/dashboard/messages/[conversationId]/page.tsx (3 hunks)
  • src/server/api/routers/chat.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/server/api/routers/chat.ts (1)
src/server/api/trpc.ts (2)
  • createTRPCRouter (78-78)
  • protectedProcedure (104-104)
src/app/dashboard/messages/[conversationId]/page.tsx (1)
src/trpc/react.tsx (1)
  • api (25-25)
🔇 Additional comments (6)
src/server/api/routers/chat.ts (2)

2-2: LGTM!

The TRPCError import is necessary for the error handling added in this PR.


68-68: LGTM!

The comment clarifies that tutorId refers to the database CUID, which is helpful for understanding the data flow.

src/app/dashboard/messages/[conversationId]/page.tsx (4)

5-5: LGTM!

Using useRef to store the WebSocket instance is the correct pattern to maintain a persistent connection across re-renders.

Also applies to: 12-12


14-17: LGTM!

The conditional query enabling prevents unnecessary API calls when conversationId is not yet available, which is a good practice.


98-98: LGTM!

The fallback text and disabled state improve the user experience by handling edge cases gracefully.

Also applies to: 102-102


107-107: LGTM!

Adding overflow-y-auto enables proper scrolling for long message histories, and the avatar placeholder comment aids maintainability.

Also applies to: 110-110

Comment on lines +22 to 25
const currentUser = messages?.find(m => m.sender.clerkUid === user?.id)?.sender;
const otherUser = messages?.[0]?.conversation?.User.find(
(u) => u.id !== user?.id
(u) => u.clerkUid !== user?.id
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: currentUser derivation fails in conversations without user's messages.

Deriving currentUser by searching messages where sender.clerkUid === user?.id will return undefined if:

  • The conversation has no messages yet
  • The current user hasn't sent any messages in this conversation

This breaks the WebSocket connection (line 28-30 checks currentUser?.name), preventing real-time messaging in new conversations.

Use the Clerk user data directly instead:

-  const currentUser = messages?.find(m => m.sender.clerkUid === user?.id)?.sender;
+  // Clerk user should have the username/name we need
+  const currentUserName = user?.username ?? user?.fullName ?? user?.id;

Then update the WebSocket connection:

-    if (!currentUser?.name) {
+    if (!currentUserName) {
       return;
     }

-    const ws = new WebSocket(`ws://localhost:8000/ws/${currentUser.name}`);
+    const ws = new WebSocket(`ws://localhost:8000/ws/${currentUserName}`);

Committable suggestion skipped: line range outside the PR's diff.


// Connect to the Python WebSocket server
// Assumes the server is running on localhost:8000
const ws = new WebSocket(`ws://localhost:8000/ws/${currentUser.name}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded WebSocket URL will break in production.

The WebSocket URL is hardcoded to localhost:8000, which will fail when deployed. Use an environment variable to configure this.

-    const ws = new WebSocket(`ws://localhost:8000/ws/${currentUser.name}`);
+    const wsUrl = process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:8000';
+    const ws = new WebSocket(`${wsUrl}/ws/${currentUser.name}`);

Add to your .env.local:

NEXT_PUBLIC_WS_URL=ws://localhost:8000
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ws = new WebSocket(`ws://localhost:8000/ws/${currentUser.name}`);
const wsUrl = process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:8000';
const ws = new WebSocket(`${wsUrl}/ws/${currentUser.name}`);
🤖 Prompt for AI Agents
In src/app/dashboard/messages/[conversationId]/page.tsx around line 34, the
WebSocket URL is hardcoded to ws://localhost:8000 which will break in
production; change it to build the URL from an environment variable
(NEXT_PUBLIC_WS_URL) plus the path (/ws/${currentUser.name}) so the base
host/protocol can be configured, add NEXT_PUBLIC_WS_URL=ws://localhost:8000 to
.env.local for local dev, and ensure the code falls back gracefully (e.g., to
window.location-derived ws/wss) if the env var is not set.

};

ws.onmessage = (event) => {
const message = JSON.parse(event.data as string) as { type: string };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unsafe type assertion for WebSocket data.

WebSocket event.data can be a string, Blob, or ArrayBuffer. The type assertion as string is unsafe and could fail at runtime if the server sends binary data.

Add runtime validation:

-      const message = JSON.parse(event.data as string) as { type: string };
+      if (typeof event.data !== 'string') {
+        console.error('Unexpected WebSocket data type:', typeof event.data);
+        return;
+      }
+      const message = JSON.parse(event.data) as { type: string };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const message = JSON.parse(event.data as string) as { type: string };
if (typeof event.data !== 'string') {
console.error('Unexpected WebSocket data type:', typeof event.data);
return;
}
const message = JSON.parse(event.data) as { type: string };
🤖 Prompt for AI Agents
In src/app/dashboard/messages/[conversationId]/page.tsx around line 42, the code
unsafely asserts WebSocket event.data as string before JSON.parse; replace the
assertion with runtime handling: check if typeof event.data === "string" and
parse directly, else if event.data is a Blob call await event.data.text(), else
if it's an ArrayBuffer decode it to a string (e.g., using TextDecoder), then
wrap JSON.parse in try/catch and validate the parsed object shape (ensure it has
a type string) before using it to avoid runtime errors from binary payloads or
invalid JSON.

ws.close();
};
}, [conversationId, refetch]);
}, [currentUser?.name, refetch]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove refetch from dependencies to prevent reconnection loops.

Including refetch in the dependency array can cause the WebSocket to reconnect whenever refetch is called, potentially creating an infinite loop since the WebSocket's onmessage handler itself calls refetch() (line 45).

The WebSocket should only reconnect when the user changes:

-  }, [currentUser?.name, refetch]);
+  }, [currentUser?.name]);

Note: You can safely call refetch inside the effect without listing it as a dependency since it's a stable function from React Query.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}, [currentUser?.name, refetch]);
}, [currentUser?.name]);
🤖 Prompt for AI Agents
In src/app/dashboard/messages/[conversationId]/page.tsx around line 61, the
effect currently lists refetch in its dependency array which causes the
WebSocket to reconnect whenever refetch is called (leading to possible
reconnection loops because onmessage calls refetch). Remove refetch from the
dependency array so the effect only depends on currentUser?.name (or other truly
changing values like conversationId), and keep calling refetch() inside the
effect/onmessage as needed (refetch is a stable React Query function so it does
not need to be listed). If your linter flags this, add a focused
eslint-disable-next-line comment for the specific rule on that line and a short
justification.

Comment on lines 63 to 87
const handleSendMessage = () => {
if (!content.trim() || !otherUser?.name) return;

// First, persist the message in our database via tRPC
sendMessage.mutate(
{
conversationId: conversationId as string,
content,
},
{
onSuccess: () => {
socket.emit('message', conversationId, content);
// After successful persistence, send it via WebSocket for real-time delivery
const messagePayload = {
to_username: otherUser.name,
content: content,
};
socketRef.current?.send(JSON.stringify(messagePayload));
setContent('');
},
onError: (error) => {
console.error("Failed to send message:", error);
}
}
);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Check WebSocket connection state before sending.

The code sends messages via WebSocket without checking if the connection is open. If the socket is connecting, closing, or closed, send() will throw an error or silently fail.

Add a ready state check:

       {
         onSuccess: () => {
           // After successful persistence, send it via WebSocket for real-time delivery
+          if (socketRef.current?.readyState !== WebSocket.OPEN) {
+            console.warn('WebSocket not connected, message persisted but not sent in real-time');
+            setContent('');
+            return;
+          }
           const messagePayload = {
             to_username: otherUser.name,
             content: content,
           };
           socketRef.current?.send(JSON.stringify(messagePayload));
           setContent('');
         },
🤖 Prompt for AI Agents
In src/app/dashboard/messages/[conversationId]/page.tsx around lines 63 to 87,
the onSuccess handler sends over the WebSocket without checking connection state
which can throw or silently fail; update the handler to first verify
socketRef.current exists and its readyState is OPEN (WebSocket.OPEN or numeric
1) before calling send, and if not open, either queue the message/retry or
log/notify the user that the message was persisted but not delivered in real
time; keep the persistence via tRPC, add a clear log or UI feedback path for the
non-open socket case, and avoid calling send when readyState is
CONNECTING/CLOSING/CLOSED.

Comment on lines +7 to +14
const user = await ctx.db.user.findUnique({ where: { clerkUid: ctx.user.id }});
if (!user) throw new TRPCError({ code: "NOT_FOUND", message: "User not found." });

return ctx.db.conversation.findMany({
where: {
User: {
some: {
id: ctx.user.id,
id: user.id,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consider moving user lookup to middleware for performance.

This pattern adds an extra database query to every protected endpoint. If the goal is to map Clerk's authentication ID to your database user record, consider implementing this lookup once in a custom middleware procedure (extending protectedProcedure) and adding the database user to the context. This would eliminate repeated queries and centralize the error handling.

Example middleware approach:

// In src/server/api/trpc.ts
const enforceUserExists = t.middleware(async ({ ctx, next }) => {
  if (!ctx.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  const dbUser = await ctx.db.user.findUnique({ 
    where: { clerkUid: ctx.user.id }
  });
  if (!dbUser) {
    throw new TRPCError({ code: "NOT_FOUND", message: "User not found." });
  }
  return next({
    ctx: {
      ...ctx,
      dbUser, // Now available in all procedures
    },
  });
});

export const authedProcedure = t.procedure
  .use(enforceUserIsAuthed)
  .use(enforceUserExists);

Then use authedProcedure instead of protectedProcedure in chat router to access ctx.dbUser.id directly.

🤖 Prompt for AI Agents
In src/server/api/routers/chat.ts around lines 7-14 the handler performs a
per-request DB lookup to map Clerk's ctx.user.id to your User row; move this
lookup into a reusable middleware so it runs once and populates the context. Add
a middleware (e.g., enforceUserExists) in src/server/api/trpc.ts that checks
ctx.user, queries ctx.db.user.findUnique({ where: { clerkUid: ctx.user.id } }),
throws if missing, and returns next() with ctx.dbUser set; then compose it into
your exported authedProcedure (after existing auth middleware). Finally update
chat router to use authedProcedure instead of protectedProcedure and replace the
local query with ctx.dbUser.id (and remove the duplicate error handling).

@AJ-505
Copy link
Copy Markdown
Collaborator

AJ-505 commented Oct 25, 2025

@Tobshub can I go ahead and deal with the CodeRabbit suggestions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants