refactor: modularize Prisma schema into directory-based components an… - #78
Conversation
…d add auth infrastructure
📝 WalkthroughWalkthroughAdds a complete Prisma schema (enums, core models, node subtype models, triggers) with migration SQL, migration lock, webhook-secret helpers and migration script, and small runtime changes: non-blocking verification emails, auth baseURL/trustedOrigins, Slack webhook removal wiring, and auth-client baseURL fallback. ChangesPrisma Schema & Auth Platform
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c6d178396c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| try { | ||
| await sendVerificationEmail(email, user.name || email, verifyToken) | ||
| } catch (emailError) { | ||
| sendVerificationEmail(email, user.name || email, verifyToken).catch(emailError => { |
There was a problem hiding this comment.
Await verification email before returning success
When this route runs on a host that can freeze or tear down the request context after the response is returned, this fire-and-forget send can be abandoned even though the client receives VERIFICATION_SENT; the same pattern was added in the resend branch as well. Since signup depends on that email to verify the account, keep the send awaited or use the platform's background-work API so success is only returned after the email work is durably scheduled.
Useful? React with 👍 / 👎.
| return NextResponse.json({ error: "Internal server error" }, { status: 500 }) | ||
| return NextResponse.json({ | ||
| error: "Internal server error", | ||
| details: error instanceof Error ? error.message : String(error) |
There was a problem hiding this comment.
Avoid exposing signup exception details
This unauthenticated signup endpoint now returns raw exception messages to the client, so database constraint names, connection errors, or provider internals can be disclosed whenever user creation or account creation fails. The server log already records the exception; the response should stay generic for production callers.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@prisma/schema/auth.prisma`:
- Around line 6-22: The User model contains two unused/ambiguous fields:
executionCount and executionResetAt; locate the incrementExecutionCount function
in src/lib/execution-gate.ts and the monthly gating fields
workflowRunsUsed/workflowRunsReset and decide on a single source of truth—either
remove executionCount and executionResetAt from the Prisma User model (and
delete or update incrementExecutionCount to operate on
workflowRunsUsed/workflowRunsReset), or update all enforcement and reset logic
to reference executionCount/executionResetAt consistently; implement the chosen
change and remove any dead references so the model and incrementExecutionCount
align with the actual gating mechanism.
In `@prisma/schema/core.prisma`:
- Around line 51-73: The Prisma model Credential is mapped to a misspelled table
via @@map("Credenial"); create a tracked issue and a dedicated migration plan to
rename the physical table and update references: (1) run ALTER TABLE "Credenial"
RENAME TO "credential"; (2) locate and update all foreign key constraints and
referencing tables to point to the new table name; (3) update or remove the
@@map("Credenial") in the model Credential to match the new physical name; (4)
include rollback steps, a migration script, and tests verifying FK integrity and
application behavior after the change (reference the Credential model and @@map
symbol in the task).
In `@prisma/schema/nodes_commerce.prisma`:
- Around line 7-10: RazorpayNode currently declares credentialId as a loose
scalar (credentialId String?) without a Prisma relation; add a proper relation
to Credential by adding a nullable relation field named credential (e.g.,
credential Credential? `@relation`(fields: [credentialId], references: [id])) to
the RazorpayNode model so Prisma enforces referential integrity and
cascades/cleanup like ShiprocketNode and CashfreeNode do; ensure the relation
field name matches usage elsewhere and run prisma format/migrate afterwards.
In `@prisma/schema/nodes_google.prisma`:
- Around line 7-10: Add proper Prisma foreign-key relations from GmailNode,
GoogleSheetsNode, and GoogleDriveNode to the Credential model by replacing the
bare credentialId fields with relation mappings (e.g., add a relation field like
credential Credential `@relation`(fields: [credentialId], references: [id]) and
keep credentialId String) so Prisma can enforce referential integrity; remove
the GoogleSheetsNode.credentialId default of "" (make it required or nullable to
match desired semantics) so FK validation won't fail; and update
GoogleDriveNode's workflow relation to include onDelete: Cascade like the other
nodes (e.g., the workflow relation on GoogleDriveNode should use `@relation`(...,
onDelete: Cascade)) to ensure workflows cascade-delete related GoogleDriveNode
rows.
- Around line 99-101: Add cascading delete behavior to the GoogleDriveNode
relation so deleting a parent workflow removes the child row instead of failing
the foreign-key constraint. Update the GoogleDriveNode.workflow relation in
prisma/schema/nodes_google.prisma to match the existing GmailNode and
GoogleSheetsNode patterns by adding onDelete: Cascade to that `@relation`
definition.
In `@prisma/schema/nodes_messaging.prisma`:
- Around line 24-25: The webhookUrl String field in nodes_messaging.prisma
(webhookUrl) must not store raw Slack webhook secrets; remove this plain-text
field and instead reference the centralized credential store by adding a
credential reference (e.g., a credentialId/credential relation that points to
the existing Credential model or an encrypted secret store key) on the same
model (nodes_messaging / BlockKit-related model), migrate any existing webhook
values out of the DB into Credential records, and update related code that reads
webhookUrl to fetch the secret via the Credential API or secret store using the
new credential reference (update functions that access webhookUrl to use the
credential lookup instead).
- Around line 68-70: The WhatsAppNode model's relation field "workflow"
currently lacks an onDelete behavior causing FK to be created as ON DELETE
RESTRICT; update the relation definition for WhatsAppNode.workflow to include
onDelete: Cascade (mirroring SlackNode, NotionNode, Msg91Node) so that deleting
a workflow will cascade and remove associated WhatsAppNode rows; locate the
relation declaration for workflow in the WhatsAppNode model and add onDelete:
Cascade to the `@relation` attributes.
- Around line 7-11: Add proper relation fields and onDelete behaviors: for
models SlackNode, WhatsAppNode, NotionNode, and Msg91Node replace the orphaned
credentialId String? usage with an explicit relation field (e.g., credential
Credential? `@relation`(fields: [credentialId], references: [id], onDelete:
SetNull)) so Prisma enforces the FK and cleans up or nulls references; ensure
the relation uses the same behavior style as other models (match Node/other
models in core.prisma). For WhatsAppNode, add onDelete: Cascade on the workflow
relation to match the other workflow relations in this schema. For SlackNode,
stop storing webhookUrl as a plain String and instead store the webhook as a
Credential (remove webhookUrl and use credential/credentialId to hold the secret
credential) so incoming webhook URLs are treated as credentials rather than
plaintext fields.
In `@prisma/schema/nodes_utility.prisma`:
- Around line 8-13: AINode and MediaUploadNode currently declare credentialId as
a bare String? which prevents Prisma from enforcing referential integrity;
update both models (AINode and MediaUploadNode) to replace the plain field with
a relation to Credential by adding a credential field typed as Credential? with
`@relation`(fields: [credentialId], references: [id], onDelete: SetNull) and keep
credentialId as the foreign key field so Prisma will enforce the relation and
set the FK to null on Credential deletion.
In `@prisma/schema/triggers.prisma`:
- Around line 7-21: The WebhookTrigger model currently stores secretToken in
plaintext; update the Prisma model by adding secretTokenEncrypted String? and
renaming secretToken to secretTokenLegacy String? with a deprecation note to
match RazorpayTrigger/WhatsAppTrigger patterns, then update application code to
read from secretTokenEncrypted first and fall back to secretTokenLegacy
(implement a helper like decryptWebhookSecret analogous to
src/lib/razorpay-secret.ts), and add a migration script to encrypt existing
secretTokenLegacy values into secretTokenEncrypted and clear the legacy field.
In `@src/app/api/auth/custom-signup/route.ts`:
- Around line 83-86: The response currently returns internal error details
(error.message) to clients in the NextResponse.json call; remove the details
field from the 500 response and return a generic payload (e.g., { error:
"Internal server error" }) while logging the full error server-side. Update the
catch block in src/app/api/auth/custom-signup/route.ts that constructs
NextResponse.json (the return that references error and status: 500) to log the
error (console.error or your server logger) and send only the generic message to
the client.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 693a741d-83c3-4c03-b87b-80407ae931de
📒 Files selected for processing (15)
prisma.config.tsprisma/schema.prisma.bakprisma/schema/auth.prismaprisma/schema/base.prismaprisma/schema/core.prismaprisma/schema/enums.prismaprisma/schema/nodes_commerce.prismaprisma/schema/nodes_crm.prismaprisma/schema/nodes_flow_control.prismaprisma/schema/nodes_google.prismaprisma/schema/nodes_messaging.prismaprisma/schema/nodes_utility.prismaprisma/schema/triggers.prismasrc/app/api/auth/custom-signup/route.tssrc/lib/auth.ts
… schema architecture
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/features/executions/components/slack/executor.ts (1)
233-239:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftBackfill existing webhook nodes before removing the config fallback.
This branch now requires
creds.webhookUrlexclusively, but the PR also removeswebhookUrlfrom the Slack UI/router and the provided files do not include a backfill for existing persisted node configs. Any deployedMESSAGE_SEND_WEBHOOKnode that only hasconfig.webhookUrlwill start failing with “webhook URL is required” after rollout. Add a migration/backfill, or keep the legacy fallback until that data is migrated.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/executions/components/slack/executor.ts` around lines 233 - 239, The MESSAGE_SEND_WEBHOOK branch now throws a NonRetriableError if creds.webhookUrl is missing, which will break existing persisted nodes that still store config.webhookUrl; restore a temporary fallback or add a backfill: update the SlackOperation.MESSAGE_SEND_WEBHOOK handling in executor.ts to use creds.webhookUrl || (config && config.webhookUrl) as the webhook URL (or invoke a migration routine that copies config.webhookUrl into creds.webhookUrl before removing the fallback), and ensure the error remains thrown only when both are absent so existing nodes continue to work until data is migrated.src/app/api/auth/custom-signup/route.ts (2)
22-32:⚠️ Potential issue | 🟠 Major | ⚡ Quick winIncrement the resend-attempt counter when issuing a new token.
The limiter checks
existing.emailVerifyAttempts >= 5, but this update never increments that field, so unverified accounts can request unlimited resend attempts.Suggested fix
await prisma.user.update({ where: { email }, data: { emailVerifyToken: token, emailVerifyExpiry: getTokenExpiry(), + emailVerifyAttempts: { + increment: 1, + }, }, })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/auth/custom-signup/route.ts` around lines 22 - 32, The update that issues a new verification token (using generateVerifyToken and getTokenExpiry) doesn't increment the existing.emailVerifyAttempts counter, so add an increment to the prisma.user.update data payload to increase emailVerifyAttempts by 1 (use Prisma's atomic increment on the emailVerifyAttempts field) while still setting emailVerifyToken and emailVerifyExpiry; ensure you update the same prisma.user.update call that targets the user by email so the limiter check existing.emailVerifyAttempts >= 5 becomes effective.
34-36:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDetached SMTP email sends can be dropped after the response returns.
In
src/app/api/auth/custom-signup/route.ts(lines 34-36, also 75-77),sendVerificationEmail(...).catch(...)is fire-and-forget, whilesendVerificationEmailactually performs an awaitednodemailerSMTPsendMail. On serverless runtimes, the handler may freeze/terminate right after returning, so the email may never be delivered. Useafter()fromnext/server(or a real background job queue) to run the email send after the response instead of relying on an unawaited promise.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/auth/custom-signup/route.ts` around lines 34 - 36, The detached sendVerificationEmail(...) call in custom-signup route.ts is fire-and-forget and can be terminated on serverless runtimes; import after from 'next/server' and replace the unawaited sendVerificationEmail(...).catch(...) with after(() => sendVerificationEmail(email, existing.name || email, token).catch(...)) so the SMTP send runs in Next's post-response work queue; apply the same change for the second occurrence (the other sendVerificationEmail call at the later block) and keep existing error handling inside the .catch.src/server/routers/webhook-trigger.router.ts (1)
8-24:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winScope trigger access to the owning workflow and never return secret columns.
protectedProcedureonly proves the caller is signed in. Here, any authenticated user who knows aworkflowIdcan read that workflow’s trigger row, andfindUnique()currently returns the full record — includingsecretTokenEncryptedand any still-unmigratedsecretTokenLegacy. The create path has the same ownership gap. Load the workflow through the current user first, thenselectonly the non-secret fields the client actually needs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/server/routers/webhook-trigger.router.ts` around lines 8 - 24, Both getByWorkflowId and createWebhookTrigger use protectedProcedure but don't verify workflow ownership and return/store secret columns; change both to first load the workflow via the current user (e.g., query the Workflow model for workflowId and userId) to ensure the caller owns it, and when returning the trigger use prisma.webhookTrigger.findUnique/findFirst with a select that excludes secretTokenEncrypted and secretTokenLegacy (only return safe fields like id, workflowId, createdAt, etc.); similarly, when creating in createWebhookTrigger, associate the trigger only after confirming ownership and return only the non-secret selected fields instead of the full record.
🧹 Nitpick comments (1)
prisma/schema/nodes_messaging.prisma (1)
54-73: ⚡ Quick winAdd a
workflowIdindex toWhatsAppNode.
WhatsAppNodeis the only messaging subtype in this file without@@index([workflowId]), and the generated migration omits that index too. Postgres does not index child FKs automatically, so workflow deletes and any workflow-scoped reads against this table will fall back to scans.Suggested change
model WhatsAppNode { id String `@id` `@default`(cuid()) nodeId String `@unique` workflowId String credentialId String? credential Credential? `@relation`(fields: [credentialId], references: [id], onDelete: SetNull) operation WhatsAppOperation `@default`(SEND_TEXT) to String `@default`("") body String `@default`("") `@db.Text` templateName String `@default`("") templateLang String `@default`("en_US") templateParams String `@default`("[]") mediaUrl String `@default`("") mediaCaption String `@default`("") reactionEmoji String `@default`("") reactionMsgId String `@default`("") createdAt DateTime `@default`(now()) updatedAt DateTime `@updatedAt` workflow workflow `@relation`(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@prisma/schema/nodes_messaging.prisma` around lines 54 - 73, Add a missing index on the WhatsAppNode model for the workflowId foreign key to match other messaging subtypes: update the Prisma model WhatsAppNode by adding an index declaration (e.g., @@index([workflowId])) so Postgres will create an index for workflow-scoped queries and deletes; ensure the change is applied to the schema (model WhatsAppNode) so the generated migration includes the new index.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@prisma/schema/nodes_commerce.prisma`:
- Around line 7-12: Change the loose scalar nodeId into a proper one-to-one
relation to Node by replacing the scalar-only nodeId with both the foreign-key
field and relation field (keep nodeId String `@unique` and add node Node
`@relation`(fields: [nodeId], references: [id], onDelete: Cascade)); update the
RazorpayNode, ShiprocketNode, CashfreeNode models (and the corresponding
declarations in prisma/schema/core.prisma) so Prisma will create a foreign key
to Node.id and cascade-delete when a Node is removed, ensuring routers can still
upsert by nodeId and workflowId behavior remains unchanged.
In `@prisma/schema/nodes_messaging.prisma`:
- Around line 7-12: The nodeId fields in the messaging subtype models are plain
unique strings and must be converted to a Prisma one-to-one relation to Node so
Prisma will create the FK and cascade cleanup: for each affected model (the
shown model and the ones at the other ranges), add a nodeId String `@unique` field
(or keep it) and add a node Node relation field with `@relation`(fields: [nodeId],
references: [id], onDelete: Cascade) (i.e., replace the current standalone
nodeId String `@unique` with an explicit relation using nodeId as the FK), and
make the same change in prisma/schema/core.prisma so Prisma generates the
FK/cascade for deletions.
In `@scripts/migrate-webhook-secrets.ts`:
- Around line 17-44: The migration currently reads candidates into triggers and
calls prisma.webhookTrigger.update by id, which can race and overwrite
concurrent migrations; change the write to an atomic conditional update using
prisma.webhookTrigger.updateMany with where: { id: trigger.id,
secretTokenEncrypted: null, secretTokenLegacy: { not: null } } and data setting
secretTokenEncrypted and nulling secretTokenLegacy, then treat result.count ===
0 as skipped and result.count === 1 as migrated (increment migrated/skipped
accordingly) and catch errors to increment failed; keep using
encrypt(trigger.secretTokenLegacy) and the same loop structure but replace the
single-row update with this conditional updateMany + count check.
In `@src/lib/auth.ts`:
- Line 41: The current baseURL assignment in src/lib/auth.ts (the baseURL
variable used when constructing the Better Auth client) must not fall back to
the hardcoded production URL; change it to remove the
"https://nodebase.mayanksaraswal.in" fallback and instead validate that either
process.env.BETTER_AUTH_URL or process.env.NEXT_PUBLIC_APP_URL is present at
startup (when initializing the auth client) and throw a clear error (or abort
initialization) if neither is set so deployments fail fast; update the code that
reads baseURL (the variable used to configure the Better Auth client) to rely
only on the env vars and include a descriptive error message if missing.
---
Outside diff comments:
In `@src/app/api/auth/custom-signup/route.ts`:
- Around line 22-32: The update that issues a new verification token (using
generateVerifyToken and getTokenExpiry) doesn't increment the
existing.emailVerifyAttempts counter, so add an increment to the
prisma.user.update data payload to increase emailVerifyAttempts by 1 (use
Prisma's atomic increment on the emailVerifyAttempts field) while still setting
emailVerifyToken and emailVerifyExpiry; ensure you update the same
prisma.user.update call that targets the user by email so the limiter check
existing.emailVerifyAttempts >= 5 becomes effective.
- Around line 34-36: The detached sendVerificationEmail(...) call in
custom-signup route.ts is fire-and-forget and can be terminated on serverless
runtimes; import after from 'next/server' and replace the unawaited
sendVerificationEmail(...).catch(...) with after(() =>
sendVerificationEmail(email, existing.name || email, token).catch(...)) so the
SMTP send runs in Next's post-response work queue; apply the same change for the
second occurrence (the other sendVerificationEmail call at the later block) and
keep existing error handling inside the .catch.
In `@src/features/executions/components/slack/executor.ts`:
- Around line 233-239: The MESSAGE_SEND_WEBHOOK branch now throws a
NonRetriableError if creds.webhookUrl is missing, which will break existing
persisted nodes that still store config.webhookUrl; restore a temporary fallback
or add a backfill: update the SlackOperation.MESSAGE_SEND_WEBHOOK handling in
executor.ts to use creds.webhookUrl || (config && config.webhookUrl) as the
webhook URL (or invoke a migration routine that copies config.webhookUrl into
creds.webhookUrl before removing the fallback), and ensure the error remains
thrown only when both are absent so existing nodes continue to work until data
is migrated.
In `@src/server/routers/webhook-trigger.router.ts`:
- Around line 8-24: Both getByWorkflowId and createWebhookTrigger use
protectedProcedure but don't verify workflow ownership and return/store secret
columns; change both to first load the workflow via the current user (e.g.,
query the Workflow model for workflowId and userId) to ensure the caller owns
it, and when returning the trigger use
prisma.webhookTrigger.findUnique/findFirst with a select that excludes
secretTokenEncrypted and secretTokenLegacy (only return safe fields like id,
workflowId, createdAt, etc.); similarly, when creating in createWebhookTrigger,
associate the trigger only after confirming ownership and return only the
non-secret selected fields instead of the full record.
---
Nitpick comments:
In `@prisma/schema/nodes_messaging.prisma`:
- Around line 54-73: Add a missing index on the WhatsAppNode model for the
workflowId foreign key to match other messaging subtypes: update the Prisma
model WhatsAppNode by adding an index declaration (e.g., @@index([workflowId]))
so Postgres will create an index for workflow-scoped queries and deletes; ensure
the change is applied to the schema (model WhatsAppNode) so the generated
migration includes the new index.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8be4a9eb-5722-448e-91cd-205d9d134b24
📒 Files selected for processing (24)
.gitignorepackage.jsonprisma/schema/auth.prismaprisma/schema/base.prismaprisma/schema/core.prismaprisma/schema/migrations/20260604130200_schema_refactoring/migration.sqlprisma/schema/migrations/migration_lock.tomlprisma/schema/nodes_commerce.prismaprisma/schema/nodes_crm.prismaprisma/schema/nodes_flow_control.prismaprisma/schema/nodes_google.prismaprisma/schema/nodes_messaging.prismaprisma/schema/nodes_utility.prismaprisma/schema/triggers.prismascripts/migrate-webhook-secrets.tssrc/app/api/auth/custom-signup/route.tssrc/features/executions/components/google-sheets/dialog.tsxsrc/features/executions/components/slack/dialog.tsxsrc/features/executions/components/slack/executor.tssrc/lib/auth-client.tssrc/lib/auth.tssrc/lib/webhook-secret.tssrc/server/routers/slack.router.tssrc/server/routers/webhook-trigger.router.ts
💤 Files with no reviewable changes (2)
- src/server/routers/slack.router.ts
- src/features/executions/components/slack/dialog.tsx
✅ Files skipped from review due to trivial changes (4)
- .gitignore
- src/lib/auth-client.ts
- prisma/schema/migrations/migration_lock.toml
- prisma/schema/base.prisma
🚧 Files skipped from review as they are similar to previous changes (7)
- prisma/schema/nodes_google.prisma
- prisma/schema/core.prisma
- prisma/schema/nodes_crm.prisma
- prisma/schema/triggers.prisma
- prisma/schema/nodes_flow_control.prisma
- prisma/schema/auth.prisma
- prisma/schema/nodes_utility.prisma
| id String @id @default(cuid()) | ||
| nodeId String @unique | ||
| workflowId String | ||
| credentialId String? | ||
| credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) | ||
| operation RazorpayOperation @default(ORDER_CREATE) |
There was a problem hiding this comment.
Model nodeId as a real relation to Node.
These commerce subtypes keep nodeId as a loose scalar, so the generated migration never emits a nodeId foreign key. workflowId only cleans them up on full workflow deletion; removing a single Node from an existing workflow will leave orphaned RazorpayNode / ShiprocketNode / CashfreeNode rows behind even though the routers upsert them by nodeId.
Make this a one-to-one relation to Node.id here and in prisma/schema/core.prisma so Prisma can enforce existence and cascade cleanup.
Also applies to: 72-76, 177-181
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@prisma/schema/nodes_commerce.prisma` around lines 7 - 12, Change the loose
scalar nodeId into a proper one-to-one relation to Node by replacing the
scalar-only nodeId with both the foreign-key field and relation field (keep
nodeId String `@unique` and add node Node `@relation`(fields: [nodeId], references:
[id], onDelete: Cascade)); update the RazorpayNode, ShiprocketNode, CashfreeNode
models (and the corresponding declarations in prisma/schema/core.prisma) so
Prisma will create a foreign key to Node.id and cascade-delete when a Node is
removed, ensuring routers can still upsert by nodeId and workflowId behavior
remains unchanged.
| id String @id @default(cuid()) | ||
| nodeId String @unique | ||
| workflowId String | ||
| credentialId String? | ||
| credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) | ||
| operation SlackOperation @default(MESSAGE_SEND_WEBHOOK) |
There was a problem hiding this comment.
Model nodeId as a real relation to Node.
These messaging subtypes still treat nodeId as a plain unique string, so the generated migration never creates a nodeId foreign key. Deleting a Node without deleting the whole workflow will orphan the subtype row, even though the routers address these records by nodeId.
This should be a one-to-one relation to Node.id here and in prisma/schema/core.prisma, with Prisma generating the FK/cascade for cleanup.
Also applies to: 55-60, 76-80, 102-106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@prisma/schema/nodes_messaging.prisma` around lines 7 - 12, The nodeId fields
in the messaging subtype models are plain unique strings and must be converted
to a Prisma one-to-one relation to Node so Prisma will create the FK and cascade
cleanup: for each affected model (the shown model and the ones at the other
ranges), add a nodeId String `@unique` field (or keep it) and add a node Node
relation field with `@relation`(fields: [nodeId], references: [id], onDelete:
Cascade) (i.e., replace the current standalone nodeId String `@unique` with an
explicit relation using nodeId as the FK), and make the same change in
prisma/schema/core.prisma so Prisma generates the FK/cascade for deletions.
| const triggers = await prisma.webhookTrigger.findMany({ | ||
| where: { | ||
| secretTokenEncrypted: null, | ||
| secretTokenLegacy: { not: null }, | ||
| }, | ||
| }) | ||
|
|
||
| console.log(`Found ${triggers.length} trigger(s) to migrate`) | ||
|
|
||
| let migrated = 0 | ||
| let skipped = 0 | ||
| let failed = 0 | ||
|
|
||
| for (const trigger of triggers) { | ||
| try { | ||
| if (!trigger.secretTokenLegacy) { | ||
| skipped++ | ||
| continue | ||
| } | ||
|
|
||
| const encrypted = encrypt(trigger.secretTokenLegacy) | ||
| await prisma.webhookTrigger.update({ | ||
| where: { id: trigger.id }, | ||
| data: { | ||
| secretTokenEncrypted: encrypted, | ||
| secretTokenLegacy: null, // clear legacy plaintext | ||
| }, | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== File: scripts/migrate-webhook-secrets.ts (relevant section) =="
sed -n '1,160p' scripts/migrate-webhook-secrets.ts | nl -ba | sed -n '1,120p'
echo
echo "== Search for writes to secretTokenEncrypted / secretTokenLegacy =="
rg -n --hidden --no-ignore -S "secretTokenEncrypted|secretTokenLegacy" .
echo
echo "== Search for update conditions on secretTokenEncrypted/Legacy in webhookTrigger updates =="
rg -n --hidden --no-ignore -S "webhookTrigger\.(update|updateMany)\(" -S .Repository: Mayank-saraswal/nodebase
Length of output: 195
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate file =="
ls -la scripts/migrate-webhook-secrets.ts 2>/dev/null || true
echo
echo "== File: scripts/migrate-webhook-secrets.ts (lines 1-120 with numbers) =="
python3 - <<'PY'
from pathlib import Path
p = Path("scripts/migrate-webhook-secrets.ts")
txt = p.read_text(encoding="utf-8", errors="replace").splitlines()
for i, line in enumerate(txt[:120], start=1):
print(f"{i:4d}\t{line}")
PY
echo
echo "== Search for writes to secretTokenEncrypted / secretTokenLegacy =="
rg -n --hidden --no-ignore -S "(secretTokenEncrypted|secretTokenLegacy)" .
echo
echo "== Search for webhookTrigger update/updateMany =="
rg -n --hidden --no-ignore -S "webhookTrigger\.(update|updateMany)\(" .Repository: Mayank-saraswal/nodebase
Length of output: 5299
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Show scripts/migrate-webhook-secrets.ts snippet around update =="
python3 - <<'PY'
from pathlib import Path
p=Path("scripts/migrate-webhook-secrets.ts")
lines=p.read_text().splitlines()
for i in range(12,70):
print(f"{i+1:4d}\t{lines[i]}")
PY
echo
echo "== Inspect src/server/routers/webhook-trigger.router.ts around line ~22 =="
python3 - <<'PY'
from pathlib import Path
p=Path("src/server/routers/webhook-trigger.router.ts")
txt=p.read_text().splitlines()
for i in range(1,120):
if 10<=i<=90:
pass
# find line containing "secretTokenEncrypted"
for idx,line in enumerate(txt, start=1):
if "secretTokenEncrypted" in line:
start=max(1,idx-20); end=min(len(txt), idx+40)
for j in range(start,end+1):
print(f"{j:4d}\t{txt[j-1]}")
break
else:
print("No occurrences found (unexpected).")
PY
echo
echo "== Search for other ways webhookTrigger rows are updated (without field-name match) =="
rg -n --hidden --no-ignore -S "webhookTrigger\.(update|updateMany|upsert)" src scripts prismaRepository: Mayank-saraswal/nodebase
Length of output: 1876
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Router file occurrences (secretTokenEncrypted/Legacy) =="
rg -n --hidden --no-ignore "secretTokenEncrypted|secretTokenLegacy" src/server/routers/webhook-trigger.router.ts
echo
echo "== Show surrounding lines in router for each occurrence =="
python3 - <<'PY'
from pathlib import Path
p = Path("src/server/routers/webhook-trigger.router.ts")
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
keys = ["secretTokenEncrypted", "secretTokenLegacy"]
for i,line in enumerate(lines, start=1):
if any(k in line for k in keys):
start=max(1,i-15); end=min(len(lines), i+25)
print(f"\n--- {p} around line {i} ---")
for j in range(start,end+1):
print(f"{j:4d}\t{lines[j-1]}")
PY
echo
echo "== Other updates/upserts touching webhookTrigger anywhere =="
rg -n --hidden --no-ignore -S "webhookTrigger\.(update|updateMany|upsert)" src scripts prisma
echo
echo "== Other direct field writes touching secretTokenEncrypted/Legacy via prisma.*.webhookTrigger.* =="
rg -n --hidden --no-ignore -S "webhookTrigger\..*(secretTokenEncrypted|secretTokenLegacy)" src scripts prismaRepository: Mayank-saraswal/nodebase
Length of output: 1399
Make migration writes safe under concurrent runs
scripts/migrate-webhook-secrets.ts fetches candidates with secretTokenEncrypted: null and then updates by id only. If another migration instance (or any concurrent writer) updates the same row between the findMany and update, this script can overwrite the already-migrated secretTokenEncrypted and re-clear secretTokenLegacy. Use an atomic predicate on the update (e.g., updateMany with where: { id: trigger.id, secretTokenEncrypted: null, secretTokenLegacy: { not: null } }) and treat count === 0 as skipped.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/migrate-webhook-secrets.ts` around lines 17 - 44, The migration
currently reads candidates into triggers and calls prisma.webhookTrigger.update
by id, which can race and overwrite concurrent migrations; change the write to
an atomic conditional update using prisma.webhookTrigger.updateMany with where:
{ id: trigger.id, secretTokenEncrypted: null, secretTokenLegacy: { not: null } }
and data setting secretTokenEncrypted and nulling secretTokenLegacy, then treat
result.count === 0 as skipped and result.count === 1 as migrated (increment
migrated/skipped accordingly) and catch errors to increment failed; keep using
encrypt(trigger.secretTokenLegacy) and the same loop structure but replace the
single-row update with this conditional updateMany + count check.
| }, | ||
| plugins:[], | ||
| baseURL: process.env.BETTER_AUTH_URL, | ||
| baseURL: process.env.BETTER_AUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "https://nodebase.mayanksaraswal.in", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show auth.ts around the referenced line
if [ -f "src/lib/auth.ts" ]; then
echo "---- src/lib/auth.ts (around line 41) ----"
nl -ba src/lib/auth.ts | sed -n '1,120p'
else
echo "File src/lib/auth.ts not found"
fi
# Search for BETTER_AUTH_URL / NEXT_PUBLIC_APP_URL usage
echo "---- rg BETTER_AUTH_URL ----"
rg -n "BETTER_AUTH_URL" . || true
echo "---- rg NEXT_PUBLIC_APP_URL ----"
rg -n "NEXT_PUBLIC_APP_URL" . || true
# Search for baseURL usage in repo
echo "---- rg baseURL in src/lib/auth.ts ----"
rg -n "baseURL" src/lib/auth.ts || true
# Show any mention of nodebase.mayanksaraswal.in
echo "---- rg nodebase.mayanksaraswal.in ----"
rg -n "nodebase\.mayanksaraswal\.in" . || trueRepository: Mayank-saraswal/nodebase
Length of output: 172
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Listing file existence:"
ls -la src/lib/auth.ts || true
echo "---- src/lib/auth.ts (line-numbered, first 140 lines) ----"
nl -ba src/lib/auth.ts | sed -n '1,140p'
echo "---- rg BETTER_AUTH_URL ----"
rg -n "BETTER_AUTH_URL" . || true
echo "---- rg NEXT_PUBLIC_APP_URL ----"
rg -n "NEXT_PUBLIC_APP_URL" . || true
echo "---- rg baseURL ----"
rg -n "baseURL" src/lib/auth.ts . || true
echo "---- rg hardcoded production domain ----"
rg -n "nodebase\.mayanksaraswal\.in" . || trueRepository: Mayank-saraswal/nodebase
Length of output: 277
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- Show src/lib/auth.ts (lines 30-60) ----"
sed -n '30,60p' src/lib/auth.ts
echo "---- Show full env fallback line (search BETTER_AUTH_URL/NEXT_PUBLIC_APP_URL) ----"
rg -n "BETTER_AUTH_URL|NEXT_PUBLIC_APP_URL|nodebase\.mayanksaraswal\.in" src/lib/auth.ts || true
echo "---- Show where baseURL is used in src/lib/auth.ts ----"
rg -n "baseURL" src/lib/auth.ts || true
echo "---- Search entire repo for nodebase.mayanksaraswal.in ----"
rg -n "nodebase\.mayanksaraswal\.in" . || true
echo "---- Search entire repo for BETTER_AUTH_URL / NEXT_PUBLIC_APP_URL usage ----"
rg -n "BETTER_AUTH_URL" . || true
rg -n "NEXT_PUBLIC_APP_URL" . || trueRepository: Mayank-saraswal/nodebase
Length of output: 5824
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- src/lib/auth.ts (lines 1-120) ----"
sed -n '1,120p' src/lib/auth.ts
echo "---- src/lib/env.ts (lines 1-120) ----"
sed -n '1,120p' src/lib/env.ts
echo "---- src/lib/google-auth.ts (lines 1-120) ----"
sed -n '1,120p' src/lib/google-auth.ts
echo "---- src/app/api/auth/google/callback/route.ts (around where baseUrl is set) ----"
rg -n "NEXTAUTH_URL|baseUrl|callback" src/app/api/auth/google/callback/route.ts
sed -n '80,140p' src/app/api/auth/google/callback/route.tsRepository: Mayank-saraswal/nodebase
Length of output: 9504
🌐 Web query:
better-auth baseURL option what it controls callbacks cookie origin
💡 Result:
In Better Auth, the baseURL option serves as the root URL of your authentication server [1][2]. It is essential for several core functionalities: 1. URL Generation for Callbacks: The baseURL is used to construct accurate, predictable callback URLs for authentication flows, such as OAuth redirects, email verification links, and password reset tokens [3]. Without an explicit baseURL, the system may rely on request inference, which is discouraged for production environments due to potential stability and security risks [1][2]. 2. Cookie Configuration: The baseURL, specifically its protocol (http vs https), influences the security configuration of session cookies [1][2]. For instance, if the protocol is set to https, Better Auth defaults to enabling the Secure flag on cookies [1][2]. If you are using cross-subdomain cookies, the cookie domain is typically derived from the resolved request host unless explicitly configured otherwise [4]. 3. Origin Validation and Security: While baseURL acts as the primary host for the auth server, it is closely linked to origin security [1][3]. When using the Dynamic Base URL feature (where baseURL is an object with allowedHosts), the hosts defined in allowedHosts are automatically added to trustedOrigins [1][5]. trustedOrigins is the primary mechanism for CSRF protection, which validates incoming Origin headers to reject unauthorized requests [3]. It is strongly recommended to define baseURL explicitly in your configuration or via the BETTER_AUTH_URL environment variable rather than relying on request inference, which can lead to undefined behavior or misconfigured security flags [1][2][3].
Citations:
- 1: https://www.better-auth.com/docs/reference/options
- 2: https://github.com/better-auth/better-auth/blob/main/docs/content/docs/reference/options.mdx
- 3: Clarification: Is
baseURLnecessary whentrustedOriginsis configured? better-auth/better-auth#7502 - 4: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/reference/options.mdx
- 5: https://better-auth.com/docs/guides/dynamic-base-url
Remove hardcoded production baseURL fallback for Better Auth
In src/lib/auth.ts (line 41), baseURL is set to process.env.BETTER_AUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "https://nodebase.mayanksaraswal.in". If both env vars are unset on preview/local/self-hosted deploys, Better Auth will generate callback/verification URLs and cookie security based on the production origin, which can misroute OAuth/callback handling and break session/cookie behavior. Fail fast when the env vars are missing (or always inject a correct deployment-specific base URL) instead of defaulting to the production domain.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/auth.ts` at line 41, The current baseURL assignment in
src/lib/auth.ts (the baseURL variable used when constructing the Better Auth
client) must not fall back to the hardcoded production URL; change it to remove
the "https://nodebase.mayanksaraswal.in" fallback and instead validate that
either process.env.BETTER_AUTH_URL or process.env.NEXT_PUBLIC_APP_URL is present
at startup (when initializing the auth client) and throw a clear error (or abort
initialization) if neither is set so deployments fail fast; update the code that
reads baseURL (the variable used to configure the Better Auth client) to rely
only on the env vars and include a descriptive error message if missing.
…d add auth infrastructure
Summary by CodeRabbit
New Features
Bug Fixes
Chores