Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Jan 21, 2026

User description

  • Fix unauthorized user error by ensuring user exists in DB on credit fetch and chat save
  • Implement chat history toggle from plant icon next to QCX
  • Move credits preview to the top of the chat
  • Ensure tent tree icon opens usage and billing UI
  • Toggle chat history from the right side

PR Type

Bug fix, Enhancement


Description

  • Ensure user exists in database before credit fetch and chat save

  • Implement chat history toggle with plant icon in header

  • Move credits display to top of chat panel conditionally

  • Add usage and billing button with tent tree icon

  • Reposition chat history panel from left to right side


Diagram Walkthrough

flowchart LR
  A["User Authentication"] -->|"Create if missing"| B["Database User"]
  B -->|"Fetch credits"| C["Credits Display"]
  D["Header"] -->|"Plant icon"| E["Chat History Toggle"]
  E -->|"Opens from"| F["Right Sidebar"]
  D -->|"Tent tree icon"| G["Usage & Billing"]
Loading

File Walkthrough

Relevant files
Bug fix
route.ts
Auto-create missing users on credit fetch                               

app/api/user/credits/route.ts

  • Auto-create user in database if not found during credit fetch
  • Initialize new users with 0 credits and 'free' tier
  • Prevents 404 errors for new authenticated users
+8/-5     
chat.ts
Ensure user exists before chat save                                           

lib/actions/chat.ts

  • Check if user exists in database before saving chat
  • Auto-create user with default values if missing
  • Prevents database errors when saving chats for new users
+17/-0   
Configuration changes
next-env.d.ts
Update Next.js types import path                                                 

next-env.d.ts

  • Update Next.js types import path from dev to production
  • Aligns with Next.js configuration standards
+1/-1     
Enhancement
chat.tsx
Conditionally position credits display                                     

components/chat.tsx

  • Move credits display inside conditional calendar check
  • Credits now only show when calendar is not open
  • Improves UI layout and space management
+1/-1     
header.tsx
Add chat history toggle and improve header UI                       

components/header.tsx

  • Add plant icon (Sprout) for chat history toggle in header
  • Separate logo link from history trigger
  • Move chat history panel to right side with rounded corners
  • Add title attributes to usage and billing button
  • Improve header layout with gap spacing
+13/-7   
history.tsx
Reposition history panel to right side                                     

components/history.tsx

  • Change history panel from left to right side
  • Update border radius for right-side panel positioning
  • Maintains credits display in history panel
+1/-1     

- Fix unauthorized user error by ensuring user exists in DB on credit fetch and chat save
- Implement chat history toggle from plant icon next to QCX
- Move credits preview to the top of the chat
- Ensure tent tree icon opens usage and billing UI
- Toggle chat history from the right side
@charliecreates charliecreates bot requested a review from CharlieHelps January 21, 2026 07:37
@vercel
Copy link

vercel bot commented Jan 21, 2026

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

Project Deployment Review Updated (UTC)
qcx Ready Ready Preview, Comment Jan 21, 2026 7:37am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logging: New code creates a users record when missing but does not emit an audit log capturing user
ID, action, timestamp, and outcome for this critical security-relevant event.

Referred Code
let dbUser = await db.query.users.findFirst({
  where: eq(users.id, user.id)
});

if (!dbUser) {
  // Create user if they don't exist in the database
  const [newUser] = await db.insert(users).values({
    id: user.id,
    credits: 0,
    tier: 'free'
  }).returning();
  dbUser = newUser;

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unhandled DB failures: The new user auto-creation flow performs DB reads/inserts without try/catch or fallback
handling, so insert/query failures (or race/duplicate constraints) can cause ungraceful
failures without controlled recovery.

Referred Code
const { db } = await import('@/lib/db');
const { users } = await import('@/lib/db/schema');
const { eq } = await import('drizzle-orm');

const dbUser = await db.query.users.findFirst({
  where: eq(users.id, effectiveUserId)
});

if (!dbUser) {
  await db.insert(users).values({
    id: effectiveUserId,
    credits: 0,
    tier: 'free'
  });
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Sensitive/unstructured logs: New console.error logging includes user identifiers and logs the raw error object, which
may contain sensitive details and is not structured for safe auditing.

Referred Code
  console.error(`saveChat: User ${authenticatedUserId} attempting to save chat for different user ${effectiveUserId}`)
  return null
}

// Ensure user exists in the database before saving chat
const { db } = await import('@/lib/db');
const { users } = await import('@/lib/db/schema');
const { eq } = await import('drizzle-orm');

const dbUser = await db.query.users.findFirst({
  where: eq(users.id, effectiveUserId)
});

if (!dbUser) {
  await db.insert(users).values({
    id: effectiveUserId,
    credits: 0,
    tier: 'free'
  });
}



 ... (clipped 4 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
User creation semantics: The new auto-create behavior inserts a users record based on external identity (user.id)
without visible validation/upsert/transaction safeguards, requiring confirmation that
constraints, authorization guarantees, and race handling are covered elsewhere.

Referred Code
let dbUser = await db.query.users.findFirst({
  where: eq(users.id, user.id)
});

if (!dbUser) {
  // Create user if they don't exist in the database
  const [newUser] = await db.insert(users).values({
    id: user.id,
    credits: 0,
    tier: 'free'
  }).returning();
  dbUser = newUser;

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent race condition in user creation

To prevent a race condition during concurrent user creation, modify the insert
operation to use onConflictDoNothing(). This will atomically handle duplicate
user insertions without causing a database error.

lib/actions/chat.ts [160-170]

 const dbUser = await db.query.users.findFirst({
   where: eq(users.id, effectiveUserId)
 });
 
 if (!dbUser) {
   await db.insert(users).values({
     id: effectiveUserId,
     credits: 0,
     tier: 'free'
-  });
+  }).onConflictDoNothing();
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential race condition in the user creation logic and proposes a valid fix using onConflictDoNothing(), which improves the robustness of the code.

Medium
Prevent duplicate user insert errors

To prevent race condition errors during user creation, use Drizzle's
.onConflictDoNothing() method on the insert operation. This will make the
database ignore insertions of users that already exist.

app/api/user/credits/route.ts [30-35]

-const [newUser] = await db.insert(users).values({
-  id: user.id,
-  credits: 0,
-  tier: 'free'
-}).returning();
+const [newUser] = await db.insert(users)
+  .values({ id: user.id, credits: 0, tier: 'free' })
+  .onConflictDoNothing()
+  .returning();
 dbUser = newUser;
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential race condition and provides a concise and effective solution using onConflictDoNothing(), which is a robust way to handle concurrent user creation.

Medium
High-level
Centralize duplicated user creation logic

Refactor the duplicated logic for creating a new user into a single, reusable
function. This function should then be used in both the credits API route and
the chat saving action.

Examples:

app/api/user/credits/route.ts [29-35]
      // Create user if they don't exist in the database
      const [newUser] = await db.insert(users).values({
        id: user.id,
        credits: 0,
        tier: 'free'
      }).returning();
      dbUser = newUser;
lib/actions/chat.ts [164-170]
    if (!dbUser) {
      await db.insert(users).values({
        id: effectiveUserId,
        credits: 0,
        tier: 'free'
      });
    }

Solution Walkthrough:

Before:

// In 'app/api/user/credits/route.ts'
async function GET(req) {
  // ...
  let dbUser = await db.query.users.findFirst(...);
  if (!dbUser) {
    // Create user if they don't exist
    const [newUser] = await db.insert(users).values({
      id: user.id,
      credits: 0,
      tier: 'free'
    }).returning();
    dbUser = newUser;
  }
  // ...
}

// In 'lib/actions/chat.ts'
async function saveChat(chat, userId) {
  // ...
  const dbUser = await db.query.users.findFirst(...);
  if (!dbUser) {
    // Create user again
    await db.insert(users).values({
      id: effectiveUserId,
      credits: 0,
      tier: 'free'
    });
  }
  // ...
}

After:

// In a new shared file, e.g., 'lib/actions/user.ts'
async function findOrCreateUser(userId) {
  let user = await db.query.users.findFirst({ where: eq(users.id, userId) });
  if (!user) {
    const [newUser] = await db.insert(users).values({
      id: userId,
      credits: 0,
      tier: 'free'
    }).returning();
    user = newUser;
  }
  return user;
}

// In 'app/api/user/credits/route.ts'
async function GET(req) {
  // ...
  const dbUser = await findOrCreateUser(user.id);
  // ...
}

// In 'lib/actions/chat.ts'
async function saveChat(chat, userId) {
  // ...
  await findOrCreateUser(effectiveUserId);
  // ...
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies duplicated business logic for user creation introduced in the PR, and centralizing it would significantly improve code maintainability and prevent future inconsistencies.

Medium
  • More

@ngoiyaeric ngoiyaeric merged commit fe8dc87 into fix/regressions-security-architecture-13947295106479740171 Jan 21, 2026
4 of 5 checks passed
Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

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

The DB-user auto-provisioning is a good direction, but both the credits endpoint and saveChat use a non-idempotent select → insert flow that can fail under concurrent requests; make inserts conflict-safe to avoid reintroducing auth-related errors. saveChat also adds dynamic imports in a hot path, which is unnecessary overhead for server actions. Finally, History’s trigger API is flexible but can easily be misused with nested/compound children, leading to brittle UI behavior.

Additional notes (2)
  • Maintainability | components/history.tsx:38-41
    SheetTrigger is typically expected to wrap a single interactive element. Passing an arbitrary children node that could include multiple elements (or another button) can lead to nested-interactive / click-target issues.

Now that the header uses <History> to wrap a Button, consider enforcing asChild + single-element children (or validating at runtime) to avoid accidental misuse elsewhere.

  • Compatibility | components/header.tsx:36-36
    Wrapping the logo button with an <a href="/"> can cause nested interactive elements depending on how Button renders (many button components render a <button>). If it’s a <button> inside an <a>, that’s invalid HTML and can lead to accessibility/interaction quirks.

In Next.js, prefer next/link and have the button render “as child” (or render the interactive element as the link).

Summary of changes

What changed

API: Credits endpoint (app/api/user/credits/route.ts)

  • Changed DB lookup to let dbUser = ... and auto-creates a DB user row when missing via db.insert(users).values({ id, credits: 0, tier: 'free' }).returning().
  • Removes the prior 404 User not found behavior and proceeds to return credits/tier.

Chat UI (components/chat.tsx)

  • Moved <CreditsDisplay /> to render only in the non-calendar view, immediately above <ChatPanel />.

Header / navigation (components/header.tsx)

  • Adds Sprout icon and introduces a dedicated chat-history toggle button next to the QCX logo.
  • Wraps the logo + title in an <a href="/"> link.
  • Adds title="Usage & Billing" to the TentTree buttons (desktop + mobile).

History panel (components/history.tsx)

  • Moves history SheetContent from left to right side and updates corner rounding classes accordingly.

Chat persistence (lib/actions/chat.ts)

  • Before saving a chat, ensures the user exists in the DB by querying users and inserting a default record if absent.
  • Implements this using dynamic await import(...) calls for db, users, and eq.

Next.js typings (next-env.d.ts)

  • Updates the referenced generated routes types path from ./.next/dev/types/routes.d.ts to ./.next/types/routes.d.ts.

Comment on lines 23 to 36
// Get user from database
const dbUser = await db.query.users.findFirst({
let dbUser = await db.query.users.findFirst({
where: eq(users.id, user.id)
});

if (!dbUser) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
// Create user if they don't exist in the database
const [newUser] = await db.insert(users).values({
id: user.id,
credits: 0,
tier: 'free'
}).returning();
dbUser = newUser;
}

Choose a reason for hiding this comment

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

The “create user if missing” flow can still throw a 500 (or fail the request) if two requests race and both attempt to insert the same users.id concurrently (e.g., multiple tabs hitting credits fetch on first login). This can reintroduce the auth/credits error you’re trying to fix.

Consider making the insert idempotent (upsert / on-conflict-do-nothing) and then re-select, or handle unique-constraint violations explicitly.

Suggestion

Make user creation race-safe by using an idempotent insert (if supported by your Drizzle dialect) and then re-fetch:

  • Prefer onConflictDoNothing / onDuplicateKeyUpdate style APIs (dialect-dependent)
  • Or catch unique-violation and continue

Example (Postgres-style Drizzle):

await db
  .insert(users)
  .values({ id: user.id, credits: 0, tier: 'free' })
  .onConflictDoNothing({ target: users.id })

const dbUser = await db.query.users.findFirst({ where: eq(users.id, user.id) })

Reply with "@CharlieHelps yes please" if you’d like me to add a commit with this change.

Comment on lines +155 to +171
// Ensure user exists in the database before saving chat
const { db } = await import('@/lib/db');
const { users } = await import('@/lib/db/schema');
const { eq } = await import('drizzle-orm');

const dbUser = await db.query.users.findFirst({
where: eq(users.id, effectiveUserId)
});

if (!dbUser) {
await db.insert(users).values({
id: effectiveUserId,
credits: 0,
tier: 'free'
});
}

Choose a reason for hiding this comment

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

Doing a sequence of findFirst then insert has the same race condition risk as the credits endpoint (double insert on concurrent requests). Also, using dynamic await import(...) inside a hot path will add overhead and makes bundling/traceability harder; this is not a great tradeoff for a core action.

Since this is server-side code, prefer static imports at module scope and make the insert idempotent.

Suggestion

Refactor to static imports and an idempotent insert:

  1. Move imports to top-level:
import { db } from '@/lib/db'
import { users } from '@/lib/db/schema'
import { eq } from 'drizzle-orm'
  1. Use conflict-safe insert + optional re-fetch (dialect-dependent):
await db
  .insert(users)
  .values({ id: effectiveUserId, credits: 0, tier: 'free' })
  .onConflictDoNothing({ target: users.id })

(Or catch unique-constraint errors.)

Reply with "@CharlieHelps yes please" if you’d like me to add a commit with this refactor.

@charliecreates charliecreates bot removed the request for review from CharlieHelps January 21, 2026 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants