-
Notifications
You must be signed in to change notification settings - Fork 1
User N8N workflows [WIP] #599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideIntroduces 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 tableserDiagram
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
Class diagram for new TypeScript user OAuth, n8n, and workflow typesclassDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughRestructures Hasura metadata (chatbot relationships and permissions), adds three new tables (n8n_credentials, user_oauth_connections, user_workflows) with migrations, introduces Gmail OAuth + n8n integration (API routes and helpers), and adds related TypeScript types and Hasura service helpers. Changes
Sequence Diagram(s)mermaid mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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_preferenceuser role permissions were changed tocheck: {}andfilter: {}, which removes the per-useruser_idscoping—this effectively lets any user read/write any preference row; if that’s not intentional, please restore theuser_id = X-Hasura-User-Idchecks. - The new tables enforce
UNIQUE (user_id)(and inuser_workflowsalsoUNIQUE (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_atisNOT NULLeven though status can beconnectedorrevoked; if active connections are expected to have no revoke time, consider makingrevoked_atnullable 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| check: {} | ||
| columns: | ||
| - deep_expertise | ||
| - favorite | ||
| - font_size | ||
| - lang | ||
| - preferred_complexity | ||
| - preferred_length | ||
| - preferred_tone |
There was a problem hiding this comment.
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")); | |||
There was a problem hiding this comment.
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")); | |||
There was a problem hiding this comment.
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_atisNOT NULLwhilestatuscan be'connected', which forces a revocation time even for active connections. Allowingrevoked_atto 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 likeUNIQUE ("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 tyingstatusto 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")); | |||
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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
}
// oauthIf 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.
There was a problem hiding this 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")); | |||
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| 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")); |
| @@ -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")); | |||
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| 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")); |
| filter: | ||
| user_id: | ||
| _eq: X-Hasura-User-Id | ||
| filter: {} |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| filter: {} | |
| filter: | |
| user_id: | |
| _eq: X-Hasura-User-Id |
| 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' |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| 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 |
| export interface UserWorkflow { | ||
| id: string | ||
| user_id: string | ||
| workflow_id: string | ||
| workflow_name: string | ||
| service: 'gmail' | ||
| } |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| columns: | ||
| - avatar | ||
| - chatbot_id | ||
| - chatbot_id |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| - chatbot_id |
| @@ -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")); | |||
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| 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")); |
| @@ -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")); | |||
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| 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"))); |
| _eq: X-Hasura-User-Id | ||
| set: | ||
| user_id: X-Hasura-User-Id | ||
| check: {} |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
| check: {} | |
| check: | |
| user_id: | |
| _eq: X-Hasura-User-Id |
| export interface UserOAuthConnection { | ||
| id: string | ||
| user_id: string | ||
| provider: 'google' | ||
| service: 'gmail' | ||
| status: 'connected' | 'revoked' | ||
| scopes: string | ||
| } |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
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.
There was a problem hiding this 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 bechatbot_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 callsactiveDepartmentstate management in sidebar/contextchatbot.department?.namereferences 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.departmentthroughout 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:
- Adding
IF EXISTSto make the migration idempotent- Considering
CASCADEif 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
CASCADEif 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
CASCADEif 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
CASCADEif 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 sinceidis already thePRIMARY 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 usingON DELETE cascadeto 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
statuscolumn 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
⛔ Files ignored due to path filters (3)
packages/mb-genql/generated/schema.graphqlis excluded by!**/generated/**packages/mb-genql/generated/schema.tsis excluded by!**/generated/**packages/mb-genql/generated/types.tsis excluded by!**/generated/**
📒 Files selected for processing (20)
apps/hasura/metadata/databases/masterbots/tables/public_chatbot.yamlapps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yamlapps/hasura/metadata/databases/masterbots/tables/public_preference.yamlapps/hasura/metadata/databases/masterbots/tables/public_thread.yamlapps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yamlapps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yamlapps/hasura/metadata/databases/masterbots/tables/tables.yamlapps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/down.sqlapps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sqlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sqlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sqlapps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sqlapps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sqlapps/web/app/api/automations/gmail/disable.tsapps/web/app/api/automations/gmail/enable.tsapps/web/app/api/automations/gmail/execute.tsapps/web/app/api/oauth/google/callback.tsapps/web/app/api/oauth/google/revoke.tsapps/web/app/api/oauth/google/start.tsapps/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.sqlapps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sqlapps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sqlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sqlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sqlapps/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.sqlapps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sqlapps/hasura/metadata/databases/masterbots/tables/public_user_workflows.yamlapps/hasura/metadata/databases/masterbots/tables/tables.yamlapps/hasura/metadata/databases/masterbots/tables/public_user_oauth_connections.yamlapps/web/types/types.tsapps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sqlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sqlapps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yamlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/down.sqlapps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/up.sqlapps/hasura/metadata/databases/masterbots/tables/public_chatbot.yamlapps/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.sqlapps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sqlapps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sqlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sqlapps/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.sqlapps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sqlapps/hasura/migrations/masterbots/1766066981705_create_table_public_n8n_credentials/down.sqlapps/hasura/migrations/masterbots/1766066591567_create_table_public_user_oauth_connections/up.sqlapps/hasura/metadata/databases/masterbots/tables/public_n8n_credentials.yamlapps/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.sqlapps/hasura/migrations/masterbots/1766065373919_create_table_public_user_workflows/up.sqlapps/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 table1761768880712_create_table_public_organization/down.sql- drops the organization table1762287961522_create_table_public_organization_chatbot/down.sql- drops the organization_chatbot tableThe database schema is consistent with the migration history.
Likely an incorrect or invalid review comment.
| columns: | ||
| - avatar | ||
| - chatbot_id | ||
| - chatbot_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| - 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.
| table: | ||
| name: n8n_credentials | ||
| schema: public |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Strict row-level security: Users should ONLY access their own credentials
- Object relationship to
public.uservia theuser_idforeign key - Limited column access: Consider whether all credential fields should be readable
- 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: {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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: {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| table: | ||
| name: user_oauth_connections | ||
| schema: public |
There was a problem hiding this comment.
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:
- Row-level security: Users should only access their own OAuth connections
- Object relationship to
public.uservia theuser_idforeign key - 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")); | |||
There was a problem hiding this comment.
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).
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.
| 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.
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.
| 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")); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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).
| export interface UserOAuthConnection { | ||
| id: string | ||
| user_id: string | ||
| provider: 'google' | ||
| service: 'gmail' | ||
| status: 'connected' | 'revoked' | ||
| scopes: string | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| export interface N8nCredential { | ||
| id: string | ||
| user_id: string | ||
| n8n_credential_id: string | ||
| service: 'gmail' | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| export interface UserWorkflow { | ||
| id: string | ||
| user_id: string | ||
| workflow_id: string | ||
| workflow_name: string | ||
| service: 'gmail' | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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').
…to fix-new-popup-issues
There was a problem hiding this 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.
getHasuraClientis imported but never used in this file.- Same App Router format issue as other files—this should be
route.tswithNextRequest/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
clientvariable 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
zodfor environment variables.
30-31: Validate JWT is present before using it.The
jwtis read from the request body but never validated for presence before being passed to functions that require authentication. WhilegetUserWorkflowByServiceand 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
📒 Files selected for processing (8)
apps/web/app/api/automations/gmail/disable.tsapps/web/app/api/automations/gmail/enable.tsapps/web/app/api/automations/gmail/execute.tsapps/web/app/api/oauth/google/callback.tsapps/web/app/api/oauth/google/start.tsapps/web/lib/n8n.tsapps/web/services/hasura/hasura.service.tsapps/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.tsapps/web/app/api/oauth/google/callback.tsapps/web/app/api/automations/gmail/enable.tsapps/web/app/api/automations/gmail/execute.tsapps/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.tsapps/web/app/api/oauth/google/callback.tsapps/web/app/api/automations/gmail/enable.tsapps/web/app/api/automations/gmail/execute.tsapps/web/app/api/automations/gmail/disable.tsapps/web/lib/n8n.tsapps/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.tsapps/web/app/api/oauth/google/callback.tsapps/web/app/api/automations/gmail/enable.tsapps/web/app/api/automations/gmail/execute.tsapps/web/app/api/automations/gmail/disable.tsapps/web/lib/n8n.tsapps/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.tsapps/web/app/api/oauth/google/callback.tsapps/web/app/api/automations/gmail/enable.tsapps/web/app/api/automations/gmail/execute.tsapps/web/app/api/automations/gmail/disable.tsapps/web/lib/n8n.tsapps/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.tsapps/web/app/api/automations/gmail/enable.tsapps/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.
| 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' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical logic error: disable endpoint never actually disables.
The conditional logic is inverted and makes the delete operation unreachable:
- Line 17: If
userWorkflowsexists → returns 200 "Workflow already enabled" (wrong message for disable) - Line 24: If
userWorkflowsdoesn't exist → returns 400 - 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| await insertUserWorkflow( | ||
| userId, | ||
| workflow.id, | ||
| workflow.name, | ||
| 'gmail', | ||
| 'Masterbots/Users Workflows/Email', | ||
| jwt, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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 }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Pass the JWT in the
stateparameter (encrypted/signed) along with userId - Use server-side session to retrieve the JWT
- 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.
| // Get template workflow | ||
| const templateRes = await fetch( | ||
| `${process.env.N8N_API_URL}/rest/workflows/${templateWorkflowId}`, | ||
| { | ||
| headers: { Authorization: `Bearer ${process.env.N8N_API_KEY}` }, | ||
| }, | ||
| ) | ||
| const templateWorkflow = await templateRes.json() | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for template workflow fetch.
If the template workflow fetch fails or returns an error, the code proceeds without checking templateRes.ok, potentially causing undefined behavior when iterating over templateWorkflow.nodes.
🔎 Proposed fix
// Get template workflow
const templateRes = await fetch(
`${process.env.N8N_API_URL}/rest/workflows/${templateWorkflowId}`,
{
headers: { Authorization: `Bearer ${process.env.N8N_API_KEY}` },
},
)
+ if (!templateRes.ok) {
+ const errorData = await templateRes.json().catch(() => ({}))
+ throw new Error(`Failed to fetch template workflow: ${JSON.stringify(errorData)}`)
+ }
const templateWorkflow = await templateRes.json()📝 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.
| // Get template workflow | |
| const templateRes = await fetch( | |
| `${process.env.N8N_API_URL}/rest/workflows/${templateWorkflowId}`, | |
| { | |
| headers: { Authorization: `Bearer ${process.env.N8N_API_KEY}` }, | |
| }, | |
| ) | |
| const templateWorkflow = await templateRes.json() | |
| // Get template workflow | |
| const templateRes = await fetch( | |
| `${process.env.N8N_API_URL}/rest/workflows/${templateWorkflowId}`, | |
| { | |
| headers: { Authorization: `Bearer ${process.env.N8N_API_KEY}` }, | |
| }, | |
| ) | |
| if (!templateRes.ok) { | |
| const errorData = await templateRes.json().catch(() => ({})) | |
| throw new Error(`Failed to fetch template workflow: ${JSON.stringify(errorData)}`) | |
| } | |
| const templateWorkflow = await templateRes.json() |
🤖 Prompt for AI Agents
In apps/web/lib/n8n.ts around lines 53 to 61, the fetch for the template
workflow does not check the HTTP response and can return an error payload or
undefined causing failures when accessing templateWorkflow.nodes; update the
code to check templateRes.ok after the await fetch, parse and handle non-OK
responses (e.g., throw a descriptive error or log and return early), wrap the
fetch/parse in a try/catch to surface network/JSON errors, and ensure you
validate templateWorkflow and templateWorkflow.nodes exist before iterating to
avoid runtime exceptions.
| // Create new workflow | ||
| const createRes = await fetch(`${process.env.N8N_API_URL}/rest/workflows`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${process.env.N8N_API_KEY}`, | ||
| }, | ||
| body: JSON.stringify(templateWorkflow), | ||
| }) | ||
|
|
||
| const newWorkflow = await createRes.json() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for workflow creation.
Similar to the credential creation, the workflow creation fetch should check createRes.ok before proceeding to use newWorkflow.nodes.
🔎 Proposed fix
// Create new workflow
const createRes = await fetch(`${process.env.N8N_API_URL}/rest/workflows`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.N8N_API_KEY}`,
},
body: JSON.stringify(templateWorkflow),
})
const newWorkflow = await createRes.json()
+ if (!createRes.ok) {
+ throw new Error(`Failed to create workflow: ${JSON.stringify(newWorkflow)}`)
+ }📝 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.
| // Create new workflow | |
| const createRes = await fetch(`${process.env.N8N_API_URL}/rest/workflows`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${process.env.N8N_API_KEY}`, | |
| }, | |
| body: JSON.stringify(templateWorkflow), | |
| }) | |
| const newWorkflow = await createRes.json() | |
| // Create new workflow | |
| const createRes = await fetch(`${process.env.N8N_API_URL}/rest/workflows`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${process.env.N8N_API_KEY}`, | |
| }, | |
| body: JSON.stringify(templateWorkflow), | |
| }) | |
| const newWorkflow = await createRes.json() | |
| if (!createRes.ok) { | |
| throw new Error(`Failed to create workflow: ${JSON.stringify(newWorkflow)}`) | |
| } |
🤖 Prompt for AI Agents
In apps/web/lib/n8n.ts around lines 79 to 89, the POST to create a new workflow
does not check the response status before parsing and using the result; add
error handling similar to credential creation: after awaiting createRes, check
if createRes.ok is true, and if not throw or return a descriptive error
(including response status/text) so we don't access newWorkflow.nodes on a
failed response; only call createRes.json() and proceed when ok, otherwise
log/throw the error for upstream handling.
| ) { | ||
| if (!jwt) | ||
| throw new Error('Authentication required to insert OAuth connection') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| ) { | |
| 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.
| export async function insertUserWorkflow( | ||
| jwt: string, | ||
| userId: string, | ||
| workflowId: string, | ||
| workflowName: string, | ||
| service: string, | ||
| folderPath: string, | ||
| ) { | ||
| const client = getHasuraClient({ jwt }) | ||
| const result = await client.mutation({ | ||
| insertUserWorkflowsOne: { | ||
| __args: { | ||
| object: { | ||
| user_id: userId, | ||
| workflow_id: workflowId, | ||
| workflow_name: workflowName, | ||
| service, | ||
| folder_path: folderPath, | ||
| created_at: new Date().toISOString(), | ||
| }, | ||
| }, | ||
| id: true, | ||
| }, | ||
| }) | ||
|
|
||
| return result.insertUserWorkflowsOne | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent parameter order causes integration bugs.
The jwt parameter is placed first, but this breaks convention with most other functions in this file (e.g., updateMessage line 579, createThread line 673, getChatbot line 706) where jwt typically appears last or in an options object. This inconsistency caused the critical bug in enable.ts where the function is called with the wrong parameter order.
🔎 Proposed fix for consistency
export async function insertUserWorkflow(
- jwt: string,
userId: string,
workflowId: string,
workflowName: string,
service: string,
folderPath: string,
+ jwt: string,
) {This would match the calling pattern in enable.ts and align with conventions in this file.
📝 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.
| export async function insertUserWorkflow( | |
| jwt: string, | |
| userId: string, | |
| workflowId: string, | |
| workflowName: string, | |
| service: string, | |
| folderPath: string, | |
| ) { | |
| const client = getHasuraClient({ jwt }) | |
| const result = await client.mutation({ | |
| insertUserWorkflowsOne: { | |
| __args: { | |
| object: { | |
| user_id: userId, | |
| workflow_id: workflowId, | |
| workflow_name: workflowName, | |
| service, | |
| folder_path: folderPath, | |
| created_at: new Date().toISOString(), | |
| }, | |
| }, | |
| id: true, | |
| }, | |
| }) | |
| return result.insertUserWorkflowsOne | |
| } | |
| export async function insertUserWorkflow( | |
| userId: string, | |
| workflowId: string, | |
| workflowName: string, | |
| service: string, | |
| folderPath: string, | |
| jwt: string, | |
| ) { | |
| const client = getHasuraClient({ jwt }) | |
| const result = await client.mutation({ | |
| insertUserWorkflowsOne: { | |
| __args: { | |
| object: { | |
| user_id: userId, | |
| workflow_id: workflowId, | |
| workflow_name: workflowName, | |
| service, | |
| folder_path: folderPath, | |
| created_at: new Date().toISOString(), | |
| }, | |
| }, | |
| id: true, | |
| }, | |
| }) | |
| return result.insertUserWorkflowsOne | |
| } |
🤖 Prompt for AI Agents
In apps/web/services/hasura/hasura.service.ts around lines 2297-2323 the
function insertUserWorkflow places the jwt as the first parameter which is
inconsistent with the rest of the file and caused call-site bugs; change the
function signature so jwt is the last parameter (e.g.,
insertUserWorkflow(userId, workflowId, workflowName, service, folderPath, jwt))
and update the body to call getHasuraClient({ jwt }) accordingly, then update
all call sites (like enable.ts and any other usages) to pass jwt as the last
argument to restore consistent parameter order.
| return null | ||
| } | ||
|
|
||
| return result.userWorkflows[0] as UserWorkflow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Read the specific lines from hasura.service.ts to see the actual code
wc -l apps/web/services/hasura/hasura.service.tsRepository: bitcashorg/masterbots
Length of output: 112
🏁 Script executed:
#!/bin/bash
# Read lines around 2350 and 2377
sed -n '2340,2360p' apps/web/services/hasura/hasura.service.ts
echo "---"
sed -n '2370,2378p' apps/web/services/hasura/hasura.service.tsRepository: bitcashorg/masterbots
Length of output: 558
🏁 Script executed:
#!/bin/bash
# Find the two functions and see their full GraphQL queries
sed -n '2320,2365p' apps/web/services/hasura/hasura.service.ts
echo "=== NEXT FUNCTION ==="
sed -n '2355,2378p' apps/web/services/hasura/hasura.service.tsRepository: bitcashorg/masterbots
Length of output: 1463
🏁 Script executed:
#!/bin/bash
# Search for UserWorkflow type definition
echo "=== UserWorkflow type ==="
rg -A 15 "type UserWorkflow|interface UserWorkflow" apps/web/
echo ""
echo "=== UserCredential type ==="
rg -A 15 "type UserCredential|interface UserCredential" apps/web/Repository: bitcashorg/masterbots
Length of output: 1192
🏁 Script executed:
#!/bin/bash
# Check context around the queries to see if there are any comments about partial types
sed -n '2315,2365p' apps/web/services/hasura/hasura.service.ts | head -40
echo "==="
# Check if Partial types are used anywhere
rg "Partial<UserWorkflow>|Partial<UserCredential>" apps/web/Repository: bitcashorg/masterbots
Length of output: 824
🏁 Script executed:
#!/bin/bash
# Check if there are any type definitions that might handle partial queries
rg "workflow_id.*true" apps/web/services/hasura/hasura.service.ts | head -5
echo "==="
# Check the return type annotations more carefully
sed -n '2324,2351p' apps/web/services/hasura/hasura.service.tsRepository: bitcashorg/masterbots
Length of output: 659
Fix type assertions to match queried fields.
getUserWorkflowByService only queries workflow_id but casts to UserWorkflow (which requires id, user_id, workflow_name, service, folder_path, created_at). Similarly, getUserCredentialByService only queries id, n8n_credential_id, and service but casts to UserCredential (which requires webhook_url). Either query all required fields or use a narrower type that matches what's actually selected.
🤖 Prompt for AI Agents
In apps/web/services/hasura/hasura.service.ts around line 2350, the function
returns result.userWorkflows[0] cast to UserWorkflow even though the GraphQL
selection only requested workflow_id (and likewise getUserCredentialByService
selects a subset but casts to UserCredential); update the code so the returned
shape matches the selected fields: either expand the GraphQL selection to
include all fields required by UserWorkflow/UserCredential (id, user_id,
workflow_name, service, folder_path, created_at for UserWorkflow; id,
n8n_credential_id, service, webhook_url for UserCredential) or change the return
types to a narrower interface that exactly matches the selected fields, and
adjust callers accordingly.
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:
Enhancements:
Deployment:
Summary by CodeRabbit
New Features
Bug Fixes & Improvements
✏️ Tip: You can customize this high-level summary in your review settings.