Skip to content

Feat(api) - migrate post chat read#624

Merged
sweetmantech merged 6 commits into
testfrom
feat/migrate-post-chat-read
May 29, 2026
Merged

Feat(api) - migrate post chat read#624
sweetmantech merged 6 commits into
testfrom
feat/migrate-post-chat-read

Conversation

@ahmednahima0-beep
Copy link
Copy Markdown
Collaborator

@ahmednahima0-beep ahmednahima0-beep commented May 29, 2026

Summary by cubic

Adds POST /api/sessions/:sessionId/chats/:chatId/read to mark a chat as read for the authenticated account and respond with { success: true }. Auth via Privy bearer token or x-api-key, validates ownership, and upserts chat_reads.last_read_at.

  • New Features

    • CORS preflight; no cache (force-dynamic, force-no-store).
    • Validation: session owned by account; chat belongs to session (403/404 on failure).
    • Idempotent persistence via upsertChatRead(accountId, chatId).
    • Consistent JSON errors with proper status codes.
  • Refactors

    • Simplified request creation in handler and validator tests; no behavior change.

Written for commit 048f939. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features
    • Users can now mark individual chats as read, with the system automatically tracking and recording the timestamp of when each chat was last viewed.
    • Read status requests are validated and authenticated with proper security checks to ensure data integrity, maintain user privacy, and prevent unauthorized modifications.

Review Change Stack

ahmednahima0-beep and others added 2 commits May 22, 2026 20:33
This commit introduces the handling of a short-lived Privy JWT in the workflow request body as `recoupAccessToken`. The implementation ensures that the token is validated and injected into the sandbox environment, allowing the `recoup-api` skill to authenticate successfully.

Key changes include:
- Updated schema in `lib/chat/validateChatWorkflow.ts` to accept `recoupAccessToken`.
- Enhanced `AgentContext` to include the new token field.
- Adjusted `handleChatWorkflowStream` to conditionally spread the token into the context.
- Modified `buildRecoupExecEnv` to inject the token into the sandbox environment when present.

Tests have been added to verify the new functionality, ensuring that the full suite passes successfully.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit modifies the response structure for the chat read endpoints to enhance consistency. The return value for successful requests has been changed from `{ status: "ok" }` to `{ success: true }` across the relevant API routes and handler functions.

Key changes include:
- Updated JSDoc comments to reflect the new response format.
- Adjusted the implementation in `markChatReadHandler` and the corresponding test cases to ensure they validate the new response structure.

All tests have been updated accordingly and pass successfully.
@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 6:53pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

New POST endpoint at /api/sessions/{sessionId}/chats/{chatId}/read validates authenticated user ownership of the session and chat, then upserts a timestamped chat_reads record tracking when the chat was last viewed. Supports preflight CORS and is configured for fresh reads on every request.

Changes

Mark Chat as Read Feature

Layer / File(s) Summary
Request validation contract and validator
lib/sessions/chats/validateMarkChatReadRequest.ts
ValidatedMarkChatReadRequest interface carries authenticated context plus session and chat rows. validateMarkChatReadRequest authenticates the caller, concurrently loads session and chat, verifies ownership, and returns typed validation result or distinct CORS-enabled errors (404/403).
Database persistence for chat reads
lib/supabase/chat_reads/upsertChatRead.ts
upsertChatRead idempotently upserts a chat_reads row with current ISO timestamp for last_read_at and updated_at, keyed by (account_id, chat_id), and returns the persisted row or null on failure.
Handler orchestration
lib/sessions/chats/markChatReadHandler.ts
markChatReadHandler validates the incoming request, calls upsertChatRead with the authenticated account, and returns CORS-enabled success (200) or error (500) responses with JSON payloads.
Route handler and runtime configuration
app/api/sessions/[sessionId]/chats/[chatId]/read/route.ts
OPTIONS and POST route handlers extract async route params and delegate to markChatReadHandler. Exports dynamic = "force-dynamic", fetchCache = "force-no-store", and revalidate = 0 to bypass caching.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • recoupable/api#552: Implements chat listing that reads from the chat_reads table via selectChatReads and getChatSummaries to compute hasUnread status, complementing this PR's write path to the same table.

Suggested reviewers

  • sweetmantech

Poem

A chat's last moment now recorded with care,
Timestamps preserved in the database air,
Read status tracked for each user's delight,
Validation and upserts working just right! 📖✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Missing Zod UUID validation for sessionId/chatId in validateMarkChatReadRequest violates API input validation guideline; similar validators do validate. Add Zod UUID schema validation for sessionId/chatId at start of validateMarkChatReadRequest, returning 400 on invalid params before database queries.
✅ Passed checks (2 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.

✏️ 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/migrate-post-chat-read

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.

This commit refactors the request creation in the `markChatReadHandler` and `validateMarkChatReadRequest` test files for improved readability. The `makeReq` function has been simplified to return a single line for the `NextRequest` instantiation, enhancing code clarity without altering functionality.

All tests remain intact and pass successfully.
@ahmednahima0-beep
Copy link
Copy Markdown
Collaborator Author

ahmednahima0-beep commented May 29, 2026

1 2

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: 1

🧹 Nitpick comments (1)
lib/sessions/chats/validateMarkChatReadRequest.ts (1)

44-63: ⚡ Quick win

Extract a shared error response helper/constants for repeated literals.

The repeated { status: "error", error: ... } + status/header blocks should be centralized to keep response formatting consistent.

As per coding guidelines, "Use constants for repeated values" and "Use configuration objects instead of hardcoded values".

🤖 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/sessions/chats/validateMarkChatReadRequest.ts` around lines 44 - 63,
Extract a reusable error response helper and constants to avoid repeating
NextResponse.json payloads and headers: create a helper function (e.g.,
buildErrorResponse(message: string, code: number)) or constant templates (e.g.,
ERROR_RESPONSE = (msg)=>({ status: "error", error: msg }) and use
getCorsHeaders() for headers) and replace the three NextResponse.json calls in
validateMarkChatReadRequest (the checks for session, session.account_id !==
auth.accountId, and !chat || chat.session_id !== sessionId) to call
buildErrorResponse/ERROR_RESPONSE with the appropriate message and HTTP status
(404/403) while keeping getCorsHeaders() for headers.
🤖 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.

Inline comments:
In `@lib/sessions/chats/validateMarkChatReadRequest.ts`:
- Around line 26-30: The validateMarkChatReadRequest function currently uses
sessionId and chatId without schema checks; add a Zod schema (e.g., z.object({
sessionId: z.string().min(1), chatId: z.string().min(1) }) or stricter if UUIDs
are expected) and run schema.parse or safeParse at the top of
validateMarkChatReadRequest to short-circuit invalid input with a NextResponse
400 before any Supabase/database calls, returning the parsed values to continue
processing; update the function signature/return path to use the validated
values from the Zod result and ensure error responses include a clear validation
message.

---

Nitpick comments:
In `@lib/sessions/chats/validateMarkChatReadRequest.ts`:
- Around line 44-63: Extract a reusable error response helper and constants to
avoid repeating NextResponse.json payloads and headers: create a helper function
(e.g., buildErrorResponse(message: string, code: number)) or constant templates
(e.g., ERROR_RESPONSE = (msg)=>({ status: "error", error: msg }) and use
getCorsHeaders() for headers) and replace the three NextResponse.json calls in
validateMarkChatReadRequest (the checks for session, session.account_id !==
auth.accountId, and !chat || chat.session_id !== sessionId) to call
buildErrorResponse/ERROR_RESPONSE with the appropriate message and HTTP status
(404/403) while keeping getCorsHeaders() for headers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d72f826-6548-42bf-a048-d829debd5825

📥 Commits

Reviewing files that changed from the base of the PR and between 49c327f and 048f939.

⛔ Files ignored due to path filters (2)
  • lib/sessions/chats/__tests__/markChatReadHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/sessions/chats/__tests__/validateMarkChatReadRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (4)
  • app/api/sessions/[sessionId]/chats/[chatId]/read/route.ts
  • lib/sessions/chats/markChatReadHandler.ts
  • lib/sessions/chats/validateMarkChatReadRequest.ts
  • lib/supabase/chat_reads/upsertChatRead.ts

Comment on lines +26 to +30
export async function validateMarkChatReadRequest(
request: NextRequest,
sessionId: string,
chatId: string,
): Promise<NextResponse | ValidatedMarkChatReadRequest> {
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 | ⚡ Quick win

Add Zod validation for sessionId/chatId before database reads.

sessionId and chatId are consumed without schema validation. Add a validate function (Zod) and short-circuit with a 400 on invalid params before calling Supabase.

Suggested direction
+import { z } from "zod";
+
+const markChatReadParamsSchema = z.object({
+  sessionId: z.string().uuid(),
+  chatId: z.string().uuid(),
+});
...
 export async function validateMarkChatReadRequest(
   request: NextRequest,
   sessionId: string,
   chatId: string,
 ): Promise<NextResponse | ValidatedMarkChatReadRequest> {
+  const parsed = markChatReadParamsSchema.safeParse({ sessionId, chatId });
+  if (!parsed.success) {
+    return NextResponse.json(
+      { status: "error", error: "Invalid route parameters" },
+      { status: 400, headers: getCorsHeaders() },
+    );
+  }

As per coding guidelines, "All API endpoints should use a validate function for input parsing using Zod for schema validation".

📝 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
export async function validateMarkChatReadRequest(
request: NextRequest,
sessionId: string,
chatId: string,
): Promise<NextResponse | ValidatedMarkChatReadRequest> {
import { z } from "zod";
const markChatReadParamsSchema = z.object({
sessionId: z.string().uuid(),
chatId: z.string().uuid(),
});
export async function validateMarkChatReadRequest(
request: NextRequest,
sessionId: string,
chatId: string,
): Promise<NextResponse | ValidatedMarkChatReadRequest> {
const parsed = markChatReadParamsSchema.safeParse({ sessionId, chatId });
if (!parsed.success) {
return NextResponse.json(
{ status: "error", error: "Invalid route parameters" },
{ status: 400, headers: getCorsHeaders() },
);
}
// ... rest of function body continues
🤖 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/sessions/chats/validateMarkChatReadRequest.ts` around lines 26 - 30, The
validateMarkChatReadRequest function currently uses sessionId and chatId without
schema checks; add a Zod schema (e.g., z.object({ sessionId: z.string().min(1),
chatId: z.string().min(1) }) or stricter if UUIDs are expected) and run
schema.parse or safeParse at the top of validateMarkChatReadRequest to
short-circuit invalid input with a NextResponse 400 before any Supabase/database
calls, returning the parsed values to continue processing; update the function
signature/return path to use the validated values from the Zod result and ensure
error responses include a clear validation message.

@sweetmantech
Copy link
Copy Markdown
Contributor

Manual test results — preview ✅

Tested against the preview deployment https://api-git-feat-migrate-post-chat-read-recoup.vercel.app, signed in as the chat-app owner of an existing chat (account fb678396…, chat fbd61e27…, session c83e32f6…), with a Privy Bearer token captured from the live session. All 8 cases pass.

# Scenario Expected Actual
1 POST without auth 401 401 "Exactly one of x-api-key or Authorization must be provided"
2 OPTIONS preflight 200 + CORS headers 200 with Access-Control-Allow-{Origin,Methods,Headers}
3 Happy path (owner's session + chat) 200 {success:true} 200 {"success":true}
4 Other-owner session (s + c both belong to another account) 403 Forbidden 403 {"status":"error","error":"Forbidden"}
5 Missing session (random uuid) 404 Session not found 404 {"status":"error","error":"Session not found"}
6 Missing chat (valid session + random chat id) 404 Chat not found 404 {"status":"error","error":"Chat not found"}
7 Chat belongs to a different session 404 Chat not found 404 {"status":"error","error":"Chat not found"}
8 Idempotency (repeat #3 after 2s) 200, single PK row, last_read_at/updated_at refresh 200, row count = 1, updated_at advanced ~5s past created_at

DB verification (chat_reads after two writes)

account_id: fb678396-a68f-4294-ae50-b8cacf9ce77b
chat_id:    fbd61e27-95e9-49f2-a6b3-f69d3fda3428
created_at: 2026-05-29 18:56:05.700
updated_at: 2026-05-29 18:56:10.302   ← advanced by the second call
last_read_at: 2026-05-29 18:56:10.29  ← refreshed by the second call
rows for (account_id, chat_id): 1

PK (account_id, chat_id) matches the onConflict: "account_id,chat_id" in upsertChatRead, so the idempotent upsert path is confirmed against the real constraint.

🤖 Tested with Claude Code

@sweetmantech sweetmantech merged commit 290783c into test May 29, 2026
6 checks passed
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