Skip to content

feat(chats): GET /api/chats/[id] (chatId → chat row incl. sessionId)#625

Closed
sweetmantech wants to merge 1 commit into
testfrom
feat/get-chat-by-id
Closed

feat(chats): GET /api/chats/[id] (chatId → chat row incl. sessionId)#625
sweetmantech wants to merge 1 commit into
testfrom
feat/get-chat-by-id

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

@sweetmantech sweetmantech commented May 29, 2026

Summary

Prerequisite for the chat-side cutover unification (recoupable/chat#1747). Adds GET /api/chats/{chatId} returning the chat row in camelCase wire format, including sessionId.

Today a client holding only a legacy /chat/[roomId] URL has no way to recover the session_id it needs to (a) target the workflow transport (/api/chat/workflow) and (b) read history from chat_messages via GET /api/sessions/{sessionId}/chats/{chatId}. /api/chats (list) still returns rooms (no session_id), and no single-chat endpoint existed. This fills that gap.

After the Phase 2 backfill (chats.id == rooms.id, chats.session_id set), this resolves the sessionId for any backfilled or new chat.

Changes

  • app/api/chats/[id]/route.tsGET + OPTIONS.
  • lib/chats/getChatHandler.ts — maps the validated chat to { chat: toChatResponse(chat) }.
  • lib/chats/validateGetChatRequest.ts — auth + load chat + confirm caller owns the chat's parent session. Mirrors validateGetSessionChatRequest, but resolves the session from the chat row instead of requiring sessionId in the path.
  • Reuses validateAuthContext, selectChats, selectSessions, toChatResponse (DRY; no new Supabase queries).

Test plan

  • validateGetChatRequest unit tests: 401 forward, 404 missing chat, 403 wrong-account, 403 orphan session, 200 owner. (5)
  • getChatHandler unit tests: forwards failure NextResponse, 200 with camelCase { chat } incl. sessionId. (2)
  • tsc --noEmit clean on changed files; eslint clean.
  • Preview: GET /api/chats/<backfilled-room-id> returns { chat: { id, sessionId, ... } } for the owner; 403 for a non-owner; 404 for an unknown id.

🤖 Generated with Claude Code


Summary by cubic

Adds GET /api/chats/{chatId} to return a single chat in camelCase, including sessionId. This lets clients with only a legacy /chat/[roomId] URL recover the sessionId for workflow transport and history reads.

  • New Features
    • GET /api/chats/{chatId} + OPTIONS with CORS; returns { chat } including sessionId.
    • Validates auth, loads the chat, and checks session ownership; responds with 401/403/404 on failure.
    • Reuses selectChats, selectSessions, and toChatResponse (no new queries).

Written for commit b67ba5a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added API endpoint to retrieve individual chat data with authentication validation.
    • Implemented access controls to ensure users can only access their own chats.
    • Added error handling for missing chats and unauthorized access attempts.

Review Change Stack

Lets a client holding only a /chat/[roomId] URL recover the sessionId the
workflow transport and chat_messages history read require. Authenticates
via validateAuthContext, loads the chat with selectChats, and confirms the
caller owns the chat's parent session (mirrors validateGetSessionChatRequest,
resolving the session from the chat instead of the path). Reuses toChatResponse
for camelCase wire format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 29, 2026

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

Project Deployment Actions Updated (UTC)
api Ready Ready Preview May 29, 2026 2:42pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

This PR implements a new GET endpoint at /api/chats/[id] for authenticated users to retrieve a single chat. The endpoint validates caller identity, verifies ownership through session association, returns the chat in wire format, and includes appropriate CORS headers and error responses for authentication, authorization, and not-found scenarios.

Changes

Single Chat Retrieval Endpoint

Layer / File(s) Summary
Request validation and authorization
lib/chats/validateGetChatRequest.ts
Authenticates the caller via validateAuthContext, loads the chat row by chatId, verifies the caller owns the chat via selectSessions and session.account_id matching, and returns 401/403/404 error responses (with CORS headers) on failure or the chat row on success.
Handler and response formatting
lib/chats/getChatHandler.ts
Invokes validateGetChatRequest to authorize the request, converts the chat row to wire format using toChatResponse, and returns a JSON response with HTTP 200, the chat payload, and CORS headers. Returns validation error responses directly.
Route handler setup
app/api/chats/[id]/route.ts
Exports OPTIONS handler returning 200 with CORS headers and GET handler that awaits the dynamic route parameter id from async context.params and delegates to getChatHandler for response generation.

Sequence Diagram

sequenceDiagram
  participant Client
  participant RouteHandler as Route Handler<br/>/api/chats/[id]
  participant getChatHandler as getChatHandler
  participant validateGetChatRequest as validateGetChatRequest
  participant AuthService as validateAuthContext
  participant ChatDB as selectChats
  participant SessionDB as selectSessions
  
  Client->>RouteHandler: GET /api/chats/[id]
  RouteHandler->>RouteHandler: await context.params.id
  RouteHandler->>getChatHandler: getChatHandler(request, id)
  getChatHandler->>validateGetChatRequest: validateGetChatRequest(request, chatId)
  validateGetChatRequest->>AuthService: validateAuthContext(request)
  alt auth fails
    AuthService-->>validateGetChatRequest: error NextResponse
    validateGetChatRequest-->>getChatHandler: error NextResponse
    getChatHandler-->>RouteHandler: error response (401)
  else auth succeeds
    validateGetChatRequest->>ChatDB: selectChats(chatId)
    alt chat not found
      ChatDB-->>validateGetChatRequest: empty
      validateGetChatRequest-->>getChatHandler: error NextResponse (404)
      getChatHandler-->>RouteHandler: error response (404)
    else chat found
      validateGetChatRequest->>SessionDB: selectSessions(chat.session_id)
      alt session missing or not owned
        SessionDB-->>validateGetChatRequest: error NextResponse (403)
        validateGetChatRequest-->>getChatHandler: error NextResponse (403)
        getChatHandler-->>RouteHandler: error response (403)
      else session owned by caller
        validateGetChatRequest-->>getChatHandler: chat row
        getChatHandler->>getChatHandler: toChatResponse(chat)
        getChatHandler-->>RouteHandler: { chat } + 200 + CORS headers
        RouteHandler-->>Client: JSON response with chat data
      end
    end
  end
Loading

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A chat endpoint takes shape and form, 🎯
With validation standing guard,
Auth and ownership checks perform,
Wire format—not too hard.
GET returns what's yours alone,
CORS headers lead you home. 🏠

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Solid & Clean Code ✅ Passed Functions follow SRP with proper file-function naming, interfaces colocated per codebase pattern, all code <20 lines, reuses existing utilities, clear error handling with comprehensive documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/get-chat-by-id

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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.

🧹 Nitpick comments (1)
lib/chats/getChatHandler.ts (1)

7-9: Interface export alongside function is idiomatic TypeScript.

While the guideline for lib/**/*.ts states "one exported function per file", exporting the ChatResponse interface alongside getChatHandler is standard TypeScript practice. The interface directly supports the function by defining its response shape and is used only for type-checking via satisfies on line 27. This doesn't violate the spirit of SRP—the file has a single responsibility: handling GET chat requests.

Consider this compliant with the architectural intent of the guideline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/chats/getChatHandler.ts` around lines 7 - 9, Keep the exported
ChatResponse interface in the file; it's fine to export ChatResponse alongside
getChatHandler because it defines the response shape used for type-checking with
the satisfies usage (and ties to toChatResponse), so no code change is
needed—leave ChatResponse exported and ensure it's only used for typing around
getChatHandler/toChatResponse.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/chats/getChatHandler.ts`:
- Around line 7-9: Keep the exported ChatResponse interface in the file; it's
fine to export ChatResponse alongside getChatHandler because it defines the
response shape used for type-checking with the satisfies usage (and ties to
toChatResponse), so no code change is needed—leave ChatResponse exported and
ensure it's only used for typing around getChatHandler/toChatResponse.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f56ac62c-c7a6-4502-ba15-1bebb1eb3d56

📥 Commits

Reviewing files that changed from the base of the PR and between ed3d425 and b67ba5a.

⛔ Files ignored due to path filters (2)
  • lib/chats/__tests__/getChatHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/chats/__tests__/validateGetChatRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (3)
  • app/api/chats/[id]/route.ts
  • lib/chats/getChatHandler.ts
  • lib/chats/validateGetChatRequest.ts

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Client as Client (browser/API consumer)
    participant Route as GET /api/chats/[id]
    participant Handler as getChatHandler
    participant Validator as validateGetChatRequest
    participant Auth as validateAuthContext
    participant DB as Supabase DB

    Client->>Route: GET /api/chats/{chatId}
    Route->>Handler: getChatHandler(request, chatId)
    Handler->>Validator: validateGetChatRequest(request, chatId)

    Validator->>Auth: validateAuthContext(request)
    alt Auth failure (401)
        Auth-->>Validator: NextResponse (401)
        Validator-->>Handler: Forward NextResponse (401)
        Handler-->>Route: 401 response
        Route-->>Client: 401 Unauthorized
    else Auth success
        Auth-->>Validator: { accountId, orgId, authToken }
        Validator->>DB: selectChats({ id: chatId })
        DB-->>Validator: chat row or empty

        alt Chat not found (404)
            Validator-->>Handler: NextResponse (404)
            Handler-->>Route: 404 response
            Route-->>Client: 404 Chat Not Found
        else Chat found
            Validator->>DB: selectSessions({ id: chat.session_id })
            DB-->>Validator: session row or empty

            alt Session missing or wrong account (403)
                Validator-->>Handler: NextResponse (403)
                Handler-->>Route: 403 response
                Route-->>Client: 403 Forbidden
            else Session owned by caller
                Validator-->>Handler: raw chat row
                Handler->>Handler: toChatResponse(chat) → camelCase
                Handler-->>Route: 200 { chat: { id, sessionId, ... } }
                Route-->>Client: 200 { chat: { id, sessionId, ... } }
            end
        end
    end
Loading

Requires human review: This PR introduces a new API endpoint with authentication, database queries, and access control logic; any bug in authorization or data exposure could have security or data integrity consequences, so it requires human review.

Re-trigger cubic

@sweetmantech
Copy link
Copy Markdown
Contributor Author

Closing — superseded by the pivot to the session-scoped URL approach (chat#1752 + arpit's series).

This endpoint was originally the lookup the legacy URL redirect (/chat/[roomId]/sessions/{lookup(id)}/chats/[id]) needed to preserve existing bookmarks. With the team's decision to let legacy /chat/[roomId] URLs deprecate rather than redirect, the endpoint has no remaining consumer:

  • canonical route /sessions/[sid]/chats/[cid] already carries both ids in the path
  • sidebar gets sessionId from the new list shape (api#626)
  • new chats get sessionId from createSession

No file overlap with api#626 (that PR migrates the LIST endpoint; this was the singular by-id reader), so closing has no impact on that work.

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.

1 participant