Skip to content

User N8N workflows [WIP]#599

Open
sheriffjimoh wants to merge 14 commits intodevelopfrom
user_workflows
Open

User N8N workflows [WIP]#599
sheriffjimoh wants to merge 14 commits intodevelopfrom
user_workflows

Conversation

@sheriffjimoh
Copy link
Contributor

@sheriffjimoh sheriffjimoh commented Dec 22, 2025

Summary by Sourcery

Introduce database and type support for user OAuth connections and n8n workflows while cleaning up unused organization/department structures and adjusting related metadata and permissions.

New Features:

  • Add TypeScript types and Hasura tables for user OAuth connections, n8n credentials, and user workflows to support n8n-based integrations.

Enhancements:

  • Remove chatbot relationships and table includes tied to deprecated department and organization entities.
  • Adjust chatbot, preference, and thread table permissions to reflect the new data model and accessible fields.
  • Regenerate the GraphQL schema artifacts to align with the updated database metadata.

Deployment:

  • Add Hasura metadata and migrations for the new n8n_credentials, user_oauth_connections, and user_workflows tables.

Summary by CodeRabbit

  • New Features
    • Added Gmail workflow automation with enable, disable, and execution controls
    • Implemented OAuth connection management for secure Google account integration
    • Added workflow template support with external service credential management
    • Introduced user workflow and credential storage and retrieval capabilities

@sheriffjimoh sheriffjimoh self-assigned this Dec 22, 2025
@vercel
Copy link

vercel bot commented Dec 22, 2025

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

Project Deployment Actions Updated (UTC)
masterbots Error Error Feb 11, 2026 8:34pm
masterbots-pro Error Error Feb 11, 2026 8:34pm

Request Review

@sourcery-ai
Copy link

sourcery-ai bot commented Dec 22, 2025

Reviewer's Guide

Introduces initial data model and Hasura metadata for user-specific OAuth connections, n8n credentials, and workflows (currently Gmail-focused), while cleaning up some legacy relationships/permissions and regenerating GraphQL schema artifacts.

ER diagram for new user OAuth, n8n credential, and workflow tables

erDiagram
  user {
    uuid user_id PK
  }

  user_oauth_connections {
    uuid id PK
    uuid user_id FK
    text provider
    text service
    text scopes
    text status
    timestamptz connected_at
    timestamptz revoked_at
  }

  n8n_credentials {
    uuid id PK
    uuid user_id FK
    text provider
    text service
    text n8n_credential_id
    timestamptz created_at
  }

  user_workflows {
    uuid id PK
    uuid user_id FK
    text workflow_name
    text workflow_id
    text service
    text folder_path
    timestamptz created_at
  }

  user ||--o| user_oauth_connections : has
  user ||--o| n8n_credentials : has
  user ||--o| user_workflows : has
Loading

Class diagram for new TypeScript user OAuth, n8n, and workflow types

classDiagram
  class UserOAuthConnection {
    string id
    string user_id
    string provider
    string service
    string status
    string scopes
  }

  class N8nCredential {
    string id
    string user_id
    string n8n_credential_id
    string service
  }

  class UserWorkflow {
    string id
    string user_id
    string workflow_id
    string workflow_name
    string service
  }

  class User {
    string user_id
  }

  User "1" --> "0..1" UserOAuthConnection : oauth_connection
  User "1" --> "0..1" N8nCredential : n8n_credential
  User "1" --> "0..1" UserWorkflow : workflow
Loading

File-Level Changes

Change Details Files
Add TypeScript interfaces for user OAuth connections, n8n credentials, and workflows aligned with new backend tables.
  • Define UserOAuthConnection interface for Google/Gmail OAuth state and scopes.
  • Define N8nCredential interface that links a user to an n8n credential for Gmail.
  • Define UserWorkflow interface representing a user-owned workflow mapped to an external workflow id and service.
apps/web/types/types.ts
Create Hasura tables and metadata for user_workflows, user_oauth_connections, and n8n_credentials to support n8n/Gmail integrations.
  • Add minimal Hasura metadata YAML entries for n8n_credentials, user_oauth_connections, and user_workflows tables.
  • Add migrations to create n8n_credentials, user_oauth_connections, and user_workflows tables with UUID PKs, user_id FKs, and uniqueness constraints.
  • Wire new tables into the Hasura tables registry so they are tracked.
apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml
apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml
apps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yaml
apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sql
apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sql
apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql
apps/hasura/metadata/databases/masterbots/tables/tables.yaml
Simplify/relax some Hasura relationships and permission rules on chatbot, preference, and thread tables.
  • Remove department and organization-related object/array relationships from chatbot metadata and adjust moderator/user select columns.
  • Relax user insert/select permission filters on preferences so they no longer check X-Hasura-User-Id and remove lang/font_size from insertable columns.
  • Remove metadata from allowed insert columns on threads for both anonymous and user roles.
apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml
apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml
apps/hasura/metadata/databases/masterbots/tables/public_thread.yaml
Regenerate GenQL GraphQL schema/types to reflect Hasura metadata changes.
  • Update generated GraphQL schema document.
  • Update generated TypeScript schema and types used by GenQL client.
packages/mb-genql/generated/schema.graphql
packages/mb-genql/generated/schema.ts
packages/mb-genql/generated/types.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces Google OAuth integration and n8n-based Gmail workflow automation. It adds three new database tables (user_workflows, user_oauth_connections, n8n_credentials) with migrations, corresponding Hasura metadata with role-based permissions, TypeScript types for OAuth and credential data, service layer functions for managing OAuth connections and workflows, and API endpoints for OAuth flow initiation/callbacks and Gmail automation control (enable, disable, execute).

Changes

Cohort / File(s) Summary
Database Schema & Migrations
apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/..., apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/..., apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/...
Creates three new tables with UUID primary keys, user_id foreign keys, and unique constraints. Includes follow-up migrations for setting UUID column defaults and altering nullable constraints.
Hasura Table Metadata
apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml, public_user_oauth_connections.yaml, public_user_workflows.yaml, public_user_token.yaml
New and updated table metadata files with role-based insert/select/update permissions for moderator and user roles, filtering on X-Hasura-User-Id session variable.
Hasura Metadata Updates
apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml, public_preference.yaml, public_thread.yaml, public_user.yaml, tables.yaml
Removes relationships (department, organization_chatbots, organizations), updates permission structures, fixes column reference typo (chabot_id), and adjusts table includes (removes public_organization files, adds three new table includes).
Type Definitions
apps/web/types/types.ts, apps/pro-web/services/hasura/hasura.service.type.ts
Adds interfaces for OAuth tokens, connections, n8n credentials, workflows, and generic credential types with provider/service/status fields.
Hasura Service Functions
apps/web/services/hasura/hasura.service.ts, apps/pro-web/services/hasura/hasura.service.ts
Exports new functions: insertUserOAuthConnection, insertN8nCredential, insertUserWorkflow, getUserWorkflowByService, getUserCredentialByService for managing OAuth and workflow data with JWT authentication.
N8n Integration Library
apps/pro-web/lib/n8n.ts
Comprehensive n8n API wrapper (~267 lines) providing credential creation, workflow duplication/provisioning from templates, and workflow control (enable/disable/execute) with error handling and node remapping logic.
Gmail Automation API Routes
apps/web/app/api/automations/gmail/enable/route.ts, apps/web/app/api/automations/gmail/disable/route.ts, apps/web/app/api/automations/gmail/execute/route.ts
POST endpoints to enable/disable Gmail workflows and execute payloads, querying user workflows and calling n8n/Hasura operations.
Google OAuth Flow Routes
apps/pro-web/app/api/oauth/google/start/route.ts, apps/pro-web/app/api/oauth/google/callback/route.ts
GET endpoints initiating Google OAuth2 consent flow and handling authorization code exchange, persisting OAuth connection via Hasura with commented-out n8n credential integration.
Test Route & Auth Changes
apps/web/app/api/test-google-start/route.ts, apps/web/auth.ts
Adds test OAuth route with mock token data; comments out password validation check in auth handler, allowing authentication to proceed regardless of bcrypt comparison result.
Workflow Templates & Utilities
apps/pro-web/lib/constants/template.ts
Defines WorkflowTemplate interface and WORKFLOW_TEMPLATES constant with Gmail-send template mapping to n8n, including optional input schema.
Test Infrastructure
apps/pro-web/lib/hooks/use-workspace-chat.*.test.tsx, apps/pro-web/lib/select-scope-conversation-window.test.ts
Adds biome-ignore comments and Capture component helper for context testing; minor scaffolding updates to test harnesses.
Minor Updates
apps/pro-web/app/actions/subscriptions.actions.ts, apps/pro-web/app/b/page.tsx, apps/pro-web/lib/chat/chat-context-utils.ts, apps/pro-web/next-env.d.ts
Biome-ignore annotations, return statement refactoring, and type declaration cleanup (no functional changes).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Client<br/>(Browser)
    participant WebApp as Web App<br/>(Next.js)
    participant Google as Google<br/>OAuth Server
    participant Hasura as Hasura<br/>(GraphQL)
    participant N8n as N8n<br/>Automation

    User->>Client: Click "Connect Gmail"
    Client->>WebApp: GET /oauth/google/start?userId=...
    WebApp->>Google: Redirect to OAuth consent screen<br/>(scopes: gmail.modify, drive)
    Google-->>Client: User grants permissions
    Client->>WebApp: GET /oauth/google/callback?code=...&state=userId
    WebApp->>Google: POST /token (exchange code for tokens)
    Google-->>WebApp: Return access_token, refresh_token
    WebApp->>Hasura: INSERT user_oauth_connection<br/>(userId, scopes, status: connected)
    Hasura-->>WebApp: Connection persisted
    WebApp-->>Client: Success (tokens stored server-side)

    rect rgba(100, 150, 200, 0.5)
    Note over Client,N8n: Enable Gmail Automation
    User->>Client: Click "Enable Workflow"
    Client->>WebApp: POST /automations/gmail/enable
    WebApp->>Hasura: Query user OAuth credentials
    Hasura-->>WebApp: Return gmail credential
    WebApp->>N8n: duplicateN8nWorkflow (from template)
    N8n-->>WebApp: Return new workflow_id & webhook_url
    WebApp->>Hasura: INSERT user_workflow<br/>(userId, workflow_id, service: gmail)
    Hasura-->>WebApp: Workflow record created
    WebApp-->>Client: Success (automation active)
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Client,N8n: Execute Workflow
    User->>Client: Trigger automation event
    Client->>WebApp: POST /automations/gmail/execute<br/>(userId, payload)
    WebApp->>Hasura: Query active user_workflow
    Hasura-->>WebApp: Return workflow_id
    WebApp->>N8n: POST to webhook with payload
    N8n-->>N8n: Execute Gmail automation
    N8n-->>WebApp: Success
    WebApp-->>Client: Execution confirmed
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Possibly related PRs


Suggested labels

chore, feature


Suggested reviewers

  • AndlerRL
  • Bran18

Poem

🐰 Hopping through the OAuth dance,
Credentials stored with proper stance,
N8n workflows choreographed with care,
Gmail magic floating through the air!
✨ Connected souls and automated dreams 🌙

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive Title 'User N8N workflows [WIP]' is vague and generic—it uses non-descriptive language like '[WIP]' and lacks specificity about the main changes despite involving significant schema updates, new tables, and removed relationships. Replace with a more specific, descriptive title that summarizes the primary change without vague tags. Example: 'Add n8n workflow and OAuth integration tables with Hasura metadata' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ 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 user_workflows

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

@sourcery-ai sourcery-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.

Hey - I've found 5 issues, and left some high level feedback:

  • The public_preference user role permissions were changed to check: {} and filter: {}, which removes the per-user user_id scoping—this effectively lets any user read/write any preference row; if that’s not intentional, please restore the user_id = X-Hasura-User-Id checks.
  • The new tables enforce UNIQUE (user_id) (and in user_workflows also UNIQUE (created_at)), which will limit each user to a single workflow/connection and require globally unique timestamps; consider whether the intent is to allow multiple workflows/connections per user and, if so, relax these constraints.
  • In user_oauth_connections, revoked_at is NOT NULL even though status can be connected or revoked; if active connections are expected to have no revoke time, consider making revoked_at nullable or enforcing consistency at the DB level (e.g., via check constraint).
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `public_preference` user role permissions were changed to `check: {}` and `filter: {}`, which removes the per-user `user_id` scoping—this effectively lets any user read/write any preference row; if that’s not intentional, please restore the `user_id = X-Hasura-User-Id` checks.
- The new tables enforce `UNIQUE (user_id)` (and in `user_workflows` also `UNIQUE (created_at)`), which will limit each user to a single workflow/connection and require globally unique timestamps; consider whether the intent is to allow multiple workflows/connections per user and, if so, relax these constraints.
- In `user_oauth_connections`, `revoked_at` is `NOT NULL` even though status can be `connected` or `revoked`; if active connections are expected to have no revoke time, consider making `revoked_at` nullable or enforcing consistency at the DB level (e.g., via check constraint).

## Individual Comments

### Comment 1
<location> `apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml:36-42` </location>
<code_context>
-          _eq: X-Hasura-User-Id
-      set:
-        user_id: X-Hasura-User-Id
+      check: {}
       columns:
         - deep_expertise
</code_context>

<issue_to_address>
**🚨 issue (security):** Removing the user_id-based row filter exposes all preferences across users to each other.

With `check: {}` (and no `set.user_id`), any authenticated `user` can now insert/select preference rows for any `user_id`, not just their own. If these are intended to remain per-user preferences, we should reinstate the row-level constraint tied to `X-Hasura-User-Id` or enforce equivalent restrictions elsewhere.
</issue_to_address>

### Comment 2
<location> `apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql:1` </location>
<code_context>
+CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("created_at"));
</code_context>

<issue_to_address>
**question (bug_risk):** The uniqueness constraints on `user_id` and `created_at` may be overly restrictive for workflows.

`UNIQUE ("user_id")` limits each user to a single row, which conflicts with the table name and presence of `workflow_id`/`workflow_name` (implying multiple workflows per user). `UNIQUE ("created_at")` is also a fragile global constraint on timestamps. If multiple workflows per user are intended, consider a composite key such as `UNIQUE ("user_id", "workflow_id")` or dropping these unique constraints entirely.
</issue_to_address>

### Comment 3
<location> `apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql:1` </location>
<code_context>
+CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
</code_context>

<issue_to_address>
**suggestion:** `revoked_at` being NOT NULL and a global UNIQUE on `user_id` may not align with typical OAuth connection lifecycles.

Two points to reconsider:
- `revoked_at` is `NOT NULL` while `status` can be `'connected'`, which forces a revocation time even for active connections. Allowing `revoked_at` to be NULL for active connections may better match the lifecycle.
- `UNIQUE ("user_id")` restricts each user to a single connection. If you expect multiple providers/services per user, a composite key like `UNIQUE ("user_id", "provider", "service")` would avoid that limitation.
If the invariant really is “exactly one connection per user with both timestamps always set”, consider adding a CHECK constraint tying `status` to the timestamp fields to make that explicit in the schema.
</issue_to_address>

### Comment 4
<location> `apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql:1` </location>
<code_context>
+CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
</code_context>

<issue_to_address>
**question:** Uniqueness on `user_id` enforces a single n8n credential per user, which might be limiting.

This constraint allows only one `n8n_credentials` row per user. If users may need multiple credentials (e.g., per provider/service), consider a composite uniqueness such as `UNIQUE ("user_id", "provider", "service")` instead, unless a single global credential per user is explicitly required.
</issue_to_address>

### Comment 5
<location> `apps/web/types/types.ts:692-686` </location>
<code_context>
+}
+
+// n8n
+export interface N8nCredential {
+	id: string
+	user_id: string
+	n8n_credential_id: string
+	service: 'gmail'
+}
+
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The N8nCredential interface doesn't reflect all DB fields and hardcodes `service` to 'gmail'.

The `n8n_credentials` table also has `provider`, free-text `service`, and `created_at`, but the interface only exposes a subset and restricts `service` to `'gmail'`. If this is intended to model the DB row, please add the missing fields and widen `service` (e.g., `string` or a union) to keep the type aligned with the stored data and avoid future drift.

Suggested implementation:

```typescript
 // n8n
export interface N8nCredential {
	id: string
	user_id: string
	n8n_credential_id: string
	provider: string
	service: string
	created_at: string
}

// oauth

```

If your codebase represents timestamps as `Date` or a more specific time type instead of `string`, update the `created_at` field type accordingly (e.g., `created_at: Date`). Also ensure any existing usages of `N8nCredential` are updated to handle the new fields and the widened `service`/`provider` types.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 36 to 42
check: {}
columns:
- deep_expertise
- favorite
- font_size
- lang
- preferred_complexity
- preferred_length
- preferred_tone
Copy link

Choose a reason for hiding this comment

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

🚨 issue (security): Removing the user_id-based row filter exposes all preferences across users to each other.

With check: {} (and no set.user_id), any authenticated user can now insert/select preference rows for any user_id, not just their own. If these are intended to remain per-user preferences, we should reinstate the row-level constraint tied to X-Hasura-User-Id or enforce equivalent restrictions elsewhere.

@@ -0,0 +1 @@
CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("created_at"));
Copy link

Choose a reason for hiding this comment

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

question (bug_risk): The uniqueness constraints on user_id and created_at may be overly restrictive for workflows.

UNIQUE ("user_id") limits each user to a single row, which conflicts with the table name and presence of workflow_id/workflow_name (implying multiple workflows per user). UNIQUE ("created_at") is also a fragile global constraint on timestamps. If multiple workflows per user are intended, consider a composite key such as UNIQUE ("user_id", "workflow_id") or dropping these unique constraints entirely.

@@ -0,0 +1 @@
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
Copy link

Choose a reason for hiding this comment

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

suggestion: revoked_at being NOT NULL and a global UNIQUE on user_id may not align with typical OAuth connection lifecycles.

Two points to reconsider:

  • revoked_at is NOT NULL while status can be 'connected', which forces a revocation time even for active connections. Allowing revoked_at to be NULL for active connections may better match the lifecycle.
  • UNIQUE ("user_id") restricts each user to a single connection. If you expect multiple providers/services per user, a composite key like UNIQUE ("user_id", "provider", "service") would avoid that limitation.
    If the invariant really is “exactly one connection per user with both timestamps always set”, consider adding a CHECK constraint tying status to the timestamp fields to make that explicit in the schema.

@@ -0,0 +1 @@
CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
Copy link

Choose a reason for hiding this comment

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

question: Uniqueness on user_id enforces a single n8n credential per user, which might be limiting.

This constraint allows only one n8n_credentials row per user. If users may need multiple credentials (e.g., per provider/service), consider a composite uniqueness such as UNIQUE ("user_id", "provider", "service") instead, unless a single global credential per user is explicitly required.

id: string
user_id: string
provider: 'google'
service: 'gmail'
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): The N8nCredential interface doesn't reflect all DB fields and hardcodes service to 'gmail'.

The n8n_credentials table also has provider, free-text service, and created_at, but the interface only exposes a subset and restricts service to 'gmail'. If this is intended to model the DB row, please add the missing fields and widen service (e.g., string or a union) to keep the type aligned with the stored data and avoid future drift.

Suggested implementation:

 // n8n
export interface N8nCredential {
	id: string
	user_id: string
	n8n_credential_id: string
	provider: string
	service: string
	created_at: string
}

// oauth

If your codebase represents timestamps as Date or a more specific time type instead of string, update the created_at field type accordingly (e.g., created_at: Date). Also ensure any existing usages of N8nCredential are updated to handle the new fields and the widened service/provider types.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This work-in-progress PR introduces database schema and infrastructure for N8N workflow automation integration, enabling users to create and manage automated workflows with OAuth-based service connections. The changes include adding three new tables for workflows, OAuth connections, and N8N credentials, while removing deprecated organization and department structures.

Key Changes:

  • Added N8N integration infrastructure with new database tables for user workflows, OAuth connections, and credentials
  • Removed deprecated organization and department tables and associated GraphQL types
  • Modified Hasura permissions for preference and thread tables

Reviewed changes

Copilot reviewed 14 out of 23 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/mb-genql/generated/schema.ts Auto-generated GraphQL types reflecting new N8N tables and removal of organization/department entities
apps/web/types/types.ts TypeScript type definitions for OAuth connections, N8N credentials, and user workflows
apps/hasura/migrations/.../user_workflows/up.sql Migration creating user_workflows table with problematic unique constraints
apps/hasura/migrations/.../user_oauth_connections/up.sql Migration creating OAuth connections table with overly restrictive user_id constraint
apps/hasura/migrations/.../n8n_credentials/up.sql Migration creating N8N credentials table with limiting user_id constraint
apps/hasura/metadata/.../tables.yaml Updated table registry removing old tables and adding new N8N-related tables
apps/hasura/metadata/.../public_preference.yaml Modified permissions removing user isolation checks (security issue)
apps/hasura/metadata/.../public_chatbot.yaml Column reordering and duplicate chatbot_id entry
apps/hasura/metadata/.../public_thread.yaml Removed metadata column from insert permissions

@@ -0,0 +1 @@
CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("created_at"));
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The unique constraint on user_id means each user can only have one workflow, which seems overly restrictive for a workflows table. Users typically need to create multiple workflows. This should either be removed or changed to a composite unique constraint like UNIQUE (user_id, workflow_id) if you want to prevent duplicate workflow associations.

Suggested change
CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("created_at"));
CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id", "workflow_id"), UNIQUE ("created_at"));

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The unique constraint on user_id restricts each user to a single N8N credential across all services, which is too restrictive. Users may need separate credentials for different services (Gmail, Slack, etc.). Consider using a composite unique constraint like UNIQUE (user_id, service) or UNIQUE (user_id, provider, service) instead.

Suggested change
CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id", "provider", "service"), UNIQUE ("n8n_credential_id"));

Copilot uses AI. Check for mistakes.
filter:
user_id:
_eq: X-Hasura-User-Id
filter: {}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The filter: {} permission allows any authenticated user to read all preferences from all users, which is a privacy violation. The original permission with user_id filter should be restored to ensure users can only access their own preferences.

Suggested change
filter: {}
filter:
user_id:
_eq: X-Hasura-User-Id

Copilot uses AI. Check for mistakes.
Comment on lines 684 to 705
user_id: string
provider: 'google'
service: 'gmail'
status: 'connected' | 'revoked'
scopes: string
}

// n8n
export interface N8nCredential {
id: string
user_id: string
n8n_credential_id: string
service: 'gmail'
}

// workflow
export interface UserWorkflow {
id: string
user_id: string
workflow_id: string
workflow_name: string
service: 'gmail'
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Naming inconsistency: TypeScript interface uses user_id and n8n_credential_id (snake_case) but the GraphQL schema uses userId and n8nCredentialId (camelCase). TypeScript types should follow the GraphQL naming convention for consistency with the auto-generated types.

Suggested change
user_id: string
provider: 'google'
service: 'gmail'
status: 'connected' | 'revoked'
scopes: string
}
// n8n
export interface N8nCredential {
id: string
user_id: string
n8n_credential_id: string
service: 'gmail'
}
// workflow
export interface UserWorkflow {
id: string
user_id: string
workflow_id: string
workflow_name: string
service: 'gmail'
userId: string
provider: 'google'
service: 'gmail'
status: 'connected' | 'revoked'
scopes: string
/**
* @deprecated Use `userId` instead. Kept for backward compatibility.
*/
user_id?: string
}
// n8n
export interface N8nCredential {
id: string
userId: string
n8nCredentialId: string
service: 'gmail'
/**
* @deprecated Use `userId` instead. Kept for backward compatibility.
*/
user_id?: string
/**
* @deprecated Use `n8nCredentialId` instead. Kept for backward compatibility.
*/
n8n_credential_id?: string
}
// workflow
export interface UserWorkflow {
id: string
userId: string
workflowId: string
workflowName: string
service: 'gmail'
/**
* @deprecated Use `userId` instead. Kept for backward compatibility.
*/
user_id?: string
/**
* @deprecated Use `workflowId` instead. Kept for backward compatibility.
*/
workflow_id?: string
/**
* @deprecated Use `workflowName` instead. Kept for backward compatibility.
*/
workflow_name?: string

Copilot uses AI. Check for mistakes.
Comment on lines 700 to 706
export interface UserWorkflow {
id: string
user_id: string
workflow_id: string
workflow_name: string
service: 'gmail'
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Naming inconsistency: TypeScript interface uses user_id, workflow_id, and workflow_name (snake_case) but the GraphQL schema uses userId, workflowId, and workflowName (camelCase). TypeScript types should follow the GraphQL naming convention for consistency with the auto-generated types.

Copilot uses AI. Check for mistakes.
columns:
- avatar
- chatbot_id
- chatbot_id
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Duplicate column chatbot_id appears twice in the select permissions. This appears to be an accidental duplication that should be removed.

Suggested change
- chatbot_id

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("created_at"));
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The unique constraint on created_at in the user_workflows table is problematic. Using a timestamp as a unique constraint can cause insertion failures if multiple workflows are created at the same time (which is possible within the same millisecond). Consider removing this constraint or using a composite unique constraint if you need to prevent duplicate workflows per user.

Suggested change
CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("created_at"));
CREATE TABLE "public"."user_workflows" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "workflow_name" text NOT NULL, "workflow_id" text NOT NULL, "service" text NOT NULL, "folder_path" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The unique constraint on user_id means each user can only have one OAuth connection, which limits the system to a single provider/service per user. If users should be able to connect multiple services (e.g., Gmail and Google Drive), consider using a composite unique constraint like UNIQUE (user_id, provider, service) instead.

Suggested change
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id", "provider", "service")));

Copilot uses AI. Check for mistakes.
_eq: X-Hasura-User-Id
set:
user_id: X-Hasura-User-Id
check: {}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The check: {} permission allows any authenticated user to insert preferences for any user, bypassing user isolation. This is a security vulnerability. The original permission with user_id check should be restored to ensure users can only create their own preferences.

Suggested change
check: {}
check:
user_id:
_eq: X-Hasura-User-Id

Copilot uses AI. Check for mistakes.
Comment on lines 682 to 689
export interface UserOAuthConnection {
id: string
user_id: string
provider: 'google'
service: 'gmail'
status: 'connected' | 'revoked'
scopes: string
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Naming inconsistency: TypeScript interface uses user_id (snake_case) but the GraphQL schema uses userId (camelCase). TypeScript types should follow the GraphQL naming convention (camelCase) for consistency with the auto-generated types from mb-genql.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@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: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml (2)

42-42: Fix the typo in the column name.

The prompts relationship references chabot_id (missing 't'), but the correct column name should be chatbot_id. This typo will break the foreign key relationship and cause GraphQL queries for prompts to fail.

🔎 Proposed fix
-        column: chabot_id
+        column: chatbot_id

4-16: Fix broken department relationship references throughout the codebase.

The department object relationship and organization_chatbots array relationship have been removed from the public_chatbot.yaml metadata, but the application code in apps/pro-web still has extensive active dependencies on the department relationship:

  • getDepartments(), getDepartment() service calls
  • activeDepartment state management in sidebar/context
  • chatbot.department?.name references in components
  • Department-based filtering and categorization logic

These references will break at runtime since the relationship is no longer exposed via GraphQL. Update or remove all references to chatbot.department throughout the codebase, or restore the department relationship to the metadata.

🧹 Nitpick comments (7)
apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql (1)

1-1: Consider adding IF EXISTS and CASCADE for robustness.

The down migration could be more robust by:

  1. Adding IF EXISTS to make the migration idempotent
  2. Considering CASCADE if there might be dependent objects (views, triggers, etc.)
🔎 Proposed enhancement
-DROP TABLE "public"."user_workflows";
+DROP TABLE IF EXISTS "public"."user_workflows" CASCADE;

Note: Only add CASCADE if you're certain there are no dependent objects you want to preserve, or if dropping them is the intended behavior.

apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sql (1)

1-1: Consider adding IF EXISTS and CASCADE for robustness.

The down migration could be more robust and idempotent.

🔎 Proposed enhancement
-DROP TABLE "public"."user_oauth_connections";
+DROP TABLE IF EXISTS "public"."user_oauth_connections" CASCADE;

Note: Only add CASCADE if dropping dependent objects is the intended behavior.

apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sql (1)

1-1: Consider adding IF EXISTS and CASCADE for robustness.

The down migration could be more robust and idempotent.

🔎 Proposed enhancement
-DROP TABLE "public"."n8n_credentials";
+DROP TABLE IF EXISTS "public"."n8n_credentials" CASCADE;

Note: Only add CASCADE if dropping dependent objects is the intended behavior.

apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql (2)

1-1: Remove redundant UNIQUE constraint on primary key.

The UNIQUE ("id") constraint is redundant since id is already the PRIMARY KEY, which implicitly enforces uniqueness.

🔎 Proposed cleanup
-CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
+CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));

1-1: Consider CASCADE delete behavior for user credentials.

The foreign key uses ON DELETE restrict, which prevents user deletion if credentials exist. Consider using ON DELETE cascade to automatically clean up credentials when a user is deleted, unless you have a specific requirement to preserve orphaned credentials.

apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql (1)

1-1: Consider enum type or check constraint for status.

The status column is free-form text, which can lead to inconsistent values. Consider using a PostgreSQL enum type or a CHECK constraint to ensure only valid status values ('connected', 'revoked') are allowed.

🔎 Example with enum type
CREATE TYPE oauth_status AS ENUM ('connected', 'revoked');

CREATE TABLE "public"."user_oauth_connections" (
  "id" uuid NOT NULL DEFAULT gen_random_uuid(),
  "user_id" uuid NOT NULL,
  "provider" text NOT NULL,
  "service" text NOT NULL,
  "scopes" text NOT NULL,
  "status" oauth_status NOT NULL DEFAULT 'connected',
  "connected_at" timestamptz NOT NULL DEFAULT now(),
  "revoked_at" timestamptz,
  PRIMARY KEY ("id"),
  FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE cascade,
  UNIQUE ("user_id", "provider", "service")
);
apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql (1)

1-1: Consider indexes for common query patterns.

Add indexes to optimize common queries by user, service, or workflow_id:

CREATE INDEX idx_user_workflows_user_id ON public.user_workflows(user_id);
CREATE INDEX idx_user_workflows_service ON public.user_workflows(service);
CREATE INDEX idx_user_workflows_workflow_id ON public.user_workflows(workflow_id);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3c6904 and bb801bd.

⛔ Files ignored due to path filters (3)
  • packages/mb-genql/generated/schema.graphql is excluded by !**/generated/**
  • packages/mb-genql/generated/schema.ts is excluded by !**/generated/**
  • packages/mb-genql/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (20)
  • apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_thread.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yaml
  • apps/hasura/metadata/databases/masterbots/tables/tables.yaml
  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql
  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sql
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
  • apps/web/app/api/automations/gmail/disable.ts
  • apps/web/app/api/automations/gmail/enable.ts
  • apps/web/app/api/automations/gmail/execute.ts
  • apps/web/app/api/oauth/google/callback.ts
  • apps/web/app/api/oauth/google/revoke.ts
  • apps/web/app/api/oauth/google/start.ts
  • apps/web/types/types.ts
💤 Files with no reviewable changes (1)
  • apps/hasura/metadata/databases/masterbots/tables/public_thread.yaml
🧰 Additional context used
📓 Path-based instructions (6)
apps/hasura/migrations/masterbots/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store and manage Hasura migrations under apps/hasura/migrations/masterbots/

Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/

Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/

Files:

  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql
  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sql
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs

Files:

  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql
  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
  • apps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yaml
  • apps/hasura/metadata/databases/masterbots/tables/tables.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml
  • apps/web/types/types.ts
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sql
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
  • apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
  • apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml
apps/{web,pro-web}/types/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place shared TypeScript definitions under types/

Keep TypeScript type definitions under types/

Files:

  • apps/web/types/types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...

Files:

  • apps/web/types/types.ts
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components

apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers

apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates

Files:

  • apps/web/types/types.ts
apps/{web,pro-web}/types/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place shared TypeScript types under types/

Files:

  • apps/web/types/types.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The backend uses Hasura with proper table relationships, permissions, and metadata structure defined in YAML configurations.
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Perform database schema changes via Hasura console (task console), then migrations (task migrate)
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Use Hasura console (task console) for schema modifications
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The project is a monorepo using Turborepo with Next.js frontend (apps/masterbots.ai), Hasura backend (apps/hasura), and shared packages for types, GraphQL operations, and utilities.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 344
File: apps/masterbots.ai/services/hasura/hasura.service.ts:0-0
Timestamp: 2025-01-25T08:43:05.202Z
Learning: In the masterbots.ai database schema:
1. Previous "categories" are now called "topics"
2. Chatbots are now referred to as "domains"
3. Domains have context-dependent "categories" and "subcategories" based on user question context
4. Domains include "tags" that are used for filtering based on the user question context
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 579
File: apps/pro-web/app/api/auth/signup/route.ts:151-157
Timestamp: 2025-11-07T18:38:42.273Z
Learning: In apps/pro-web/app/actions/admin.actions.ts, the insertUserOrganizations function should use Drizzle DB transactions to ensure atomicity when inserting organizations and their associated chatbots, preventing partial persistence issues.
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/

Applied to files:

  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql
  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sql
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store and manage Hasura migrations under apps/hasura/migrations/masterbots/

Applied to files:

  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql
  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sql
  • apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
  • apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/

Applied to files:

  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sql
  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
  • apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Perform database schema changes via Hasura console (task console), then migrations (task migrate)

Applied to files:

  • apps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sql
📚 Learning: 2025-11-06T22:31:20.903Z
Learnt from: derianrddev
Repo: bitcashorg/masterbots PR: 579
File: apps/hasura/migrations/masterbots/1762286346002_alter_table_public_chatbot_add_column_department_id/up.sql:1-2
Timestamp: 2025-11-06T22:31:20.903Z
Learning: In the masterbots codebase, the `department_id` column in the `public.chatbot` table is intentionally nullable because not all chatbots are part of the onboarding flow. Only chatbots used in onboarding have an associated department.

Applied to files:

  • apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml
📚 Learning: 2025-01-25T08:43:05.202Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 344
File: apps/masterbots.ai/services/hasura/hasura.service.ts:0-0
Timestamp: 2025-01-25T08:43:05.202Z
Learning: In the masterbots.ai database schema:
1. Previous "categories" are now called "topics"
2. Chatbots are now referred to as "domains"
3. Domains have context-dependent "categories" and "subcategories" based on user question context
4. Domains include "tags" that are used for filtering based on the user question context

Applied to files:

  • apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml
🔇 Additional comments (2)
apps/hasura/metadata/databases/masterbots/tables/tables.yaml (2)

14-14: LGTM! New table metadata files properly included.

The three new table metadata files are correctly included in alphabetical order:

  • public_n8n_credentials.yaml (line 14)
  • public_user_oauth_connections.yaml (line 29)
  • public_user_workflows.yaml (line 31)

Also applies to: 29-29, 31-31


1-31: The metadata files (public_department.yaml, public_organization.yaml, public_organization_chatbot.yaml) have not been removed. All three files continue to exist in the metadata directory. Additionally, proper down migrations exist for each table:

  • 1762284507301_create_table_public_department/down.sql - drops the department table
  • 1761768880712_create_table_public_organization/down.sql - drops the organization table
  • 1762287961522_create_table_public_organization_chatbot/down.sql - drops the organization_chatbot table

The database schema is consistent with the migration history.

Likely an incorrect or invalid review comment.

columns:
- avatar
- chatbot_id
- chatbot_id
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate column entry.

The chatbot_id column appears twice in the anonymous role's select permissions (lines 58 and 59). This duplication is redundant and may cause metadata validation warnings.

🔎 Proposed fix
       columns:
         - avatar
         - chatbot_id
-        - chatbot_id
         - created_by
📝 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
- chatbot_id
columns:
- avatar
- chatbot_id
- created_by
🤖 Prompt for AI Agents
In apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yaml around
line 59, the anonymous role's select permissions include a duplicated
"chatbot_id" column entry; remove the second occurrence (the entry on line 59)
so each column appears only once in the columns list, then save and run Hasura
metadata validation to ensure no warnings remain.

Comment on lines +1 to +3
table:
name: n8n_credentials
schema: public
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Critical: Add strict permissions for credential data.

This table stores n8n credentials, which are highly sensitive. This MUST have proper permissions before merging to any non-development environment:

  1. Strict row-level security: Users should ONLY access their own credentials
  2. Object relationship to public.user via the user_id foreign key
  3. Limited column access: Consider whether all credential fields should be readable
  4. Audit logging: Consider tracking access to credential data
🔎 Example with strict security controls
table:
  name: n8n_credentials
  schema: public
object_relationships:
  - name: user
    using:
      foreign_key_constraint_on: user_id
select_permissions:
  - role: user
    permission:
      columns:
        - id
        - user_id
        - provider
        - service
        - n8n_credential_id
        - created_at
      filter:
        user_id:
          _eq: X-Hasura-User-Id
insert_permissions:
  - role: user
    permission:
      check:
        user_id:
          _eq: X-Hasura-User-Id
      columns:
        - provider
        - service
        - n8n_credential_id
delete_permissions:
  - role: user
    permission:
      filter:
        user_id:
          _eq: X-Hasura-User-Id
🤖 Prompt for AI Agents
In apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml
lines 1-3: this table holds sensitive n8n credentials and needs strict security
before non-dev deployment—enable row-level security, add an object_relationship
named "user" using foreign_key_constraint_on: user_id, and add
select/insert/delete permissions scoped to role "user" that filter/check user_id
equals X-Hasura-User-Id; restrict select columns to only non-secret fields
(e.g., id, user_id, provider, service, n8n_credential_id, created_at), restrict
insert columns to only allowed writable fields, and restrict delete to the same
user filter; additionally ensure any remaining secret columns are excluded from
select and consider adding audit logging/triggers for access events.

_eq: X-Hasura-User-Id
set:
user_id: X-Hasura-User-Id
check: {}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical security vulnerability: Empty check allows privilege escalation.

The empty check: {} for the user role permits any authenticated user to insert preferences with any user_id, effectively allowing users to create or modify preferences for other users.

This should enforce row-level security:

check:
  user_id:
    _eq: X-Hasura-User-Id
🔎 Proposed fix
   - role: user
     permission:
-      check: {}
+      check:
+        user_id:
+          _eq: X-Hasura-User-Id
       columns:
🤖 Prompt for AI Agents
In apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml
around line 36, the `check: {}` for the `user` role allows any authenticated
user to insert or modify rows for arbitrary `user_id`; replace the empty check
with a row-level security condition that enforces user_id equals the Hasura
session variable (X-Hasura-User-Id) for inserts/updates (e.g. set check to
require user_id _eq to the X-Hasura-User-Id session variable), ensuring the
comparison type matches the column type (cast the session var if needed) and
keep the rest of the permission block intact.

filter:
user_id:
_eq: X-Hasura-User-Id
filter: {}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical security vulnerability: Empty filter exposes all user preferences.

The empty filter: {} for the user role allows any authenticated user to query all preferences across all users, leaking sensitive user data.

This should enforce row-level security to restrict users to their own preferences.

🔎 Proposed fix
   - role: user
     permission:
       columns:
         - deep_expertise
         - favorite
         - font_size
         - lang
         - preference_id
         - preferred_complexity
         - preferred_length
         - preferred_tone
         - preferred_type
         - user_id
         - web_search
-      filter: {}
+      filter:
+        user_id:
+          _eq: X-Hasura-User-Id
     comment: ""
🤖 Prompt for AI Agents
In apps/hasura/metadata/databases/masterbots/tables/public_preference.yaml
around line 94, the permission filter is currently empty (filter: {}), exposing
all preferences; replace it with a row-level filter that restricts results to
the logged-in user by comparing the preference's user_id to the Hasura session
variable (for example: set filter to {"user_id": {"_eq": "X-Hasura-User-Id"}} or
to the correct JWT claim/key your app uses, e.g. "x-hasura-user-id"), ensuring
the permission only returns rows where user_id equals the session user id.

Comment on lines +1 to +3
table:
name: user_oauth_connections
schema: public
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add permissions and relationships with privacy controls.

This table stores OAuth connection data, which is sensitive user information. Before merging, you should add:

  1. Row-level security: Users should only access their own OAuth connections
  2. Object relationship to public.user via the user_id foreign key
  3. Restricted permissions: Carefully control which roles can select/insert/update/delete
🔎 Example with privacy controls
table:
  name: user_oauth_connections
  schema: public
object_relationships:
  - name: user
    using:
      foreign_key_constraint_on: user_id
select_permissions:
  - role: user
    permission:
      columns:
        - id
        - user_id
        - provider
        - service
        - scopes
        - status
        - connected_at
        - revoked_at
      filter:
        user_id:
          _eq: X-Hasura-User-Id
insert_permissions:
  - role: user
    permission:
      check:
        user_id:
          _eq: X-Hasura-User-Id
      columns:
        - provider
        - service
        - scopes
        - status
update_permissions:
  - role: user
    permission:
      columns:
        - status
        - revoked_at
      filter:
        user_id:
          _eq: X-Hasura-User-Id
🤖 Prompt for AI Agents
In
apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml
around lines 1-3, the table currently lacks row-level security, object
relationship to public.user, and role-restricted permissions; add an
object_relationship mapping on user_id to public.user, enable/select row-level
security policies so users can only access their own rows (filters using
X-Hasura-User-Id), and add select/insert/update (and delete if needed)
permission entries for the user role that explicitly list allowed columns, use
filters like user_id: {_eq: X-Hasura-User-Id} for selects/updates and checks for
inserts, and restrict update columns to only safe fields (e.g., status,
revoked_at) while preventing exposing sensitive columns.

@@ -0,0 +1 @@
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add timestamp defaults and updated_at column.

The connected_at column lacks a default value. Additionally, tracking when connection details (like scopes or status) change requires an updated_at column.

🔎 Proposed improvements
-CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
+CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL DEFAULT now(), "revoked_at" timestamptz, "updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
around line 1, the connected_at column has no default and there's no updated_at
column; alter the CREATE TABLE to set connected_at timestamptz NOT NULL DEFAULT
now() and add updated_at timestamptz NOT NULL DEFAULT now(); optionally add a DB
trigger or application logic to set updated_at on row updates if you want
automatic updates (but at minimum add the column with a default).

⚠️ Potential issue | 🔴 Critical

Critical constraint: UNIQUE user_id prevents multiple OAuth connections.

The UNIQUE ("user_id") constraint allows only one OAuth connection per user across all providers and services. This is severely limiting—users cannot connect both Google and another provider, or even multiple Google services.

This should likely be a composite unique constraint on (user_id, provider, service) to allow multiple connections:

🔎 Proposed fix
-CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
+CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL DEFAULT now(), "revoked_at" timestamptz, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE cascade, UNIQUE ("user_id", "provider", "service"));
📝 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
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL DEFAULT now(), "revoked_at" timestamptz, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE cascade, UNIQUE ("user_id", "provider", "service"));
🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
around line 1, the migration defines UNIQUE("user_id") which wrongly prevents a
user from having multiple OAuth connections; remove that single-column unique
constraint and replace it with a composite unique constraint on (user_id,
provider, service) so a user can have multiple provider/service connections but
still avoid duplicate entries for the same provider+service combination.

⚠️ Potential issue | 🟠 Major

Incorrect nullability: revoked_at should be nullable.

The revoked_at column is defined as NOT NULL, but it should only be populated when a connection is revoked. For active connections, this field should be NULL.

This forces inserting a placeholder value (like a distant past/future date) for active connections, which is an anti-pattern.

🔎 Proposed fix
-CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
+CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL DEFAULT now(), "revoked_at" timestamptz, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
📝 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
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL, "revoked_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
CREATE TABLE "public"."user_oauth_connections" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "scopes" text NOT NULL, "status" text NOT NULL, "connected_at" timestamptz NOT NULL DEFAULT now(), "revoked_at" timestamptz, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sql
around line 1, the revoked_at column is declared as NOT NULL but should be
nullable; update the CREATE TABLE statement to remove the NOT NULL from
revoked_at so it becomes nullable, and if applying to an existing DB include a
migration step to ALTER TABLE ... ALTER COLUMN revoked_at DROP NOT NULL and
ensure any existing placeholder values are corrected to NULL as appropriate.

@@ -0,0 +1 @@
CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

UNIQUE constraint on user_id limits users to one n8n credential.

The UNIQUE ("user_id") constraint prevents users from having multiple n8n credentials. This is overly restrictive if users need credentials for multiple services or multiple credentials for the same service.

Consider removing this constraint or making it a composite unique key with service if you want to allow one credential per service per user:

UNIQUE ("user_id", "service")
🔎 Proposed fix
-CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
+CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE cascade, UNIQUE ("n8n_credential_id"), UNIQUE ("user_id", "service"));
🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
around line 1, the current UNIQUE("user_id") constraint prevents a user from
having multiple n8n credentials; remove the standalone UNIQUE("user_id") or
replace it with a composite unique constraint such as
UNIQUE("user_id","service") to allow multiple credentials per user while still
enforcing one credential per service per user; update the CREATE TABLE statement
to drop the single-column unique and add the composite unique (or omit
uniqueness entirely) and ensure any downstream references or migrations are
adjusted accordingly.

🛠️ Refactor suggestion | 🟠 Major

Add default timestamp and updated_at column.

The created_at column lacks a default value, requiring manual timestamp insertion on every insert. Additionally, there's no updated_at column to track modifications.

🔎 Proposed improvements
-CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
+CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));

Also consider adding a trigger to automatically update updated_at:

CREATE TRIGGER set_updated_at
BEFORE UPDATE ON public.n8n_credentials
FOR EACH ROW
EXECUTE FUNCTION public.set_current_timestamp_updated_at();
📝 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
CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
CREATE TABLE "public"."n8n_credentials" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "user_id" uuid NOT NULL, "provider" text NOT NULL, "service" text NOT NULL, "n8n_credential_id" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "public"."user"("user_id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("user_id"), UNIQUE ("n8n_credential_id"));
🤖 Prompt for AI Agents
In
apps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sql
around line 1, the CREATE TABLE lacks a default for created_at and omits an
updated_at column; modify the table definition so created_at has a default of
now() and add an updated_at timestamptz column also defaulting to now() (both
NOT NULL), and after the table creation add a trigger (or trigger creation
statement) to set updated_at on each UPDATE using the existing
public.set_current_timestamp_updated_at() function (or create that function if
missing).

Comment on lines 682 to 689
export interface UserOAuthConnection {
id: string
user_id: string
provider: 'google'
service: 'gmail'
status: 'connected' | 'revoked'
scopes: string
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Schema mismatch: Missing timestamp fields from database.

The UserOAuthConnection interface is missing connected_at, revoked_at, and potentially updated_at fields that exist in the database schema (see migration file). This mismatch will cause runtime errors when querying these fields from Hasura.

Additionally, revoked_at should be optional (revoked_at?: string) since it's nullable in the database.

🔎 Proposed fix to align with database schema
 export interface UserOAuthConnection {
   id: string
   user_id: string
   provider: 'google'
   service: 'gmail'
   status: 'connected' | 'revoked'
   scopes: string
+  connected_at: string
+  revoked_at?: string
+  updated_at?: 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
export interface UserOAuthConnection {
id: string
user_id: string
provider: 'google'
service: 'gmail'
status: 'connected' | 'revoked'
scopes: string
}
export interface UserOAuthConnection {
id: string
user_id: string
provider: 'google'
service: 'gmail'
status: 'connected' | 'revoked'
scopes: string
connected_at: string
revoked_at?: string
updated_at?: string
}
🤖 Prompt for AI Agents
In apps/web/types/types.ts around lines 682 to 689, the UserOAuthConnection
interface omits timestamp fields present in the DB schema; add connected_at:
string, revoked_at?: string (nullable/optional), and updated_at: string to the
interface and ensure revoked_at is optional to match the migration schema so
TypeScript matches Hasura query results.

🛠️ Refactor suggestion | 🟠 Major

Inflexible literal types prevent future extensibility.

The hardcoded literal types provider: 'google' and service: 'gmail' make this interface unusable for other providers (Microsoft, Apple) or services (calendar, drive). Consider using string types or a union of known values that can be extended:

provider: string  // or 'google' | 'microsoft' | 'apple'
service: string   // or 'gmail' | 'calendar' | 'drive'
🔎 Proposed refactor for extensibility
+export type OAuthProvider = 'google' | 'microsoft' | 'apple'
+export type OAuthService = 'gmail' | 'calendar' | 'drive' | 'outlook'
+export type OAuthStatus = 'connected' | 'revoked'
+
 export interface UserOAuthConnection {
   id: string
   user_id: string
-  provider: 'google'
-  service: 'gmail'
-  status: 'connected' | 'revoked'
+  provider: OAuthProvider
+  service: OAuthService
+  status: OAuthStatus
   scopes: string
+  connected_at: string
+  revoked_at?: string
 }
🤖 Prompt for AI Agents
In apps/web/types/types.ts around lines 682 to 689, the UserOAuthConnection
interface uses hardcoded literal types provider: 'google' and service: 'gmail'
which prevents adding other providers/services; change those properties to
broader types (either plain string or a union of known providers/services) so
the interface is extensible and can accept additional providers/services in the
future; update any dependent code types/consumers accordingly to handle the
wider type and, if using unions, add tests or type assertions for newly
supported values.

Comment on lines 692 to 697
export interface N8nCredential {
id: string
user_id: string
n8n_credential_id: string
service: 'gmail'
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Schema mismatch: Missing fields from n8n_credentials table.

The N8nCredential interface is missing provider, created_at, and potentially updated_at fields from the database schema. It also uses an inflexible service: 'gmail' literal type.

🔎 Proposed fix
 export interface N8nCredential {
   id: string
   user_id: string
+  provider: string
   n8n_credential_id: string
-  service: 'gmail'
+  service: string
+  created_at: string
+  updated_at?: 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
export interface N8nCredential {
id: string
user_id: string
n8n_credential_id: string
service: 'gmail'
}
export interface N8nCredential {
id: string
user_id: string
provider: string
n8n_credential_id: string
service: string
created_at: string
updated_at?: string
}
🤖 Prompt for AI Agents
In apps/web/types/types.ts around lines 692 to 697, the N8nCredential interface
is incomplete and too restrictive: it currently lacks the database fields
provider, created_at (and likely updated_at) and constrains service to the
literal 'gmail'. Update the interface to include provider: string; created_at:
string (or Date) and updated_at?: string | null (optional if DB allows null),
and change service to a more flexible string type (e.g., service: string) so it
matches the n8n_credentials schema and allows other providers.

Comment on lines 700 to 706
export interface UserWorkflow {
id: string
user_id: string
workflow_id: string
workflow_name: string
service: 'gmail'
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Schema mismatch: Missing folder_path and timestamp fields.

The UserWorkflow interface is missing folder_path and created_at fields that exist in the database schema. Additionally, service: 'gmail' is overly restrictive.

🔎 Proposed fix to match database schema
 export interface UserWorkflow {
   id: string
   user_id: string
   workflow_id: string
   workflow_name: string
-  service: 'gmail'
+  service: string
+  folder_path: string
+  created_at: string
+  updated_at?: string
 }
🤖 Prompt for AI Agents
In apps/web/types/types.ts around lines 700 to 706, the UserWorkflow interface
is missing the database fields folder_path and created_at and currently
constrains service to the literal 'gmail'; update the interface to include
folder_path (string | null or optional string to reflect nullable DB column),
created_at (string in ISO timestamp format or Date depending on project
convention), and relax service to a more accurate type (string or a union of
allowed service literals instead of the single 'gmail').

Copy link
Contributor

@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: 14

🧹 Nitpick comments (5)
apps/web/app/api/automations/gmail/execute.ts (1)

1-3: Unused import and file format issues.

  1. getHasuraClient is imported but never used in this file.
  2. Same App Router format issue as other files—this should be route.ts with NextRequest/NextResponse.
-import { getHasuraClient, getUserWorkflowByService } from '@/services/hasura'
+import { getUserWorkflowByService } from '@/services/hasura'
apps/web/app/api/automations/gmail/disable.ts (1)

12-12: Unused Hasura client in current (broken) code path.

The client variable is created but never used in any reachable code path. Once the logic is fixed, ensure this is actually needed or remove if the delete can be done via a service function.

apps/web/app/api/automations/gmail/enable.ts (2)

9-23: Consider centralizing environment variable validation.

The IIFE pattern for environment variable validation works but leads to repetition. Consider extracting this into a reusable helper function or using a validation library like zod for environment variables.


30-31: Validate JWT is present before using it.

The jwt is read from the request body but never validated for presence before being passed to functions that require authentication. While getUserWorkflowByService and other functions check for JWT internally, it's better to fail fast at the API boundary.

🔎 Proposed fix
 		const { userId, jwt } = req.body
-		if (!userId) return res.status(400).json({ error: 'Missing userId' })
+		if (!userId) return res.status(400).json({ error: 'Missing userId' })
+		if (!jwt) return res.status(401).json({ error: 'Authentication required' })
apps/web/services/hasura/hasura.service.ts (1)

2239-2266: Consider consolidating duplicate ISO timestamp generation.

Multiple functions use new Date().toISOString() for timestamp generation. Consider extracting this into a helper or using a consistent approach across all insertion functions.

Also applies to: 2269-2294, 2297-2323

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb801bd and 4f3830b.

📒 Files selected for processing (8)
  • apps/web/app/api/automations/gmail/disable.ts
  • apps/web/app/api/automations/gmail/enable.ts
  • apps/web/app/api/automations/gmail/execute.ts
  • apps/web/app/api/oauth/google/callback.ts
  • apps/web/app/api/oauth/google/start.ts
  • apps/web/lib/n8n.ts
  • apps/web/services/hasura/hasura.service.ts
  • apps/web/types/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/types/types.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/{web,pro-web}/app/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/app/**: Prefer Next.js Server Components; use 'use client' only when necessary
Leverage Streaming and Suspense for responsiveness in Next.js 15
Follow Next.js file conventions in the App Router

apps/{web,pro-web}/app/**: Leverage Server Components by default; use the use client directive only when client-side features are needed
Implement Nested Layouts using the App Router
Use Streaming and Suspense features in Next.js 14/15 where appropriate
Follow Next.js file conventions for routing and layout in the App Router
Optimize data fetching: fetch on the server, fetch where needed, and use preload patterns to avoid waterfalls

Files:

  • apps/web/app/api/oauth/google/start.ts
  • apps/web/app/api/oauth/google/callback.ts
  • apps/web/app/api/automations/gmail/enable.ts
  • apps/web/app/api/automations/gmail/execute.ts
  • apps/web/app/api/automations/gmail/disable.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...

Files:

  • apps/web/app/api/oauth/google/start.ts
  • apps/web/app/api/oauth/google/callback.ts
  • apps/web/app/api/automations/gmail/enable.ts
  • apps/web/app/api/automations/gmail/execute.ts
  • apps/web/app/api/automations/gmail/disable.ts
  • apps/web/lib/n8n.ts
  • apps/web/services/hasura/hasura.service.ts
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components

apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers

apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates

Files:

  • apps/web/app/api/oauth/google/start.ts
  • apps/web/app/api/oauth/google/callback.ts
  • apps/web/app/api/automations/gmail/enable.ts
  • apps/web/app/api/automations/gmail/execute.ts
  • apps/web/app/api/automations/gmail/disable.ts
  • apps/web/lib/n8n.ts
  • apps/web/services/hasura/hasura.service.ts
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs

Files:

  • apps/web/app/api/oauth/google/start.ts
  • apps/web/app/api/oauth/google/callback.ts
  • apps/web/app/api/automations/gmail/enable.ts
  • apps/web/app/api/automations/gmail/execute.ts
  • apps/web/app/api/automations/gmail/disable.ts
  • apps/web/lib/n8n.ts
  • apps/web/services/hasura/hasura.service.ts
apps/{web,pro-web}/services/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place external API integrations under services/

Files:

  • apps/web/services/hasura/hasura.service.ts
apps/{web,pro-web}/services/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use RORO (Receive an object, Return an object) for functions, especially when calling external services

Place external API integrations under services/

Files:

  • apps/web/services/hasura/hasura.service.ts
🧠 Learnings (10)
📓 Common learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-09-04T21:31:18.830Z
Learning: Successfully created comprehensive GitHub issue #555 consolidating performance feedback and edge-case management for pro-web workspace features, including prioritized action items, testing requirements, and specific file references for implementation.
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2025-11-07T18:41:56.562Z
Learning: Successfully created comprehensive GitHub issue #581 consolidating 16 unresolved observations from PR #579 review, including critical TDZ crash, migration bugs, transaction safety issues, and validation improvements, with specific file paths, line numbers, and action items organized by priority (4 critical, 8 major, 2 minor, 2 acknowledged for future PRs).
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The backend uses Hasura with proper table relationships, permissions, and metadata structure defined in YAML configurations.
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Perform database schema changes via Hasura console (task console), then migrations (task migrate)
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The project is a monorepo using Turborepo with Next.js frontend (apps/masterbots.ai), Hasura backend (apps/hasura), and shared packages for types, GraphQL operations, and utilities.
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/ai-main-call.actions.{ts,tsx} : Route AI API handling through ai-main-call.actions for unified processing

Applied to files:

  • apps/web/app/api/oauth/google/callback.ts
  • apps/web/app/api/automations/gmail/enable.ts
  • apps/web/app/api/automations/gmail/disable.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Follow Next.js file conventions as per documentation

Applied to files:

  • apps/web/app/api/oauth/google/callback.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Use NextAuth.js with custom user management; JWT for API auth; roles/subscriptions for access control

Applied to files:

  • apps/web/app/api/oauth/google/callback.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single interaction point for GraphQL operations with Hasura

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single point for all GraphQL interactions

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use hasura.service.ts for all GraphQL operations from UI layers

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/**/services/hasura.service.ts : Use hasura.service.ts as the single point of GraphQL interaction in each app

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-11-21T18:00:08.455Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:60-78
Timestamp: 2025-11-21T18:00:08.455Z
Learning: In apps/web/lib/hooks/use-profile.tsx: The sessionUser state should be populated directly from session.user (JWT) and not by fetching from Hasura on the client side. The useEffect should only synchronize sessionUser with session.user. Client-side Hasura fetches for user data should be avoided; user data comes from the JWT/session. Only when users update their profile should we refresh both the database and JWT token (via NextAuth session update trigger).

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-05-06T09:54:25.327Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 468
File: apps/masterbots.ai/app/api/cron/delete-requested-accounts/route.ts:70-81
Timestamp: 2025-05-06T09:54:25.327Z
Learning: For Hasura GraphQL operations in server-side code (including API routes and cron jobs), use the `getHasuraClient()` function from mb-lib rather than implementing custom fetch functions. This provides proper error handling, environment validation, and follows the established pattern in the codebase.

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
🧬 Code graph analysis (5)
apps/web/app/api/oauth/google/start.ts (1)
apps/web/app/api/oauth/google/callback.ts (1)
  • handler (44-123)
apps/web/app/api/oauth/google/callback.ts (4)
apps/web/app/api/oauth/google/start.ts (1)
  • handler (9-23)
apps/web/lib/n8n.ts (3)
  • TokenData (1-7)
  • createN8nCredential (10-43)
  • duplicateN8nWorkflow (46-105)
apps/web/types/types.ts (1)
  • TokenData (681-687)
apps/web/services/hasura/hasura.service.ts (3)
  • insertUserOAuthConnection (2239-2266)
  • insertN8nCredential (2269-2294)
  • insertUserWorkflow (2297-2323)
apps/web/app/api/automations/gmail/enable.ts (6)
apps/web/app/api/automations/gmail/disable.ts (1)
  • handler (4-42)
apps/web/app/api/automations/gmail/execute.ts (1)
  • handler (5-36)
apps/web/app/api/oauth/google/callback.ts (1)
  • handler (44-123)
apps/web/app/api/oauth/google/start.ts (1)
  • handler (9-23)
apps/web/services/hasura/hasura.service.ts (3)
  • getUserWorkflowByService (2325-2351)
  • getUserCredentialByService (2353-2378)
  • insertUserWorkflow (2297-2323)
apps/web/lib/n8n.ts (1)
  • duplicateN8nWorkflow (46-105)
apps/web/app/api/automations/gmail/execute.ts (1)
apps/web/services/hasura/hasura.service.ts (1)
  • getUserWorkflowByService (2325-2351)
apps/web/services/hasura/hasura.service.ts (2)
apps/web/services/hasura/hasura.service.type.ts (1)
  • GetHasuraClientParams (3-7)
apps/web/types/types.ts (2)
  • UserWorkflow (708-716)
  • UserCredential (722-727)
🔇 Additional comments (1)
apps/web/services/hasura/hasura.service.ts (1)

55-69: LGTM: getHasuraClient now properly exported.

The function signature change to accept a parameters object follows best practices (RORO pattern) and makes the client reusable across the codebase.

Comment on lines +14 to +36
const userWorkflows = await getUserWorkflowByService(userId, 'gmail', jwt)
const workflowId = userWorkflows?.workflow_id

if (userWorkflows) {
return res.status(200).json({
message: 'Workflow already enabled',
workflowId: userWorkflows.workflow_id,
})
}

if (!userWorkflows) {
return res.status(400).json({ error: 'No workflow found for this user' })
}

// Delete the workflow record from Hasura
await client.mutation({
deleteUserWorkflows: {
__args: { where: { workflowId: { _eq: workflowId } } },
affected_rows: true,
},
})

res.status(200).json({ message: 'Workflow disabled successfully' })
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical logic error: disable endpoint never actually disables.

The conditional logic is inverted and makes the delete operation unreachable:

  1. Line 17: If userWorkflows exists → returns 200 "Workflow already enabled" (wrong message for disable)
  2. Line 24: If userWorkflows doesn't exist → returns 400
  3. Lines 28-34: Delete mutation is unreachable since all code paths return before it

This means the disable endpoint can never actually delete a workflow.

🔎 Proposed fix
 export default async function handler(
 	req: NextApiRequest,
 	res: NextApiResponse,
 ) {
 	try {
 		const { userId, jwt } = req.body
 		if (!userId) return res.status(400).json({ error: 'Missing userId' })

 		const client = getHasuraClient({ jwt })

 		const userWorkflows = await getUserWorkflowByService(userId, 'gmail', jwt)
-		const workflowId = userWorkflows?.workflow_id
-
-		if (userWorkflows) {
-			return res.status(200).json({
-				message: 'Workflow already enabled',
-				workflowId: userWorkflows.workflow_id,
-			})
-		}

 		if (!userWorkflows) {
 			return res.status(400).json({ error: 'No workflow found for this user' })
 		}

+		const workflowId = userWorkflows.workflow_id
+
 		// Delete the workflow record from Hasura
 		await client.mutation({
 			deleteUserWorkflows: {
-				__args: { where: { workflowId: { _eq: workflowId } } },
+				__args: { where: { workflow_id: { _eq: workflowId } } },
 				affected_rows: true,
 			},
 		})

 		res.status(200).json({ message: 'Workflow disabled successfully' })
🤖 Prompt for AI Agents
In apps/web/app/api/automations/gmail/disable.ts around lines 14 to 36, the
conditional logic is inverted so the delete code never runs: change the flow to
return 400 if no userWorkflows, and otherwise perform the delete and then return
a 200 success; remove the early return that says "Workflow already enabled" and
instead run the client.mutation when userWorkflows exists, then return a clear
"Workflow disabled successfully" response; also ensure you use the correct
workflow ID variable/DB column name in the delete where clause (match
workflow_id vs workflowId).

throw new Error('N8N_WEBHOOK_BASE_URL is not defined')
})()

export default async function handler(
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use named export instead of default export.

The coding guidelines specify: "Avoid default exports; prefer named exports."

🔎 Proposed fix
-export default async function handler(
+export async function handler(
 	req: NextApiRequest,
 	res: NextApiResponse,
 ) {

Based on coding guidelines.

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

🤖 Prompt for AI Agents
In apps/web/app/api/automations/gmail/enable.ts around line 25, the handler is
exported as a default export; change it to a named export (export async function
handler(...)) and update any imports or references to use the named export
(import { handler } from '...') so code follows the "no default exports"
guideline; ensure TypeScript/server framework routing (if it relies on default)
is adjusted to accept the named export or add an explicit re-export where
necessary.

Comment on lines +65 to +72
await insertUserWorkflow(
userId,
workflow.id,
workflow.name,
'gmail',
'Masterbots/Users Workflows/Email',
jwt,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Parameter order mismatch causes runtime error.

The call to insertUserWorkflow passes parameters in the wrong order. The function signature in hasura.service.ts (line 2297) expects jwt as the first parameter, but you're passing it last.

🔎 Proposed fix
 		await insertUserWorkflow(
+			jwt,
 			userId,
 			workflow.id,
 			workflow.name,
 			'gmail',
 			'Masterbots/Users Workflows/Email',
-			jwt,
 		)
📝 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
await insertUserWorkflow(
userId,
workflow.id,
workflow.name,
'gmail',
'Masterbots/Users Workflows/Email',
jwt,
)
await insertUserWorkflow(
jwt,
userId,
workflow.id,
workflow.name,
'gmail',
'Masterbots/Users Workflows/Email',
)
🤖 Prompt for AI Agents
In apps/web/app/api/automations/gmail/enable.ts around lines 65-72, the call to
insertUserWorkflow passes arguments in the wrong order (jwt is last) while the
hasura.service.ts signature expects jwt as the first parameter; reorder the call
so jwt is passed first followed by userId, workflow.id, workflow.name, 'gmail',
'Masterbots/Users Workflows/Email' to match the function signature.

Comment on lines +22 to +30
const triggerRes = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload || {}),
})

const triggerData = await triggerRes.json()

res.status(200).json({ message: 'Workflow executed', data: triggerData })
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing error handling for webhook trigger response.

The code assumes the webhook call succeeds without checking triggerRes.ok. If the n8n webhook returns an error status, this should be surfaced to the caller.

🔎 Proposed fix
 		// 2️⃣ Trigger workflow via POST
 		const triggerRes = await fetch(webhookUrl, {
 			method: 'POST',
 			headers: { 'Content-Type': 'application/json' },
 			body: JSON.stringify(payload || {}),
 		})

 		const triggerData = await triggerRes.json()
+		if (!triggerRes.ok) {
+			return res.status(triggerRes.status).json({
+				error: 'Failed to trigger workflow',
+				details: triggerData,
+			})
+		}

 		res.status(200).json({ message: 'Workflow executed', data: triggerData })
📝 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 triggerRes = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload || {}),
})
const triggerData = await triggerRes.json()
res.status(200).json({ message: 'Workflow executed', data: triggerData })
const triggerRes = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload || {}),
})
const triggerData = await triggerRes.json()
if (!triggerRes.ok) {
return res.status(triggerRes.status).json({
error: 'Failed to trigger workflow',
details: triggerData,
})
}
res.status(200).json({ message: 'Workflow executed', data: triggerData })
🤖 Prompt for AI Agents
In apps/web/app/api/automations/gmail/execute.ts around lines 22 to 30, the code
posts to the webhook but does not check triggerRes.ok or handle fetch errors;
update the implementation to check triggerRes.ok and, if false, read the
response body (await triggerRes.text() or json when possible) and return a
non-200 response to the caller (e.g., res.status(triggerRes.status).json({
message: 'Webhook error', status: triggerRes.status, details: <body> })), and
wrap the fetch in try/catch to handle network/throwing errors and return a
502/500 with the error message if the fetch itself fails.

Comment on lines 49 to 54
const { code, state } = req.query
const userId = state as string
const jwt = req.headers.authorization?.split(' ')[1]

if (!code || !userId)
return res.status(400).json({ error: 'Missing code or userId' })
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

JWT will not be available in OAuth callback redirects.

OAuth callbacks are browser redirects from Google, which means no Authorization header will be present. The JWT extraction on line 51 will always result in undefined, causing the subsequent Hasura operations to fail (they throw if JWT is missing).

Consider alternative approaches:

  1. Pass the JWT in the state parameter (encrypted/signed) along with userId
  2. Use server-side session to retrieve the JWT
  3. Create a separate authenticated endpoint that the frontend calls after redirect
🤖 Prompt for AI Agents
In apps/web/app/api/oauth/google/callback.ts around lines 49–54, the code tries
to read a JWT from req.headers.authorization which will be undefined on Google
redirect callbacks; remove reliance on the Authorization header and instead
retrieve the JWT from a safe alternative: either decode/verify a
signed+encrypted payload passed via the state parameter (e.g., state contains an
encrypted JSON with userId and jwt; validate signature and decrypt to extract
both), or look up the JWT from a server-side session store keyed by a secure
state value, or convert this flow to have the frontend read the callback and
then call a separate authenticated backend endpoint; implement one of these
approaches and update the missing-token check to use the JWT obtained from that
mechanism rather than req.headers.authorization.

Comment on lines +2274 to +2276
) {
if (!jwt)
throw new Error('Authentication required to insert OAuth connection')
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect error message.

The error message says "insert OAuth connection" but this function inserts N8N credentials, not OAuth connections.

🔎 Proposed fix
 	if (!jwt)
-		throw new Error('Authentication required to insert OAuth connection')
+		throw new Error('Authentication required to insert N8N credential')
📝 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
) {
if (!jwt)
throw new Error('Authentication required to insert OAuth connection')
) {
if (!jwt)
throw new Error('Authentication required to insert N8N credential')
🤖 Prompt for AI Agents
In apps/web/services/hasura/hasura.service.ts around lines 2274 to 2276, the
thrown error message incorrectly references "insert OAuth connection" though
this function inserts N8N credentials; update the thrown Error to reflect the
correct operation (for example: "Authentication required to insert N8N
credentials") so the message accurately describes the failure context.

Copy link
Contributor

@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: 4

🤖 Fix all issues with AI agents
In @apps/web/app/api/oauth/google/callback/route.ts:
- Around line 50-70: The GET handler is missing validation of the OAuth state
parameter; read the incoming state via url.searchParams.get('state') and compare
it against the expected value tied to the authenticated session (e.g.,
session.user.id or a session-stored CSRF token), and if it’s missing or does not
match return a 400/401 error (similar to the existing missing-code/userId
branch); update the GET function to perform this check before exchanging code
and include a clear error response on mismatch to prevent CSRF.

In @apps/web/app/api/oauth/google/start/route.ts:
- Around line 17-49: The GET handler exposes userId from the querystring and
logs it, causing auth bypass, CSRF risk and PII leakage; update the GET function
to require and validate an authenticated session (e.g., obtain the current
userId from your session/auth middleware instead of url.searchParams.get),
return 401 if no authenticated user, ensure the requested userId (if ever
passed) matches the session user, remove any console.log of userId/PII, and
implement a secure CSRF/state flow by generating a cryptographically random
state tied to the session (store it server-side or in a signed cookie) and use
that state parameter in the URLSearchParams before redirecting to Google; keep
the redirect building logic (URLSearchParams, authUrl) but only after these
checks and state creation and log only non-PII debug info.

In @apps/web/app/api/test-google-start/route.ts:
- Around line 1-10: This test API route (exported GET in route.ts) is an
unauthenticated debug endpoint and must be removed before merging; delete the
file that defines the GET handler (apps/web/app/api/test-google-start/route.ts)
or, if you absolutely need runtime testing, replace the export with a
non-deployed guard: remove the GET export or wrap it behind a development-only
feature flag or explicit environment check so it cannot be invoked in
production.
🧹 Nitpick comments (1)
apps/web/app/api/oauth/google/callback/route.ts (1)

99-134: Consider adding transaction handling for multi-step workflow.

The OAuth callback performs 5 database/external operations sequentially (lines 100-134). If any step after the first fails, earlier steps are not rolled back, potentially leaving orphaned records in the database (e.g., OAuth connections without corresponding workflows).

While this may be acceptable depending on your error recovery strategy, consider whether you need:

  • Transaction boundaries to ensure atomicity
  • Idempotency keys to allow safe retries
  • Cleanup logic in the catch block to remove partial state

Based on learnings, in apps/pro-web/app/actions/admin.actions.ts, the team uses Drizzle DB transactions to ensure atomicity when inserting related records.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f3830b and 6172ec4.

📒 Files selected for processing (3)
  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/oauth/google/start/route.ts
  • apps/web/app/api/test-google-start/route.ts
🧰 Additional context used
📓 Path-based instructions (4)
apps/{web,pro-web}/app/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/app/**: Prefer Next.js Server Components; use 'use client' only when necessary
Leverage Streaming and Suspense for responsiveness in Next.js 15
Follow Next.js file conventions in the App Router

apps/{web,pro-web}/app/**: Leverage Server Components by default; use the use client directive only when client-side features are needed
Implement Nested Layouts using the App Router
Use Streaming and Suspense features in Next.js 14/15 where appropriate
Follow Next.js file conventions for routing and layout in the App Router
Optimize data fetching: fetch on the server, fetch where needed, and use preload patterns to avoid waterfalls

Files:

  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/oauth/google/start/route.ts
  • apps/web/app/api/test-google-start/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...

Files:

  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/oauth/google/start/route.ts
  • apps/web/app/api/test-google-start/route.ts
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components

apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers

apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates

Files:

  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/oauth/google/start/route.ts
  • apps/web/app/api/test-google-start/route.ts
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs

Files:

  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/oauth/google/start/route.ts
  • apps/web/app/api/test-google-start/route.ts
🧠 Learnings (6)
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/ai-main-call.actions.{ts,tsx} : Route AI API handling through ai-main-call.actions for unified processing

Applied to files:

  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/test-google-start/route.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Follow Next.js file conventions as per documentation

Applied to files:

  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/test-google-start/route.ts
📚 Learning: 2025-11-07T18:38:42.273Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 579
File: apps/pro-web/app/api/auth/signup/route.ts:151-157
Timestamp: 2025-11-07T18:38:42.273Z
Learning: In apps/pro-web/app/actions/admin.actions.ts, the insertUserOrganizations function should use Drizzle DB transactions to ensure atomicity when inserting organizations and their associated chatbots, preventing partial persistence issues.

Applied to files:

  • apps/web/app/api/oauth/google/callback/route.ts
📚 Learning: 2025-11-21T18:00:08.455Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:60-78
Timestamp: 2025-11-21T18:00:08.455Z
Learning: In apps/web/lib/hooks/use-profile.tsx: The sessionUser state should be populated directly from session.user (JWT) and not by fetching from Hasura on the client side. The useEffect should only synchronize sessionUser with session.user. Client-side Hasura fetches for user data should be avoided; user data comes from the JWT/session. Only when users update their profile should we refresh both the database and JWT token (via NextAuth session update trigger).

Applied to files:

  • apps/web/app/api/oauth/google/callback/route.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/app/** : Follow Next.js file conventions for routing and layout in the App Router

Applied to files:

  • apps/web/app/api/test-google-start/route.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/app/** : Follow Next.js file conventions in the App Router

Applied to files:

  • apps/web/app/api/test-google-start/route.ts
🧬 Code graph analysis (3)
apps/web/app/api/oauth/google/callback/route.ts (3)
apps/web/app/api/oauth/google/start/route.ts (1)
  • GET (17-49)
apps/web/services/hasura/hasura.service.ts (3)
  • insertUserOAuthConnection (2239-2266)
  • insertN8nCredential (2269-2294)
  • insertUserWorkflow (2297-2323)
apps/web/lib/n8n.ts (2)
  • createN8nCredential (10-43)
  • duplicateN8nWorkflow (46-105)
apps/web/app/api/oauth/google/start/route.ts (2)
apps/web/app/api/oauth/google/callback/route.ts (1)
  • GET (50-148)
apps/web/app/api/test-google-start/route.ts (1)
  • GET (3-10)
apps/web/app/api/test-google-start/route.ts (2)
apps/web/app/api/oauth/google/callback/route.ts (1)
  • GET (50-148)
apps/web/app/api/oauth/google/start/route.ts (1)
  • GET (17-49)
🔇 Additional comments (4)
apps/web/app/api/oauth/google/start/route.ts (1)

3-15: LGTM!

Environment variable validation with IIFE pattern ensures the route fails fast on misconfiguration. The scope definition is appropriate for Gmail access.

apps/web/app/api/oauth/google/callback/route.ts (3)

15-48: LGTM!

Environment variable validation is comprehensive and uses the IIFE pattern appropriately to fail fast on misconfiguration.


136-147: LGTM with minor note on error exposure.

The error handling pattern is appropriate. One consideration: returning error.message on Line 144 might expose internal details. If errors could contain sensitive information, consider sanitizing or using a generic message for client responses while logging the full error server-side (which you already do on Line 142).


99-134: Critical: Parameter order mismatch in insertUserWorkflow call.

According to apps/web/services/hasura/hasura.service.ts (lines 2296-2322), insertUserWorkflow expects parameters in this order:

insertUserWorkflow(jwt, userId, workflowId, workflowName, service, folderPath)

However, lines 127-134 pass them as:

insertUserWorkflow(userId, workflow.id, workflow.name, 'gmail', 'Masterbots/Users Workflows/Email', jwt)

This will cause runtime errors or incorrect data persistence.

🔧 Proposed fix
 		// 6️⃣ Store workflow info in Hasura
 		await insertUserWorkflow(
+			jwt,
 			userId,
 			workflow.id,
 			workflow.name,
 			'gmail',
 			'Masterbots/Users Workflows/Email',
-			jwt,
 		)
⛔ Skipped due to learnings
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 579
File: apps/pro-web/app/api/auth/signup/route.ts:151-157
Timestamp: 2025-11-07T18:38:42.273Z
Learning: In apps/pro-web/app/actions/admin.actions.ts, the insertUserOrganizations function should use Drizzle DB transactions to ensure atomicity when inserting organizations and their associated chatbots, preventing partial persistence issues.

Comment on lines +50 to +70
export async function GET(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session) {
return NextResponse.json(
{ error: 'User not authenticated' },
{ status: 401 },
)
}

const jwt = session.user.hasuraJwt
const userId = session.user.id
const url = new URL(req.url)
const code = url.searchParams.get('code')

if (!code || !userId) {
return NextResponse.json(
{ error: 'Missing code or userId' },
{ status: 400 },
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing CSRF state parameter validation.

While session authentication is correctly implemented, the OAuth state parameter returned by Google is not validated. The state should be checked to ensure it matches the userId from the session to prevent CSRF attacks where an attacker tricks a victim into completing an OAuth flow for the attacker's account.

🔒 Proposed fix for state validation
 		const jwt = session.user.hasuraJwt
 		const userId = session.user.id
 		const url = new URL(req.url)
 		const code = url.searchParams.get('code')
+		const state = url.searchParams.get('state')

-		if (!code || !userId) {
+		if (!code || !userId || !state) {
 			return NextResponse.json(
-				{ error: 'Missing code or userId' },
+				{ error: 'Missing code, userId, or state' },
 				{ status: 400 },
 			)
 		}
+
+		// Validate state parameter matches userId to prevent CSRF
+		const decodedState = decodeURIComponent(state)
+		if (decodedState !== userId) {
+			return NextResponse.json(
+				{ error: 'Invalid state parameter' },
+				{ status: 400 },
+			)
+		}

 		console.log('Google callback hit for userId:', userId)
🤖 Prompt for AI Agents
In @apps/web/app/api/oauth/google/callback/route.ts around lines 50 - 70, The
GET handler is missing validation of the OAuth state parameter; read the
incoming state via url.searchParams.get('state') and compare it against the
expected value tied to the authenticated session (e.g., session.user.id or a
session-stored CSRF token), and if it’s missing or does not match return a
400/401 error (similar to the existing missing-code/userId branch); update the
GET function to perform this check before exchanging code and include a clear
error response on mismatch to prevent CSRF.

Comment on lines 72 to 97
console.log('Google callback hit for userId:', userId)

// 1️⃣ Exchange code for tokens
const params = new URLSearchParams({
code,
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code',
})

const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
})

const tokenData: TokenData = await tokenRes.json()
if (!tokenData.access_token) {
return NextResponse.json(
{ error: 'No access_token returned from Google' },
{ status: 400 },
)
}

console.log('Received Google tokens for userId:', userId)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove PII from logs and add fetch timeout.

Two issues:

  1. Compliance concern: Lines 72 and 97 log userId, which is personally identifiable information. This may violate GDPR/CCPA requirements.
  2. Reliability gap: The fetch call on Line 83 has no timeout, which could cause the request to hang indefinitely if Google's API is slow or unresponsive.
♻️ Proposed improvements
-		console.log('Google callback hit for userId:', userId)
+		console.log('Google callback hit')

 		// 1️⃣ Exchange code for tokens
 		const params = new URLSearchParams({
 			code,
 			client_id: GOOGLE_CLIENT_ID,
 			client_secret: GOOGLE_CLIENT_SECRET,
 			redirect_uri: REDIRECT_URI,
 			grant_type: 'authorization_code',
 		})

+		const controller = new AbortController()
+		const timeoutId = setTimeout(() => controller.abort(), 10000) // 10s timeout
+
 		const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
 			method: 'POST',
 			headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
 			body: params.toString(),
+			signal: controller.signal,
 		})
+		clearTimeout(timeoutId)

 		const tokenData: TokenData = await tokenRes.json()
 		if (!tokenData.access_token) {
 			return NextResponse.json(
 				{ error: 'No access_token returned from Google' },
 				{ status: 400 },
 			)
 		}

-		console.log('Received Google tokens for userId:', userId)
+		console.log('Received Google tokens')

Comment on lines 17 to 49
export async function GET(req: NextRequest) {
try {
const url = new URL(req.url)
const userId = url.searchParams.get('userId')

if (!userId) {
return NextResponse.json({ error: 'Missing userId' }, { status: 400 })
}

console.log('Google start route hit with userId:', userId)

const params = new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
scope: SCOPES.join(' '),
access_type: 'offline',
prompt: 'consent',
state: encodeURIComponent(userId),
})

const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`
console.log('Redirecting to Google OAuth URL:', authUrl)

return NextResponse.redirect(authUrl)
} catch (err) {
console.error('Error in Google start route:', err)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 },
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing authentication and insecure userId handling.

This endpoint has several security vulnerabilities:

  1. No authentication check: Anyone can initiate OAuth for any user by passing an arbitrary userId in the query string
  2. CSRF vulnerability: No validation that the requester owns the userId
  3. PII logging: Line 26 logs userId, which is personally identifiable information

The userId should be extracted from an authenticated session (like in the callback route), not from query parameters.

🔒 Proposed security fixes
+import { authOptions } from '@/auth'
+import { getServerSession } from 'next-auth'
 import { type NextRequest, NextResponse } from 'next/server'

 const GOOGLE_CLIENT_ID =
 	process.env.GOOGLE_CLIENT_ID ??
 	(() => {
 		throw new Error('Missing GOOGLE_CLIENT_ID environment variable')
 	})()
 const REDIRECT_URI =
 	process.env.NEXT_PUBLIC_GOOGLE_OAUTH_REDIRECT_URI ??
 	(() => {
 		throw new Error(
 			'Missing NEXT_PUBLIC_GOOGLE_OAUTH_REDIRECT_URI environment variable',
 		)
 	})()
 const SCOPES = ['https://www.googleapis.com/auth/gmail.modify']

 export async function GET(req: NextRequest) {
 	try {
+		const session = await getServerSession(authOptions)
+		if (!session) {
+			return NextResponse.json(
+				{ error: 'User not authenticated' },
+				{ status: 401 },
+			)
+		}
+
-		const url = new URL(req.url)
-		const userId = url.searchParams.get('userId')
-
-		if (!userId) {
-			return NextResponse.json({ error: 'Missing userId' }, { status: 400 })
-		}
-
-		console.log('Google start route hit with userId:', userId)
+		const userId = session.user.id

 		const params = new URLSearchParams({
 			client_id: GOOGLE_CLIENT_ID,
 			redirect_uri: REDIRECT_URI,
 			response_type: 'code',
 			scope: SCOPES.join(' '),
 			access_type: 'offline',
 			prompt: 'consent',
 			state: encodeURIComponent(userId),
 		})

 		const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`
-		console.log('Redirecting to Google OAuth URL:', authUrl)

 		return NextResponse.redirect(authUrl)
 	} catch (err) {
 		console.error('Error in Google start route:', err)
 		return NextResponse.json(
 			{ error: 'Internal server error' },
 			{ status: 500 },
 		)
 	}
 }
🤖 Prompt for AI Agents
In @apps/web/app/api/oauth/google/start/route.ts around lines 17 - 49, The GET
handler exposes userId from the querystring and logs it, causing auth bypass,
CSRF risk and PII leakage; update the GET function to require and validate an
authenticated session (e.g., obtain the current userId from your session/auth
middleware instead of url.searchParams.get), return 401 if no authenticated
user, ensure the requested userId (if ever passed) matches the session user,
remove any console.log of userId/PII, and implement a secure CSRF/state flow by
generating a cryptographically random state tied to the session (store it
server-side or in a signed cookie) and use that state parameter in the
URLSearchParams before redirecting to Google; keep the redirect building logic
(URLSearchParams, authUrl) but only after these checks and state creation and
log only non-PII debug info.

Comment on lines 1 to 10
import { type NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
const url = new URL(req.url)
const userId = url.searchParams.get('userId')

console.log('Test route hit!', userId)

return NextResponse.json({ message: 'Route works', userId })
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove test route before merging to production.

This test endpoint has no authentication and appears to be for debugging purposes only. Test/debug routes should not be committed to the main codebase, especially in a production branch like develop.

🧹 Recommended action

Remove this file entirely before merging. If route testing is needed during development, use local-only files or feature flags to prevent test routes from being deployed.

🤖 Prompt for AI Agents
In @apps/web/app/api/test-google-start/route.ts around lines 1 - 10, This test
API route (exported GET in route.ts) is an unauthenticated debug endpoint and
must be removed before merging; delete the file that defines the GET handler
(apps/web/app/api/test-google-start/route.ts) or, if you absolutely need runtime
testing, replace the export with a non-deployed guard: remove the GET export or
wrap it behind a development-only feature flag or explicit environment check so
it cannot be invoked in production.

Copy link
Contributor

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/auth.ts (1)

67-84: Critical: Password validation is disabled - authentication bypass vulnerability.

The bcrypt password comparison result (isValid) is computed but the validation check is commented out. This allows any user to authenticate with any password as long as their email exists in the database, completely bypassing credential verification.

This must be restored before merging:

🔒 Required fix to restore password validation
 				//* Verify the password using bcrypt (hash comparison)
 				const isValid = bcrypt.compareSync(
 					credentials.password,
 					user[0].password,
 				)
-				// if (!isValid) {
-				// 	console.error('NextUser authentication failed: Invalid password')
-				// 	throw new Error('Invalid credentials')
-				// }
+				if (!isValid) {
+					console.error('NextUser authentication failed: Invalid password')
+					throw new Error('Invalid credentials')
+				}
 				console.log('NextUser authenticated successfully')
🤖 Fix all issues with AI agents
In
`@apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/down.sql`:
- Around line 1-2: The down migration is identical to the up migration causing a
no-op; fix by ensuring up.sql and down.sql are complementary for the constraint
"user_oauth_connections_user_id_key" on table "public"."user_oauth_connections":
if the intent is to add a unique constraint, make up.sql only add the constraint
and make down.sql only drop the constraint (DROP CONSTRAINT
"user_oauth_connections_user_id_key"), or if the constraint already exists
remove this migration entirely; update the SQL files so up adds and down removes
the unique constraint (or delete the migration) and then re-run migrations to
verify correct behavior.

In
`@apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/up.sql`:
- Around line 1-2: The migration is a no-op and its down.sql is wrong: it drops
and re-adds the existing constraint "user_oauth_connections_user_id_key" (from
migration 1766066591567) then repeats the same in down.sql; either delete this
migration or implement a real change. Fix by either removing this migration file
pair entirely if no schema change is needed, or if you intended a rename or
change, modify up.sql to perform the actual change (e.g., rename constraint or
alter uniqueness) and update down.sql to reverse that change; reference the
constraint name "user_oauth_connections_user_id_key" and the migration id
1768003427670 when making the edits so both up.sql and down.sql are
complementary and no longer a bidirectional no-op.

In
`@apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/down.sql`:
- Line 1: The down migration alters user_oauth_connections.revoked_at to NOT
NULL which will fail if any NULLs exist; before running ALTER TABLE
"user_oauth_connections" ALTER COLUMN "revoked_at" SET NOT NULL you must handle
existing NULL values (e.g. UPDATE user_oauth_connections SET revoked_at =
<safe_default_timestamp> WHERE revoked_at IS NULL, or DELETE/flag those rows, or
decide to skip/remove the NOT NULL change), or alternatively make the migration
idempotent by checking for NULLs and only applying the constraint when none
exist; update the down migration to perform the NULL-handling step(s) and
document the chosen semantic for revoked_at so rollback won't error.

In `@apps/web/services/hasura/hasura.service.ts`:
- Around line 2386-2390: The returned object includes a hardcoded empty
webhook_url which can mislead callers; update the code that builds the
UserCredential (the return using result.n8nCredentials[0] and casting to
UserCredential) to either (a) retrieve the real webhook URL from the DB result
(e.g., include the webhook field from result.n8nCredentials[0] or join the table
that contains it) and set webhook_url to that value, or (b) change the return
type to a narrower interface that omits webhook_url, or (c) make webhook_url
optional on the UserCredential type and omit it when not available—choose one
approach and remove the hardcoded '' placeholder so callers do not receive a
false empty URL.
- Around line 2299-2332: The insertUserWorkflow function should validate the jwt
before calling getHasuraClient: if jwt is falsy, throw the same error used by
getUserWorkflowByService (e.g., throw new Error('JWT is required') or match its
exact message) to maintain consistency; add this check at the start of
insertUserWorkflow (before getHasuraClient) so getHasuraClient is only called
with a valid token and the error behavior matches other functions in this file.
♻️ Duplicate comments (4)
apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml (1)

1-3: Missing object relationship to the user table.

The table lacks an object_relationships definition to link user_id to public.user. This relationship is important for GraphQL query traversal and data integrity.

♻️ Add object relationship
table:
  name: user_oauth_connections
  schema: public
object_relationships:
  - name: user
    using:
      foreign_key_constraint_on: user_id
apps/web/services/hasura/hasura.service.ts (2)

2276-2277: Incorrect error message.

The error message says "insert OAuth connection" but this function inserts N8N credentials.

🔎 Proposed fix
 	if (!jwt)
-		throw new Error('Authentication required to insert OAuth connection')
+		throw new Error('Authentication required to insert N8N credential')

2342-2359: Type assertion mismatch - only workflow_id is queried but full UserWorkflow type is asserted.

The query only selects workflow_id but the return value is cast to UserWorkflow. If UserWorkflow has additional required fields (like id, user_id, workflow_name, service, folder_path, created_at), callers may encounter runtime errors when accessing undefined properties.

Either expand the query to fetch all fields or define a narrower return type.

🔎 Proposed fix - expand query fields
 	const result = await client.query({
 		userWorkflows: {
-			workflow_id: true,
+			id: true,
+			userId: true,
+			workflowId: true,
+			workflowName: true,
+			service: true,
+			folderPath: true,
+			createdAt: true,
 			__args: {
 				where: {
 					userId: { _eq: userId },
 					service: { _eq: service },
 				},
 				limit: 1,
 			},
 		},
 	})
apps/web/app/api/test-google-start/route.ts (1)

1-41: Remove test route before merging to production.

This test endpoint exposes sensitive information (session data and JWT) in the response and contains mock/debug code. Test routes should not be committed to the develop branch.

Additional concerns:

  • Line 1: Unused import insertUserOAuthConnection (the call is commented out)
  • Lines 25-32: Mock token data serves no purpose
  • Line 40: Response leaks session and jwt which could aid attackers
🧹 Recommended action

Delete this file entirely before merging. If OAuth flow testing is needed during development, use:

  • Local-only test scripts outside the committed codebase
  • Environment-gated routes that only run in development:
if (process.env.NODE_ENV === 'production') {
  return NextResponse.json({ error: 'Not found' }, { status: 404 })
}
🧹 Nitpick comments (3)
apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/down.sql (1)

1-1: LGTM!

The down migration correctly reverses the up migration by dropping the default value.

Minor style nit: The up migration uses lowercase SQL keywords (alter table) while this down migration uses uppercase (ALTER TABLE). Consider keeping consistent casing across migration pairs for readability.

♻️ Optional: Consistent casing
-ALTER TABLE "public"."user_workflows" ALTER COLUMN "id" drop default;
+alter table "public"."user_workflows" alter column "id" drop default;
apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml (1)

10-18: Consider removing id from insertable columns.

Since the migration sets id to default to gen_random_uuid(), users don't need to (and shouldn't) provide this value. Removing it prevents potential conflicts or misuse.

♻️ Suggested change for moderator role
      columns:
        - provider
        - scopes
        - service
        - status
        - connected_at
        - revoked_at
-       - id
        - user_id

Apply the same change to the user role insert permission (lines 25-33).

apps/web/services/hasura/hasura.service.ts (1)

2238-2266: Consider parameterizing provider and service for reusability.

The function hardcodes provider: 'google' and service: 'gmail'. If you anticipate supporting other providers (e.g., Microsoft) or services (e.g., Drive, Calendar), consider accepting these as parameters similar to insertN8nCredential.

♻️ Suggested refactor for flexibility
 export async function insertUserOAuthConnection(
 	userId: string,
 	scopes: string[],
 	jwt: string,
+	provider: 'google' | 'microsoft' = 'google',
+	service: 'gmail' | 'drive' | 'calendar' = 'gmail',
 ) {
 	if (!jwt)
 		throw new Error('Authentication required to insert OAuth connection')

 	const client = getHasuraClient({ jwt })

 	const result = await client.mutation({
 		insertUserOauthConnectionsOne: {
 			__args: {
 				object: {
 					userId,
-					provider: 'google',
-					service: 'gmail',
+					provider,
+					service,
 					scopes: scopes.join(' '),
 					status: 'connected',
 					connectedAt: new Date().toISOString(),
 				},
 			},
 			id: true,
 		},
 	})

 	return result.insertUserOauthConnectionsOne
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd9a346 and fb961ff.

⛔ Files ignored due to path filters (3)
  • packages/mb-genql/generated/schema.graphql is excluded by !**/generated/**
  • packages/mb-genql/generated/schema.ts is excluded by !**/generated/**
  • packages/mb-genql/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (24)
  • apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_user.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_user_token.yaml
  • apps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yaml
  • apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/down.sql
  • apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/up.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/down.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/up.sql
  • apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/down.sql
  • apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/up.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768100079972_alter_table_public_user_token_alter_column_user_id/down.sql
  • apps/hasura/migrations/masterbots/1768100079972_alter_table_public_user_token_alter_column_user_id/up.sql
  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/app/api/test-google-start/route.ts
  • apps/web/auth.ts
  • apps/web/lib/n8n.ts
  • apps/web/services/hasura/hasura.service.ts
💤 Files with no reviewable changes (1)
  • apps/hasura/metadata/databases/masterbots/tables/public_user.yaml
✅ Files skipped from review due to trivial changes (3)
  • apps/hasura/migrations/masterbots/1768100079972_alter_table_public_user_token_alter_column_user_id/up.sql
  • apps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yaml
  • apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/up.sql
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yaml
  • apps/web/app/api/oauth/google/callback/route.ts
  • apps/web/lib/n8n.ts
🧰 Additional context used
📓 Path-based instructions (7)
apps/hasura/migrations/masterbots/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store and manage Hasura migrations under apps/hasura/migrations/masterbots/

Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/

Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/

Files:

  • apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/up.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/down.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/down.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/up.sql
  • apps/hasura/migrations/masterbots/1768100079972_alter_table_public_user_token_alter_column_user_id/down.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/down.sql
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Use lowercase with dash-separated directory and file names; use specific extensions: .config.ts, .test.ts, .context.tsx, .type.ts, .hook.ts; components may omit extension
Use Bun for package management and scripts instead of npm/yarn
Use Biome for formatting and linting via bun format-and-lint:fix; avoid Prettier/ESLint configs

Files:

  • apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/up.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/down.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/down.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/up.sql
  • apps/web/auth.ts
  • apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/up.sql
  • apps/hasura/metadata/databases/masterbots/tables/public_user_token.yaml
  • apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/up.sql
  • apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml
  • apps/hasura/migrations/masterbots/1768100079972_alter_table_public_user_token_alter_column_user_id/down.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/down.sql
  • apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/down.sql
  • apps/web/app/api/test-google-start/route.ts
  • apps/web/services/hasura/hasura.service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use meaningful variable names; prefix booleans with is/has/does/should
Use interface for objects/classes; type for unions/tuples/aliases; enum for fixed sets; const for literals
Avoid any; prefer specific types
Leverage type inference where clear; annotate function params and return types
Prefer type assertions with 'as' over angle-bracket syntax
Use type guards to narrow types in conditionals

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Use modern JavaScript features with TypeScript across the codebase
Use meaningful names; prefix booleans with is/has/does/should (e.g., isDisabled)
Receive an object and return an object (RORO) for functions interfacing with external services
Type definitions: use interface for objects/classes; type for unions/tuples/aliases; const for literals; enum for fixed sets
Avoid any; prefer specific types
Leverage type inference; omit explicit annotations when obvious
Add explicit type annotations for function parameters and return values
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types within conditionals
Favor composition over inheritance in code structure

**/*.{ts,tsx}: Avoid default exports; prefer named exports
Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions
Use interface for object shapes/classes; type for unions/tuples/aliases; const for literals; enum for fixed enumerations
Avoid using any; prefer precise types
Leverage type inference when clear; omit redundant annotations
Explicitly annotate function parameters and return types
Prefer type assertions with as over angle-bracket syntax
Use type guards to narrow types safely
Use meaningful variable names; prefix booleans with is/has/does/should
Favor composition over inheritance
Use lowercase dash-separated names for files and folders; use extensions like .config.ts, .test.ts, .context.tsx, .typ...

Files:

  • apps/web/auth.ts
  • apps/web/app/api/test-google-start/route.ts
  • apps/web/services/hasura/hasura.service.ts
apps/{web,pro-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/**/*.{ts,tsx}: Use lowercase kebab-case for directories and filenames (components can omit extension)
Verify DOM operations and consider timing (RAF/delays) when manipulating the DOM in React
Use Tailwind CSS utility classes for styling React components

apps/{web,pro-web}/**/*.{ts,tsx}: Use TypeScript to define React props types (interfaces for props)
Use custom hooks (useMBChat, useThread, useSidebar, useModel) for domain-specific state and keep providers focused and composed hierarchically
Separate presentation from business logic and compose small components
Use hasura.service.ts for all GraphQL operations from UI layers

apps/{web,pro-web}/**/*.{ts,tsx}: Prefer Server Components; use the use client directive only when client-side features are required
Implement Nested Layouts using the App Router
Use Streaming and Suspense in Next.js 14/15 where appropriate
Follow Next.js file conventions as per documentation
Use IndexedDB for local caching to enable immediate UI updates

Files:

  • apps/web/auth.ts
  • apps/web/app/api/test-google-start/route.ts
  • apps/web/services/hasura/hasura.service.ts
apps/{web,pro-web}/app/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

apps/{web,pro-web}/app/**: Prefer Next.js Server Components; use 'use client' only when necessary
Leverage Streaming and Suspense for responsiveness in Next.js 15
Follow Next.js file conventions in the App Router

apps/{web,pro-web}/app/**: Leverage Server Components by default; use the use client directive only when client-side features are needed
Implement Nested Layouts using the App Router
Use Streaming and Suspense features in Next.js 14/15 where appropriate
Follow Next.js file conventions for routing and layout in the App Router
Optimize data fetching: fetch on the server, fetch where needed, and use preload patterns to avoid waterfalls

Files:

  • apps/web/app/api/test-google-start/route.ts
apps/{web,pro-web}/services/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place external API integrations under services/

Files:

  • apps/web/services/hasura/hasura.service.ts
apps/{web,pro-web}/services/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use RORO (Receive an object, Return an object) for functions, especially when calling external services

Place external API integrations under services/

Files:

  • apps/web/services/hasura/hasura.service.ts
🧠 Learnings (18)
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/

Applied to files:

  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/up.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/down.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/down.sql
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/

Applied to files:

  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/up.sql
  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/down.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/up.sql
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store and manage Hasura migrations under apps/hasura/migrations/masterbots/

Applied to files:

  • apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/up.sql
  • apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/up.sql
📚 Learning: 2025-11-21T18:02:42.653Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:79-81
Timestamp: 2025-11-21T18:02:42.653Z
Learning: Preference: In apps/web/lib/hooks/use-profile.tsx, avoid client-side re-fetching the current user in ProfileProvider. Initialize sessionUser from the server-provided NextAuth session (initialSession) and treat it as the source of truth; remove the useEffect that calls getUserByID. Persist client edits via NextAuth’s session update when needed.

Applied to files:

  • apps/web/auth.ts
  • apps/web/app/api/test-google-start/route.ts
📚 Learning: 2025-11-28T03:44:16.389Z
Learnt from: sheriffjimoh
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:60-78
Timestamp: 2025-11-28T03:44:16.389Z
Learning: In apps/web/components/routes/preferences/preference-section.tsx: After updating user profile in the database via updateUserDetails, must call session.update() (from useSession hook) with the updated fields to trigger NextAuth's JWT callback with trigger='update'. This refreshes the JWT token and session automatically. Do not manually call updateSessionUser; the useEffect in use-profile.tsx will sync sessionUser from the refreshed session.user.

Applied to files:

  • apps/web/auth.ts
📚 Learning: 2025-11-21T18:00:08.455Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 590
File: apps/web/lib/hooks/use-profile.tsx:60-78
Timestamp: 2025-11-21T18:00:08.455Z
Learning: In apps/web/lib/hooks/use-profile.tsx: The sessionUser state should be populated directly from session.user (JWT) and not by fetching from Hasura on the client side. The useEffect should only synchronize sessionUser with session.user. Client-side Hasura fetches for user data should be avoided; user data comes from the JWT/session. Only when users update their profile should we refresh both the database and JWT token (via NextAuth session update trigger).

Applied to files:

  • apps/web/auth.ts
  • apps/web/app/api/test-google-start/route.ts
  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Use NextAuth.js with custom user management; JWT for API auth; roles/subscriptions for access control

Applied to files:

  • apps/web/auth.ts
  • apps/web/app/api/test-google-start/route.ts
📚 Learning: 2024-12-05T01:08:50.085Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 0
File: :0-0
Timestamp: 2024-12-05T01:08:50.085Z
Learning: The backend uses Hasura with proper table relationships, permissions, and metadata structure defined in YAML configurations.

Applied to files:

  • apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/{web,pro-web}/**/ai-main-call.actions.{ts,tsx} : Route AI API handling through ai-main-call.actions for unified processing

Applied to files:

  • apps/web/app/api/test-google-start/route.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single interaction point for GraphQL operations with Hasura

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to **/services/hasura.service.ts : Use hasura.service.ts as the single point for all GraphQL interactions

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/{web,pro-web}/**/*.{ts,tsx} : Use hasura.service.ts for all GraphQL operations from UI layers

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:09:37.969Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/**/services/hasura.service.ts : Use hasura.service.ts as the single point of GraphQL interaction in each app

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-05-06T09:54:25.327Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 468
File: apps/masterbots.ai/app/api/cron/delete-requested-accounts/route.ts:70-81
Timestamp: 2025-05-06T09:54:25.327Z
Learning: For Hasura GraphQL operations in server-side code (including API routes and cron jobs), use the `getHasuraClient()` function from mb-lib rather than implementing custom fetch functions. This provides proper error handling, environment validation, and follows the established pattern in the codebase.

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-11-07T18:38:42.273Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 579
File: apps/pro-web/app/api/auth/signup/route.ts:151-157
Timestamp: 2025-11-07T18:38:42.273Z
Learning: In apps/pro-web/app/actions/admin.actions.ts, the insertUserOrganizations function should use Drizzle DB transactions to ensure atomicity when inserting organizations and their associated chatbots, preventing partial persistence issues.

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2024-11-14T21:12:19.789Z
Learnt from: AndlerRL
Repo: bitcashorg/masterbots PR: 302
File: apps/masterbots.ai/lib/hooks/use-mb-chat.ts:0-0
Timestamp: 2024-11-14T21:12:19.789Z
Learning: In `apps/masterbots.ai/lib/hooks/use-mb-chat.ts`, for handling message ordering issues, prefer simple solutions like using `setTimeout` over adding sequence numbers to the message schema, to keep the codebase simple.

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:08:30.965Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/{web,pro-web}/services/**/*.ts : Use RORO (Receive an object, Return an object) for functions, especially when calling external services

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
📚 Learning: 2025-09-30T22:11:18.270Z
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to **/*.{ts,tsx} : Prefer RORO (Receive an object, return an object) for function signatures, especially for external service interactions

Applied to files:

  • apps/web/services/hasura/hasura.service.ts
🔇 Additional comments (12)
apps/hasura/migrations/masterbots/1768100057144_alter_table_public_user_workflows_alter_column_id/up.sql (1)

1-1: LGTM!

The migration correctly sets gen_random_uuid() as the default value for the id column, ensuring new user_workflows rows automatically receive a UUID. This is a safe, non-blocking DDL operation.

apps/hasura/migrations/masterbots/1768100079972_alter_table_public_user_token_alter_column_user_id/down.sql (1)

1-1: LGTM!

The down migration correctly reverses the corresponding up migration by dropping the default value on user_id.

apps/hasura/metadata/databases/masterbots/tables/public_user_token.yaml (2)

11-29: Verify interaction between PostgreSQL default and Hasura check.

The insert permissions correctly enforce that user_id must match the session user's ID. However, the corresponding up migration sets gen_random_uuid() as the default for user_id.

Since a random UUID won't match X-Hasura-User-Id, clients must always explicitly provide user_id — the PostgreSQL default becomes effectively unused for Hasura operations. Confirm this is intentional, or consider removing the default in the migration if it serves no purpose.


54-72: LGTM!

The update permissions correctly restrict updates to only the token column while enforcing row-level security via user_id filter. Preventing user_id updates is good security practice as it blocks ownership transfer.

apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/down.sql (1)

1-1: LGTM!

The down migration correctly reverses the up migration by dropping the default value from the id column. This ensures clean rollback capability.

apps/hasura/migrations/masterbots/1768099921973_alter_table_public_n8n_credentials_alter_column_id/up.sql (1)

1-1: Migration is missing CREATE EXTENSION IF NOT EXISTS pgcrypto; for consistency.

The migration uses gen_random_uuid() without explicitly creating the pgcrypto extension, unlike other similar migrations in the codebase that use the same function (e.g., 1726202691962_create_table_public_example/up.sql, 1703627082924_alter_table_public_thread_add_column_id/up.sql).

Verify that pgcrypto is already initialized in the database or add CREATE EXTENSION IF NOT EXISTS pgcrypto; before the ALTER statement to ensure consistency with established migration patterns.

⛔ Skipped due to learnings
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-30T22:11:18.270Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Keep Hasura-managed database migrations in timestamped folders under apps/hasura/migrations/masterbots/
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-30T22:09:37.969Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store Hasura-managed migrations in timestamped folders under apps/hasura/migrations/masterbots/
Learnt from: CR
Repo: bitcashorg/masterbots PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-30T22:08:30.965Z
Learning: Applies to apps/hasura/migrations/masterbots/** : Store and manage Hasura migrations under apps/hasura/migrations/masterbots/
apps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yaml (1)

35-92: LGTM on select and update permissions.

Row-level security is properly enforced via user_id filter, and update permissions appropriately exclude id, user_id, and connected_at from modifiable columns.

apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/up.sql (1)

1-1: LGTM!

Using gen_random_uuid() as the default for the id column is the correct approach for auto-generating UUIDs in PostgreSQL 13+.

Ensure the target PostgreSQL version is 13 or later, as gen_random_uuid() is a built-in function starting from that version.

apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/down.sql (1)

1-1: LGTM!

The down migration correctly reverses the up migration by dropping the default value on connected_at.

apps/hasura/migrations/masterbots/1768003504213_alter_table_public_user_oauth_connections_alter_column_id/down.sql (1)

1-1: LGTM!

The down migration correctly reverses the up migration by dropping the UUID default on the id column.

apps/hasura/migrations/masterbots/1768003794823_alter_table_public_user_oauth_connections_alter_column_connected_at/up.sql (1)

1-1: LGTM!

Setting a default of now() for the connected_at column is appropriate for automatically tracking when OAuth connections are established. This follows standard patterns for timestamp columns.

apps/web/services/hasura/hasura.service.ts (1)

55-69: LGTM!

The explicit parameter destructuring improves readability and makes the function signature clearer.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +1 to +2
alter table "public"."user_oauth_connections" drop constraint "user_oauth_connections_user_id_key";
alter table "public"."user_oauth_connections" add constraint "user_oauth_connections_user_id_key" unique ("user_id");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Down migration is identical to up migration — this is a no-op.

Both up.sql and down.sql contain the exact same statements: dropping and re-adding the same constraint with the same name on the same column. This means:

  1. The up migration performs no actual schema change (drops and immediately re-creates identical constraint)
  2. The down migration cannot rollback anything since it does the same operation

Either remove this migration entirely if no constraint change is needed, or fix the up/down pair to represent actual schema changes. If the intent was to add a new unique constraint, the down.sql should only drop the constraint:

Suggested fix if the intent is to add a unique constraint

down.sql should only drop the constraint:

 alter table "public"."user_oauth_connections" drop constraint "user_oauth_connections_user_id_key";
-alter table "public"."user_oauth_connections" add constraint "user_oauth_connections_user_id_key" unique ("user_id");

up.sql should only add the constraint:

-alter table "public"."user_oauth_connections" drop constraint "user_oauth_connections_user_id_key";
 alter table "public"."user_oauth_connections" add constraint "user_oauth_connections_user_id_key" unique ("user_id");

However, if the constraint already exists from the original table creation, this entire migration may be unnecessary.

📝 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
alter table "public"."user_oauth_connections" drop constraint "user_oauth_connections_user_id_key";
alter table "public"."user_oauth_connections" add constraint "user_oauth_connections_user_id_key" unique ("user_id");
alter table "public"."user_oauth_connections" drop constraint "user_oauth_connections_user_id_key";
🤖 Prompt for AI Agents
In
`@apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/down.sql`
around lines 1 - 2, The down migration is identical to the up migration causing
a no-op; fix by ensuring up.sql and down.sql are complementary for the
constraint "user_oauth_connections_user_id_key" on table
"public"."user_oauth_connections": if the intent is to add a unique constraint,
make up.sql only add the constraint and make down.sql only drop the constraint
(DROP CONSTRAINT "user_oauth_connections_user_id_key"), or if the constraint
already exists remove this migration entirely; update the SQL files so up adds
and down removes the unique constraint (or delete the migration) and then re-run
migrations to verify correct behavior.

Comment on lines +1 to +2
alter table "public"."user_oauth_connections" drop constraint "user_oauth_connections_user_id_key";
alter table "public"."user_oauth_connections" add constraint "user_oauth_connections_user_id_key" unique ("user_id");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the structure and list the migration files
fd -t d "masterbots" apps/hasura/migrations/ | head -20

Repository: bitcashorg/masterbots

Length of output: 99


🏁 Script executed:

# Find the specific migration file mentioned
cat apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/up.sql

Repository: bitcashorg/masterbots

Length of output: 282


🏁 Script executed:

# Check the down.sql for this migration
cat apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/down.sql

Repository: bitcashorg/masterbots

Length of output: 282


🏁 Script executed:

# Find the table creation migration for user_oauth_connections
fd -t f "up.sql" apps/hasura/migrations/masterbots | xargs grep -l "create table.*user_oauth_connections" 2>/dev/null | head -1 | xargs cat

Repository: bitcashorg/masterbots

Length of output: 47


🏁 Script executed:

# List all migrations in chronological order
fd -t d "masterbots" apps/hasura/migrations/ | sort | head -30

Repository: bitcashorg/masterbots

Length of output: 99


🏁 Script executed:

# Search for user_oauth_connections table creation without case-sensitivity
fd -t f "up.sql" apps/hasura/migrations/masterbots | xargs grep -i "create.*user_oauth_connections" 2>/dev/null | head -5

Repository: bitcashorg/masterbots

Length of output: 584


🏁 Script executed:

# Look for any mention of user_oauth_connections in migrations
fd -t f "up.sql" apps/hasura/migrations/masterbots | xargs grep -i "user_oauth_connections" 2>/dev/null | head -20

Repository: bitcashorg/masterbots

Length of output: 1666


🏁 Script executed:

# Examine the down.sql more carefully - show with context
echo "=== UP.SQL ===" && cat apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/up.sql && echo -e "\n=== DOWN.SQL ===" && cat apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/down.sql

Repository: bitcashorg/masterbots

Length of output: 533


Migration is a no-op and the down migration is structurally broken.

The constraint user_oauth_connections_user_id_key already exists from the table creation migration (1766066591567), which includes UNIQUE ("user_id"). This migration drops and immediately re-creates it with the identical definition, causing:

  1. up.sql is redundant — drops and re-adds the same constraint, making no schema change
  2. down.sql is incorrect — it is identical to up.sql instead of reversing the change; down migrations should undo the up migration

This creates a bidirectional no-op migration. Either:

  • Delete this migration entirely (if no schema change is needed), or
  • If the intent was to rename the constraint or modify its definition, rewrite both up.sql and down.sql to reflect the actual change
🤖 Prompt for AI Agents
In
`@apps/hasura/migrations/masterbots/1768003427670_alter_table_public_user_oauth_connections_add_unique_user_id/up.sql`
around lines 1 - 2, The migration is a no-op and its down.sql is wrong: it drops
and re-adds the existing constraint "user_oauth_connections_user_id_key" (from
migration 1766066591567) then repeats the same in down.sql; either delete this
migration or implement a real change. Fix by either removing this migration file
pair entirely if no schema change is needed, or if you intended a rename or
change, modify up.sql to perform the actual change (e.g., rename constraint or
alter uniqueness) and update down.sql to reverse that change; reference the
constraint name "user_oauth_connections_user_id_key" and the migration id
1768003427670 when making the edits so both up.sql and down.sql are
complementary and no longer a bidirectional no-op.

@@ -0,0 +1 @@
alter table "public"."user_oauth_connections" alter column "revoked_at" set not null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Down migration may fail if NULL values exist.

This rollback sets revoked_at to NOT NULL, but if the up migration was applied and rows were inserted with NULL in revoked_at (which is the expected state for active connections), this rollback will fail.

Consider adding a safe default or handling NULL values before applying the constraint:

♻️ Safer rollback approach
+-- Set a placeholder value for NULL entries before enforcing NOT NULL
+UPDATE "public"."user_oauth_connections" SET "revoked_at" = '1970-01-01 00:00:00' WHERE "revoked_at" IS NULL;
 alter table "public"."user_oauth_connections" alter column "revoked_at" set not null;

Note: The placeholder value approach may not be semantically correct for your domain. Alternatively, consider if this rollback is even necessary, or document that rollback requires manual data handling.

📝 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
alter table "public"."user_oauth_connections" alter column "revoked_at" set not null;
-- Set a placeholder value for NULL entries before enforcing NOT NULL
UPDATE "public"."user_oauth_connections" SET "revoked_at" = '1970-01-01 00:00:00' WHERE "revoked_at" IS NULL;
alter table "public"."user_oauth_connections" alter column "revoked_at" set not null;
🤖 Prompt for AI Agents
In
`@apps/hasura/migrations/masterbots/1768003836473_alter_table_public_user_oauth_connections_alter_column_revoked_at/down.sql`
at line 1, The down migration alters user_oauth_connections.revoked_at to NOT
NULL which will fail if any NULLs exist; before running ALTER TABLE
"user_oauth_connections" ALTER COLUMN "revoked_at" SET NOT NULL you must handle
existing NULL values (e.g. UPDATE user_oauth_connections SET revoked_at =
<safe_default_timestamp> WHERE revoked_at IS NULL, or DELETE/flag those rows, or
decide to skip/remove the NOT NULL change), or alternatively make the migration
idempotent by checking for NULLs and only applying the constraint when none
exist; update the down migration to perform the NULL-handling step(s) and
document the chosen semantic for revoked_at so rollback won't error.

Comment on lines 2299 to 2332
export async function insertUserWorkflow({
userId,
workflowId,
workflowName,
service,
folderPath,
jwt,
}: {
userId: string
workflowId: string
workflowName: string
service: 'gmail' | 'drive'
folderPath: string
jwt: string
}) {
const client = getHasuraClient({ jwt })
const result = await client.mutation({
insertUserWorkflowsOne: {
__args: {
object: {
userId,
workflowId: workflowId,
workflowName: workflowName,
service,
folderPath: folderPath,
createdAt: new Date().toISOString(),
},
},
id: true,
},
})

return result.insertUserWorkflowsOne
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add JWT validation before database operation.

The function accepts jwt as a required parameter but doesn't validate it before calling getHasuraClient. Other functions in this file (e.g., getUserWorkflowByService at line 2339) throw an error if JWT is missing. This inconsistency could lead to confusing errors when the JWT is empty or undefined.

🔎 Proposed fix
 export async function insertUserWorkflow({
 	userId,
 	workflowId,
 	workflowName,
 	service,
 	folderPath,
 	jwt,
 }: {
 	userId: string
 	workflowId: string
 	workflowName: string
 	service: 'gmail' | 'drive'
 	folderPath: string
 	jwt: string
 }) {
+	if (!jwt) throw new Error('Authentication required to insert user workflow')
 	const client = getHasuraClient({ jwt })
🤖 Prompt for AI Agents
In `@apps/web/services/hasura/hasura.service.ts` around lines 2299 - 2332, The
insertUserWorkflow function should validate the jwt before calling
getHasuraClient: if jwt is falsy, throw the same error used by
getUserWorkflowByService (e.g., throw new Error('JWT is required') or match its
exact message) to maintain consistency; add this check at the start of
insertUserWorkflow (before getHasuraClient) so getHasuraClient is only called
with a valid token and the error behavior matches other functions in this file.

Comment on lines +2386 to +2390
return {
...result.n8nCredentials[0],
n8n_credential_id: result.n8nCredentials[0].n8nCredentialId,
webhook_url: '', // Provide a default or fetch the actual value if available
} as UserCredential
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded empty webhook_url may cause issues for callers.

The function returns webhook_url: '' as a placeholder to satisfy the UserCredential type. This can mislead callers expecting a valid webhook URL. Consider either:

  1. Fetching webhook_url from the database if it exists
  2. Defining a narrower return type that doesn't include webhook_url
  3. Making webhook_url optional in the UserCredential type
🤖 Prompt for AI Agents
In `@apps/web/services/hasura/hasura.service.ts` around lines 2386 - 2390, The
returned object includes a hardcoded empty webhook_url which can mislead
callers; update the code that builds the UserCredential (the return using
result.n8nCredentials[0] and casting to UserCredential) to either (a) retrieve
the real webhook URL from the DB result (e.g., include the webhook field from
result.n8nCredentials[0] or join the table that contains it) and set webhook_url
to that value, or (b) change the return type to a narrower interface that omits
webhook_url, or (c) make webhook_url optional on the UserCredential type and
omit it when not available—choose one approach and remove the hardcoded ''
placeholder so callers do not receive a false empty URL.

Copy link
Contributor

@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: 15

🤖 Fix all issues with AI agents
In `@apps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yaml`:
- Around line 37-51: The moderator select_permissions currently omit is_active
and web_hook_url while the moderator update_permissions allow them; update the
select permission for role "moderator" (the select_permissions -> permission ->
columns list) to include "is_active" and "web_hook_url" so moderators can read
any columns they are allowed to update.

In
`@apps/hasura/migrations/masterbots/1769201248583_alter_table_public_user_workflows_add_column_is_active/down.sql`:
- Around line 1-4: The down migration is missing and must remove the column
added in the up migration: add a DROP statement that undoes alter table
"public"."user_workflows" add column "is_active" boolean not null default
'false'; — specifically execute ALTER TABLE "public"."user_workflows" DROP
COLUMN IF EXISTS "is_active"; (include IF EXISTS and handle any dependent
constraints as needed, e.g., DROP COLUMN ... CASCADE only if safe) so rolling
back actually removes the is_active column.

In `@apps/pro-web/app/api/oauth/google/callback/route.ts`:
- Around line 128-131: Remove sensitive token material from the HTTP response:
do not return the full tokenData object (which contains access_token and
refresh_token) in the NextResponse.json call inside route.ts. Instead, return a
minimal success payload (e.g., a message or status) or perform a redirect to a
success page; persist tokenData server-side (DB or secure session) via the
existing storage/update logic and ensure only non-sensitive confirmation is sent
to the client. Replace the NextResponse.json usage that includes tokenData with
a response that omits tokens.
- Line 72: The console.log call in route.ts ("console.log('Google callback hit
for userId:', userId)") emits a raw user identifier (userId); remove or replace
it with non-PII structured debug logging - e.g., use your app's logger
(processLogger or appLogger) at debug level without the full userId (omit, mask,
or log a boolean/trace id only) inside the Google OAuth callback handler so no
personally identifiable information is written to production logs; update
references to the console.log line and ensure any tests or callers expecting
that log are adjusted.

In `@apps/pro-web/lib/constants/template.ts`:
- Line 15: The value assigned to n8nTemplateId is not a valid UUID due to the
trailing 'g'; update the n8nTemplateId constant to either a valid UUID (e.g.,
replace the string with a correctly-formed UUID) or explicitly mark it as a
placeholder (add a TODO comment and a clearly non-UUID placeholder like
'TODO_N8N_TEMPLATE_ID') so it's obvious it must be replaced before use; locate
the n8nTemplateId constant in the file and make this correction.

In `@apps/pro-web/lib/n8n.ts`:
- Around line 225-249: The n8nRequest function uses N8N_BASE_URL without
validating it, which can produce confusing runtime URLs; update the code to
validate N8N_BASE_URL (either at module initialization or inline in n8nRequest)
and throw a clear error if it's undefined—e.g., mirror the existing IIFE-throw
pattern used for N8N_API_KEY so that calls in n8nRequest (where the URL is
built) never proceed with an undefined base URL; reference N8N_BASE_URL and the
n8nRequest function when making this change.
- Around line 37-41: The file mixes process.env.N8N_API_URL and
process.env.N8N_BASE_URL causing inconsistent endpoints; define module-level
constants (e.g., const N8N_API_URL = process.env.N8N_API_URL ||
process.env.N8N_BASE_URL || ''; const N8N_API_KEY = process.env.N8N_API_KEY ||
'') and replace all direct environment accesses in createN8nCredential,
duplicateN8nWorkflow, n8nRequest, enableWorkflow, disableWorkflow, and
executeWorkflow to use these constants so every request target and API key is
consistent across the module.
- Around line 74-220: duplicateN8nWorkflow makes three fetch calls (templateRes,
createRes, transferRes) with no timeouts, repeats the IIFE API-key throw inline,
and can leave an orphaned workflow if transfer fails; fix by (1) extracting the
API key once (e.g., const N8N_API_KEY = process.env.N8N_API_KEY ?? throw...) at
module scope and using that in all headers, (2) add AbortSignal.timeout(...) (or
create an AbortController with a timeout) to each fetch call (templateRes,
createRes, transferRes) to avoid hanging requests, and (3) on transferRes
failure attempt cleanup by deleting the newly created workflow (call DELETE
/workflows/{newWorkflow.id}) inside the error branch (or at minimum log a clear
warning including newWorkflow.id and the transfer error) so created workflows
aren’t orphaned.

In `@apps/pro-web/services/hasura/hasura.service.ts`:
- Around line 2496-2653: The five functions insertUserOAuthConnection,
insertN8nCredential, insertUserWorkflow, getUserWorkflowByService, and
getUserCredentialByService are duplicated from
apps/web/services/hasura/hasura.service.ts; extract their implementations into a
shared module (e.g., mb-lib or a new shared hasura service) that exports these
named functions, update both apps/pro-web and apps/web to import them from the
shared module, and remove the duplicate definitions from the pro-web file;
ensure the shared module reuses getHasuraClient and preserves the same
signatures (including types like UserWorkflow/UserCredential) and error checks
so callers work unchanged.
- Around line 2557-2594: The mutation field name used inside the
insertUserWorkflow function is incorrect: update the GraphQL mutation key from
insertUserWorkflowOne to insertUserWorkflowsOne within the insertUserWorkflow
function so it matches the Hasura table user_workflows and the generated
mutation name; locate the client.mutation call in insertUserWorkflow and replace
the mutation object key insertUserWorkflowOne with insertUserWorkflowsOne
(keeping the same __args/object shape and returned id).

In `@apps/pro-web/services/hasura/hasura.service.type.ts`:
- Around line 131-177: TokenData, UserOAuthConnection, N8nCredential,
UserWorkflow, UserWorkflowReturn, and UserCredential are duplicated across
files; extract and export a single shared type module and replace the local
definitions with imports. For DB-shaped types (UserOAuthConnection,
N8nCredential, UserWorkflow) prefer reusing the mb-genql generated types
(UserOauthConnections, N8nCredentials, UserWorkflows) or create adapter types
that match their camelCase fields (userId, createdAt, n8nCredentialId) if the
service returns raw DB rows; for utility wrappers (UserWorkflowReturn,
UserCredential, TokenData) move them into the shared types file and update all
references to import the consolidated definitions. Ensure function signatures in
hasura.service functions reference the imported types (e.g., TokenData,
UserOAuthConnection, N8nCredential, UserWorkflow, UserWorkflowReturn,
UserCredential) and remove the local duplicates.

In `@apps/web/app/api/automations/gmail/disable/route.ts`:
- Around line 5-46: The POST handler accepts JWT from the request body and
duplicates an inline Hasura query instead of using the centralized helper; fix
by retrieving authentication from the server session (not req.json) and passing
server-side jwt to getHasuraClient, replace the manual client.query block with a
call to getUserWorkflowByService (or the existing helper used in
execute/route.ts) to fetch the workflow and its workflow_id, verify the fetched
workflow belongs to the authenticated user, then call
disableWorkflow(workflowId) and the existing client.mutation to set
isActive=false; update error handling paths accordingly and remove trusting
user-supplied jwt/userId from the request body.

In `@apps/web/app/api/automations/gmail/enable/route.ts`:
- Line 38: The enable route currently returns the wrong success message; update
the NextResponse.json call in the enable route handler (the NextResponse.json({
message: 'Workflow disabled' }) line) to return { message: 'Workflow enabled' }
so the response correctly reflects the action performed.
- Around line 5-46: The POST handler currently reads a JWT from the request body
and passes it straight to getHasuraClient; instead validate the caller
server-side (do not trust JWT in body) by extracting and verifying the
session/auth token from the incoming request headers or server session API and
only then create the Hasura client; update the POST function to call your
server-side auth helper (e.g., getServerSession or verifyAuthToken) and use the
validated credentials/userId when invoking getHasuraClient and enableWorkflow;
also ensure error responses reflect authentication failures (401) and remove
acceptance of jwt from req.json to prevent bypass.

In `@apps/web/app/api/automations/gmail/execute/route.ts`:
- Around line 5-46: The POST handler accepts an untrusted jwt and userId from
the request body—replace that by calling getServerSession to obtain the
authenticated user's id and server-side JWT, remove reading jwt/userId from
req.json, and ensure the session user id matches the requested user (or simply
use the session id). Swap the inline Hasura GraphQL query with the hasura
service helper getUserWorkflowByService (use the session user id and service
name to fetch the workflow) and then call executeWorkflow(workflowId, payload)
as before; ensure errors log safely and return the same JSON shape on
success/failure.
🧹 Nitpick comments (8)
apps/pro-web/lib/hooks/use-workspace-chat.ask.test.tsx (1)

24-181: Consider extracting shared test scaffolding into a common fixture.

All four use-workspace-chat.*.test.tsx files duplicate ~150 lines of nearly identical mock setup (useSidebar, useSonner, use-thread, next-auth/react, @/lib/queries, use-mb-chat, use-workspace-structure, etc.). Extracting this into a shared test helper (e.g., use-workspace-chat.test-utils.ts) would reduce maintenance burden when mocks need updating and make individual test files easier to read.

This is a suggestion across all four test files and can be deferred.

apps/hasura/migrations/masterbots/1769123034988_alter_table_public_user_workflows_add_column_WebhookUrl/up.sql (1)

1-2: Consider squashing with the subsequent rename migration.

This migration adds "WebhookUrl" (PascalCase) only for the next migration (1769123055595) to immediately rename it to "web_hook_url". Since neither has been applied to production yet (WIP PR), these two migrations could be collapsed into a single one that adds the column with the correct snake_case name directly, reducing migration noise.

apps/hasura/migrations/masterbots/1769123055595_alter_table_public_user_workflows_alter_column_WebhookUrl/up.sql (1)

1-1: Naming nit: webhook_url vs web_hook_url.

"Webhook" is a single compound word. The more conventional snake_case column name would be webhook_url rather than web_hook_url. This affects the Hasura metadata, TypeScript types, and all downstream references, so worth deciding now before this ships.

apps/pro-web/lib/constants/template.ts (1)

6-7: Replace any with a more specific type for inputSchema.

The coding guidelines discourage any. A simple Record<string, string> matches the current usage and preserves type safety. As per coding guidelines, "Avoid using the any type; use more specific types instead to maintain type safety."

♻️ Suggested fix
-	// biome-ignore lint/suspicious/noExplicitAny: <explanation>
-	inputSchema?: any // optional, schema of inputs the workflow needs
+	inputSchema?: Record<string, string> // optional, schema of inputs the workflow needs
apps/hasura/migrations/masterbots/1769201248583_alter_table_public_user_workflows_add_column_is_active/up.sql (1)

1-2: Use the boolean literal false instead of the string 'false'.

PostgreSQL will implicitly cast the string, but the idiomatic default for a boolean column is the unquoted keyword.

🔧 Suggested fix
-alter table "public"."user_workflows" add column "is_active" boolean
- not null default 'false';
+alter table "public"."user_workflows" add column "is_active" boolean
+ not null default false;
apps/web/services/hasura/hasura.service.ts (1)

2237-2266: Inconsistent function signature style — use RORO pattern for consistency.

insertUserOAuthConnection uses positional parameters while the sibling insertUserWorkflow on Line 2299 uses a destructured object. The coding guidelines recommend the RORO pattern for functions interacting with external services, and consistency within the same file makes the API easier to use. The same applies to insertN8nCredential on Line 2269.

♻️ Proposed refactor
-export async function insertUserOAuthConnection(
-	userId: string,
-	scopes: string[],
-	jwt: string,
-) {
+export async function insertUserOAuthConnection({
+	userId,
+	scopes,
+	jwt,
+}: {
+	userId: string
+	scopes: string[]
+	jwt: string
+}) {

As per coding guidelines, "Define functions with RORO pattern (Receive an Object, Return an Object), especially for functions interacting with external services."

apps/pro-web/lib/n8n.ts (1)

1-7: TokenData is duplicated.

This interface is already defined in apps/pro-web/services/hasura/hasura.service.type.ts (Lines 130-136). Import it from there instead of redefining it.

♻️ Proposed fix
-export interface TokenData {
-	access_token: string
-	refresh_token: string
-	expires_in: number
-	scope: string
-	token_type: string
-}
+export type { TokenData } from '@/services/hasura/hasura.service.type'
apps/pro-web/app/api/oauth/google/callback/route.ts (1)

15-48: Env var validation uses || instead of ??.

Using || means an empty string '' will also trigger the throw, which may be desirable but is inconsistent with the start route which uses ??. More importantly, six env vars are validated at module load time — if any is missing, the entire module fails to load, which will break the callback route even if the missing var is only needed by the commented-out n8n code (e.g., N8N_TEMPLATE_WORKFLOW_ID).

Consider deferring validation of env vars that are only used by the commented-out code, or removing those constants until the code is uncommented.

Comment on lines +37 to +51
select_permissions:
- role: moderator
permission:
columns:
- folder_path
- service
- workflow_id
- workflow_name
- created_at
- id
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
comment: ""
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Moderator select is missing is_active and web_hook_url — but moderator can update them.

The moderator role can update is_active and web_hook_url (lines 72–77) but cannot read them back (lines 40–47). This is almost certainly unintentional — a moderator should be able to select any column they can update.

🔧 Suggested fix
   - role: moderator
     permission:
       columns:
         - folder_path
+        - is_active
         - service
+        - web_hook_url
         - workflow_id
         - workflow_name
         - created_at
         - id
         - user_id
       filter:
         user_id:
           _eq: X-Hasura-User-Id
📝 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
select_permissions:
- role: moderator
permission:
columns:
- folder_path
- service
- workflow_id
- workflow_name
- created_at
- id
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
comment: ""
select_permissions:
- role: moderator
permission:
columns:
- folder_path
- is_active
- service
- web_hook_url
- workflow_id
- workflow_name
- created_at
- id
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
comment: ""
🤖 Prompt for AI Agents
In `@apps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yaml`
around lines 37 - 51, The moderator select_permissions currently omit is_active
and web_hook_url while the moderator update_permissions allow them; update the
select permission for role "moderator" (the select_permissions -> permission ->
columns list) to include "is_active" and "web_hook_url" so moderators can read
any columns they are allowed to update.

Comment on lines +1 to +4
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."user_workflows" add column "is_active" boolean
-- not null default 'false';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Down migration is a no-op — column will not be dropped on rollback.

The file only contains comments. A rollback of this migration will silently succeed without actually removing the is_active column, leaving the schema in an inconsistent state.

🔧 Suggested fix
--- Could not auto-generate a down migration.
--- Please write an appropriate down migration for the SQL below:
--- alter table "public"."user_workflows" add column "is_active" boolean
---  not null default 'false';
+alter table "public"."user_workflows" drop column "is_active";
🤖 Prompt for AI Agents
In
`@apps/hasura/migrations/masterbots/1769201248583_alter_table_public_user_workflows_add_column_is_active/down.sql`
around lines 1 - 4, The down migration is missing and must remove the column
added in the up migration: add a DROP statement that undoes alter table
"public"."user_workflows" add column "is_active" boolean not null default
'false'; — specifically execute ALTER TABLE "public"."user_workflows" DROP
COLUMN IF EXISTS "is_active"; (include IF EXISTS and handle any dependent
constraints as needed, e.g., DROP COLUMN ... CASCADE only if safe) so rolling
back actually removes the is_active column.

)
}

console.log('Google callback hit for userId:', userId)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

PII in server logs.

console.log('Google callback hit for userId:', userId) logs a user identifier. In production, this creates compliance concerns (GDPR/CCPA). Use structured logging at debug level, or omit the userId from logs entirely.

🔎 Proposed fix
-		console.log('Google callback hit for userId:', userId)
+		// Avoid logging PII in production
📝 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
console.log('Google callback hit for userId:', userId)
// Avoid logging PII in production
🤖 Prompt for AI Agents
In `@apps/pro-web/app/api/oauth/google/callback/route.ts` at line 72, The
console.log call in route.ts ("console.log('Google callback hit for userId:',
userId)") emits a raw user identifier (userId); remove or replace it with
non-PII structured debug logging - e.g., use your app's logger (processLogger or
appLogger) at debug level without the full userId (omit, mask, or log a
boolean/trace id only) inside the Google OAuth callback handler so no personally
identifiable information is written to production logs; update references to the
console.log line and ensure any tests or callers expecting that log are
adjusted.

Comment on lines +128 to +131
return NextResponse.json({
message: 'Google OAuth connected successfully',
tokenData,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Security: OAuth tokens (including refresh_token) leaked to the client.

The response includes the full tokenData object containing access_token and refresh_token. These are long-lived credentials that should be stored server-side only and never exposed in API responses. An attacker intercepting this response gains full access to the user's Gmail/Drive.

🔒 Proposed fix
 		return NextResponse.json({
 			message: 'Google OAuth connected successfully',
-			tokenData,
 		})

If the client needs to know the connection succeeded, the success message alone is sufficient. Alternatively, redirect the user to a success page.

🤖 Prompt for AI Agents
In `@apps/pro-web/app/api/oauth/google/callback/route.ts` around lines 128 - 131,
Remove sensitive token material from the HTTP response: do not return the full
tokenData object (which contains access_token and refresh_token) in the
NextResponse.json call inside route.ts. Instead, return a minimal success
payload (e.g., a message or status) or perform a redirect to a success page;
persist tokenData server-side (DB or secure session) via the existing
storage/update logic and ensure only non-sensitive confirmation is sent to the
client. Replace the NextResponse.json usage that includes tokenData with a
response that omits tokens.

id: 'gmail-send',
name: 'Send Gmail Email',
description: 'Sends an email via Gmail using user credentials',
n8nTemplateId: 'c1b2e5f8-9a3d-4e5f-8c2a-1b2c3d4e5f6g',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Invalid UUID: 'g' is not a valid hexadecimal character.

c1b2e5f8-9a3d-4e5f-8c2a-1b2c3d4e5f6g — the trailing g makes this an invalid UUID. If this is a placeholder, mark it clearly (e.g., with a TODO comment). If it's meant to be a real n8n workflow ID, fix the value.

🤖 Prompt for AI Agents
In `@apps/pro-web/lib/constants/template.ts` at line 15, The value assigned to
n8nTemplateId is not a valid UUID due to the trailing 'g'; update the
n8nTemplateId constant to either a valid UUID (e.g., replace the string with a
correctly-formed UUID) or explicitly mark it as a placeholder (add a TODO
comment and a clearly non-UUID placeholder like 'TODO_N8N_TEMPLATE_ID') so it's
obvious it must be replaced before use; locate the n8nTemplateId constant in the
file and make this correction.

Comment on lines +131 to +177
export interface TokenData {
access_token: string
refresh_token: string
expires_in: number
scope: string
token_type: string
}

export interface UserOAuthConnection {
id: string
user_id: string
provider: 'google'
service: 'gmail'
scopes: string
status: 'connected' | 'revoked'
connected_at: string
revoked_at?: string
}

export interface N8nCredential {
id: string
user_id: string
n8n_credential_id: string
service: 'gmail'
created_at: string
}

export interface UserWorkflow {
id: string
user_id: string
workflow_id: string
workflow_name: string
service: 'gmail'
folder_path: string
created_at: string
}

export interface UserWorkflowReturn {
workflow_id: string
}

export type UserCredential = {
id: string
n8n_credential_id: string
service: string
webhook_url: string
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify duplication of these interfaces across the codebase
echo "=== TokenData definitions ==="
rg -n "export interface TokenData|export type TokenData" --type=ts

echo ""
echo "=== UserOAuthConnection definitions ==="
rg -n "export interface UserOAuthConnection|export type UserOAuthConnection" --type=ts

echo ""
echo "=== N8nCredential definitions ==="
rg -n "export interface N8nCredential|export type N8nCredential" --type=ts

echo ""
echo "=== UserWorkflow definitions ==="
rg -n "export interface UserWorkflow|export type UserWorkflow" --type=ts

echo ""
echo "=== UserWorkflowReturn definitions ==="
rg -n "export interface UserWorkflowReturn|export type UserWorkflowReturn" --type=ts

echo ""
echo "=== UserCredential definitions ==="
rg -n "export interface UserCredential|export type UserCredential" --type=ts

Repository: bitcashorg/masterbots

Length of output: 5749


🏁 Script executed:

#!/bin/bash
# Compare duplicate definitions
echo "=== TokenData in apps/pro-web/services/hasura/hasura.service.type.ts ==="
sed -n '131,137p' apps/pro-web/services/hasura/hasura.service.type.ts

echo ""
echo "=== TokenData in apps/web/types/types.ts ==="
sed -n '681,687p' apps/web/types/types.ts

echo ""
echo "=== TokenData in apps/pro-web/lib/n8n.ts ==="
sed -n '1,7p' apps/pro-web/lib/n8n.ts

echo ""
echo "=== UserOAuthConnection in apps/pro-web/services/hasura/hasura.service.type.ts ==="
sed -n '139,148p' apps/pro-web/services/hasura/hasura.service.type.ts

echo ""
echo "=== UserOAuthConnection in apps/web/types/types.ts ==="
sed -n '689,698p' apps/web/types/types.ts

echo ""
echo "=== Check mb-genql N8nCredentials structure ==="
sed -n '2179,2188p' packages/mb-genql/generated/schema.ts

echo ""
echo "=== Check mb-genql UserWorkflows structure ==="
sed -n '4274,4287p' packages/mb-genql/generated/schema.ts

Repository: bitcashorg/masterbots

Length of output: 1919


🏁 Script executed:

#!/bin/bash
# Check usage of these manual types
echo "=== Usage of manual N8nCredential (not plural) ==="
rg "N8nCredential" apps/pro-web apps/web --type=ts -l

echo ""
echo "=== Usage of manual UserWorkflow (checking if using mb-genql version) ==="
rg "UserWorkflow" apps/pro-web apps/web --type=ts -l

echo ""
echo "=== Check if mb-genql types are imported anywhere ==="
rg "from.*mb-genql" apps/pro-web apps/web --type=ts | head -20

Repository: bitcashorg/masterbots

Length of output: 2612


🏁 Script executed:

#!/bin/bash
echo "=== Check for UserOAuth or OAuth types in mb-genql ==="
rg "OAuthConnection|UserOAuth" packages/mb-genql/generated/schema.ts --type=ts | head -20

echo ""
echo "=== Check actual field names in manual vs generated types ==="
echo "Manual N8nCredential fields:"
sed -n '150,157p' apps/pro-web/services/hasura/hasura.service.type.ts

echo ""
echo "mb-genql N8nCredentials fields:"
sed -n '2179,2188p' packages/mb-genql/generated/schema.ts

echo ""
echo "=== Check where hasura.service.ts uses these types ==="
grep -n "N8nCredential\|UserWorkflow\|TokenData" apps/pro-web/services/hasura/hasura.service.ts | head -15

Repository: bitcashorg/masterbots

Length of output: 1096


🏁 Script executed:

#!/bin/bash
echo "=== Check how UserWorkflows is used and its field structure ==="
sed -n '4274,4287p' packages/mb-genql/generated/schema.ts

echo ""
echo "=== Compare manual UserWorkflow with mb-genql UserWorkflows ==="
echo "Manual UserWorkflow:"
sed -n '158,167p' apps/pro-web/services/hasura/hasura.service.type.ts

echo ""
echo "=== Check imports in hasura.service.ts ==="
head -50 apps/pro-web/services/hasura/hasura.service.ts | grep -E "^import|^export"

echo ""
echo "=== Check if these manual types are re-exported or used as interfaces ==="
grep -A2 "return result" apps/pro-web/services/hasura/hasura.service.ts | grep -E "UserWorkflow|N8nCredential" | head -10

Repository: bitcashorg/masterbots

Length of output: 1176


🏁 Script executed:

#!/bin/bash
echo "=== Full imports section of hasura.service.ts ==="
sed -n '1,60p' apps/pro-web/services/hasura/hasura.service.ts

echo ""
echo "=== Check what the GraphQL query returns vs what the manual type expects ==="
sed -n '2528,2560p' apps/pro-web/services/hasura/hasura.service.ts

Repository: bitcashorg/masterbots

Length of output: 1938


🏁 Script executed:

#!/bin/bash
echo "=== Check full structure of insertUserWorkflow function ==="
sed -n '2558,2595p' apps/pro-web/services/hasura/hasura.service.ts

echo ""
echo "=== Check if N8nCredential is actually subset of N8nCredentials ==="
echo "Manual N8nCredential fields:"
rg -A6 "^export interface N8nCredential {" apps/pro-web/services/hasura/hasura.service.type.ts

echo ""
echo "=== Check if UserOAuthConnection exists in mb-genql ==="
rg "UserOAuthConnection|UserOAuth" packages/mb-genql/generated/schema.ts

Repository: bitcashorg/masterbots

Length of output: 1085


🏁 Script executed:

#!/bin/bash
echo "=== Manual N8nCredential definition ==="
sed -n '150,157p' apps/pro-web/services/hasura/hasura.service.type.ts

echo ""
echo "=== Check actual GraphQL mutation input for N8nCredentials ==="
grep -A15 "insertN8nCredentialsOne" apps/pro-web/services/hasura/hasura.service.ts | head -20

echo ""
echo "=== Verify field name discrepancy: snake_case vs camelCase ==="
echo "Manual types use snake_case (user_id, n8n_credential_id, created_at)"
echo "mb-genql uses camelCase (userId, n8nCredentialId, createdAt)"

echo ""
echo "=== Check if there's any UserOAuthConnection in the db schema ==="
rg "user_oauth_connection|UserOAuthConnection" packages/mb-genql/generated/schema.ts

Repository: bitcashorg/masterbots

Length of output: 4291


🏁 Script executed:

#!/bin/bash
echo "=== Find UserOAuthConnections type in mb-genql ==="
rg "export interface UserOAuthConnections" packages/mb-genql/generated/schema.ts -A10

echo ""
echo "=== Summary: Check which files import from hasura.service.type.ts ==="
rg "from.*hasura.service.type" apps/pro-web apps/web --type=ts

Repository: bitcashorg/masterbots

Length of output: 1122


🏁 Script executed:

#!/bin/bash
echo "=== Check if UserOAuthConnections type exists but with different naming ==="
rg "export (interface|type) UserOAuthConnections" packages/mb-genql/generated/schema.ts

echo ""
echo "=== Check all OAuth-related types in mb-genql ==="
rg "export (interface|type).*[Oo]auth" packages/mb-genql/generated/schema.ts | head -20

echo ""
echo "=== Verify UserWorkflowReturn and UserCredential purpose ==="
echo "UserWorkflowReturn in type file:"
sed -n '168,171p' apps/pro-web/services/hasura/hasura.service.type.ts

echo ""
echo "UserCredential in type file:"
sed -n '172,177p' apps/pro-web/services/hasura/hasura.service.type.ts

Repository: bitcashorg/masterbots

Length of output: 1556


Consolidate duplicated type definitions across service files.

TokenData appears in three identical locations (apps/pro-web/services/hasura/hasura.service.type.ts, apps/web/types/types.ts, apps/pro-web/lib/n8n.ts) and should be defined once in a shared location.

UserOAuthConnection, N8nCredential, UserWorkflow, UserWorkflowReturn, and UserCredential are duplicated between apps/pro-web/services/hasura/hasura.service.type.ts and apps/web/types/types.ts.

For UserOAuthConnection, N8nCredential, and UserWorkflow, the codebase has corresponding mb-genql generated types (UserOauthConnections, N8nCredentials, UserWorkflows), but they use camelCase field naming (userId, createdAt, n8nCredentialId) instead of the snake_case in the manual definitions. If these service functions are intended to return raw database shapes, align field naming with mb-genql conventions; otherwise, define a shared return-type file and import from it in both locations.

UserWorkflowReturn and UserCredential appear to be utility/wrapper types—consolidate these as well to a single location and import where needed.

🤖 Prompt for AI Agents
In `@apps/pro-web/services/hasura/hasura.service.type.ts` around lines 131 - 177,
TokenData, UserOAuthConnection, N8nCredential, UserWorkflow, UserWorkflowReturn,
and UserCredential are duplicated across files; extract and export a single
shared type module and replace the local definitions with imports. For DB-shaped
types (UserOAuthConnection, N8nCredential, UserWorkflow) prefer reusing the
mb-genql generated types (UserOauthConnections, N8nCredentials, UserWorkflows)
or create adapter types that match their camelCase fields (userId, createdAt,
n8nCredentialId) if the service returns raw DB rows; for utility wrappers
(UserWorkflowReturn, UserCredential, TokenData) move them into the shared types
file and update all references to import the consolidated definitions. Ensure
function signatures in hasura.service functions reference the imported types
(e.g., TokenData, UserOAuthConnection, N8nCredential, UserWorkflow,
UserWorkflowReturn, UserCredential) and remove the local duplicates.

Comment on lines +5 to +46
export async function POST(req: Request) {
try {
const { userId, jwt } = await req.json()
if (!userId) {
return NextResponse.json({ error: 'Missing userId' }, { status: 400 })
}

const client = getHasuraClient({ jwt })

const { userWorkflows } = await client.query({
userWorkflows: {
__args: { where: { userId: { _eq: userId } } },
workflow_id: true,
},
})

if (!userWorkflows?.[0]) {
return NextResponse.json({ error: 'No workflow found' }, { status: 404 })
}

const workflowId = userWorkflows[0]?.workflow_id as string

await disableWorkflow(workflowId)
await client.mutation({
updateUserWorkflows: {
__args: {
where: { workflowId: { _eq: workflowId } },
_set: { isActive: false },
},
affected_rows: true,
},
})

return NextResponse.json({ message: 'Workflow disabled' })
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
} catch (err: any) {
console.error(err)
return NextResponse.json(
{ error: err.message || 'Internal error' },
{ status: 500 },
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same authentication and abstraction issues as the execute route.

This route has the same critical security gap: JWT accepted from the request body instead of server-side session. It also duplicates the inline Hasura query instead of using getUserWorkflowByService. See the detailed comment on apps/web/app/api/automations/gmail/execute/route.ts for the proposed fix pattern — the same approach applies here.

🤖 Prompt for AI Agents
In `@apps/web/app/api/automations/gmail/disable/route.ts` around lines 5 - 46, The
POST handler accepts JWT from the request body and duplicates an inline Hasura
query instead of using the centralized helper; fix by retrieving authentication
from the server session (not req.json) and passing server-side jwt to
getHasuraClient, replace the manual client.query block with a call to
getUserWorkflowByService (or the existing helper used in execute/route.ts) to
fetch the workflow and its workflow_id, verify the fetched workflow belongs to
the authenticated user, then call disableWorkflow(workflowId) and the existing
client.mutation to set isActive=false; update error handling paths accordingly
and remove trusting user-supplied jwt/userId from the request body.

Comment on lines +5 to +46
export async function POST(req: Request) {
try {
const { userId, jwt } = await req.json()
if (!userId) {
return NextResponse.json({ error: 'Missing userId' }, { status: 400 })
}

const client = getHasuraClient({ jwt })

const { userWorkflows } = await client.query({
userWorkflows: {
__args: { where: { userId: { _eq: userId } } },
workflow_id: true,
},
})

if (!userWorkflows?.[0]) {
return NextResponse.json({ error: 'No workflow found' }, { status: 404 })
}

const workflowId = userWorkflows[0]?.workflow_id as string

await enableWorkflow(workflowId)
await client.mutation({
updateUserWorkflows: {
__args: {
where: { workflowId: { _eq: workflowId } },
_set: { isActive: true },
},
affected_rows: true,
},
})

return NextResponse.json({ message: 'Workflow disabled' })
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
} catch (err: any) {
console.error(err)
return NextResponse.json(
{ error: err.message || 'Internal error' },
{ status: 500 },
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same authentication and abstraction issues as the other automation routes.

JWT accepted from the request body without server-side session validation. See the detailed comment on apps/web/app/api/automations/gmail/execute/route.ts.

🤖 Prompt for AI Agents
In `@apps/web/app/api/automations/gmail/enable/route.ts` around lines 5 - 46, The
POST handler currently reads a JWT from the request body and passes it straight
to getHasuraClient; instead validate the caller server-side (do not trust JWT in
body) by extracting and verifying the session/auth token from the incoming
request headers or server session API and only then create the Hasura client;
update the POST function to call your server-side auth helper (e.g.,
getServerSession or verifyAuthToken) and use the validated credentials/userId
when invoking getHasuraClient and enableWorkflow; also ensure error responses
reflect authentication failures (401) and remove acceptance of jwt from req.json
to prevent bypass.

},
})

return NextResponse.json({ message: 'Workflow disabled' })
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Copy-paste bug: success message says "Workflow disabled" in the enable route.

This route enables a workflow but returns { message: 'Workflow disabled' }. Should be 'Workflow enabled'.

🔎 Proposed fix
-		return NextResponse.json({ message: 'Workflow disabled' })
+		return NextResponse.json({ message: 'Workflow enabled' })
📝 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
return NextResponse.json({ message: 'Workflow disabled' })
return NextResponse.json({ message: 'Workflow enabled' })
🤖 Prompt for AI Agents
In `@apps/web/app/api/automations/gmail/enable/route.ts` at line 38, The enable
route currently returns the wrong success message; update the NextResponse.json
call in the enable route handler (the NextResponse.json({ message: 'Workflow
disabled' }) line) to return { message: 'Workflow enabled' } so the response
correctly reflects the action performed.

Comment on lines +5 to +46
export async function POST(req: Request) {
try {
const body = await req.json()
const { userId, jwt, payload } = body

if (!userId) {
return NextResponse.json({ error: 'Missing userId' }, { status: 400 })
}

const client = getHasuraClient({ jwt })

const { userWorkflows } = await client.query({
userWorkflows: {
__args: { where: { userId: { _eq: userId } } },
workflow_id: true,
},
})
const user_workflow = userWorkflows

if (!user_workflow?.[0]) {
return NextResponse.json(
{ error: 'No active workflow found' },
{ status: 404 },
)
}

const workflowId = user_workflow[0]?.workflow_id as string

const executionResult = await executeWorkflow(workflowId, payload)

return NextResponse.json({
message: 'Workflow executed successfully',
execution: executionResult,
})
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
} catch (err: any) {
console.error('Error executing workflow:', err)
return NextResponse.json(
{ error: err.message || 'Internal server error' },
{ status: 500 },
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: No server-side authentication — JWT accepted from untrusted request body.

All three Gmail automation routes (execute, enable, disable) accept jwt directly from the JSON body, meaning any unauthenticated caller can pass an arbitrary or missing JWT. The userId is also client-supplied with no verification that it matches the token.

Use getServerSession (NextAuth) to obtain the authenticated user's identity and JWT server-side, as the OAuth callback route correctly does.

Additionally, the route performs an inline GraphQL query instead of using the already-available getUserWorkflowByService helper from the Hasura service layer, which undermines the single-point-of-interaction pattern.

🔒 Proposed fix sketch
+import { authOptions } from '@/auth'
+import { getServerSession } from 'next-auth'
 import { executeWorkflow } from '@/lib/n8n'
-import { getHasuraClient } from '@/services/hasura'
+import { getUserWorkflowByService } from '@/services/hasura'
 import { NextResponse } from 'next/server'

 export async function POST(req: Request) {
 	try {
-		const body = await req.json()
-		const { userId, jwt, payload } = body
+		const session = await getServerSession(authOptions)
+		if (!session?.user?.id) {
+			return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+		}
+		const userId = session.user.id
+		const jwt = session.user.hasuraJwt
+		const { payload } = await req.json()

-		if (!userId) {
-			return NextResponse.json({ error: 'Missing userId' }, { status: 400 })
-		}
-
-		const client = getHasuraClient({ jwt })
-
-		const { userWorkflows } = await client.query({...})
-		const user_workflow = userWorkflows
-
-		if (!user_workflow?.[0]) {
+		const workflow = await getUserWorkflowByService(userId, 'gmail', jwt)
+		if (!workflow) {
 			return NextResponse.json(
 				{ error: 'No active workflow found' },
 				{ status: 404 },
 			)
 		}
-		const workflowId = user_workflow[0]?.workflow_id as string
+		const workflowId = workflow.workflow_id

Based on learnings: "Use NextAuth.js with custom user management and JWT tokens for API authentication" and "Use hasura.service.ts as the single point of GraphQL interaction for all data operations."

🤖 Prompt for AI Agents
In `@apps/web/app/api/automations/gmail/execute/route.ts` around lines 5 - 46, The
POST handler accepts an untrusted jwt and userId from the request body—replace
that by calling getServerSession to obtain the authenticated user's id and
server-side JWT, remove reading jwt/userId from req.json, and ensure the session
user id matches the requested user (or simply use the session id). Swap the
inline Hasura GraphQL query with the hasura service helper
getUserWorkflowByService (use the session user id and service name to fetch the
workflow) and then call executeWorkflow(workflowId, payload) as before; ensure
errors log safely and return the same JSON shape on success/failure.

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