From c6d178396cf065e51efd23a353c618e81b7629f3 Mon Sep 17 00:00:00 2001 From: Mayank Saini Date: Thu, 4 Jun 2026 01:50:20 +0530 Subject: [PATCH 1/6] refactor: modularize Prisma schema into directory-based components and add auth infrastructure --- prisma.config.ts | 6 + prisma/{schema.prisma => schema.prisma.bak} | 0 prisma/schema/auth.prisma | 100 ++++ prisma/schema/base.prisma | 15 + prisma/schema/core.prisma | 147 ++++++ prisma/schema/enums.prisma | 518 ++++++++++++++++++++ prisma/schema/nodes_commerce.prisma | 293 +++++++++++ prisma/schema/nodes_crm.prisma | 318 ++++++++++++ prisma/schema/nodes_flow_control.prisma | 173 +++++++ prisma/schema/nodes_google.prisma | 102 ++++ prisma/schema/nodes_messaging.prisma | 152 ++++++ prisma/schema/nodes_utility.prisma | 310 ++++++++++++ prisma/schema/triggers.prisma | 136 +++++ src/app/api/auth/custom-signup/route.ts | 17 +- src/lib/auth.ts | 2 +- 15 files changed, 2279 insertions(+), 10 deletions(-) create mode 100644 prisma.config.ts rename prisma/{schema.prisma => schema.prisma.bak} (100%) create mode 100644 prisma/schema/auth.prisma create mode 100644 prisma/schema/base.prisma create mode 100644 prisma/schema/core.prisma create mode 100644 prisma/schema/enums.prisma create mode 100644 prisma/schema/nodes_commerce.prisma create mode 100644 prisma/schema/nodes_crm.prisma create mode 100644 prisma/schema/nodes_flow_control.prisma create mode 100644 prisma/schema/nodes_google.prisma create mode 100644 prisma/schema/nodes_messaging.prisma create mode 100644 prisma/schema/nodes_utility.prisma create mode 100644 prisma/schema/triggers.prisma diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..5a93722 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from '@prisma/config' +import 'dotenv/config' + +export default defineConfig({ + schema: "prisma/schema" +}) diff --git a/prisma/schema.prisma b/prisma/schema.prisma.bak similarity index 100% rename from prisma/schema.prisma rename to prisma/schema.prisma.bak diff --git a/prisma/schema/auth.prisma b/prisma/schema/auth.prisma new file mode 100644 index 0000000..9c04e62 --- /dev/null +++ b/prisma/schema/auth.prisma @@ -0,0 +1,100 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Auth & Billing Models +// User, Session, Account, Verification, BillingEvent +// ───────────────────────────────────────────────────────────── + +model User { + id String @id + name String + email String + emailVerified Boolean @default(false) + emailVerifyToken String? @unique + emailVerifyExpiry DateTime? + emailVerifyAttempts Int @default(0) + image String? + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + sessions Session[] + accounts Account[] + workflows workflow[] + credentials Credential[] + executionCount Int @default(0) + executionResetAt DateTime @default(now()) + + // ── Subscription / Billing ─────────────────────────────────────────────── + plan String @default("FREE") // FREE|STARTER|PRO|TEAM + planStatus String @default("active") // active|past_due|cancelled|paused + razorpayCustomerId String? @unique // cust_xxxx + razorpaySubId String? @unique // sub_xxxx + currentPeriodEnd DateTime? + cancelAtPeriodEnd Boolean @default(false) + workflowRunsUsed Int @default(0) + workflowRunsReset DateTime @default(now()) + billingEvents BillingEvent[] + + @@unique([email]) + @@map("user") +} + +model BillingEvent { + id String @id @default(cuid()) + userId String + type String // subscription.activated | payment.captured | subscription.cancelled | etc. + razorpayEventId String? @unique + amount Int? // paise + currency String @default("INR") + plan String? + status String // success | failed | pending + rawPayload String @db.Text // full Razorpay webhook JSON + createdAt DateTime @default(now()) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) + @@index([type]) +} + +model Session { + id String @id + expiresAt DateTime + token String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + ipAddress String? + userAgent String? + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([token]) + @@map("session") +} + +model Account { + id String @id + accountId String + providerId String + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("account") +} + +model Verification { + id String @id + identifier String + value String + expiresAt DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@map("verification") +} diff --git a/prisma/schema/base.prisma b/prisma/schema/base.prisma new file mode 100644 index 0000000..dbeb797 --- /dev/null +++ b/prisma/schema/base.prisma @@ -0,0 +1,15 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Base Configuration +// Generator and datasource definitions +// ───────────────────────────────────────────────────────────── + +generator client { + provider = "prisma-client-js" + output = "../../src/generated/prisma" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_DATABASE_URL") +} diff --git a/prisma/schema/core.prisma b/prisma/schema/core.prisma new file mode 100644 index 0000000..1c99c19 --- /dev/null +++ b/prisma/schema/core.prisma @@ -0,0 +1,147 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Core Models +// workflow, Node, Connection, Credential, Execution, NodeExecution +// ───────────────────────────────────────────────────────────── + +model workflow { + id String @id @default(cuid()) + name String + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + nodes Node[] + connections Connection[] + executions Execution[] + webhookTrigger WebhookTrigger? + scheduleTrigger ScheduleTrigger? + ifElseNodes IfElseNode[] + gmailNodes GmailNode[] + setVariableNodes SetVariableNode[] + googleSheetsNodes GoogleSheetsNode[] + googleDriveNodes GoogleDriveNode[] + codeNodes CodeNode[] + whatsAppNodes WhatsAppNode[] + loopNodes LoopNode[] + notionNodes NotionNode[] + razorpayNodes RazorpayNode[] + slackNodes SlackNode[] + gmailWatchers GmailWatcher[] + switchNodes SwitchNode[] + waitNodes WaitNode[] + mergeNodes MergeNode[] + errorTriggerNodes ErrorTriggerNode[] + razorpayTriggers RazorpayTrigger[] + whatsappTriggers WhatsAppTrigger[] + msg91Nodes Msg91Node[] + shiprocketNodes ShiprocketNode[] + zohoCrmNodes ZohoCrmNode[] + hubspotNodes HubspotNode[] + freshdeskNodes FreshdeskNode[] + aiNodes AINode[] + mediaUploadNodes MediaUploadNode[] + sortNodes SortNode[] + filterNodes FilterNode[] + cashfreeNodes CashfreeNode[] + aggregateNodes AggregateNode[] + postgresNodes PostgresNode[] + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model Credential { + id String @id @default(cuid()) + name String + value String + userId String + type CredentialType + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + Node Node[] + shiprocketNodes ShiprocketNode[] + zohoCrmNodes ZohoCrmNode[] + hubspotNodes HubspotNode[] + freshdeskNodes FreshdeskNode[] + cashfreeNodes CashfreeNode[] + postgresNodes PostgresNode[] + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + // TODO: @@map("Credenial") has a typo — should be "credential" + // Fixing requires: ALTER TABLE "Credenial" RENAME TO "credential"; + // plus updating all existing FK references. Do in a dedicated migration sprint. + @@map("Credenial") +} + +model Node { + id String @id @default(cuid()) + name String + type NodeType + workflowId String + position Json + data Json @default("{}") + outputConnections Connection[] @relation("FromNode") + inputConnections Connection[] @relation("ToNode") + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt +} + +model Connection { + id String @id @default(cuid()) + workflowId String + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + fromNodeId String + fromNode Node @relation("FromNode", fields: [fromNodeId], references: [id], onDelete: Cascade) + toNodeId String + toNode Node @relation("ToNode", fields: [toNodeId], references: [id], onDelete: Cascade) + fromOutput String @default("main") + toInput String @default("main") + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@unique([fromNodeId, toNodeId, fromOutput, toInput]) +} + +model Execution { + id String @id @default(cuid()) + workflowId String + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + startedAt DateTime @default(now()) + completedAt DateTime? + inngestEventId String @unique + output Json? + error String? @db.Text + errorStack String? @db.Text + status ExecutionStatus @default(RUNNING) + nodeExecutions NodeExecution[] +} + +model NodeExecution { + id String @id @default(cuid()) + executionId String + execution Execution @relation(fields: [executionId], references: [id], onDelete: Cascade) + + nodeId String + nodeName String @default("") + nodeType String @default("") + + status String @default("success") + + inputJson String @default("") @db.Text + outputJson String @default("") @db.Text + + errorMessage String @default("") + + durationMs Int @default(0) + + executionOrder Int @default(0) + + createdAt DateTime @default(now()) + + @@index([executionId]) + @@index([nodeId]) + @@index([executionId, executionOrder]) +} diff --git a/prisma/schema/enums.prisma b/prisma/schema/enums.prisma new file mode 100644 index 0000000..650f788 --- /dev/null +++ b/prisma/schema/enums.prisma @@ -0,0 +1,518 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: All Enums +// ───────────────────────────────────────────────────────────── + +enum NodeType { + INITIAL + MANUAL_TRIGGER + HTTP_REQUEST + GOOGLE_FORM_TRIGGER + STRIPE_TRIGGER + WEBHOOK_TRIGGER + SCHEDULE_TRIGGER + ANTHROPIC + GEMINI + OPENAI + GROQ + XAI + DISCORD + SLACK + DEEPSEEK + PERPLEXITY + TELEGRAM + X + WORKDAY + IF_ELSE + GMAIL + SET_VARIABLE + GOOGLE_SHEETS + GOOGLE_DRIVE + CODE + WHATSAPP + LOOP + NOTION + RAZORPAY + SWITCH + WAIT + MERGE + ERROR_TRIGGER + RAZORPAY_TRIGGER + WHATSAPP_TRIGGER + MSG91 + SHIPROCKET + ZOHO_CRM + HUBSPOT + FRESHDESK + MEDIA_UPLOAD + SORT + FILTER + CASHFREE + CASHFREE_TRIGGER + AGGREGATE + POSTGRES + // In a future sprint: ZOHO_CRM_TRIGGER + // Registers a Zoho Notification API webhook for lead/contact/deal events. + // Ref: https://www.zoho.com/crm/developer/docs/api/v6/notifications/overview.html +} + +enum AIProvider { + OPENAI + ANTHROPIC + GEMINI + GROQ + XAI + DEEPSEEK + PERPLEXITY +} + +enum AIOperation { + CHAT + CHAT_WITH_HISTORY + STRUCTURED_OUTPUT + TOOL_USE + VISION + EMBED + TRANSCRIBE + GENERATE_IMAGE + CLASSIFY +} + +enum HttpMethod { + GET + POST + PUT + PATCH +} + +enum ExecutionStatus { + RUNNING + SUCCESS + FAILED +} + +enum IfElseOperator { + EQUALS + NOT_EQUALS + CONTAINS + NOT_CONTAINS + STARTS_WITH + ENDS_WITH + GREATER_THAN + LESS_THAN + GREATER_THAN_OR_EQUAL + LESS_THAN_OR_EQUAL + IS_EMPTY + IS_NOT_EMPTY + IS_TRUE + IS_FALSE + REGEX_MATCH +} + +enum CredentialType { + OPENAI + ANTHROPIC + GEMINI + DEEPSEEK + PERPLEXITY + XAI + GROQ + GMAIL + GMAIL_OAUTH + GOOGLE_SHEETS + GOOGLE_DRIVE + WHATSAPP + NOTION + RAZORPAY + SLACK + MSG91 + SHIPROCKET + ZOHO_CRM + HUBSPOT + FRESHDESK + CASHFREE + POSTGRES +} + +enum GmailOperation { + SEND + REPLY + FORWARD + GET_MESSAGE + LIST_MESSAGES + SEARCH_MESSAGES + ADD_LABEL + REMOVE_LABEL + MARK_READ + MARK_UNREAD + MOVE_TO_TRASH + CREATE_DRAFT + GET_ATTACHMENT + GET_THREAD + LIST_LABELS + CREATE_LABEL + LIST_DRAFTS + SEND_DRAFT +} + +enum GoogleSheetsOp { + APPEND_ROW + READ_ROWS + UPDATE_ROW + UPDATE_ROWS_BY_QUERY + DELETE_ROW + GET_ROW_BY_NUMBER + SEARCH_ROWS + CLEAR_RANGE + CREATE_SHEET + GET_SHEET_INFO +} + +enum GoogleDriveOperation { + UPLOAD_FILE + DOWNLOAD_FILE + LIST_FILES + CREATE_FOLDER +} + +enum WhatsAppOperation { + SEND_TEXT + SEND_TEMPLATE + SEND_IMAGE + SEND_DOCUMENT + SEND_REACTION +} + +enum NotionOperation { + QUERY_DATABASE + CREATE_DATABASE_PAGE + UPDATE_DATABASE_PAGE + GET_PAGE + ARCHIVE_PAGE + APPEND_BLOCK + GET_BLOCK_CHILDREN + SEARCH + GET_DATABASE + GET_USER + GET_USERS +} + +enum RazorpayOperation { + ORDER_CREATE + ORDER_FETCH + ORDER_FETCH_PAYMENTS + ORDER_LIST + PAYMENT_FETCH + PAYMENT_CAPTURE + PAYMENT_LIST + PAYMENT_UPDATE + REFUND_CREATE + REFUND_FETCH + REFUND_LIST + CUSTOMER_CREATE + CUSTOMER_FETCH + CUSTOMER_UPDATE + SUBSCRIPTION_CREATE + SUBSCRIPTION_FETCH + SUBSCRIPTION_CANCEL + INVOICE_CREATE + INVOICE_FETCH + INVOICE_SEND + INVOICE_CANCEL + PAYMENT_LINK_CREATE + PAYMENT_LINK_FETCH + PAYMENT_LINK_UPDATE + PAYMENT_LINK_CANCEL + PAYOUT_CREATE + PAYOUT_FETCH + VERIFY_PAYMENT_SIGNATURE +} + +enum SlackOperation { + MESSAGE_SEND_WEBHOOK + MESSAGE_SEND + MESSAGE_UPDATE + MESSAGE_DELETE + MESSAGE_GET_PERMALINK + MESSAGE_SCHEDULE + // legacy — kept for backward compat + MESSAGE_SEARCH + CHANNEL_GET + CHANNEL_LIST + CHANNEL_CREATE + CHANNEL_ARCHIVE + CHANNEL_UNARCHIVE + CHANNEL_INVITE + CHANNEL_KICK + CHANNEL_SET_TOPIC + CHANNEL_SET_PURPOSE + // legacy — kept for backward compat + CHANNEL_HISTORY + CHANNEL_INFO + CHANNEL_RENAME + USER_GET + USER_GET_BY_EMAIL + USER_LIST + USER_SET_STATUS + // legacy — kept for backward compat + USER_INFO + USER_GET_PRESENCE + REACTION_ADD + REACTION_REMOVE + REACTION_GET + FILE_UPLOAD + FILE_GET + FILE_DELETE + // legacy — kept for backward compat + FILE_LIST + FILE_INFO + CONVERSATION_OPEN +} + +enum Msg91Operation { + SEND_SMS + SEND_BULK_SMS + SEND_TRANSACTIONAL + SCHEDULE_SMS + SEND_OTP + VERIFY_OTP + RESEND_OTP + INVALIDATE_OTP + SEND_WHATSAPP + SEND_WHATSAPP_MEDIA + SEND_VOICE_OTP + SEND_EMAIL + GET_BALANCE + GET_REPORT +} + +enum ShiprocketOperation { + CREATE_ORDER + GET_ORDER + CANCEL_ORDER + UPDATE_ORDER + GET_ORDER_TRACKING + CLONE_ORDER + GENERATE_AWB + GET_ORDERS_LIST + TRACK_SHIPMENT + ASSIGN_COURIER + GENERATE_LABEL + GENERATE_MANIFEST + REQUEST_PICKUP + GET_COURIER_LIST + GET_RATE + CHECK_SERVICEABILITY + CREATE_RETURN + GET_RETURN_REASONS + TRACK_RETURN + CREATE_PRODUCT + GET_PRODUCTS + GET_PICKUP_LOCATIONS + CREATE_PICKUP_LOCATION +} + +// In a future sprint: ZOHO_CRM_TRIGGER +// Registers Zoho Notification API watches and receives callbacks at /api/webhooks/zoho/{webhookId}. +enum ZohoCrmOperation { + CREATE_LEAD + GET_LEAD + UPDATE_LEAD + DELETE_LEAD + SEARCH_LEADS + CONVERT_LEAD + + CREATE_CONTACT + GET_CONTACT + UPDATE_CONTACT + DELETE_CONTACT + SEARCH_CONTACTS + GET_CONTACT_DEALS + + CREATE_DEAL + GET_DEAL + UPDATE_DEAL + DELETE_DEAL + SEARCH_DEALS + UPDATE_DEAL_STAGE + + CREATE_ACCOUNT + GET_ACCOUNT + UPDATE_ACCOUNT + DELETE_ACCOUNT + SEARCH_ACCOUNTS + + CREATE_TASK + CREATE_CALL_LOG + CREATE_MEETING + GET_ACTIVITIES + + ADD_NOTE + GET_NOTES + + UPSERT_RECORD + SEARCH_RECORDS + GET_FIELDS +} + +enum HubspotOperation { + // ── CONTACTS ────────────────────────────────────────────── + CREATE_CONTACT + GET_CONTACT + UPDATE_CONTACT + DELETE_CONTACT + SEARCH_CONTACTS + GET_CONTACT_PROPERTIES + UPSERT_CONTACT + GET_CONTACT_ASSOCIATIONS + + // ── COMPANIES ───────────────────────────────────────────── + CREATE_COMPANY + GET_COMPANY + UPDATE_COMPANY + DELETE_COMPANY + SEARCH_COMPANIES + + // ── DEALS ───────────────────────────────────────────────── + CREATE_DEAL + GET_DEAL + UPDATE_DEAL + DELETE_DEAL + SEARCH_DEALS + UPDATE_DEAL_STAGE + + // ── TICKETS ─────────────────────────────────────────────── + CREATE_TICKET + GET_TICKET + UPDATE_TICKET + DELETE_TICKET + SEARCH_TICKETS + + // ── ACTIVITIES ──────────────────────────────────────────── + CREATE_NOTE + CREATE_TASK + CREATE_CALL + CREATE_EMAIL_LOG + + // ── ASSOCIATIONS ────────────────────────────────────────── + CREATE_ASSOCIATION + DELETE_ASSOCIATION + + // ── LISTS ───────────────────────────────────────────────── + ADD_CONTACT_TO_LIST + REMOVE_CONTACT_FROM_LIST + GET_LIST_CONTACTS + + // ── GENERIC ─────────────────────────────────────────────── + SEARCH_OBJECTS + GET_PROPERTIES +} + +enum FreshdeskOperation { + // ── TICKETS ────────────────────────────────────────────── + CREATE_TICKET + GET_TICKET + UPDATE_TICKET + DELETE_TICKET + LIST_TICKETS + SEARCH_TICKETS + GET_TICKET_FIELDS + RESTORE_TICKET + + // ── TICKET NOTES ───────────────────────────────────────── + ADD_NOTE + LIST_NOTES + UPDATE_NOTE + DELETE_NOTE + + // ── CONTACTS ───────────────────────────────────────────── + CREATE_CONTACT + GET_CONTACT + UPDATE_CONTACT + DELETE_CONTACT + LIST_CONTACTS + SEARCH_CONTACTS + MERGE_CONTACT + + // ── COMPANIES ──────────────────────────────────────────── + CREATE_COMPANY + GET_COMPANY + UPDATE_COMPANY + DELETE_COMPANY + LIST_COMPANIES + + // ── AGENTS ─────────────────────────────────────────────── + LIST_AGENTS + GET_AGENT + UPDATE_AGENT + + // ── CONVERSATIONS ──────────────────────────────────────── + LIST_CONVERSATIONS + SEND_REPLY + CREATE_OUTBOUND_EMAIL + + // ── GENERIC ────────────────────────────────────────────── + GET_TICKET_STATS +} + +enum MediaUploadSource { + URL + BASE64 + GOOGLE_DRIVE +} + +enum CashfreeOperation { + // ── ORDERS (Payment Gateway) ────────────────────────────── + CREATE_ORDER + GET_ORDER + TERMINATE_ORDER + PAY_ORDER + + // ── PAYMENTS ────────────────────────────────────────────── + GET_PAYMENTS_FOR_ORDER + GET_PAYMENT_BY_ID + + // ── REFUNDS ─────────────────────────────────────────────── + CREATE_REFUND + GET_REFUND + GET_ALL_REFUNDS_FOR_ORDER + + // ── SETTLEMENTS ─────────────────────────────────────────── + GET_SETTLEMENTS_FOR_ORDER + GET_ALL_SETTLEMENTS + GET_SETTLEMENT_RECON + + // ── PAYMENT LINKS ───────────────────────────────────────── + CREATE_PAYMENT_LINK + GET_PAYMENT_LINK + CANCEL_PAYMENT_LINK + GET_ORDERS_FOR_LINK + + // ── SUBSCRIPTIONS ───────────────────────────────────────── + CREATE_SUBSCRIPTION_PLAN + GET_SUBSCRIPTION_PLAN + CREATE_SUBSCRIPTION + GET_SUBSCRIPTION + MANAGE_SUBSCRIPTION + GET_SUBSCRIPTION_PAYMENTS + + // ── PAYOUTS ─────────────────────────────────────────────── + GET_PAYOUT_BALANCE + ADD_BENEFICIARY + GET_BENEFICIARY + REMOVE_BENEFICIARY + TRANSFER_TO_BENEFICIARY + GET_TRANSFER_STATUS + BULK_TRANSFER + GET_BATCH_TRANSFER_STATUS + + // ── UPI ─────────────────────────────────────────────────── + VALIDATE_UPI_ID + CREATE_UPI_PAYMENT_LINK + + // ── OFFERS ──────────────────────────────────────────────── + CREATE_OFFER + GET_OFFER + + // ── WEBHOOKS ────────────────────────────────────────────── + VERIFY_WEBHOOK_SIGNATURE +} diff --git a/prisma/schema/nodes_commerce.prisma b/prisma/schema/nodes_commerce.prisma new file mode 100644 index 0000000..0a81ca2 --- /dev/null +++ b/prisma/schema/nodes_commerce.prisma @@ -0,0 +1,293 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Commerce Node Models +// RazorpayNode, ShiprocketNode, CashfreeNode +// ───────────────────────────────────────────────────────────── + +model RazorpayNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + operation RazorpayOperation @default(ORDER_CREATE) + variableName String @default("razorpay") + amount String @default("") + currency String @default("INR") + description String @default("") + receipt String @default("") + notes String @default("") + partialPayment Boolean @default(false) + orderId String @default("") + paymentId String @default("") + captureAmount String @default("") + refundAmount String @default("") + refundSpeed String @default("normal") + refundId String @default("") + customerId String @default("") + customerName String @default("") + customerEmail String @default("") + customerContact String @default("") + failExisting Boolean @default(false) + planId String @default("") + totalCount String @default("") + quantity String @default("1") + startAt String @default("") + subscriptionId String @default("") + cancelAtCycleEnd Boolean @default(false) + invoiceType String @default("invoice") + lineItems String @default("") + expireBy String @default("") + smsNotify Boolean @default(true) + emailNotify Boolean @default(true) + invoiceId String @default("") + paymentLinkId String @default("") + referenceId String @default("") + reminderEnable Boolean @default(true) + callbackUrl String @default("") + callbackMethod String @default("") + accountNumber String @default("") + fundAccountId String @default("") + payoutMode String @default("") + payoutPurpose String @default("payout") + narration String @default("") + queueIfLowBalance Boolean @default(false) + payoutId String @default("") + signature String @default("") + throwOnInvalid Boolean @default(true) + count String @default("") + skip String @default("") + fromDate String @default("") + toDate String @default("") + authorized String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model ShiprocketNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + + operation ShiprocketOperation @default(CREATE_ORDER) + variableName String @default("shiprocket") + + // ── ORDER fields ────────────────────────────────────────────────── + orderId String @default("") @db.Text + orderDate String @default("") + channelId String @default("") + + billingName String @default("") @db.Text + billingAddress String @default("") @db.Text + billingAddress2 String @default("") @db.Text + billingCity String @default("") @db.Text + billingState String @default("") @db.Text + billingCountry String @default("India") + billingPincode String @default("") @db.Text + billingEmail String @default("") @db.Text + billingPhone String @default("") @db.Text + billingAlternatePhone String @default("") @db.Text + + shippingIsBilling Boolean @default(true) + shippingName String @default("") @db.Text + shippingAddress String @default("") @db.Text + shippingAddress2 String @default("") @db.Text + shippingCity String @default("") @db.Text + shippingState String @default("") @db.Text + shippingCountry String @default("India") + shippingPincode String @default("") @db.Text + shippingEmail String @default("") @db.Text + shippingPhone String @default("") @db.Text + + orderItems String @default("[]") @db.Text + + paymentMethod String @default("prepaid") + subTotal String @default("") @db.Text + codAmount String @default("0") + + length String @default("") @db.Text + breadth String @default("") @db.Text + height String @default("") @db.Text + weight String @default("") @db.Text + + shiprocketOrderId String @default("") @db.Text + shipmentId String @default("") @db.Text + awbCode String @default("") @db.Text + + courierId String @default("") @db.Text + courierName String @default("") @db.Text + + // ── PICKUP ──────────────────────────────────────────────────────── + pickupLocation String @default("") @db.Text + + // ── RATE/SERVICEABILITY ─────────────────────────────────────────── + pickupPostcode String @default("") @db.Text + deliveryPostcode String @default("") @db.Text + cod String @default("0") + + // ── RETURN ─────────────────────────────────────────────────────── + returnOrderId String @default("") @db.Text + returnReason String @default("") @db.Text + returnPickupLocation String @default("") @db.Text + + // ── PRODUCT ────────────────────────────────────────────────────── + productName String @default("") @db.Text + productSku String @default("") @db.Text + productMrp String @default("") @db.Text + productSellingPrice String @default("") @db.Text + productWeight String @default("") @db.Text + productCategory String @default("") @db.Text + productHsn String @default("") @db.Text + + // ── LIST filters ───────────────────────────────────────────────── + filterStatus String @default("") @db.Text + pageNo Int @default(1) + perPage Int @default(10) + + // ── WAREHOUSE ──────────────────────────────────────────────────── + warehouseName String @default("") @db.Text + warehouseEmail String @default("") @db.Text + warehousePhone String @default("") @db.Text + warehouseAddress String @default("") @db.Text + warehouseCity String @default("") @db.Text + warehouseState String @default("") @db.Text + warehousePincode String @default("") @db.Text + warehouseCountry String @default("India") + + cancelReason String @default("") @db.Text + + continueOnFail Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + credential Credential? @relation(fields: [credentialId], references: [id]) + + @@index([workflowId]) + @@index([nodeId]) +} + +model CashfreeNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id]) + + operation CashfreeOperation @default(CREATE_ORDER) + variableName String @default("cashfree") + continueOnFail Boolean @default(false) + + // ── ORDER FIELDS ────────────────────────────────────────── + orderId String @default("") @db.Text + orderAmount String @default("") @db.Text + orderCurrency String @default("INR") + orderNote String @default("") @db.Text + orderMeta String @default("{}") @db.Text + + // ── CUSTOMER FIELDS ─────────────────────────────────────── + customerId String @default("") @db.Text + customerEmail String @default("") @db.Text + customerPhone String @default("") @db.Text + customerName String @default("") @db.Text + customerBankAccountNumber String @default("") @db.Text + customerBankIfsc String @default("") @db.Text + customerBankCode String @default("") + + // ── PAYMENT FIELDS ──────────────────────────────────────── + cfPaymentId String @default("") @db.Text + cfOrderId String @default("") @db.Text + paymentMethod String @default("") @db.Text + + // ── REFUND FIELDS ───────────────────────────────────────── + refundId String @default("") @db.Text + refundAmount String @default("") @db.Text + refundNote String @default("") @db.Text + refundSpeed String @default("STANDARD") + refundSplits String @default("[]") @db.Text + + // ── SETTLEMENT FIELDS ───────────────────────────────────── + settlementId String @default("") @db.Text + startDate String @default("") @db.Text + endDate String @default("") @db.Text + cursor String @default("") + limit Int @default(10) + + // ── PAYMENT LINK FIELDS ─────────────────────────────────── + linkId String @default("") @db.Text + linkAmount String @default("") @db.Text + linkCurrency String @default("INR") + linkPurpose String @default("") @db.Text + linkDescription String @default("") @db.Text + linkExpiryTime String @default("") @db.Text + linkNotifyPhone Boolean @default(true) + linkNotifyEmail Boolean @default(true) + linkAutoReminders Boolean @default(true) + linkMinPartialAmount String @default("") @db.Text + linkMeta String @default("{}") @db.Text + + // ── SUBSCRIPTION FIELDS ─────────────────────────────────── + subscriptionId String @default("") @db.Text + planId String @default("") @db.Text + planName String @default("") @db.Text + planType String @default("PERIODIC") + planIntervalType String @default("MONTH") + planIntervals Int @default(1) + planMaxCycles Int @default(0) + planMaxAmount String @default("") @db.Text + subscriptionFirstChargeTime String @default("") @db.Text + subscriptionExpiryTime String @default("") @db.Text + subscriptionReturnUrl String @default("") @db.Text + subscriptionNotifyUrl String @default("") @db.Text + subscriptionAction String @default("PAUSE") + + // ── PAYOUT FIELDS ───────────────────────────────────────── + beneId String @default("") @db.Text + beneName String @default("") @db.Text + beneEmail String @default("") @db.Text + benePhone String @default("") @db.Text + beneBankAccount String @default("") @db.Text + beneBankIfsc String @default("") @db.Text + beneVpa String @default("") @db.Text + beneAddress String @default("") @db.Text + beneCity String @default("") @db.Text + beneState String @default("India") + benePincode String @default("") @db.Text + + transferId String @default("") @db.Text + transferAmount String @default("") @db.Text + transferRemarks String @default("") @db.Text + transferMode String @default("banktransfer") + batchTransferId String @default("") @db.Text + batchEntries String @default("[]") @db.Text + + // ── UPI FIELDS ──────────────────────────────────────────── + upiVpa String @default("") @db.Text + upiAmount String @default("") @db.Text + upiDescription String @default("") @db.Text + + // ── OFFER FIELDS ────────────────────────────────────────── + offerId String @default("") @db.Text + offerMeta String @default("{}") @db.Text + offerValidations String @default("{}") @db.Text + offerDetails String @default("{}") @db.Text + + // ── WEBHOOK FIELDS ──────────────────────────────────────── + webhookSignature String @default("") @db.Text + webhookTimestamp String @default("") @db.Text + webhookRawBody String @default("") @db.Text + webhookThrowOnFail Boolean @default(true) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} diff --git a/prisma/schema/nodes_crm.prisma b/prisma/schema/nodes_crm.prisma new file mode 100644 index 0000000..0f62fa6 --- /dev/null +++ b/prisma/schema/nodes_crm.prisma @@ -0,0 +1,318 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: CRM Node Models +// ZohoCrmNode, HubspotNode, FreshdeskNode +// ───────────────────────────────────────────────────────────── + +model ZohoCrmNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id]) + + operation ZohoCrmOperation @default(CREATE_LEAD) + variableName String @default("zoho") + + module String @default("Leads") + recordId String @default("") @db.Text + + // Lead / Contact shared fields + firstName String @default("") @db.Text + lastName String @default("") @db.Text + email String @default("") @db.Text + phone String @default("") @db.Text + mobile String @default("") @db.Text + company String @default("") @db.Text + title String @default("") @db.Text + website String @default("") @db.Text + leadSource String @default("") + leadStatus String @default("") + industry String @default("") + annualRevenue String @default("") @db.Text + noOfEmployees String @default("") @db.Text + rating String @default("") + description String @default("") @db.Text + + // Address (Lead / Contact / Account) + street String @default("") @db.Text + city String @default("") @db.Text + state String @default("") @db.Text + country String @default("India") + zipCode String @default("") @db.Text + + // Deal fields + dealName String @default("") @db.Text + dealStage String @default("") + dealAmount String @default("") @db.Text + closingDate String @default("") @db.Text + accountName String @default("") @db.Text + contactName String @default("") @db.Text + probability String @default("") @db.Text + dealType String @default("") + + // Account fields + accountOwner String @default("") @db.Text + billingCity String @default("") @db.Text + billingState String @default("") @db.Text + + // Task / Activity fields + subject String @default("") @db.Text + dueDate String @default("") @db.Text + priority String @default("High") + status String @default("Not Started") + whoId String @default("") @db.Text + whatId String @default("") @db.Text + whoModule String @default("Contacts") + whatModule String @default("Deals") + + // Call log fields + callDuration String @default("") @db.Text + callDirection String @default("Outbound") + callResult String @default("") + callStartTime String @default("") @db.Text + callDescription String @default("") @db.Text + + // Meeting fields + meetingStart String @default("") @db.Text + meetingEnd String @default("") @db.Text + meetingAgenda String @default("") @db.Text + participants String @default("[]") @db.Text + + // Note fields + noteTitle String @default("") @db.Text + noteContent String @default("") @db.Text + parentModule String @default("Leads") + + // Search / filter fields + searchTerm String @default("") @db.Text + searchField String @default("Email") + criteria String @default("") @db.Text + page Int @default(1) + perPage Int @default(10) + + // Convert lead + createDeal Boolean @default(false) + overwrite Boolean @default(false) + + // Custom fields JSON — escape hatch for any Zoho field not listed above + customFields String @default("{}") @db.Text + + // Upsert + duplicateCheckField String @default("Email") + + continueOnFail Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model HubspotNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id]) + + operation HubspotOperation @default(CREATE_CONTACT) + variableName String @default("hubspot") + + // ── OBJECT TYPE (for generic ops) ──────────────────────── + objectType String @default("contacts") + + // ── RECORD IDENTIFICATION ───────────────────────────────── + recordId String @default("") @db.Text + + // ── CONTACT FIELDS ──────────────────────────────────────── + email String @default("") @db.Text + firstName String @default("") @db.Text + lastName String @default("") @db.Text + phone String @default("") @db.Text + website String @default("") @db.Text + company String @default("") @db.Text + jobTitle String @default("") @db.Text + lifecycleStage String @default("") + leadStatus String @default("") + + // ── COMPANY FIELDS ──────────────────────────────────────── + companyName String @default("") @db.Text + domain String @default("") @db.Text + industry String @default("") + annualRevenue String @default("") @db.Text + numberOfEmployees String @default("") @db.Text + city String @default("") @db.Text + state String @default("") @db.Text + country String @default("India") + + // ── DEAL FIELDS ─────────────────────────────────────────── + dealName String @default("") @db.Text + dealStage String @default("") + pipeline String @default("default") + amount String @default("") @db.Text + closeDate String @default("") @db.Text + dealType String @default("") + priority String @default("") + + // ── TICKET FIELDS ───────────────────────────────────────── + ticketName String @default("") @db.Text + ticketPipeline String @default("0") + ticketStatus String @default("") + ticketPriority String @default("") + ticketDescription String @default("") @db.Text + ticketSource String @default("") + + // ── ACTIVITY FIELDS ─────────────────────────────────────── + noteBody String @default("") @db.Text + taskSubject String @default("") @db.Text + taskBody String @default("") @db.Text + taskStatus String @default("NOT_STARTED") + taskPriority String @default("NONE") + taskDueDate String @default("") @db.Text + callBody String @default("") @db.Text + callDuration String @default("") @db.Text + callDirection String @default("OUTBOUND") + callDisposition String @default("") + emailSubject String @default("") @db.Text + emailBody String @default("") @db.Text + emailFrom String @default("") @db.Text + emailTo String @default("") @db.Text + + // ── ASSOCIATION FIELDS ──────────────────────────────────── + fromObjectType String @default("contacts") + fromObjectId String @default("") @db.Text + toObjectType String @default("deals") + toObjectId String @default("") @db.Text + associationType String @default("") + + // ── LIST FIELDS ─────────────────────────────────────────── + listId String @default("") @db.Text + + // ── SEARCH / FILTER FIELDS ──────────────────────────────── + searchQuery String @default("") @db.Text + filterProperty String @default("") + filterOperator String @default("EQ") + filterValue String @default("") @db.Text + sortProperty String @default("createdate") + sortDirection String @default("DESCENDING") + limit Int @default(10) + after String @default("") + + // ── CUSTOM PROPERTIES (JSON) ────────────────────────────── + // Escape hatch for any HubSpot property not listed above + customProperties String @default("{}") @db.Text + + continueOnFail Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model FreshdeskNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id]) + + operation FreshdeskOperation @default(CREATE_TICKET) + variableName String @default("freshdesk") + + // ── TICKET FIELDS ───────────────────────────────────────── + subject String @default("") @db.Text + description String @default("") @db.Text + descriptionHtml String @default("") @db.Text + email String @default("") @db.Text + name String @default("") @db.Text + phone String @default("") @db.Text + mobilePhone String @default("") @db.Text + subject2 String @default("") @db.Text + ticketId String @default("") @db.Text + + // Priority: 1=Low, 2=Medium, 3=High, 4=Urgent + priority Int @default(2) + + // Status: 2=Open, 3=Pending, 4=Resolved, 5=Closed + status Int @default(2) + + // Source: 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Twitter DM, 10=WhatsApp + source Int @default(2) + + // Type: Question, Incident, Problem, Feature Request, Refund Request, etc. + ticketType String @default("") + + responderId String @default("") @db.Text + groupId String @default("") @db.Text + productId String @default("") @db.Text + companyId String @default("") @db.Text + fwdEmail String @default("") @db.Text + + // Tags — comma-separated list + tags String @default("") @db.Text + + // Custom fields JSON — { "cf_order_id": "ORD-123" } + customFields String @default("{}") @db.Text + + // ── NOTE FIELDS ─────────────────────────────────────────── + noteId String @default("") @db.Text + noteBody String @default("") @db.Text + notePrivate Boolean @default(false) + noteUserId String @default("") @db.Text + + // ── CONTACT FIELDS ──────────────────────────────────────── + contactId String @default("") @db.Text + contactEmail String @default("") @db.Text + contactName String @default("") @db.Text + contactPhone String @default("") @db.Text + contactMobile String @default("") @db.Text + contactJobTitle String @default("") @db.Text + contactTimeZone String @default("") @db.Text + contactLanguage String @default("") @db.Text + contactTags String @default("") @db.Text + contactCompanyId String @default("") @db.Text + mergeTargetId String @default("") @db.Text + + // ── COMPANY FIELDS ──────────────────────────────────────── + companyName String @default("") @db.Text + companyDomain String @default("") @db.Text + companyDescription String @default("") @db.Text + companyNote String @default("") @db.Text + + // ── AGENT FIELDS ────────────────────────────────────────── + agentId String @default("") @db.Text + + // ── REPLY FIELDS ────────────────────────────────────────── + replyBody String @default("") @db.Text + replyFrom String @default("") @db.Text + replyTo String @default("") @db.Text + replyCc String @default("") @db.Text + replyBcc String @default("") @db.Text + + // ── SEARCH / FILTER FIELDS ──────────────────────────────── + searchQuery String @default("") @db.Text + filterBy String @default("") + orderBy String @default("created_at") + orderType String @default("desc") + page Int @default(1) + perPage Int @default(30) + updatedSince String @default("") @db.Text + includeStats Boolean @default(false) + + continueOnFail Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} diff --git a/prisma/schema/nodes_flow_control.prisma b/prisma/schema/nodes_flow_control.prisma new file mode 100644 index 0000000..43b7562 --- /dev/null +++ b/prisma/schema/nodes_flow_control.prisma @@ -0,0 +1,173 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Flow Control Node Models +// IfElseNode, SwitchNode, LoopNode, WaitNode, +// MergeNode, SetVariableNode, CodeNode +// ───────────────────────────────────────────────────────────── + +model IfElseNode { + id String @id @default(cuid()) + workflowId String + nodeId String @unique + + // Condition configuration (legacy single-condition fields, kept for backward compatibility) + field String @default("") + operator IfElseOperator @default(EQUALS) + value String @default("") + + // Compound conditions JSON (AND/OR groups) + conditionsJson String @default("") @db.Text + + // Metadata + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model SwitchNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + variableName String @default("switch") + casesJson String @default("[]") @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([nodeId]) +} + +model SetVariableNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + pairs Json // stores array of {key: string, value: string} + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model CodeNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + code String @db.Text + language String @default("javascript") + outputMode String @default("append") + timeout Int @default(5000) + continueOnFail Boolean @default(false) + allowedDomains String @default("") + variableName String @default("codeOutput") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model LoopNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + inputPath String @default("googleSheets.rows") + itemVariable String @default("item") + maxIterations Int @default(100) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model WaitNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + // Resume mode: "duration" | "until" | "webhook" + waitMode String @default("duration") + + // Duration mode fields + duration Int @default(30) + durationUnit String @default("minutes") + + // Until mode fields + untilDatetime String @default("") + timezone String @default("UTC") + + // Webhook mode fields + timeoutDuration Int @default(24) + timeoutUnit String @default("hours") + continueOnTimeout Boolean @default(true) + + // Output + variableName String @default("wait") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model MergeNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + // How many input branches to wait for (2-10) + inputCount Int @default(2) + + // Merge strategy: + // "position" — zip by index position + // "combine" — combine all fields from all branches into one object (default) + // "crossJoin" — cartesian product of all items + // "keyMatch" — inner join by a key field + // "keyDiff" — outer difference — items in branch1 NOT in branch2 + mergeMode String @default("combine") + + // For keyMatch and keyDiff modes: + // The dot-path field to match on e.g. "orderId" or "email" + // Supports {{variables}} — resolved at runtime + matchKey1 String @default("") + matchKey2 String @default("") + + // For position mode: + // "shortest" — zip to shortest branch length + // "longest" — pad shorter branches with null + positionFill String @default("shortest") + + // Branch variable name selection + // branchKey1/branchKey2 — variable names of the first/second branch (e.g. "gmail", "slack") + // branchKeys — comma-separated list for 3+ branch modes (e.g. "gmail,slack,notion") + branchKey1 String @default("") + branchKey2 String @default("") + branchKeys String @default("") + + // Wait for ALL branches to complete before merging + waitForAll Boolean @default(true) + + // Output variable name + variableName String @default("merge") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} diff --git a/prisma/schema/nodes_google.prisma b/prisma/schema/nodes_google.prisma new file mode 100644 index 0000000..5ee3158 --- /dev/null +++ b/prisma/schema/nodes_google.prisma @@ -0,0 +1,102 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Google Integration Node Models +// GmailNode, GoogleSheetsNode, GoogleDriveNode +// ───────────────────────────────────────────────────────────── + +model GmailNode { + id String @id @default(cuid()) + workflowId String + nodeId String @unique + credentialId String + + operation GmailOperation @default(SEND) + variableName String @default("gmail") + + // Email fields — support template variables like {{body.name}} + to String @default("") + subject String @default("") + body String @default("") @db.Text + isHtml Boolean @default(false) + + // New fields for extended operations + cc String @default("") + bcc String @default("") + replyTo String @default("") + messageId String @default("") + threadId String @default("") + searchQuery String @default("") + maxResults Int @default(10) + labelIds String @default("") + includeBody Boolean @default(true) + includeHeaders Boolean @default(false) + pageToken String @default("") + attachmentData String @default("") @db.Text + attachmentName String @default("") + attachmentMime String @default("application/octet-stream") + + // New fields for extended operations + attachmentId String @default("") + attachmentOutputFormat String @default("base64") + removeLabelIds String @default("") + labelName String @default("") + draftId String @default("") + messageIds String @default("") @db.Text + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model GoogleSheetsNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + operation GoogleSheetsOp @default(APPEND_ROW) + spreadsheetId String @default("") + sheetName String @default("Sheet1") + range String @default("A:Z") + rowData Json @default("[]") + credentialId String @default("") + variableName String @default("googleSheets") + headerRow Boolean @default(true) + rowNumber String @default("") + rowValues String @default("") @db.Text + searchColumn String @default("") + searchValue String @default("") + clearRange String @default("") + newSheetName String @default("") + valueInputOption String @default("USER_ENTERED") + matchColumn String @default("") + matchValue String @default("") + updateValues String @default("") @db.Text + maxResults Int @default(100) + includeEmptyRows Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model GoogleDriveNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + operation GoogleDriveOperation @default(UPLOAD_FILE) + folderId String? + fileId String? + fileName String? + mimeType String? + query String? + maxResults Int @default(10) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + workflow workflow @relation(fields: [workflowId], references: [id]) +} diff --git a/prisma/schema/nodes_messaging.prisma b/prisma/schema/nodes_messaging.prisma new file mode 100644 index 0000000..4b818ca --- /dev/null +++ b/prisma/schema/nodes_messaging.prisma @@ -0,0 +1,152 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Messaging Node Models +// SlackNode, WhatsAppNode, NotionNode, Msg91Node +// ───────────────────────────────────────────────────────────── + +model SlackNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + operation SlackOperation @default(MESSAGE_SEND_WEBHOOK) + variableName String @default("slack") + channel String @default("") + message String @default("") @db.Text + threadTs String @default("") + messageTs String @default("") + searchQuery String @default("") + channelName String @default("") + channelTopic String @default("") + channelPurpose String @default("") + userId String @default("") + emoji String @default("") + fileComment String @default("") + webhookUrl String @default("") + blockKit String @default("") @db.Text + botName String @default("") + iconEmoji String @default("") + unfurlLinks Boolean @default(true) + channelTypes String @default("public_channel,private_channel") + limit Int @default(100) + excludeArchived Boolean @default(true) + isPrivate Boolean @default(false) + filename String @default("") + fileType String @default("") + title String @default("") + initialComment String @default("") + email String @default("") + statusText String @default("") + statusEmoji String @default("") + statusExpiration String @default("") + sendAt String @default("") + content String @default("") @db.Text + fileId String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model WhatsAppNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + 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]) +} + +model NotionNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + operation NotionOperation @default(QUERY_DATABASE) + databaseId String @default("") + pageId String @default("") + blockContent String @default("") @db.Text + searchQuery String @default("") + filterJson String @default("{}") @db.Text + sortsJson String @default("[]") @db.Text + propertiesJson String @default("{}") @db.Text + notionUserId String @default("") + pageSize Int @default(100) + startCursor String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model Msg91Node { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + + operation Msg91Operation @default(SEND_OTP) + variableName String @default("msg91") + + mobile String @default("") @db.Text + senderId String @default("") + flowId String @default("") + smsVariables String @default("{}") @db.Text + message String @default("") @db.Text + route String @default("4") + + bulkData String @default("[]") @db.Text + + scheduleTime String @default("") + + otpTemplateId String @default("") + otpExpiry Int @default(10) + otpLength Int @default(6) + otpValue String @default("") + retryType String @default("text") + + whatsappTemplate String @default("") + whatsappLang String @default("en") + whatsappParams String @default("[]") @db.Text + integratedNumber String @default("") + + mediaType String @default("image") + mediaUrl String @default("") + mediaCaption String @default("") + + voiceMessage String @default("") + + toEmail String @default("") + subject String @default("") + emailBody String @default("") @db.Text + fromEmail String @default("") + fromName String @default("") + + requestId String @default("") + + continueOnFail Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} diff --git a/prisma/schema/nodes_utility.prisma b/prisma/schema/nodes_utility.prisma new file mode 100644 index 0000000..9057f61 --- /dev/null +++ b/prisma/schema/nodes_utility.prisma @@ -0,0 +1,310 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Utility & Misc Node Models +// AINode, MediaUploadNode, SortNode, FilterNode, +// AggregateNode, PostgresNode +// ───────────────────────────────────────────────────────────── + +model AINode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + credentialId String? + + provider AIProvider + operation AIOperation @default(CHAT) + variableName String @default("ai") + + model String @default("") + + systemPrompt String @default("") @db.Text + userPrompt String @default("") @db.Text + + temperature Float @default(0.7) + maxTokens Int @default(1000) + topP Float @default(1.0) + frequencyPenalty Float @default(0.0) + presencePenalty Float @default(0.0) + + responseFormat String @default("text") + jsonSchema String @default("") @db.Text + + toolsJson String @default("[]") @db.Text + toolChoice String @default("auto") + + imageUrl String @default("") + imageUrls String @default("[]") @db.Text + imageDetail String @default("auto") + + historyKey String @default("") + maxHistory Int @default(10) + + embeddingInput String @default("") @db.Text + + audioUrl String @default("") + audioLanguage String @default("") + transcriptionFormat String @default("text") + + imagePrompt String @default("") @db.Text + imageSize String @default("1024x1024") + imageQuality String @default("standard") + imageStyle String @default("vivid") + imageCount Int @default(1) + + classifyLabels String @default("") @db.Text + classifyExamples String @default("[]") @db.Text + + continueOnFail Boolean @default(false) + timeout Int @default(60000) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model MediaUploadNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + source MediaUploadSource @default(URL) + inputField String @default("") + mimeTypeHint String @default("image/png") + filename String @default("") + + // For Google Drive source + credentialId String? + + variableName String @default("media") + continueOnFail Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model SortNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + // Operation mode + // "SORT_ARRAY" — sort array of objects or primitives + // "SORT_KEYS" — sort the keys of an object + // "REVERSE" — reverse array order + // "SHUFFLE" — random order (Fisher-Yates) + operation String @default("SORT_ARRAY") + + // JSON-serialized array of SortKey objects: + // [{ field: string, direction: "asc"|"desc", type: "auto"|"string"| + // "number"|"date"|"boolean", nulls: "first"|"last", + // caseSensitive: boolean, locale: string, natural: boolean }] + sortKeys String @default("[]") @db.Text + + // Input path — dot-notation path to the array inside context + // e.g. "googleSheets.rows" or "razorpayTrigger.payload.items" + // Empty string = use the entire previous node output + inputPath String @default("") + + // Variable name for output + variableName String @default("sort") + + // Whether to sort in-place (mutate) or return new array + // Always false in executor — immutable by default + // Kept for future UI hint + inPlace Boolean @default(false) + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model FilterNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + operation String @default("FILTER_ARRAY") + inputArray String @default("") @db.Text + inputObject String @default("") @db.Text + conditionGroups String @default("[]") @db.Text + rootLogic String @default("AND") + outputMode String @default("filtered") + variableName String @default("filter") + stopOnEmpty Boolean @default(false) + includeMetadata Boolean @default(false) + continueOnFail Boolean @default(false) + + // FILTER_OBJECT_KEYS specific: + keyFilterMode String @default("key_name") + keepMatching Boolean @default(true) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) + @@index([nodeId]) +} + +model AggregateNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + // Primary operation + operation String @default("COUNT") + + // Dot-notation path to the input array + inputPath String @default("") @db.Text + + // Dot-notation field within each item to aggregate + field String @default("") @db.Text + + // For GROUP_BY: field to group items by + groupByField String @default("") + + // For PIVOT: row field, column field, value field, value operation + pivotRowField String @default("") + pivotColField String @default("") + pivotValueField String @default("") + pivotValueOp String @default("SUM") + + // For CONCATENATE: separator string (default ", ") + separator String @default(", ") + + // For PERCENTILE: the percentile to compute (0–100) + percentile Float @default(90) + + // For COUNT: optional filter condition (JSON FilterCondition[]) + countFilter String @default("") @db.Text + + // For MULTI: JSON array of AggregateOperation objects + multiOps String @default("[]") @db.Text + + // For GROUP_BY with nested aggregation: + groupAggOps String @default("[]") @db.Text + + // Output options + variableName String @default("aggregate") + includeInput Boolean @default(false) + sortOutput Boolean @default(true) + topN Int @default(0) + nullHandling String @default("exclude") + roundDecimals Int @default(2) + continueOnFail Boolean @default(false) + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([workflowId]) + @@index([nodeId]) +} + +model PostgresNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + // The operation to perform + operation String @default("EXECUTE_QUERY") + + // Credential reference + credentialId String? + + // For visual query builder (SELECT, UPDATE, DELETE, COUNT, EXISTS, UPSERT): + tableName String @default("") // target table + schemaName String @default("public") + + // SELECT options: + // JSON array of column names to return. [] = SELECT * + selectColumns String @default("[]") @db.Text + // JSON array of WhereCondition objects + whereConditions String @default("[]") @db.Text + // JSON array of OrderBy objects: [{ column, direction }] + orderBy String @default("[]") @db.Text + // LIMIT rows. 0 = no limit + limitRows Int @default(0) + // OFFSET for pagination + offsetRows Int @default(0) + // JOIN configuration: JSON array of JoinClause objects + joins String @default("[]") @db.Text + + // INSERT / UPSERT: + // JSON object or template string that resolves to row data + insertData String @default("") @db.Text + // For UPSERT: column(s) to check for conflict + conflictColumns String @default("[]") @db.Text + // For UPSERT: columns to update on conflict. [] = all non-conflict columns + updateOnConflict String @default("[]") @db.Text + + // UPDATE: + // JSON object of { column: newValue } — supports template expressions + updateData String @default("") @db.Text + + // INSERT_MANY: + // Template path to array of row objects: "{{filter.items}}" + insertManyPath String @default("") @db.Text + // Column names to insert. [] = infer from first row + insertManyColumns String @default("[]") @db.Text + + // EXECUTE_QUERY: + // Raw SQL with $1, $2... placeholders + query String @default("") @db.Text + // JSON array of query parameters [value1, value2...] + queryParams String @default("[]") @db.Text + + // EXECUTE_TRANSACTION: + // JSON array of { query, params } objects — run atomically + transactionStatements String @default("[]") @db.Text + + // EXECUTE_FUNCTION: + functionName String @default("") + functionArgs String @default("[]") @db.Text // JSON array + + // FULL_TEXT_SEARCH: + searchColumn String @default("") + searchQuery String @default("") + searchLanguage String @default("english") // pg language: "simple", "english", etc. + searchLimit Int @default(10) + + // JSON_PATH_QUERY: + jsonColumn String @default("") + jsonPath String @default("") // e.g. '$.payment.amount' + + // JSON_SET_FIELD: + jsonSetColumn String @default("") + jsonSetPath String @default("") // e.g. '{payment,status}' + jsonSetValue String @default("") // new value (supports template) + + // CREATE_TABLE: + // JSON array of ColumnDefinition objects + columnDefinitions String @default("[]") @db.Text + createTableIfNotExists Boolean @default(true) + + // Output options + variableName String @default("postgres") + returnData Boolean @default(true) // whether to return affected rows + continueOnFail Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) + + @@index([workflowId]) + @@index([nodeId]) + @@index([credentialId]) +} diff --git a/prisma/schema/triggers.prisma b/prisma/schema/triggers.prisma new file mode 100644 index 0000000..3df5cda --- /dev/null +++ b/prisma/schema/triggers.prisma @@ -0,0 +1,136 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: Trigger Models +// WebhookTrigger, ScheduleTrigger, RazorpayTrigger, +// WhatsAppTrigger, ErrorTriggerNode, GmailWatcher +// ───────────────────────────────────────────────────────────── + +model WebhookTrigger { + id String @id @default(cuid()) + workflowId String @unique + webhookId String @unique @default(uuid()) + secretToken String + isActive Boolean @default(true) + httpMethod HttpMethod @default(POST) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([webhookId]) + @@index([workflowId]) +} + +model ScheduleTrigger { + id String @id @default(cuid()) + workflowId String @unique + cronExpression String + timezone String @default("UTC") + isActive Boolean @default(true) + inngestFunctionId String @unique + lastRunAt DateTime? + nextRunAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([workflowId]) +} + +model ErrorTriggerNode { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + variableName String @default("errorTrigger") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([nodeId]) + @@index([workflowId]) +} + +model RazorpayTrigger { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + webhookId String @unique @default(uuid()) + + // DEPRECATED — plaintext, kept for backward-compat until data migration is complete. + // TODO: Once all rows have webhookSecretEncrypted set and webhookSecret IS NULL, + // create a follow-up migration to: DROP COLUMN webhookSecret and set + // webhookSecretEncrypted NOT NULL. + webhookSecret String? // legacy plaintext (pre-encryption rows) + + // AES-256 encrypted webhook secret. Prefer this over webhookSecret. + // Populated by new saves and by `npm run migrate:razorpay-secrets`. + webhookSecretEncrypted String? // encrypted; null until migrated + + activeEvents String @default("[]") @db.Text + variableName String @default("razorpayTrigger") + isActive Boolean @default(true) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([nodeId]) + @@index([workflowId]) + @@index([webhookId]) +} + +model WhatsAppTrigger { + id String @id @default(cuid()) + nodeId String @unique + workflowId String + + webhookId String @unique @default(uuid()) + + // DEPRECATED — plaintext, kept for backward-compat until data migration is complete. + // TODO: Once all rows have verifyTokenEncrypted set and verifyToken IS NULL, + // create a follow-up migration to: DROP COLUMN verifyToken and set + // verifyTokenEncrypted NOT NULL. + verifyToken String? // legacy plaintext (pre-encryption rows) + + // AES-256 encrypted verify token. Prefer this over verifyToken. + // Populated by new saves and by `npm run migrate:whatsapp-tokens`. + verifyTokenEncrypted String? // encrypted; null until migrated + + phoneNumberId String @default("") + activeEvents String @default("[]") @db.Text + messageTypes String @default("[]") @db.Text + ignoreOwnMessages Boolean @default(true) + variableName String @default("whatsappTrigger") + isActive Boolean @default(true) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([nodeId]) + @@index([workflowId]) + @@index([webhookId]) +} + +model GmailWatcher { + id String @id @default(cuid()) + workflowId String + nodeId String @unique + credentialId String + email String + active Boolean @default(true) + lastHistoryId String @default("") + expiration String @default("") + filterQuery String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + @@index([email, active]) +} diff --git a/src/app/api/auth/custom-signup/route.ts b/src/app/api/auth/custom-signup/route.ts index 54a80ca..0456ef5 100644 --- a/src/app/api/auth/custom-signup/route.ts +++ b/src/app/api/auth/custom-signup/route.ts @@ -31,11 +31,9 @@ export async function POST(req: NextRequest) { emailVerifyExpiry: getTokenExpiry(), }, }) - try { - await sendVerificationEmail(email, existing.name || email, token) - } catch (emailError) { + sendVerificationEmail(email, existing.name || email, token).catch(emailError => { console.error("Failed to send verification email:", emailError) - } + }) return NextResponse.json({ success: "VERIFICATION_SENT", email }) } } @@ -74,16 +72,17 @@ export async function POST(req: NextRequest) { }) // 5. Send verification email (non-blocking) - try { - await sendVerificationEmail(email, user.name || email, verifyToken) - } catch (emailError) { + sendVerificationEmail(email, user.name || email, verifyToken).catch(emailError => { console.error("Failed to send verification email:", emailError) - } + }) // 6. DO NOT log them in — return success directive return NextResponse.json({ success: "VERIFICATION_SENT", email }) } catch (error) { console.error("Signup error:", error) - return NextResponse.json({ error: "Internal server error" }, { status: 500 }) + return NextResponse.json({ + error: "Internal server error", + details: error instanceof Error ? error.message : String(error) + }, { status: 500 }) } } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index fa0fad8..e6af4dd 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -23,7 +23,7 @@ import prisma from "./db" trustedOrigins: [ "https://nodebase.mayanksaraswal.in", "https://www.nodebase.mayanksaraswal.in", - // DigitalOcean App Platform URLs are handled by nodebase.mayanksaraswal.in domain + "https://nodebase-l8md4.ondigitalocean.app", "https://vast-lemur-notable.ngrok-free.app", "http://localhost:3000" ], From 9b76b95e8f488c50d68b38820cb364595298a696 Mon Sep 17 00:00:00 2001 From: Mayank Saini Date: Thu, 4 Jun 2026 18:48:22 +0530 Subject: [PATCH 2/6] feat: implement Slack integration with tRPC router and modular Prisma schema architecture --- .gitignore | 1 + package.json | 3 +- prisma/schema/auth.prisma | 55 +- prisma/schema/base.prisma | 4 +- prisma/schema/core.prisma | 86 +- .../migration.sql | 1795 +++++++++++++++++ prisma/schema/migrations/migration_lock.toml | 3 + prisma/schema/nodes_commerce.prisma | 409 ++-- prisma/schema/nodes_crm.prisma | 392 ++-- prisma/schema/nodes_flow_control.prisma | 54 +- prisma/schema/nodes_google.prisma | 41 +- prisma/schema/nodes_messaging.prisma | 140 +- prisma/schema/nodes_utility.prisma | 162 +- prisma/schema/triggers.prisma | 92 +- scripts/migrate-webhook-secrets.ts | 68 + src/app/api/auth/custom-signup/route.ts | 3 +- .../components/google-sheets/dialog.tsx | 2 +- .../executions/components/slack/dialog.tsx | 6 - .../executions/components/slack/executor.ts | 4 +- src/lib/auth-client.ts | 2 +- src/lib/auth.ts | 2 +- src/lib/webhook-secret.ts | 51 + src/server/routers/slack.router.ts | 2 - src/server/routers/webhook-trigger.router.ts | 3 +- 24 files changed, 2658 insertions(+), 722 deletions(-) create mode 100644 prisma/schema/migrations/20260604130200_schema_refactoring/migration.sql create mode 100644 prisma/schema/migrations/migration_lock.toml create mode 100644 scripts/migrate-webhook-secrets.ts create mode 100644 src/lib/webhook-secret.ts diff --git a/.gitignore b/.gitignore index 68d0225..7ae61b6 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ scripts/set-azure-env-vars.sh NODEBASE_CONTEXT.md tsc-errors.txt *.tsc-errors.txt +github-node.md \ No newline at end of file diff --git a/package.json b/package.json index 2cfe8cd..9c46d33 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "test": "vitest run", "test:watch": "vitest", "migrate:razorpay-secrets": "tsx scripts/migrate-razorpay-secrets.ts", - "migrate:whatsapp-tokens": "tsx scripts/migrate-whatsapp-tokens.ts" + "migrate:whatsapp-tokens": "tsx scripts/migrate-whatsapp-tokens.ts", + "migrate:webhook-secrets": "tsx scripts/migrate-webhook-secrets.ts" }, "dependencies": { "@ai-sdk/anthropic": "^2.0.37", diff --git a/prisma/schema/auth.prisma b/prisma/schema/auth.prisma index 9c04e62..cc7df0a 100644 --- a/prisma/schema/auth.prisma +++ b/prisma/schema/auth.prisma @@ -4,33 +4,32 @@ // ───────────────────────────────────────────────────────────── model User { - id String @id - name String - email String - emailVerified Boolean @default(false) - emailVerifyToken String? @unique - emailVerifyExpiry DateTime? - emailVerifyAttempts Int @default(0) - image String? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - sessions Session[] - accounts Account[] - workflows workflow[] - credentials Credential[] - executionCount Int @default(0) - executionResetAt DateTime @default(now()) + id String @id + name String + email String + emailVerified Boolean @default(false) + emailVerifyToken String? @unique + emailVerifyExpiry DateTime? + emailVerifyAttempts Int @default(0) + image String? + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + sessions Session[] + accounts Account[] + workflows workflow[] + credentials Credential[] + executionCount Int @default(0) // ── Subscription / Billing ─────────────────────────────────────────────── - plan String @default("FREE") // FREE|STARTER|PRO|TEAM - planStatus String @default("active") // active|past_due|cancelled|paused - razorpayCustomerId String? @unique // cust_xxxx - razorpaySubId String? @unique // sub_xxxx - currentPeriodEnd DateTime? - cancelAtPeriodEnd Boolean @default(false) - workflowRunsUsed Int @default(0) - workflowRunsReset DateTime @default(now()) - billingEvents BillingEvent[] + plan String @default("FREE") // FREE|STARTER|PRO|TEAM + planStatus String @default("active") // active|past_due|cancelled|paused + razorpayCustomerId String? @unique // cust_xxxx + razorpaySubId String? @unique // sub_xxxx + currentPeriodEnd DateTime? + cancelAtPeriodEnd Boolean @default(false) + workflowRunsUsed Int @default(0) + workflowRunsReset DateTime @default(now()) + billingEvents BillingEvent[] @@unique([email]) @@map("user") @@ -39,12 +38,12 @@ model User { model BillingEvent { id String @id @default(cuid()) userId String - type String // subscription.activated | payment.captured | subscription.cancelled | etc. + type String // subscription.activated | payment.captured | subscription.cancelled | etc. razorpayEventId String? @unique - amount Int? // paise + amount Int? // paise currency String @default("INR") plan String? - status String // success | failed | pending + status String // success | failed | pending rawPayload String @db.Text // full Razorpay webhook JSON createdAt DateTime @default(now()) diff --git a/prisma/schema/base.prisma b/prisma/schema/base.prisma index dbeb797..b76d153 100644 --- a/prisma/schema/base.prisma +++ b/prisma/schema/base.prisma @@ -4,8 +4,8 @@ // ───────────────────────────────────────────────────────────── generator client { - provider = "prisma-client-js" - output = "../../src/generated/prisma" + provider = "prisma-client-js" + output = "../../src/generated/prisma" } datasource db { diff --git a/prisma/schema/core.prisma b/prisma/schema/core.prisma index 1c99c19..da94ceb 100644 --- a/prisma/schema/core.prisma +++ b/prisma/schema/core.prisma @@ -29,33 +29,33 @@ model workflow { switchNodes SwitchNode[] waitNodes WaitNode[] mergeNodes MergeNode[] - errorTriggerNodes ErrorTriggerNode[] - razorpayTriggers RazorpayTrigger[] - whatsappTriggers WhatsAppTrigger[] - msg91Nodes Msg91Node[] - shiprocketNodes ShiprocketNode[] - zohoCrmNodes ZohoCrmNode[] - hubspotNodes HubspotNode[] - freshdeskNodes FreshdeskNode[] - aiNodes AINode[] - mediaUploadNodes MediaUploadNode[] - sortNodes SortNode[] - filterNodes FilterNode[] - cashfreeNodes CashfreeNode[] - aggregateNodes AggregateNode[] - postgresNodes PostgresNode[] + errorTriggerNodes ErrorTriggerNode[] + razorpayTriggers RazorpayTrigger[] + whatsappTriggers WhatsAppTrigger[] + msg91Nodes Msg91Node[] + shiprocketNodes ShiprocketNode[] + zohoCrmNodes ZohoCrmNode[] + hubspotNodes HubspotNode[] + freshdeskNodes FreshdeskNode[] + aiNodes AINode[] + mediaUploadNodes MediaUploadNode[] + sortNodes SortNode[] + filterNodes FilterNode[] + cashfreeNodes CashfreeNode[] + aggregateNodes AggregateNode[] + postgresNodes PostgresNode[] userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model Credential { - id String @id @default(cuid()) - name String - value String - userId String - type CredentialType - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - Node Node[] + id String @id @default(cuid()) + name String + value String + userId String + type CredentialType + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + Node Node[] shiprocketNodes ShiprocketNode[] zohoCrmNodes ZohoCrmNode[] hubspotNodes HubspotNode[] @@ -63,13 +63,23 @@ model Credential { cashfreeNodes CashfreeNode[] postgresNodes PostgresNode[] - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + razorpayNodes RazorpayNode[] + gmailNodes GmailNode[] + googleSheetsNodes GoogleSheetsNode[] + googleDriveNodes GoogleDriveNode[] + slackNodes SlackNode[] + whatsAppNodes WhatsAppNode[] + notionNodes NotionNode[] + msg91Nodes Msg91Node[] + ainodes AINode[] + mediaUploadNodes MediaUploadNode[] // TODO: @@map("Credenial") has a typo — should be "credential" // Fixing requires: ALTER TABLE "Credenial" RENAME TO "credential"; // plus updating all existing FK references. Do in a dedicated migration sprint. - @@map("Credenial") + @@map("credential") } model Node { @@ -120,26 +130,26 @@ model Execution { } model NodeExecution { - id String @id @default(cuid()) - executionId String - execution Execution @relation(fields: [executionId], references: [id], onDelete: Cascade) + id String @id @default(cuid()) + executionId String + execution Execution @relation(fields: [executionId], references: [id], onDelete: Cascade) - nodeId String - nodeName String @default("") - nodeType String @default("") + nodeId String + nodeName String @default("") + nodeType String @default("") - status String @default("success") + status String @default("success") - inputJson String @default("") @db.Text - outputJson String @default("") @db.Text + inputJson String @default("") @db.Text + outputJson String @default("") @db.Text - errorMessage String @default("") + errorMessage String @default("") - durationMs Int @default(0) + durationMs Int @default(0) - executionOrder Int @default(0) + executionOrder Int @default(0) - createdAt DateTime @default(now()) + createdAt DateTime @default(now()) @@index([executionId]) @@index([nodeId]) diff --git a/prisma/schema/migrations/20260604130200_schema_refactoring/migration.sql b/prisma/schema/migrations/20260604130200_schema_refactoring/migration.sql new file mode 100644 index 0000000..560c35f --- /dev/null +++ b/prisma/schema/migrations/20260604130200_schema_refactoring/migration.sql @@ -0,0 +1,1795 @@ +-- CreateEnum +CREATE TYPE "NodeType" AS ENUM ('INITIAL', 'MANUAL_TRIGGER', 'HTTP_REQUEST', 'GOOGLE_FORM_TRIGGER', 'STRIPE_TRIGGER', 'WEBHOOK_TRIGGER', 'SCHEDULE_TRIGGER', 'ANTHROPIC', 'GEMINI', 'OPENAI', 'GROQ', 'XAI', 'DISCORD', 'SLACK', 'DEEPSEEK', 'PERPLEXITY', 'TELEGRAM', 'X', 'WORKDAY', 'IF_ELSE', 'GMAIL', 'SET_VARIABLE', 'GOOGLE_SHEETS', 'GOOGLE_DRIVE', 'CODE', 'WHATSAPP', 'LOOP', 'NOTION', 'RAZORPAY', 'SWITCH', 'WAIT', 'MERGE', 'ERROR_TRIGGER', 'RAZORPAY_TRIGGER', 'WHATSAPP_TRIGGER', 'MSG91', 'SHIPROCKET', 'ZOHO_CRM', 'HUBSPOT', 'FRESHDESK', 'MEDIA_UPLOAD', 'SORT', 'FILTER', 'CASHFREE', 'CASHFREE_TRIGGER', 'AGGREGATE', 'POSTGRES'); + +-- CreateEnum +CREATE TYPE "AIProvider" AS ENUM ('OPENAI', 'ANTHROPIC', 'GEMINI', 'GROQ', 'XAI', 'DEEPSEEK', 'PERPLEXITY'); + +-- CreateEnum +CREATE TYPE "AIOperation" AS ENUM ('CHAT', 'CHAT_WITH_HISTORY', 'STRUCTURED_OUTPUT', 'TOOL_USE', 'VISION', 'EMBED', 'TRANSCRIBE', 'GENERATE_IMAGE', 'CLASSIFY'); + +-- CreateEnum +CREATE TYPE "HttpMethod" AS ENUM ('GET', 'POST', 'PUT', 'PATCH'); + +-- CreateEnum +CREATE TYPE "ExecutionStatus" AS ENUM ('RUNNING', 'SUCCESS', 'FAILED'); + +-- CreateEnum +CREATE TYPE "IfElseOperator" AS ENUM ('EQUALS', 'NOT_EQUALS', 'CONTAINS', 'NOT_CONTAINS', 'STARTS_WITH', 'ENDS_WITH', 'GREATER_THAN', 'LESS_THAN', 'GREATER_THAN_OR_EQUAL', 'LESS_THAN_OR_EQUAL', 'IS_EMPTY', 'IS_NOT_EMPTY', 'IS_TRUE', 'IS_FALSE', 'REGEX_MATCH'); + +-- CreateEnum +CREATE TYPE "CredentialType" AS ENUM ('OPENAI', 'ANTHROPIC', 'GEMINI', 'DEEPSEEK', 'PERPLEXITY', 'XAI', 'GROQ', 'GMAIL', 'GMAIL_OAUTH', 'GOOGLE_SHEETS', 'GOOGLE_DRIVE', 'WHATSAPP', 'NOTION', 'RAZORPAY', 'SLACK', 'MSG91', 'SHIPROCKET', 'ZOHO_CRM', 'HUBSPOT', 'FRESHDESK', 'CASHFREE', 'POSTGRES'); + +-- CreateEnum +CREATE TYPE "GmailOperation" AS ENUM ('SEND', 'REPLY', 'FORWARD', 'GET_MESSAGE', 'LIST_MESSAGES', 'SEARCH_MESSAGES', 'ADD_LABEL', 'REMOVE_LABEL', 'MARK_READ', 'MARK_UNREAD', 'MOVE_TO_TRASH', 'CREATE_DRAFT', 'GET_ATTACHMENT', 'GET_THREAD', 'LIST_LABELS', 'CREATE_LABEL', 'LIST_DRAFTS', 'SEND_DRAFT'); + +-- CreateEnum +CREATE TYPE "GoogleSheetsOp" AS ENUM ('APPEND_ROW', 'READ_ROWS', 'UPDATE_ROW', 'UPDATE_ROWS_BY_QUERY', 'DELETE_ROW', 'GET_ROW_BY_NUMBER', 'SEARCH_ROWS', 'CLEAR_RANGE', 'CREATE_SHEET', 'GET_SHEET_INFO'); + +-- CreateEnum +CREATE TYPE "GoogleDriveOperation" AS ENUM ('UPLOAD_FILE', 'DOWNLOAD_FILE', 'LIST_FILES', 'CREATE_FOLDER'); + +-- CreateEnum +CREATE TYPE "WhatsAppOperation" AS ENUM ('SEND_TEXT', 'SEND_TEMPLATE', 'SEND_IMAGE', 'SEND_DOCUMENT', 'SEND_REACTION'); + +-- CreateEnum +CREATE TYPE "NotionOperation" AS ENUM ('QUERY_DATABASE', 'CREATE_DATABASE_PAGE', 'UPDATE_DATABASE_PAGE', 'GET_PAGE', 'ARCHIVE_PAGE', 'APPEND_BLOCK', 'GET_BLOCK_CHILDREN', 'SEARCH', 'GET_DATABASE', 'GET_USER', 'GET_USERS'); + +-- CreateEnum +CREATE TYPE "RazorpayOperation" AS ENUM ('ORDER_CREATE', 'ORDER_FETCH', 'ORDER_FETCH_PAYMENTS', 'ORDER_LIST', 'PAYMENT_FETCH', 'PAYMENT_CAPTURE', 'PAYMENT_LIST', 'PAYMENT_UPDATE', 'REFUND_CREATE', 'REFUND_FETCH', 'REFUND_LIST', 'CUSTOMER_CREATE', 'CUSTOMER_FETCH', 'CUSTOMER_UPDATE', 'SUBSCRIPTION_CREATE', 'SUBSCRIPTION_FETCH', 'SUBSCRIPTION_CANCEL', 'INVOICE_CREATE', 'INVOICE_FETCH', 'INVOICE_SEND', 'INVOICE_CANCEL', 'PAYMENT_LINK_CREATE', 'PAYMENT_LINK_FETCH', 'PAYMENT_LINK_UPDATE', 'PAYMENT_LINK_CANCEL', 'PAYOUT_CREATE', 'PAYOUT_FETCH', 'VERIFY_PAYMENT_SIGNATURE'); + +-- CreateEnum +CREATE TYPE "SlackOperation" AS ENUM ('MESSAGE_SEND_WEBHOOK', 'MESSAGE_SEND', 'MESSAGE_UPDATE', 'MESSAGE_DELETE', 'MESSAGE_GET_PERMALINK', 'MESSAGE_SCHEDULE', 'MESSAGE_SEARCH', 'CHANNEL_GET', 'CHANNEL_LIST', 'CHANNEL_CREATE', 'CHANNEL_ARCHIVE', 'CHANNEL_UNARCHIVE', 'CHANNEL_INVITE', 'CHANNEL_KICK', 'CHANNEL_SET_TOPIC', 'CHANNEL_SET_PURPOSE', 'CHANNEL_HISTORY', 'CHANNEL_INFO', 'CHANNEL_RENAME', 'USER_GET', 'USER_GET_BY_EMAIL', 'USER_LIST', 'USER_SET_STATUS', 'USER_INFO', 'USER_GET_PRESENCE', 'REACTION_ADD', 'REACTION_REMOVE', 'REACTION_GET', 'FILE_UPLOAD', 'FILE_GET', 'FILE_DELETE', 'FILE_LIST', 'FILE_INFO', 'CONVERSATION_OPEN'); + +-- CreateEnum +CREATE TYPE "Msg91Operation" AS ENUM ('SEND_SMS', 'SEND_BULK_SMS', 'SEND_TRANSACTIONAL', 'SCHEDULE_SMS', 'SEND_OTP', 'VERIFY_OTP', 'RESEND_OTP', 'INVALIDATE_OTP', 'SEND_WHATSAPP', 'SEND_WHATSAPP_MEDIA', 'SEND_VOICE_OTP', 'SEND_EMAIL', 'GET_BALANCE', 'GET_REPORT'); + +-- CreateEnum +CREATE TYPE "ShiprocketOperation" AS ENUM ('CREATE_ORDER', 'GET_ORDER', 'CANCEL_ORDER', 'UPDATE_ORDER', 'GET_ORDER_TRACKING', 'CLONE_ORDER', 'GENERATE_AWB', 'GET_ORDERS_LIST', 'TRACK_SHIPMENT', 'ASSIGN_COURIER', 'GENERATE_LABEL', 'GENERATE_MANIFEST', 'REQUEST_PICKUP', 'GET_COURIER_LIST', 'GET_RATE', 'CHECK_SERVICEABILITY', 'CREATE_RETURN', 'GET_RETURN_REASONS', 'TRACK_RETURN', 'CREATE_PRODUCT', 'GET_PRODUCTS', 'GET_PICKUP_LOCATIONS', 'CREATE_PICKUP_LOCATION'); + +-- CreateEnum +CREATE TYPE "ZohoCrmOperation" AS ENUM ('CREATE_LEAD', 'GET_LEAD', 'UPDATE_LEAD', 'DELETE_LEAD', 'SEARCH_LEADS', 'CONVERT_LEAD', 'CREATE_CONTACT', 'GET_CONTACT', 'UPDATE_CONTACT', 'DELETE_CONTACT', 'SEARCH_CONTACTS', 'GET_CONTACT_DEALS', 'CREATE_DEAL', 'GET_DEAL', 'UPDATE_DEAL', 'DELETE_DEAL', 'SEARCH_DEALS', 'UPDATE_DEAL_STAGE', 'CREATE_ACCOUNT', 'GET_ACCOUNT', 'UPDATE_ACCOUNT', 'DELETE_ACCOUNT', 'SEARCH_ACCOUNTS', 'CREATE_TASK', 'CREATE_CALL_LOG', 'CREATE_MEETING', 'GET_ACTIVITIES', 'ADD_NOTE', 'GET_NOTES', 'UPSERT_RECORD', 'SEARCH_RECORDS', 'GET_FIELDS'); + +-- CreateEnum +CREATE TYPE "HubspotOperation" AS ENUM ('CREATE_CONTACT', 'GET_CONTACT', 'UPDATE_CONTACT', 'DELETE_CONTACT', 'SEARCH_CONTACTS', 'GET_CONTACT_PROPERTIES', 'UPSERT_CONTACT', 'GET_CONTACT_ASSOCIATIONS', 'CREATE_COMPANY', 'GET_COMPANY', 'UPDATE_COMPANY', 'DELETE_COMPANY', 'SEARCH_COMPANIES', 'CREATE_DEAL', 'GET_DEAL', 'UPDATE_DEAL', 'DELETE_DEAL', 'SEARCH_DEALS', 'UPDATE_DEAL_STAGE', 'CREATE_TICKET', 'GET_TICKET', 'UPDATE_TICKET', 'DELETE_TICKET', 'SEARCH_TICKETS', 'CREATE_NOTE', 'CREATE_TASK', 'CREATE_CALL', 'CREATE_EMAIL_LOG', 'CREATE_ASSOCIATION', 'DELETE_ASSOCIATION', 'ADD_CONTACT_TO_LIST', 'REMOVE_CONTACT_FROM_LIST', 'GET_LIST_CONTACTS', 'SEARCH_OBJECTS', 'GET_PROPERTIES'); + +-- CreateEnum +CREATE TYPE "FreshdeskOperation" AS ENUM ('CREATE_TICKET', 'GET_TICKET', 'UPDATE_TICKET', 'DELETE_TICKET', 'LIST_TICKETS', 'SEARCH_TICKETS', 'GET_TICKET_FIELDS', 'RESTORE_TICKET', 'ADD_NOTE', 'LIST_NOTES', 'UPDATE_NOTE', 'DELETE_NOTE', 'CREATE_CONTACT', 'GET_CONTACT', 'UPDATE_CONTACT', 'DELETE_CONTACT', 'LIST_CONTACTS', 'SEARCH_CONTACTS', 'MERGE_CONTACT', 'CREATE_COMPANY', 'GET_COMPANY', 'UPDATE_COMPANY', 'DELETE_COMPANY', 'LIST_COMPANIES', 'LIST_AGENTS', 'GET_AGENT', 'UPDATE_AGENT', 'LIST_CONVERSATIONS', 'SEND_REPLY', 'CREATE_OUTBOUND_EMAIL', 'GET_TICKET_STATS'); + +-- CreateEnum +CREATE TYPE "MediaUploadSource" AS ENUM ('URL', 'BASE64', 'GOOGLE_DRIVE'); + +-- CreateEnum +CREATE TYPE "CashfreeOperation" AS ENUM ('CREATE_ORDER', 'GET_ORDER', 'TERMINATE_ORDER', 'PAY_ORDER', 'GET_PAYMENTS_FOR_ORDER', 'GET_PAYMENT_BY_ID', 'CREATE_REFUND', 'GET_REFUND', 'GET_ALL_REFUNDS_FOR_ORDER', 'GET_SETTLEMENTS_FOR_ORDER', 'GET_ALL_SETTLEMENTS', 'GET_SETTLEMENT_RECON', 'CREATE_PAYMENT_LINK', 'GET_PAYMENT_LINK', 'CANCEL_PAYMENT_LINK', 'GET_ORDERS_FOR_LINK', 'CREATE_SUBSCRIPTION_PLAN', 'GET_SUBSCRIPTION_PLAN', 'CREATE_SUBSCRIPTION', 'GET_SUBSCRIPTION', 'MANAGE_SUBSCRIPTION', 'GET_SUBSCRIPTION_PAYMENTS', 'GET_PAYOUT_BALANCE', 'ADD_BENEFICIARY', 'GET_BENEFICIARY', 'REMOVE_BENEFICIARY', 'TRANSFER_TO_BENEFICIARY', 'GET_TRANSFER_STATUS', 'BULK_TRANSFER', 'GET_BATCH_TRANSFER_STATUS', 'VALIDATE_UPI_ID', 'CREATE_UPI_PAYMENT_LINK', 'CREATE_OFFER', 'GET_OFFER', 'VERIFY_WEBHOOK_SIGNATURE'); + +-- CreateTable +CREATE TABLE "user" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "emailVerifyToken" TEXT, + "emailVerifyExpiry" TIMESTAMP(3), + "emailVerifyAttempts" INTEGER NOT NULL DEFAULT 0, + "image" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "executionCount" INTEGER NOT NULL DEFAULT 0, + "plan" TEXT NOT NULL DEFAULT 'FREE', + "planStatus" TEXT NOT NULL DEFAULT 'active', + "razorpayCustomerId" TEXT, + "razorpaySubId" TEXT, + "currentPeriodEnd" TIMESTAMP(3), + "cancelAtPeriodEnd" BOOLEAN NOT NULL DEFAULT false, + "workflowRunsUsed" INTEGER NOT NULL DEFAULT 0, + "workflowRunsReset" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "user_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BillingEvent" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "razorpayEventId" TEXT, + "amount" INTEGER, + "currency" TEXT NOT NULL DEFAULT 'INR', + "plan" TEXT, + "status" TEXT NOT NULL, + "rawPayload" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "BillingEvent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "session" ( + "id" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "token" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "userId" TEXT NOT NULL, + + CONSTRAINT "session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "account" ( + "id" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "providerId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "accessToken" TEXT, + "refreshToken" TEXT, + "idToken" TEXT, + "accessTokenExpiresAt" TIMESTAMP(3), + "refreshTokenExpiresAt" TIMESTAMP(3), + "scope" TEXT, + "password" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "verification" ( + "id" TEXT NOT NULL, + "identifier" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "verification_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "workflow" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" TEXT NOT NULL, + + CONSTRAINT "workflow_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "credential" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "value" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "type" "CredentialType" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "credential_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Node" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "type" "NodeType" NOT NULL, + "workflowId" TEXT NOT NULL, + "position" JSONB NOT NULL, + "data" JSONB NOT NULL DEFAULT '{}', + "credentialId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Node_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Connection" ( + "id" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "fromNodeId" TEXT NOT NULL, + "toNodeId" TEXT NOT NULL, + "fromOutput" TEXT NOT NULL DEFAULT 'main', + "toInput" TEXT NOT NULL DEFAULT 'main', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Connection_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Execution" ( + "id" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "completedAt" TIMESTAMP(3), + "inngestEventId" TEXT NOT NULL, + "output" JSONB, + "error" TEXT, + "errorStack" TEXT, + "status" "ExecutionStatus" NOT NULL DEFAULT 'RUNNING', + + CONSTRAINT "Execution_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "NodeExecution" ( + "id" TEXT NOT NULL, + "executionId" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "nodeName" TEXT NOT NULL DEFAULT '', + "nodeType" TEXT NOT NULL DEFAULT '', + "status" TEXT NOT NULL DEFAULT 'success', + "inputJson" TEXT NOT NULL DEFAULT '', + "outputJson" TEXT NOT NULL DEFAULT '', + "errorMessage" TEXT NOT NULL DEFAULT '', + "durationMs" INTEGER NOT NULL DEFAULT 0, + "executionOrder" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "NodeExecution_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RazorpayNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "RazorpayOperation" NOT NULL DEFAULT 'ORDER_CREATE', + "variableName" TEXT NOT NULL DEFAULT 'razorpay', + "amount" TEXT NOT NULL DEFAULT '', + "currency" TEXT NOT NULL DEFAULT 'INR', + "description" TEXT NOT NULL DEFAULT '', + "receipt" TEXT NOT NULL DEFAULT '', + "notes" TEXT NOT NULL DEFAULT '', + "partialPayment" BOOLEAN NOT NULL DEFAULT false, + "orderId" TEXT NOT NULL DEFAULT '', + "paymentId" TEXT NOT NULL DEFAULT '', + "captureAmount" TEXT NOT NULL DEFAULT '', + "refundAmount" TEXT NOT NULL DEFAULT '', + "refundSpeed" TEXT NOT NULL DEFAULT 'normal', + "refundId" TEXT NOT NULL DEFAULT '', + "customerId" TEXT NOT NULL DEFAULT '', + "customerName" TEXT NOT NULL DEFAULT '', + "customerEmail" TEXT NOT NULL DEFAULT '', + "customerContact" TEXT NOT NULL DEFAULT '', + "failExisting" BOOLEAN NOT NULL DEFAULT false, + "planId" TEXT NOT NULL DEFAULT '', + "totalCount" TEXT NOT NULL DEFAULT '', + "quantity" TEXT NOT NULL DEFAULT '1', + "startAt" TEXT NOT NULL DEFAULT '', + "subscriptionId" TEXT NOT NULL DEFAULT '', + "cancelAtCycleEnd" BOOLEAN NOT NULL DEFAULT false, + "invoiceType" TEXT NOT NULL DEFAULT 'invoice', + "lineItems" TEXT NOT NULL DEFAULT '', + "expireBy" TEXT NOT NULL DEFAULT '', + "smsNotify" BOOLEAN NOT NULL DEFAULT true, + "emailNotify" BOOLEAN NOT NULL DEFAULT true, + "invoiceId" TEXT NOT NULL DEFAULT '', + "paymentLinkId" TEXT NOT NULL DEFAULT '', + "referenceId" TEXT NOT NULL DEFAULT '', + "reminderEnable" BOOLEAN NOT NULL DEFAULT true, + "callbackUrl" TEXT NOT NULL DEFAULT '', + "callbackMethod" TEXT NOT NULL DEFAULT '', + "accountNumber" TEXT NOT NULL DEFAULT '', + "fundAccountId" TEXT NOT NULL DEFAULT '', + "payoutMode" TEXT NOT NULL DEFAULT '', + "payoutPurpose" TEXT NOT NULL DEFAULT 'payout', + "narration" TEXT NOT NULL DEFAULT '', + "queueIfLowBalance" BOOLEAN NOT NULL DEFAULT false, + "payoutId" TEXT NOT NULL DEFAULT '', + "signature" TEXT NOT NULL DEFAULT '', + "throwOnInvalid" BOOLEAN NOT NULL DEFAULT true, + "count" TEXT NOT NULL DEFAULT '', + "skip" TEXT NOT NULL DEFAULT '', + "fromDate" TEXT NOT NULL DEFAULT '', + "toDate" TEXT NOT NULL DEFAULT '', + "authorized" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "RazorpayNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ShiprocketNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "ShiprocketOperation" NOT NULL DEFAULT 'CREATE_ORDER', + "variableName" TEXT NOT NULL DEFAULT 'shiprocket', + "orderId" TEXT NOT NULL DEFAULT '', + "orderDate" TEXT NOT NULL DEFAULT '', + "channelId" TEXT NOT NULL DEFAULT '', + "billingName" TEXT NOT NULL DEFAULT '', + "billingAddress" TEXT NOT NULL DEFAULT '', + "billingAddress2" TEXT NOT NULL DEFAULT '', + "billingCity" TEXT NOT NULL DEFAULT '', + "billingState" TEXT NOT NULL DEFAULT '', + "billingCountry" TEXT NOT NULL DEFAULT 'India', + "billingPincode" TEXT NOT NULL DEFAULT '', + "billingEmail" TEXT NOT NULL DEFAULT '', + "billingPhone" TEXT NOT NULL DEFAULT '', + "billingAlternatePhone" TEXT NOT NULL DEFAULT '', + "shippingIsBilling" BOOLEAN NOT NULL DEFAULT true, + "shippingName" TEXT NOT NULL DEFAULT '', + "shippingAddress" TEXT NOT NULL DEFAULT '', + "shippingAddress2" TEXT NOT NULL DEFAULT '', + "shippingCity" TEXT NOT NULL DEFAULT '', + "shippingState" TEXT NOT NULL DEFAULT '', + "shippingCountry" TEXT NOT NULL DEFAULT 'India', + "shippingPincode" TEXT NOT NULL DEFAULT '', + "shippingEmail" TEXT NOT NULL DEFAULT '', + "shippingPhone" TEXT NOT NULL DEFAULT '', + "orderItems" TEXT NOT NULL DEFAULT '[]', + "paymentMethod" TEXT NOT NULL DEFAULT 'prepaid', + "subTotal" TEXT NOT NULL DEFAULT '', + "codAmount" TEXT NOT NULL DEFAULT '0', + "length" TEXT NOT NULL DEFAULT '', + "breadth" TEXT NOT NULL DEFAULT '', + "height" TEXT NOT NULL DEFAULT '', + "weight" TEXT NOT NULL DEFAULT '', + "shiprocketOrderId" TEXT NOT NULL DEFAULT '', + "shipmentId" TEXT NOT NULL DEFAULT '', + "awbCode" TEXT NOT NULL DEFAULT '', + "courierId" TEXT NOT NULL DEFAULT '', + "courierName" TEXT NOT NULL DEFAULT '', + "pickupLocation" TEXT NOT NULL DEFAULT '', + "pickupPostcode" TEXT NOT NULL DEFAULT '', + "deliveryPostcode" TEXT NOT NULL DEFAULT '', + "cod" TEXT NOT NULL DEFAULT '0', + "returnOrderId" TEXT NOT NULL DEFAULT '', + "returnReason" TEXT NOT NULL DEFAULT '', + "returnPickupLocation" TEXT NOT NULL DEFAULT '', + "productName" TEXT NOT NULL DEFAULT '', + "productSku" TEXT NOT NULL DEFAULT '', + "productMrp" TEXT NOT NULL DEFAULT '', + "productSellingPrice" TEXT NOT NULL DEFAULT '', + "productWeight" TEXT NOT NULL DEFAULT '', + "productCategory" TEXT NOT NULL DEFAULT '', + "productHsn" TEXT NOT NULL DEFAULT '', + "filterStatus" TEXT NOT NULL DEFAULT '', + "pageNo" INTEGER NOT NULL DEFAULT 1, + "perPage" INTEGER NOT NULL DEFAULT 10, + "warehouseName" TEXT NOT NULL DEFAULT '', + "warehouseEmail" TEXT NOT NULL DEFAULT '', + "warehousePhone" TEXT NOT NULL DEFAULT '', + "warehouseAddress" TEXT NOT NULL DEFAULT '', + "warehouseCity" TEXT NOT NULL DEFAULT '', + "warehouseState" TEXT NOT NULL DEFAULT '', + "warehousePincode" TEXT NOT NULL DEFAULT '', + "warehouseCountry" TEXT NOT NULL DEFAULT 'India', + "cancelReason" TEXT NOT NULL DEFAULT '', + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ShiprocketNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CashfreeNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "CashfreeOperation" NOT NULL DEFAULT 'CREATE_ORDER', + "variableName" TEXT NOT NULL DEFAULT 'cashfree', + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "orderId" TEXT NOT NULL DEFAULT '', + "orderAmount" TEXT NOT NULL DEFAULT '', + "orderCurrency" TEXT NOT NULL DEFAULT 'INR', + "orderNote" TEXT NOT NULL DEFAULT '', + "orderMeta" TEXT NOT NULL DEFAULT '{}', + "customerId" TEXT NOT NULL DEFAULT '', + "customerEmail" TEXT NOT NULL DEFAULT '', + "customerPhone" TEXT NOT NULL DEFAULT '', + "customerName" TEXT NOT NULL DEFAULT '', + "customerBankAccountNumber" TEXT NOT NULL DEFAULT '', + "customerBankIfsc" TEXT NOT NULL DEFAULT '', + "customerBankCode" TEXT NOT NULL DEFAULT '', + "cfPaymentId" TEXT NOT NULL DEFAULT '', + "cfOrderId" TEXT NOT NULL DEFAULT '', + "paymentMethod" TEXT NOT NULL DEFAULT '', + "refundId" TEXT NOT NULL DEFAULT '', + "refundAmount" TEXT NOT NULL DEFAULT '', + "refundNote" TEXT NOT NULL DEFAULT '', + "refundSpeed" TEXT NOT NULL DEFAULT 'STANDARD', + "refundSplits" TEXT NOT NULL DEFAULT '[]', + "settlementId" TEXT NOT NULL DEFAULT '', + "startDate" TEXT NOT NULL DEFAULT '', + "endDate" TEXT NOT NULL DEFAULT '', + "cursor" TEXT NOT NULL DEFAULT '', + "limit" INTEGER NOT NULL DEFAULT 10, + "linkId" TEXT NOT NULL DEFAULT '', + "linkAmount" TEXT NOT NULL DEFAULT '', + "linkCurrency" TEXT NOT NULL DEFAULT 'INR', + "linkPurpose" TEXT NOT NULL DEFAULT '', + "linkDescription" TEXT NOT NULL DEFAULT '', + "linkExpiryTime" TEXT NOT NULL DEFAULT '', + "linkNotifyPhone" BOOLEAN NOT NULL DEFAULT true, + "linkNotifyEmail" BOOLEAN NOT NULL DEFAULT true, + "linkAutoReminders" BOOLEAN NOT NULL DEFAULT true, + "linkMinPartialAmount" TEXT NOT NULL DEFAULT '', + "linkMeta" TEXT NOT NULL DEFAULT '{}', + "subscriptionId" TEXT NOT NULL DEFAULT '', + "planId" TEXT NOT NULL DEFAULT '', + "planName" TEXT NOT NULL DEFAULT '', + "planType" TEXT NOT NULL DEFAULT 'PERIODIC', + "planIntervalType" TEXT NOT NULL DEFAULT 'MONTH', + "planIntervals" INTEGER NOT NULL DEFAULT 1, + "planMaxCycles" INTEGER NOT NULL DEFAULT 0, + "planMaxAmount" TEXT NOT NULL DEFAULT '', + "subscriptionFirstChargeTime" TEXT NOT NULL DEFAULT '', + "subscriptionExpiryTime" TEXT NOT NULL DEFAULT '', + "subscriptionReturnUrl" TEXT NOT NULL DEFAULT '', + "subscriptionNotifyUrl" TEXT NOT NULL DEFAULT '', + "subscriptionAction" TEXT NOT NULL DEFAULT 'PAUSE', + "beneId" TEXT NOT NULL DEFAULT '', + "beneName" TEXT NOT NULL DEFAULT '', + "beneEmail" TEXT NOT NULL DEFAULT '', + "benePhone" TEXT NOT NULL DEFAULT '', + "beneBankAccount" TEXT NOT NULL DEFAULT '', + "beneBankIfsc" TEXT NOT NULL DEFAULT '', + "beneVpa" TEXT NOT NULL DEFAULT '', + "beneAddress" TEXT NOT NULL DEFAULT '', + "beneCity" TEXT NOT NULL DEFAULT '', + "beneState" TEXT NOT NULL DEFAULT 'India', + "benePincode" TEXT NOT NULL DEFAULT '', + "transferId" TEXT NOT NULL DEFAULT '', + "transferAmount" TEXT NOT NULL DEFAULT '', + "transferRemarks" TEXT NOT NULL DEFAULT '', + "transferMode" TEXT NOT NULL DEFAULT 'banktransfer', + "batchTransferId" TEXT NOT NULL DEFAULT '', + "batchEntries" TEXT NOT NULL DEFAULT '[]', + "upiVpa" TEXT NOT NULL DEFAULT '', + "upiAmount" TEXT NOT NULL DEFAULT '', + "upiDescription" TEXT NOT NULL DEFAULT '', + "offerId" TEXT NOT NULL DEFAULT '', + "offerMeta" TEXT NOT NULL DEFAULT '{}', + "offerValidations" TEXT NOT NULL DEFAULT '{}', + "offerDetails" TEXT NOT NULL DEFAULT '{}', + "webhookSignature" TEXT NOT NULL DEFAULT '', + "webhookTimestamp" TEXT NOT NULL DEFAULT '', + "webhookRawBody" TEXT NOT NULL DEFAULT '', + "webhookThrowOnFail" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CashfreeNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ZohoCrmNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "ZohoCrmOperation" NOT NULL DEFAULT 'CREATE_LEAD', + "variableName" TEXT NOT NULL DEFAULT 'zoho', + "module" TEXT NOT NULL DEFAULT 'Leads', + "recordId" TEXT NOT NULL DEFAULT '', + "firstName" TEXT NOT NULL DEFAULT '', + "lastName" TEXT NOT NULL DEFAULT '', + "email" TEXT NOT NULL DEFAULT '', + "phone" TEXT NOT NULL DEFAULT '', + "mobile" TEXT NOT NULL DEFAULT '', + "company" TEXT NOT NULL DEFAULT '', + "title" TEXT NOT NULL DEFAULT '', + "website" TEXT NOT NULL DEFAULT '', + "leadSource" TEXT NOT NULL DEFAULT '', + "leadStatus" TEXT NOT NULL DEFAULT '', + "industry" TEXT NOT NULL DEFAULT '', + "annualRevenue" TEXT NOT NULL DEFAULT '', + "noOfEmployees" TEXT NOT NULL DEFAULT '', + "rating" TEXT NOT NULL DEFAULT '', + "description" TEXT NOT NULL DEFAULT '', + "street" TEXT NOT NULL DEFAULT '', + "city" TEXT NOT NULL DEFAULT '', + "state" TEXT NOT NULL DEFAULT '', + "country" TEXT NOT NULL DEFAULT 'India', + "zipCode" TEXT NOT NULL DEFAULT '', + "dealName" TEXT NOT NULL DEFAULT '', + "dealStage" TEXT NOT NULL DEFAULT '', + "dealAmount" TEXT NOT NULL DEFAULT '', + "closingDate" TEXT NOT NULL DEFAULT '', + "accountName" TEXT NOT NULL DEFAULT '', + "contactName" TEXT NOT NULL DEFAULT '', + "probability" TEXT NOT NULL DEFAULT '', + "dealType" TEXT NOT NULL DEFAULT '', + "accountOwner" TEXT NOT NULL DEFAULT '', + "billingCity" TEXT NOT NULL DEFAULT '', + "billingState" TEXT NOT NULL DEFAULT '', + "subject" TEXT NOT NULL DEFAULT '', + "dueDate" TEXT NOT NULL DEFAULT '', + "priority" TEXT NOT NULL DEFAULT 'High', + "status" TEXT NOT NULL DEFAULT 'Not Started', + "whoId" TEXT NOT NULL DEFAULT '', + "whatId" TEXT NOT NULL DEFAULT '', + "whoModule" TEXT NOT NULL DEFAULT 'Contacts', + "whatModule" TEXT NOT NULL DEFAULT 'Deals', + "callDuration" TEXT NOT NULL DEFAULT '', + "callDirection" TEXT NOT NULL DEFAULT 'Outbound', + "callResult" TEXT NOT NULL DEFAULT '', + "callStartTime" TEXT NOT NULL DEFAULT '', + "callDescription" TEXT NOT NULL DEFAULT '', + "meetingStart" TEXT NOT NULL DEFAULT '', + "meetingEnd" TEXT NOT NULL DEFAULT '', + "meetingAgenda" TEXT NOT NULL DEFAULT '', + "participants" TEXT NOT NULL DEFAULT '[]', + "noteTitle" TEXT NOT NULL DEFAULT '', + "noteContent" TEXT NOT NULL DEFAULT '', + "parentModule" TEXT NOT NULL DEFAULT 'Leads', + "searchTerm" TEXT NOT NULL DEFAULT '', + "searchField" TEXT NOT NULL DEFAULT 'Email', + "criteria" TEXT NOT NULL DEFAULT '', + "page" INTEGER NOT NULL DEFAULT 1, + "perPage" INTEGER NOT NULL DEFAULT 10, + "createDeal" BOOLEAN NOT NULL DEFAULT false, + "overwrite" BOOLEAN NOT NULL DEFAULT false, + "customFields" TEXT NOT NULL DEFAULT '{}', + "duplicateCheckField" TEXT NOT NULL DEFAULT 'Email', + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ZohoCrmNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "HubspotNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "HubspotOperation" NOT NULL DEFAULT 'CREATE_CONTACT', + "variableName" TEXT NOT NULL DEFAULT 'hubspot', + "objectType" TEXT NOT NULL DEFAULT 'contacts', + "recordId" TEXT NOT NULL DEFAULT '', + "email" TEXT NOT NULL DEFAULT '', + "firstName" TEXT NOT NULL DEFAULT '', + "lastName" TEXT NOT NULL DEFAULT '', + "phone" TEXT NOT NULL DEFAULT '', + "website" TEXT NOT NULL DEFAULT '', + "company" TEXT NOT NULL DEFAULT '', + "jobTitle" TEXT NOT NULL DEFAULT '', + "lifecycleStage" TEXT NOT NULL DEFAULT '', + "leadStatus" TEXT NOT NULL DEFAULT '', + "companyName" TEXT NOT NULL DEFAULT '', + "domain" TEXT NOT NULL DEFAULT '', + "industry" TEXT NOT NULL DEFAULT '', + "annualRevenue" TEXT NOT NULL DEFAULT '', + "numberOfEmployees" TEXT NOT NULL DEFAULT '', + "city" TEXT NOT NULL DEFAULT '', + "state" TEXT NOT NULL DEFAULT '', + "country" TEXT NOT NULL DEFAULT 'India', + "dealName" TEXT NOT NULL DEFAULT '', + "dealStage" TEXT NOT NULL DEFAULT '', + "pipeline" TEXT NOT NULL DEFAULT 'default', + "amount" TEXT NOT NULL DEFAULT '', + "closeDate" TEXT NOT NULL DEFAULT '', + "dealType" TEXT NOT NULL DEFAULT '', + "priority" TEXT NOT NULL DEFAULT '', + "ticketName" TEXT NOT NULL DEFAULT '', + "ticketPipeline" TEXT NOT NULL DEFAULT '0', + "ticketStatus" TEXT NOT NULL DEFAULT '', + "ticketPriority" TEXT NOT NULL DEFAULT '', + "ticketDescription" TEXT NOT NULL DEFAULT '', + "ticketSource" TEXT NOT NULL DEFAULT '', + "noteBody" TEXT NOT NULL DEFAULT '', + "taskSubject" TEXT NOT NULL DEFAULT '', + "taskBody" TEXT NOT NULL DEFAULT '', + "taskStatus" TEXT NOT NULL DEFAULT 'NOT_STARTED', + "taskPriority" TEXT NOT NULL DEFAULT 'NONE', + "taskDueDate" TEXT NOT NULL DEFAULT '', + "callBody" TEXT NOT NULL DEFAULT '', + "callDuration" TEXT NOT NULL DEFAULT '', + "callDirection" TEXT NOT NULL DEFAULT 'OUTBOUND', + "callDisposition" TEXT NOT NULL DEFAULT '', + "emailSubject" TEXT NOT NULL DEFAULT '', + "emailBody" TEXT NOT NULL DEFAULT '', + "emailFrom" TEXT NOT NULL DEFAULT '', + "emailTo" TEXT NOT NULL DEFAULT '', + "fromObjectType" TEXT NOT NULL DEFAULT 'contacts', + "fromObjectId" TEXT NOT NULL DEFAULT '', + "toObjectType" TEXT NOT NULL DEFAULT 'deals', + "toObjectId" TEXT NOT NULL DEFAULT '', + "associationType" TEXT NOT NULL DEFAULT '', + "listId" TEXT NOT NULL DEFAULT '', + "searchQuery" TEXT NOT NULL DEFAULT '', + "filterProperty" TEXT NOT NULL DEFAULT '', + "filterOperator" TEXT NOT NULL DEFAULT 'EQ', + "filterValue" TEXT NOT NULL DEFAULT '', + "sortProperty" TEXT NOT NULL DEFAULT 'createdate', + "sortDirection" TEXT NOT NULL DEFAULT 'DESCENDING', + "limit" INTEGER NOT NULL DEFAULT 10, + "after" TEXT NOT NULL DEFAULT '', + "customProperties" TEXT NOT NULL DEFAULT '{}', + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "HubspotNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FreshdeskNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "FreshdeskOperation" NOT NULL DEFAULT 'CREATE_TICKET', + "variableName" TEXT NOT NULL DEFAULT 'freshdesk', + "subject" TEXT NOT NULL DEFAULT '', + "description" TEXT NOT NULL DEFAULT '', + "descriptionHtml" TEXT NOT NULL DEFAULT '', + "email" TEXT NOT NULL DEFAULT '', + "name" TEXT NOT NULL DEFAULT '', + "phone" TEXT NOT NULL DEFAULT '', + "mobilePhone" TEXT NOT NULL DEFAULT '', + "subject2" TEXT NOT NULL DEFAULT '', + "ticketId" TEXT NOT NULL DEFAULT '', + "priority" INTEGER NOT NULL DEFAULT 2, + "status" INTEGER NOT NULL DEFAULT 2, + "source" INTEGER NOT NULL DEFAULT 2, + "ticketType" TEXT NOT NULL DEFAULT '', + "responderId" TEXT NOT NULL DEFAULT '', + "groupId" TEXT NOT NULL DEFAULT '', + "productId" TEXT NOT NULL DEFAULT '', + "companyId" TEXT NOT NULL DEFAULT '', + "fwdEmail" TEXT NOT NULL DEFAULT '', + "tags" TEXT NOT NULL DEFAULT '', + "customFields" TEXT NOT NULL DEFAULT '{}', + "noteId" TEXT NOT NULL DEFAULT '', + "noteBody" TEXT NOT NULL DEFAULT '', + "notePrivate" BOOLEAN NOT NULL DEFAULT false, + "noteUserId" TEXT NOT NULL DEFAULT '', + "contactId" TEXT NOT NULL DEFAULT '', + "contactEmail" TEXT NOT NULL DEFAULT '', + "contactName" TEXT NOT NULL DEFAULT '', + "contactPhone" TEXT NOT NULL DEFAULT '', + "contactMobile" TEXT NOT NULL DEFAULT '', + "contactJobTitle" TEXT NOT NULL DEFAULT '', + "contactTimeZone" TEXT NOT NULL DEFAULT '', + "contactLanguage" TEXT NOT NULL DEFAULT '', + "contactTags" TEXT NOT NULL DEFAULT '', + "contactCompanyId" TEXT NOT NULL DEFAULT '', + "mergeTargetId" TEXT NOT NULL DEFAULT '', + "companyName" TEXT NOT NULL DEFAULT '', + "companyDomain" TEXT NOT NULL DEFAULT '', + "companyDescription" TEXT NOT NULL DEFAULT '', + "companyNote" TEXT NOT NULL DEFAULT '', + "agentId" TEXT NOT NULL DEFAULT '', + "replyBody" TEXT NOT NULL DEFAULT '', + "replyFrom" TEXT NOT NULL DEFAULT '', + "replyTo" TEXT NOT NULL DEFAULT '', + "replyCc" TEXT NOT NULL DEFAULT '', + "replyBcc" TEXT NOT NULL DEFAULT '', + "searchQuery" TEXT NOT NULL DEFAULT '', + "filterBy" TEXT NOT NULL DEFAULT '', + "orderBy" TEXT NOT NULL DEFAULT 'created_at', + "orderType" TEXT NOT NULL DEFAULT 'desc', + "page" INTEGER NOT NULL DEFAULT 1, + "perPage" INTEGER NOT NULL DEFAULT 30, + "updatedSince" TEXT NOT NULL DEFAULT '', + "includeStats" BOOLEAN NOT NULL DEFAULT false, + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FreshdeskNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IfElseNode" ( + "id" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "field" TEXT NOT NULL DEFAULT '', + "operator" "IfElseOperator" NOT NULL DEFAULT 'EQUALS', + "value" TEXT NOT NULL DEFAULT '', + "conditionsJson" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "IfElseNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SwitchNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "variableName" TEXT NOT NULL DEFAULT 'switch', + "casesJson" TEXT NOT NULL DEFAULT '[]', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "SwitchNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SetVariableNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "pairs" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "SetVariableNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CodeNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "code" TEXT NOT NULL, + "language" TEXT NOT NULL DEFAULT 'javascript', + "outputMode" TEXT NOT NULL DEFAULT 'append', + "timeout" INTEGER NOT NULL DEFAULT 5000, + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "allowedDomains" TEXT NOT NULL DEFAULT '', + "variableName" TEXT NOT NULL DEFAULT 'codeOutput', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CodeNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LoopNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "inputPath" TEXT NOT NULL DEFAULT 'googleSheets.rows', + "itemVariable" TEXT NOT NULL DEFAULT 'item', + "maxIterations" INTEGER NOT NULL DEFAULT 100, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LoopNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WaitNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "waitMode" TEXT NOT NULL DEFAULT 'duration', + "duration" INTEGER NOT NULL DEFAULT 30, + "durationUnit" TEXT NOT NULL DEFAULT 'minutes', + "untilDatetime" TEXT NOT NULL DEFAULT '', + "timezone" TEXT NOT NULL DEFAULT 'UTC', + "timeoutDuration" INTEGER NOT NULL DEFAULT 24, + "timeoutUnit" TEXT NOT NULL DEFAULT 'hours', + "continueOnTimeout" BOOLEAN NOT NULL DEFAULT true, + "variableName" TEXT NOT NULL DEFAULT 'wait', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "WaitNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MergeNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "inputCount" INTEGER NOT NULL DEFAULT 2, + "mergeMode" TEXT NOT NULL DEFAULT 'combine', + "matchKey1" TEXT NOT NULL DEFAULT '', + "matchKey2" TEXT NOT NULL DEFAULT '', + "positionFill" TEXT NOT NULL DEFAULT 'shortest', + "branchKey1" TEXT NOT NULL DEFAULT '', + "branchKey2" TEXT NOT NULL DEFAULT '', + "branchKeys" TEXT NOT NULL DEFAULT '', + "waitForAll" BOOLEAN NOT NULL DEFAULT true, + "variableName" TEXT NOT NULL DEFAULT 'merge', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "MergeNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "GmailNode" ( + "id" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "GmailOperation" NOT NULL DEFAULT 'SEND', + "variableName" TEXT NOT NULL DEFAULT 'gmail', + "to" TEXT NOT NULL DEFAULT '', + "subject" TEXT NOT NULL DEFAULT '', + "body" TEXT NOT NULL DEFAULT '', + "isHtml" BOOLEAN NOT NULL DEFAULT false, + "cc" TEXT NOT NULL DEFAULT '', + "bcc" TEXT NOT NULL DEFAULT '', + "replyTo" TEXT NOT NULL DEFAULT '', + "messageId" TEXT NOT NULL DEFAULT '', + "threadId" TEXT NOT NULL DEFAULT '', + "searchQuery" TEXT NOT NULL DEFAULT '', + "maxResults" INTEGER NOT NULL DEFAULT 10, + "labelIds" TEXT NOT NULL DEFAULT '', + "includeBody" BOOLEAN NOT NULL DEFAULT true, + "includeHeaders" BOOLEAN NOT NULL DEFAULT false, + "pageToken" TEXT NOT NULL DEFAULT '', + "attachmentData" TEXT NOT NULL DEFAULT '', + "attachmentName" TEXT NOT NULL DEFAULT '', + "attachmentMime" TEXT NOT NULL DEFAULT 'application/octet-stream', + "attachmentId" TEXT NOT NULL DEFAULT '', + "attachmentOutputFormat" TEXT NOT NULL DEFAULT 'base64', + "removeLabelIds" TEXT NOT NULL DEFAULT '', + "labelName" TEXT NOT NULL DEFAULT '', + "draftId" TEXT NOT NULL DEFAULT '', + "messageIds" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GmailNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "GoogleSheetsNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "operation" "GoogleSheetsOp" NOT NULL DEFAULT 'APPEND_ROW', + "spreadsheetId" TEXT NOT NULL DEFAULT '', + "sheetName" TEXT NOT NULL DEFAULT 'Sheet1', + "range" TEXT NOT NULL DEFAULT 'A:Z', + "rowData" JSONB NOT NULL DEFAULT '[]', + "credentialId" TEXT, + "variableName" TEXT NOT NULL DEFAULT 'googleSheets', + "headerRow" BOOLEAN NOT NULL DEFAULT true, + "rowNumber" TEXT NOT NULL DEFAULT '', + "rowValues" TEXT NOT NULL DEFAULT '', + "searchColumn" TEXT NOT NULL DEFAULT '', + "searchValue" TEXT NOT NULL DEFAULT '', + "clearRange" TEXT NOT NULL DEFAULT '', + "newSheetName" TEXT NOT NULL DEFAULT '', + "valueInputOption" TEXT NOT NULL DEFAULT 'USER_ENTERED', + "matchColumn" TEXT NOT NULL DEFAULT '', + "matchValue" TEXT NOT NULL DEFAULT '', + "updateValues" TEXT NOT NULL DEFAULT '', + "maxResults" INTEGER NOT NULL DEFAULT 100, + "includeEmptyRows" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GoogleSheetsNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "GoogleDriveNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "GoogleDriveOperation" NOT NULL DEFAULT 'UPLOAD_FILE', + "folderId" TEXT, + "fileId" TEXT, + "fileName" TEXT, + "mimeType" TEXT, + "query" TEXT, + "maxResults" INTEGER NOT NULL DEFAULT 10, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GoogleDriveNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SlackNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "SlackOperation" NOT NULL DEFAULT 'MESSAGE_SEND_WEBHOOK', + "variableName" TEXT NOT NULL DEFAULT 'slack', + "channel" TEXT NOT NULL DEFAULT '', + "message" TEXT NOT NULL DEFAULT '', + "threadTs" TEXT NOT NULL DEFAULT '', + "messageTs" TEXT NOT NULL DEFAULT '', + "searchQuery" TEXT NOT NULL DEFAULT '', + "channelName" TEXT NOT NULL DEFAULT '', + "channelTopic" TEXT NOT NULL DEFAULT '', + "channelPurpose" TEXT NOT NULL DEFAULT '', + "userId" TEXT NOT NULL DEFAULT '', + "emoji" TEXT NOT NULL DEFAULT '', + "fileComment" TEXT NOT NULL DEFAULT '', + "blockKit" TEXT NOT NULL DEFAULT '', + "botName" TEXT NOT NULL DEFAULT '', + "iconEmoji" TEXT NOT NULL DEFAULT '', + "unfurlLinks" BOOLEAN NOT NULL DEFAULT true, + "channelTypes" TEXT NOT NULL DEFAULT 'public_channel,private_channel', + "limit" INTEGER NOT NULL DEFAULT 100, + "excludeArchived" BOOLEAN NOT NULL DEFAULT true, + "isPrivate" BOOLEAN NOT NULL DEFAULT false, + "filename" TEXT NOT NULL DEFAULT '', + "fileType" TEXT NOT NULL DEFAULT '', + "title" TEXT NOT NULL DEFAULT '', + "initialComment" TEXT NOT NULL DEFAULT '', + "email" TEXT NOT NULL DEFAULT '', + "statusText" TEXT NOT NULL DEFAULT '', + "statusEmoji" TEXT NOT NULL DEFAULT '', + "statusExpiration" TEXT NOT NULL DEFAULT '', + "sendAt" TEXT NOT NULL DEFAULT '', + "content" TEXT NOT NULL DEFAULT '', + "fileId" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "SlackNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WhatsAppNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "WhatsAppOperation" NOT NULL DEFAULT 'SEND_TEXT', + "to" TEXT NOT NULL DEFAULT '', + "body" TEXT NOT NULL DEFAULT '', + "templateName" TEXT NOT NULL DEFAULT '', + "templateLang" TEXT NOT NULL DEFAULT 'en_US', + "templateParams" TEXT NOT NULL DEFAULT '[]', + "mediaUrl" TEXT NOT NULL DEFAULT '', + "mediaCaption" TEXT NOT NULL DEFAULT '', + "reactionEmoji" TEXT NOT NULL DEFAULT '', + "reactionMsgId" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "WhatsAppNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "NotionNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "NotionOperation" NOT NULL DEFAULT 'QUERY_DATABASE', + "databaseId" TEXT NOT NULL DEFAULT '', + "pageId" TEXT NOT NULL DEFAULT '', + "blockContent" TEXT NOT NULL DEFAULT '', + "searchQuery" TEXT NOT NULL DEFAULT '', + "filterJson" TEXT NOT NULL DEFAULT '{}', + "sortsJson" TEXT NOT NULL DEFAULT '[]', + "propertiesJson" TEXT NOT NULL DEFAULT '{}', + "notionUserId" TEXT NOT NULL DEFAULT '', + "pageSize" INTEGER NOT NULL DEFAULT 100, + "startCursor" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "NotionNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Msg91Node" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "operation" "Msg91Operation" NOT NULL DEFAULT 'SEND_OTP', + "variableName" TEXT NOT NULL DEFAULT 'msg91', + "mobile" TEXT NOT NULL DEFAULT '', + "senderId" TEXT NOT NULL DEFAULT '', + "flowId" TEXT NOT NULL DEFAULT '', + "smsVariables" TEXT NOT NULL DEFAULT '{}', + "message" TEXT NOT NULL DEFAULT '', + "route" TEXT NOT NULL DEFAULT '4', + "bulkData" TEXT NOT NULL DEFAULT '[]', + "scheduleTime" TEXT NOT NULL DEFAULT '', + "otpTemplateId" TEXT NOT NULL DEFAULT '', + "otpExpiry" INTEGER NOT NULL DEFAULT 10, + "otpLength" INTEGER NOT NULL DEFAULT 6, + "otpValue" TEXT NOT NULL DEFAULT '', + "retryType" TEXT NOT NULL DEFAULT 'text', + "whatsappTemplate" TEXT NOT NULL DEFAULT '', + "whatsappLang" TEXT NOT NULL DEFAULT 'en', + "whatsappParams" TEXT NOT NULL DEFAULT '[]', + "integratedNumber" TEXT NOT NULL DEFAULT '', + "mediaType" TEXT NOT NULL DEFAULT 'image', + "mediaUrl" TEXT NOT NULL DEFAULT '', + "mediaCaption" TEXT NOT NULL DEFAULT '', + "voiceMessage" TEXT NOT NULL DEFAULT '', + "toEmail" TEXT NOT NULL DEFAULT '', + "subject" TEXT NOT NULL DEFAULT '', + "emailBody" TEXT NOT NULL DEFAULT '', + "fromEmail" TEXT NOT NULL DEFAULT '', + "fromName" TEXT NOT NULL DEFAULT '', + "requestId" TEXT NOT NULL DEFAULT '', + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Msg91Node_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AINode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "credentialId" TEXT, + "provider" "AIProvider" NOT NULL, + "operation" "AIOperation" NOT NULL DEFAULT 'CHAT', + "variableName" TEXT NOT NULL DEFAULT 'ai', + "model" TEXT NOT NULL DEFAULT '', + "systemPrompt" TEXT NOT NULL DEFAULT '', + "userPrompt" TEXT NOT NULL DEFAULT '', + "temperature" DOUBLE PRECISION NOT NULL DEFAULT 0.7, + "maxTokens" INTEGER NOT NULL DEFAULT 1000, + "topP" DOUBLE PRECISION NOT NULL DEFAULT 1.0, + "frequencyPenalty" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "presencePenalty" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "responseFormat" TEXT NOT NULL DEFAULT 'text', + "jsonSchema" TEXT NOT NULL DEFAULT '', + "toolsJson" TEXT NOT NULL DEFAULT '[]', + "toolChoice" TEXT NOT NULL DEFAULT 'auto', + "imageUrl" TEXT NOT NULL DEFAULT '', + "imageUrls" TEXT NOT NULL DEFAULT '[]', + "imageDetail" TEXT NOT NULL DEFAULT 'auto', + "historyKey" TEXT NOT NULL DEFAULT '', + "maxHistory" INTEGER NOT NULL DEFAULT 10, + "embeddingInput" TEXT NOT NULL DEFAULT '', + "audioUrl" TEXT NOT NULL DEFAULT '', + "audioLanguage" TEXT NOT NULL DEFAULT '', + "transcriptionFormat" TEXT NOT NULL DEFAULT 'text', + "imagePrompt" TEXT NOT NULL DEFAULT '', + "imageSize" TEXT NOT NULL DEFAULT '1024x1024', + "imageQuality" TEXT NOT NULL DEFAULT 'standard', + "imageStyle" TEXT NOT NULL DEFAULT 'vivid', + "imageCount" INTEGER NOT NULL DEFAULT 1, + "classifyLabels" TEXT NOT NULL DEFAULT '', + "classifyExamples" TEXT NOT NULL DEFAULT '[]', + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "timeout" INTEGER NOT NULL DEFAULT 60000, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "AINode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MediaUploadNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "source" "MediaUploadSource" NOT NULL DEFAULT 'URL', + "inputField" TEXT NOT NULL DEFAULT '', + "mimeTypeHint" TEXT NOT NULL DEFAULT 'image/png', + "filename" TEXT NOT NULL DEFAULT '', + "credentialId" TEXT, + "variableName" TEXT NOT NULL DEFAULT 'media', + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "MediaUploadNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SortNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "operation" TEXT NOT NULL DEFAULT 'SORT_ARRAY', + "sortKeys" TEXT NOT NULL DEFAULT '[]', + "inputPath" TEXT NOT NULL DEFAULT '', + "variableName" TEXT NOT NULL DEFAULT 'sort', + "inPlace" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "SortNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "FilterNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "operation" TEXT NOT NULL DEFAULT 'FILTER_ARRAY', + "inputArray" TEXT NOT NULL DEFAULT '', + "inputObject" TEXT NOT NULL DEFAULT '', + "conditionGroups" TEXT NOT NULL DEFAULT '[]', + "rootLogic" TEXT NOT NULL DEFAULT 'AND', + "outputMode" TEXT NOT NULL DEFAULT 'filtered', + "variableName" TEXT NOT NULL DEFAULT 'filter', + "stopOnEmpty" BOOLEAN NOT NULL DEFAULT false, + "includeMetadata" BOOLEAN NOT NULL DEFAULT false, + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "keyFilterMode" TEXT NOT NULL DEFAULT 'key_name', + "keepMatching" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FilterNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AggregateNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "operation" TEXT NOT NULL DEFAULT 'COUNT', + "inputPath" TEXT NOT NULL DEFAULT '', + "field" TEXT NOT NULL DEFAULT '', + "groupByField" TEXT NOT NULL DEFAULT '', + "pivotRowField" TEXT NOT NULL DEFAULT '', + "pivotColField" TEXT NOT NULL DEFAULT '', + "pivotValueField" TEXT NOT NULL DEFAULT '', + "pivotValueOp" TEXT NOT NULL DEFAULT 'SUM', + "separator" TEXT NOT NULL DEFAULT ', ', + "percentile" DOUBLE PRECISION NOT NULL DEFAULT 90, + "countFilter" TEXT NOT NULL DEFAULT '', + "multiOps" TEXT NOT NULL DEFAULT '[]', + "groupAggOps" TEXT NOT NULL DEFAULT '[]', + "variableName" TEXT NOT NULL DEFAULT 'aggregate', + "includeInput" BOOLEAN NOT NULL DEFAULT false, + "sortOutput" BOOLEAN NOT NULL DEFAULT true, + "topN" INTEGER NOT NULL DEFAULT 0, + "nullHandling" TEXT NOT NULL DEFAULT 'exclude', + "roundDecimals" INTEGER NOT NULL DEFAULT 2, + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "AggregateNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PostgresNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "operation" TEXT NOT NULL DEFAULT 'EXECUTE_QUERY', + "credentialId" TEXT, + "tableName" TEXT NOT NULL DEFAULT '', + "schemaName" TEXT NOT NULL DEFAULT 'public', + "selectColumns" TEXT NOT NULL DEFAULT '[]', + "whereConditions" TEXT NOT NULL DEFAULT '[]', + "orderBy" TEXT NOT NULL DEFAULT '[]', + "limitRows" INTEGER NOT NULL DEFAULT 0, + "offsetRows" INTEGER NOT NULL DEFAULT 0, + "joins" TEXT NOT NULL DEFAULT '[]', + "insertData" TEXT NOT NULL DEFAULT '', + "conflictColumns" TEXT NOT NULL DEFAULT '[]', + "updateOnConflict" TEXT NOT NULL DEFAULT '[]', + "updateData" TEXT NOT NULL DEFAULT '', + "insertManyPath" TEXT NOT NULL DEFAULT '', + "insertManyColumns" TEXT NOT NULL DEFAULT '[]', + "query" TEXT NOT NULL DEFAULT '', + "queryParams" TEXT NOT NULL DEFAULT '[]', + "transactionStatements" TEXT NOT NULL DEFAULT '[]', + "functionName" TEXT NOT NULL DEFAULT '', + "functionArgs" TEXT NOT NULL DEFAULT '[]', + "searchColumn" TEXT NOT NULL DEFAULT '', + "searchQuery" TEXT NOT NULL DEFAULT '', + "searchLanguage" TEXT NOT NULL DEFAULT 'english', + "searchLimit" INTEGER NOT NULL DEFAULT 10, + "jsonColumn" TEXT NOT NULL DEFAULT '', + "jsonPath" TEXT NOT NULL DEFAULT '', + "jsonSetColumn" TEXT NOT NULL DEFAULT '', + "jsonSetPath" TEXT NOT NULL DEFAULT '', + "jsonSetValue" TEXT NOT NULL DEFAULT '', + "columnDefinitions" TEXT NOT NULL DEFAULT '[]', + "createTableIfNotExists" BOOLEAN NOT NULL DEFAULT true, + "variableName" TEXT NOT NULL DEFAULT 'postgres', + "returnData" BOOLEAN NOT NULL DEFAULT true, + "continueOnFail" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "PostgresNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WebhookTrigger" ( + "id" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "webhookId" TEXT NOT NULL, + "secretToken" TEXT, + "secretTokenEncrypted" TEXT, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "httpMethod" "HttpMethod" NOT NULL DEFAULT 'POST', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "WebhookTrigger_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ScheduleTrigger" ( + "id" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "cronExpression" TEXT NOT NULL, + "timezone" TEXT NOT NULL DEFAULT 'UTC', + "isActive" BOOLEAN NOT NULL DEFAULT true, + "inngestFunctionId" TEXT NOT NULL, + "lastRunAt" TIMESTAMP(3), + "nextRunAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ScheduleTrigger_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ErrorTriggerNode" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "variableName" TEXT NOT NULL DEFAULT 'errorTrigger', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ErrorTriggerNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RazorpayTrigger" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "webhookId" TEXT NOT NULL, + "webhookSecret" TEXT, + "webhookSecretEncrypted" TEXT, + "activeEvents" TEXT NOT NULL DEFAULT '[]', + "variableName" TEXT NOT NULL DEFAULT 'razorpayTrigger', + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "RazorpayTrigger_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "WhatsAppTrigger" ( + "id" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "webhookId" TEXT NOT NULL, + "verifyToken" TEXT, + "verifyTokenEncrypted" TEXT, + "phoneNumberId" TEXT NOT NULL DEFAULT '', + "activeEvents" TEXT NOT NULL DEFAULT '[]', + "messageTypes" TEXT NOT NULL DEFAULT '[]', + "ignoreOwnMessages" BOOLEAN NOT NULL DEFAULT true, + "variableName" TEXT NOT NULL DEFAULT 'whatsappTrigger', + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "WhatsAppTrigger_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "GmailWatcher" ( + "id" TEXT NOT NULL, + "workflowId" TEXT NOT NULL, + "nodeId" TEXT NOT NULL, + "credentialId" TEXT NOT NULL, + "email" TEXT NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "lastHistoryId" TEXT NOT NULL DEFAULT '', + "expiration" TEXT NOT NULL DEFAULT '', + "filterQuery" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GmailWatcher_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "user_emailVerifyToken_key" ON "user"("emailVerifyToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_razorpayCustomerId_key" ON "user"("razorpayCustomerId"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_razorpaySubId_key" ON "user"("razorpaySubId"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "BillingEvent_razorpayEventId_key" ON "BillingEvent"("razorpayEventId"); + +-- CreateIndex +CREATE INDEX "BillingEvent_userId_idx" ON "BillingEvent"("userId"); + +-- CreateIndex +CREATE INDEX "BillingEvent_type_idx" ON "BillingEvent"("type"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_token_key" ON "session"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "Connection_fromNodeId_toNodeId_fromOutput_toInput_key" ON "Connection"("fromNodeId", "toNodeId", "fromOutput", "toInput"); + +-- CreateIndex +CREATE UNIQUE INDEX "Execution_inngestEventId_key" ON "Execution"("inngestEventId"); + +-- CreateIndex +CREATE INDEX "NodeExecution_executionId_idx" ON "NodeExecution"("executionId"); + +-- CreateIndex +CREATE INDEX "NodeExecution_nodeId_idx" ON "NodeExecution"("nodeId"); + +-- CreateIndex +CREATE INDEX "NodeExecution_executionId_executionOrder_idx" ON "NodeExecution"("executionId", "executionOrder"); + +-- CreateIndex +CREATE UNIQUE INDEX "RazorpayNode_nodeId_key" ON "RazorpayNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "RazorpayNode_workflowId_idx" ON "RazorpayNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "RazorpayNode_nodeId_idx" ON "RazorpayNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ShiprocketNode_nodeId_key" ON "ShiprocketNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "ShiprocketNode_workflowId_idx" ON "ShiprocketNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "ShiprocketNode_nodeId_idx" ON "ShiprocketNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "CashfreeNode_nodeId_key" ON "CashfreeNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "CashfreeNode_workflowId_idx" ON "CashfreeNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "CashfreeNode_nodeId_idx" ON "CashfreeNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ZohoCrmNode_nodeId_key" ON "ZohoCrmNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "ZohoCrmNode_workflowId_idx" ON "ZohoCrmNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "ZohoCrmNode_nodeId_idx" ON "ZohoCrmNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "HubspotNode_nodeId_key" ON "HubspotNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "HubspotNode_workflowId_idx" ON "HubspotNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "HubspotNode_nodeId_idx" ON "HubspotNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "FreshdeskNode_nodeId_key" ON "FreshdeskNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "FreshdeskNode_workflowId_idx" ON "FreshdeskNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "FreshdeskNode_nodeId_idx" ON "FreshdeskNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IfElseNode_nodeId_key" ON "IfElseNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "IfElseNode_workflowId_idx" ON "IfElseNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "IfElseNode_nodeId_idx" ON "IfElseNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "SwitchNode_nodeId_key" ON "SwitchNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "SwitchNode_nodeId_idx" ON "SwitchNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "SetVariableNode_nodeId_key" ON "SetVariableNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "SetVariableNode_workflowId_idx" ON "SetVariableNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "SetVariableNode_nodeId_idx" ON "SetVariableNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "CodeNode_nodeId_key" ON "CodeNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "CodeNode_workflowId_idx" ON "CodeNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "CodeNode_nodeId_idx" ON "CodeNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "LoopNode_nodeId_key" ON "LoopNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "LoopNode_workflowId_idx" ON "LoopNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "LoopNode_nodeId_idx" ON "LoopNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "WaitNode_nodeId_key" ON "WaitNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "WaitNode_workflowId_idx" ON "WaitNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "WaitNode_nodeId_idx" ON "WaitNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "MergeNode_nodeId_key" ON "MergeNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "MergeNode_workflowId_idx" ON "MergeNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "MergeNode_nodeId_idx" ON "MergeNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GmailNode_nodeId_key" ON "GmailNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "GmailNode_workflowId_idx" ON "GmailNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "GmailNode_nodeId_idx" ON "GmailNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GoogleSheetsNode_nodeId_key" ON "GoogleSheetsNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "GoogleSheetsNode_workflowId_idx" ON "GoogleSheetsNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "GoogleSheetsNode_nodeId_idx" ON "GoogleSheetsNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GoogleDriveNode_nodeId_key" ON "GoogleDriveNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "SlackNode_nodeId_key" ON "SlackNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "SlackNode_workflowId_idx" ON "SlackNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "SlackNode_nodeId_idx" ON "SlackNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "WhatsAppNode_nodeId_key" ON "WhatsAppNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "NotionNode_nodeId_key" ON "NotionNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "NotionNode_workflowId_idx" ON "NotionNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "NotionNode_nodeId_idx" ON "NotionNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Msg91Node_nodeId_key" ON "Msg91Node"("nodeId"); + +-- CreateIndex +CREATE INDEX "Msg91Node_workflowId_idx" ON "Msg91Node"("workflowId"); + +-- CreateIndex +CREATE INDEX "Msg91Node_nodeId_idx" ON "Msg91Node"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "AINode_nodeId_key" ON "AINode"("nodeId"); + +-- CreateIndex +CREATE INDEX "AINode_workflowId_idx" ON "AINode"("workflowId"); + +-- CreateIndex +CREATE INDEX "AINode_nodeId_idx" ON "AINode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "MediaUploadNode_nodeId_key" ON "MediaUploadNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "MediaUploadNode_workflowId_idx" ON "MediaUploadNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "MediaUploadNode_nodeId_idx" ON "MediaUploadNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "SortNode_nodeId_key" ON "SortNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "FilterNode_nodeId_key" ON "FilterNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "FilterNode_workflowId_idx" ON "FilterNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "FilterNode_nodeId_idx" ON "FilterNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "AggregateNode_nodeId_key" ON "AggregateNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "AggregateNode_workflowId_idx" ON "AggregateNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "AggregateNode_nodeId_idx" ON "AggregateNode"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PostgresNode_nodeId_key" ON "PostgresNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "PostgresNode_workflowId_idx" ON "PostgresNode"("workflowId"); + +-- CreateIndex +CREATE INDEX "PostgresNode_nodeId_idx" ON "PostgresNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "PostgresNode_credentialId_idx" ON "PostgresNode"("credentialId"); + +-- CreateIndex +CREATE UNIQUE INDEX "WebhookTrigger_workflowId_key" ON "WebhookTrigger"("workflowId"); + +-- CreateIndex +CREATE UNIQUE INDEX "WebhookTrigger_webhookId_key" ON "WebhookTrigger"("webhookId"); + +-- CreateIndex +CREATE INDEX "WebhookTrigger_webhookId_idx" ON "WebhookTrigger"("webhookId"); + +-- CreateIndex +CREATE INDEX "WebhookTrigger_workflowId_idx" ON "WebhookTrigger"("workflowId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ScheduleTrigger_workflowId_key" ON "ScheduleTrigger"("workflowId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ScheduleTrigger_inngestFunctionId_key" ON "ScheduleTrigger"("inngestFunctionId"); + +-- CreateIndex +CREATE INDEX "ScheduleTrigger_workflowId_idx" ON "ScheduleTrigger"("workflowId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ErrorTriggerNode_nodeId_key" ON "ErrorTriggerNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "ErrorTriggerNode_nodeId_idx" ON "ErrorTriggerNode"("nodeId"); + +-- CreateIndex +CREATE INDEX "ErrorTriggerNode_workflowId_idx" ON "ErrorTriggerNode"("workflowId"); + +-- CreateIndex +CREATE UNIQUE INDEX "RazorpayTrigger_nodeId_key" ON "RazorpayTrigger"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "RazorpayTrigger_webhookId_key" ON "RazorpayTrigger"("webhookId"); + +-- CreateIndex +CREATE INDEX "RazorpayTrigger_nodeId_idx" ON "RazorpayTrigger"("nodeId"); + +-- CreateIndex +CREATE INDEX "RazorpayTrigger_workflowId_idx" ON "RazorpayTrigger"("workflowId"); + +-- CreateIndex +CREATE INDEX "RazorpayTrigger_webhookId_idx" ON "RazorpayTrigger"("webhookId"); + +-- CreateIndex +CREATE UNIQUE INDEX "WhatsAppTrigger_nodeId_key" ON "WhatsAppTrigger"("nodeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "WhatsAppTrigger_webhookId_key" ON "WhatsAppTrigger"("webhookId"); + +-- CreateIndex +CREATE INDEX "WhatsAppTrigger_nodeId_idx" ON "WhatsAppTrigger"("nodeId"); + +-- CreateIndex +CREATE INDEX "WhatsAppTrigger_workflowId_idx" ON "WhatsAppTrigger"("workflowId"); + +-- CreateIndex +CREATE INDEX "WhatsAppTrigger_webhookId_idx" ON "WhatsAppTrigger"("webhookId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GmailWatcher_nodeId_key" ON "GmailWatcher"("nodeId"); + +-- CreateIndex +CREATE INDEX "GmailWatcher_email_active_idx" ON "GmailWatcher"("email", "active"); + +-- AddForeignKey +ALTER TABLE "BillingEvent" ADD CONSTRAINT "BillingEvent_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "workflow" ADD CONSTRAINT "workflow_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "credential" ADD CONSTRAINT "credential_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Node" ADD CONSTRAINT "Node_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Node" ADD CONSTRAINT "Node_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Connection" ADD CONSTRAINT "Connection_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Connection" ADD CONSTRAINT "Connection_fromNodeId_fkey" FOREIGN KEY ("fromNodeId") REFERENCES "Node"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Connection" ADD CONSTRAINT "Connection_toNodeId_fkey" FOREIGN KEY ("toNodeId") REFERENCES "Node"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Execution" ADD CONSTRAINT "Execution_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "NodeExecution" ADD CONSTRAINT "NodeExecution_executionId_fkey" FOREIGN KEY ("executionId") REFERENCES "Execution"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RazorpayNode" ADD CONSTRAINT "RazorpayNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RazorpayNode" ADD CONSTRAINT "RazorpayNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ShiprocketNode" ADD CONSTRAINT "ShiprocketNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ShiprocketNode" ADD CONSTRAINT "ShiprocketNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CashfreeNode" ADD CONSTRAINT "CashfreeNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CashfreeNode" ADD CONSTRAINT "CashfreeNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ZohoCrmNode" ADD CONSTRAINT "ZohoCrmNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ZohoCrmNode" ADD CONSTRAINT "ZohoCrmNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "HubspotNode" ADD CONSTRAINT "HubspotNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "HubspotNode" ADD CONSTRAINT "HubspotNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FreshdeskNode" ADD CONSTRAINT "FreshdeskNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FreshdeskNode" ADD CONSTRAINT "FreshdeskNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IfElseNode" ADD CONSTRAINT "IfElseNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SwitchNode" ADD CONSTRAINT "SwitchNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SetVariableNode" ADD CONSTRAINT "SetVariableNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CodeNode" ADD CONSTRAINT "CodeNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LoopNode" ADD CONSTRAINT "LoopNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WaitNode" ADD CONSTRAINT "WaitNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MergeNode" ADD CONSTRAINT "MergeNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GmailNode" ADD CONSTRAINT "GmailNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GmailNode" ADD CONSTRAINT "GmailNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GoogleSheetsNode" ADD CONSTRAINT "GoogleSheetsNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GoogleSheetsNode" ADD CONSTRAINT "GoogleSheetsNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GoogleDriveNode" ADD CONSTRAINT "GoogleDriveNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GoogleDriveNode" ADD CONSTRAINT "GoogleDriveNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SlackNode" ADD CONSTRAINT "SlackNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SlackNode" ADD CONSTRAINT "SlackNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WhatsAppNode" ADD CONSTRAINT "WhatsAppNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WhatsAppNode" ADD CONSTRAINT "WhatsAppNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "NotionNode" ADD CONSTRAINT "NotionNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "NotionNode" ADD CONSTRAINT "NotionNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Msg91Node" ADD CONSTRAINT "Msg91Node_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Msg91Node" ADD CONSTRAINT "Msg91Node_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AINode" ADD CONSTRAINT "AINode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AINode" ADD CONSTRAINT "AINode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MediaUploadNode" ADD CONSTRAINT "MediaUploadNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MediaUploadNode" ADD CONSTRAINT "MediaUploadNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SortNode" ADD CONSTRAINT "SortNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FilterNode" ADD CONSTRAINT "FilterNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AggregateNode" ADD CONSTRAINT "AggregateNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostgresNode" ADD CONSTRAINT "PostgresNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostgresNode" ADD CONSTRAINT "PostgresNode_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "credential"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WebhookTrigger" ADD CONSTRAINT "WebhookTrigger_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ScheduleTrigger" ADD CONSTRAINT "ScheduleTrigger_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ErrorTriggerNode" ADD CONSTRAINT "ErrorTriggerNode_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RazorpayTrigger" ADD CONSTRAINT "RazorpayTrigger_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WhatsAppTrigger" ADD CONSTRAINT "WhatsAppTrigger_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GmailWatcher" ADD CONSTRAINT "GmailWatcher_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema/migrations/migration_lock.toml b/prisma/schema/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/schema/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema/nodes_commerce.prisma b/prisma/schema/nodes_commerce.prisma index 0a81ca2..55a4880 100644 --- a/prisma/schema/nodes_commerce.prisma +++ b/prisma/schema/nodes_commerce.prisma @@ -4,62 +4,63 @@ // ───────────────────────────────────────────────────────────── model RazorpayNode { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String credentialId String? - operation RazorpayOperation @default(ORDER_CREATE) - variableName String @default("razorpay") - amount String @default("") - currency String @default("INR") - description String @default("") - receipt String @default("") - notes String @default("") - partialPayment Boolean @default(false) - orderId String @default("") - paymentId String @default("") - captureAmount String @default("") - refundAmount String @default("") - refundSpeed String @default("normal") - refundId String @default("") - customerId String @default("") - customerName String @default("") - customerEmail String @default("") - customerContact String @default("") - failExisting Boolean @default(false) - planId String @default("") - totalCount String @default("") - quantity String @default("1") - startAt String @default("") - subscriptionId String @default("") - cancelAtCycleEnd Boolean @default(false) - invoiceType String @default("invoice") - lineItems String @default("") - expireBy String @default("") - smsNotify Boolean @default(true) - emailNotify Boolean @default(true) - invoiceId String @default("") - paymentLinkId String @default("") - referenceId String @default("") - reminderEnable Boolean @default(true) - callbackUrl String @default("") - callbackMethod String @default("") - accountNumber String @default("") - fundAccountId String @default("") - payoutMode String @default("") - payoutPurpose String @default("payout") - narration String @default("") + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) + operation RazorpayOperation @default(ORDER_CREATE) + variableName String @default("razorpay") + amount String @default("") + currency String @default("INR") + description String @default("") + receipt String @default("") + notes String @default("") + partialPayment Boolean @default(false) + orderId String @default("") + paymentId String @default("") + captureAmount String @default("") + refundAmount String @default("") + refundSpeed String @default("normal") + refundId String @default("") + customerId String @default("") + customerName String @default("") + customerEmail String @default("") + customerContact String @default("") + failExisting Boolean @default(false) + planId String @default("") + totalCount String @default("") + quantity String @default("1") + startAt String @default("") + subscriptionId String @default("") + cancelAtCycleEnd Boolean @default(false) + invoiceType String @default("invoice") + lineItems String @default("") + expireBy String @default("") + smsNotify Boolean @default(true) + emailNotify Boolean @default(true) + invoiceId String @default("") + paymentLinkId String @default("") + referenceId String @default("") + reminderEnable Boolean @default(true) + callbackUrl String @default("") + callbackMethod String @default("") + accountNumber String @default("") + fundAccountId String @default("") + payoutMode String @default("") + payoutPurpose String @default("payout") + narration String @default("") queueIfLowBalance Boolean @default(false) - payoutId String @default("") - signature String @default("") - throwOnInvalid Boolean @default(true) - count String @default("") - skip String @default("") - fromDate String @default("") - toDate String @default("") - authorized String @default("") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + payoutId String @default("") + signature String @default("") + throwOnInvalid Boolean @default(true) + count String @default("") + skip String @default("") + fromDate String @default("") + toDate String @default("") + authorized String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@ -68,8 +69,8 @@ model RazorpayNode { } model ShiprocketNode { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String credentialId String? @@ -77,216 +78,216 @@ model ShiprocketNode { variableName String @default("shiprocket") // ── ORDER fields ────────────────────────────────────────────────── - orderId String @default("") @db.Text - orderDate String @default("") - channelId String @default("") - - billingName String @default("") @db.Text - billingAddress String @default("") @db.Text - billingAddress2 String @default("") @db.Text - billingCity String @default("") @db.Text - billingState String @default("") @db.Text - billingCountry String @default("India") - billingPincode String @default("") @db.Text - billingEmail String @default("") @db.Text - billingPhone String @default("") @db.Text + orderId String @default("") @db.Text + orderDate String @default("") + channelId String @default("") + + billingName String @default("") @db.Text + billingAddress String @default("") @db.Text + billingAddress2 String @default("") @db.Text + billingCity String @default("") @db.Text + billingState String @default("") @db.Text + billingCountry String @default("India") + billingPincode String @default("") @db.Text + billingEmail String @default("") @db.Text + billingPhone String @default("") @db.Text billingAlternatePhone String @default("") @db.Text - shippingIsBilling Boolean @default(true) - shippingName String @default("") @db.Text - shippingAddress String @default("") @db.Text - shippingAddress2 String @default("") @db.Text - shippingCity String @default("") @db.Text - shippingState String @default("") @db.Text - shippingCountry String @default("India") - shippingPincode String @default("") @db.Text - shippingEmail String @default("") @db.Text - shippingPhone String @default("") @db.Text + shippingIsBilling Boolean @default(true) + shippingName String @default("") @db.Text + shippingAddress String @default("") @db.Text + shippingAddress2 String @default("") @db.Text + shippingCity String @default("") @db.Text + shippingState String @default("") @db.Text + shippingCountry String @default("India") + shippingPincode String @default("") @db.Text + shippingEmail String @default("") @db.Text + shippingPhone String @default("") @db.Text - orderItems String @default("[]") @db.Text + orderItems String @default("[]") @db.Text - paymentMethod String @default("prepaid") - subTotal String @default("") @db.Text - codAmount String @default("0") + paymentMethod String @default("prepaid") + subTotal String @default("") @db.Text + codAmount String @default("0") - length String @default("") @db.Text - breadth String @default("") @db.Text - height String @default("") @db.Text - weight String @default("") @db.Text + length String @default("") @db.Text + breadth String @default("") @db.Text + height String @default("") @db.Text + weight String @default("") @db.Text - shiprocketOrderId String @default("") @db.Text - shipmentId String @default("") @db.Text - awbCode String @default("") @db.Text + shiprocketOrderId String @default("") @db.Text + shipmentId String @default("") @db.Text + awbCode String @default("") @db.Text - courierId String @default("") @db.Text - courierName String @default("") @db.Text + courierId String @default("") @db.Text + courierName String @default("") @db.Text // ── PICKUP ──────────────────────────────────────────────────────── - pickupLocation String @default("") @db.Text + pickupLocation String @default("") @db.Text // ── RATE/SERVICEABILITY ─────────────────────────────────────────── - pickupPostcode String @default("") @db.Text - deliveryPostcode String @default("") @db.Text - cod String @default("0") + pickupPostcode String @default("") @db.Text + deliveryPostcode String @default("") @db.Text + cod String @default("0") // ── RETURN ─────────────────────────────────────────────────────── - returnOrderId String @default("") @db.Text - returnReason String @default("") @db.Text + returnOrderId String @default("") @db.Text + returnReason String @default("") @db.Text returnPickupLocation String @default("") @db.Text // ── PRODUCT ────────────────────────────────────────────────────── - productName String @default("") @db.Text - productSku String @default("") @db.Text - productMrp String @default("") @db.Text + productName String @default("") @db.Text + productSku String @default("") @db.Text + productMrp String @default("") @db.Text productSellingPrice String @default("") @db.Text - productWeight String @default("") @db.Text - productCategory String @default("") @db.Text - productHsn String @default("") @db.Text + productWeight String @default("") @db.Text + productCategory String @default("") @db.Text + productHsn String @default("") @db.Text // ── LIST filters ───────────────────────────────────────────────── - filterStatus String @default("") @db.Text - pageNo Int @default(1) - perPage Int @default(10) + filterStatus String @default("") @db.Text + pageNo Int @default(1) + perPage Int @default(10) // ── WAREHOUSE ──────────────────────────────────────────────────── - warehouseName String @default("") @db.Text - warehouseEmail String @default("") @db.Text - warehousePhone String @default("") @db.Text - warehouseAddress String @default("") @db.Text - warehouseCity String @default("") @db.Text - warehouseState String @default("") @db.Text - warehousePincode String @default("") @db.Text - warehouseCountry String @default("India") + warehouseName String @default("") @db.Text + warehouseEmail String @default("") @db.Text + warehousePhone String @default("") @db.Text + warehouseAddress String @default("") @db.Text + warehouseCity String @default("") @db.Text + warehouseState String @default("") @db.Text + warehousePincode String @default("") @db.Text + warehouseCountry String @default("India") - cancelReason String @default("") @db.Text + cancelReason String @default("") @db.Text - continueOnFail Boolean @default(false) + continueOnFail Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) - credential Credential? @relation(fields: [credentialId], references: [id]) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + credential Credential? @relation(fields: [credentialId], references: [id]) @@index([workflowId]) @@index([nodeId]) } model CashfreeNode { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String credentialId String? - credential Credential? @relation(fields: [credentialId], references: [id]) + credential Credential? @relation(fields: [credentialId], references: [id]) - operation CashfreeOperation @default(CREATE_ORDER) - variableName String @default("cashfree") - continueOnFail Boolean @default(false) + operation CashfreeOperation @default(CREATE_ORDER) + variableName String @default("cashfree") + continueOnFail Boolean @default(false) // ── ORDER FIELDS ────────────────────────────────────────── - orderId String @default("") @db.Text - orderAmount String @default("") @db.Text - orderCurrency String @default("INR") - orderNote String @default("") @db.Text - orderMeta String @default("{}") @db.Text + orderId String @default("") @db.Text + orderAmount String @default("") @db.Text + orderCurrency String @default("INR") + orderNote String @default("") @db.Text + orderMeta String @default("{}") @db.Text // ── CUSTOMER FIELDS ─────────────────────────────────────── - customerId String @default("") @db.Text - customerEmail String @default("") @db.Text - customerPhone String @default("") @db.Text - customerName String @default("") @db.Text - customerBankAccountNumber String @default("") @db.Text - customerBankIfsc String @default("") @db.Text - customerBankCode String @default("") + customerId String @default("") @db.Text + customerEmail String @default("") @db.Text + customerPhone String @default("") @db.Text + customerName String @default("") @db.Text + customerBankAccountNumber String @default("") @db.Text + customerBankIfsc String @default("") @db.Text + customerBankCode String @default("") // ── PAYMENT FIELDS ──────────────────────────────────────── - cfPaymentId String @default("") @db.Text - cfOrderId String @default("") @db.Text - paymentMethod String @default("") @db.Text + cfPaymentId String @default("") @db.Text + cfOrderId String @default("") @db.Text + paymentMethod String @default("") @db.Text // ── REFUND FIELDS ───────────────────────────────────────── - refundId String @default("") @db.Text - refundAmount String @default("") @db.Text - refundNote String @default("") @db.Text - refundSpeed String @default("STANDARD") - refundSplits String @default("[]") @db.Text + refundId String @default("") @db.Text + refundAmount String @default("") @db.Text + refundNote String @default("") @db.Text + refundSpeed String @default("STANDARD") + refundSplits String @default("[]") @db.Text // ── SETTLEMENT FIELDS ───────────────────────────────────── - settlementId String @default("") @db.Text - startDate String @default("") @db.Text - endDate String @default("") @db.Text - cursor String @default("") - limit Int @default(10) + settlementId String @default("") @db.Text + startDate String @default("") @db.Text + endDate String @default("") @db.Text + cursor String @default("") + limit Int @default(10) // ── PAYMENT LINK FIELDS ─────────────────────────────────── - linkId String @default("") @db.Text - linkAmount String @default("") @db.Text - linkCurrency String @default("INR") - linkPurpose String @default("") @db.Text - linkDescription String @default("") @db.Text - linkExpiryTime String @default("") @db.Text - linkNotifyPhone Boolean @default(true) - linkNotifyEmail Boolean @default(true) - linkAutoReminders Boolean @default(true) - linkMinPartialAmount String @default("") @db.Text - linkMeta String @default("{}") @db.Text + linkId String @default("") @db.Text + linkAmount String @default("") @db.Text + linkCurrency String @default("INR") + linkPurpose String @default("") @db.Text + linkDescription String @default("") @db.Text + linkExpiryTime String @default("") @db.Text + linkNotifyPhone Boolean @default(true) + linkNotifyEmail Boolean @default(true) + linkAutoReminders Boolean @default(true) + linkMinPartialAmount String @default("") @db.Text + linkMeta String @default("{}") @db.Text // ── SUBSCRIPTION FIELDS ─────────────────────────────────── - subscriptionId String @default("") @db.Text - planId String @default("") @db.Text - planName String @default("") @db.Text - planType String @default("PERIODIC") - planIntervalType String @default("MONTH") - planIntervals Int @default(1) - planMaxCycles Int @default(0) - planMaxAmount String @default("") @db.Text + subscriptionId String @default("") @db.Text + planId String @default("") @db.Text + planName String @default("") @db.Text + planType String @default("PERIODIC") + planIntervalType String @default("MONTH") + planIntervals Int @default(1) + planMaxCycles Int @default(0) + planMaxAmount String @default("") @db.Text subscriptionFirstChargeTime String @default("") @db.Text - subscriptionExpiryTime String @default("") @db.Text - subscriptionReturnUrl String @default("") @db.Text - subscriptionNotifyUrl String @default("") @db.Text - subscriptionAction String @default("PAUSE") + subscriptionExpiryTime String @default("") @db.Text + subscriptionReturnUrl String @default("") @db.Text + subscriptionNotifyUrl String @default("") @db.Text + subscriptionAction String @default("PAUSE") // ── PAYOUT FIELDS ───────────────────────────────────────── - beneId String @default("") @db.Text - beneName String @default("") @db.Text - beneEmail String @default("") @db.Text - benePhone String @default("") @db.Text - beneBankAccount String @default("") @db.Text - beneBankIfsc String @default("") @db.Text - beneVpa String @default("") @db.Text - beneAddress String @default("") @db.Text - beneCity String @default("") @db.Text - beneState String @default("India") - benePincode String @default("") @db.Text - - transferId String @default("") @db.Text - transferAmount String @default("") @db.Text - transferRemarks String @default("") @db.Text - transferMode String @default("banktransfer") - batchTransferId String @default("") @db.Text - batchEntries String @default("[]") @db.Text + beneId String @default("") @db.Text + beneName String @default("") @db.Text + beneEmail String @default("") @db.Text + benePhone String @default("") @db.Text + beneBankAccount String @default("") @db.Text + beneBankIfsc String @default("") @db.Text + beneVpa String @default("") @db.Text + beneAddress String @default("") @db.Text + beneCity String @default("") @db.Text + beneState String @default("India") + benePincode String @default("") @db.Text + + transferId String @default("") @db.Text + transferAmount String @default("") @db.Text + transferRemarks String @default("") @db.Text + transferMode String @default("banktransfer") + batchTransferId String @default("") @db.Text + batchEntries String @default("[]") @db.Text // ── UPI FIELDS ──────────────────────────────────────────── - upiVpa String @default("") @db.Text - upiAmount String @default("") @db.Text - upiDescription String @default("") @db.Text + upiVpa String @default("") @db.Text + upiAmount String @default("") @db.Text + upiDescription String @default("") @db.Text // ── OFFER FIELDS ────────────────────────────────────────── - offerId String @default("") @db.Text - offerMeta String @default("{}") @db.Text - offerValidations String @default("{}") @db.Text - offerDetails String @default("{}") @db.Text + offerId String @default("") @db.Text + offerMeta String @default("{}") @db.Text + offerValidations String @default("{}") @db.Text + offerDetails String @default("{}") @db.Text // ── WEBHOOK FIELDS ──────────────────────────────────────── - webhookSignature String @default("") @db.Text - webhookTimestamp String @default("") @db.Text - webhookRawBody String @default("") @db.Text - webhookThrowOnFail Boolean @default(true) + webhookSignature String @default("") @db.Text + webhookTimestamp String @default("") @db.Text + webhookRawBody String @default("") @db.Text + webhookThrowOnFail Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([workflowId]) @@index([nodeId]) diff --git a/prisma/schema/nodes_crm.prisma b/prisma/schema/nodes_crm.prisma index 0f62fa6..3b8d9ef 100644 --- a/prisma/schema/nodes_crm.prisma +++ b/prisma/schema/nodes_crm.prisma @@ -4,8 +4,8 @@ // ───────────────────────────────────────────────────────────── model ZohoCrmNode { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String credentialId String? credential Credential? @relation(fields: [credentialId], references: [id]) @@ -13,305 +13,305 @@ model ZohoCrmNode { operation ZohoCrmOperation @default(CREATE_LEAD) variableName String @default("zoho") - module String @default("Leads") - recordId String @default("") @db.Text + module String @default("Leads") + recordId String @default("") @db.Text // Lead / Contact shared fields - firstName String @default("") @db.Text - lastName String @default("") @db.Text - email String @default("") @db.Text - phone String @default("") @db.Text - mobile String @default("") @db.Text - company String @default("") @db.Text - title String @default("") @db.Text - website String @default("") @db.Text - leadSource String @default("") - leadStatus String @default("") - industry String @default("") - annualRevenue String @default("") @db.Text - noOfEmployees String @default("") @db.Text - rating String @default("") - description String @default("") @db.Text + firstName String @default("") @db.Text + lastName String @default("") @db.Text + email String @default("") @db.Text + phone String @default("") @db.Text + mobile String @default("") @db.Text + company String @default("") @db.Text + title String @default("") @db.Text + website String @default("") @db.Text + leadSource String @default("") + leadStatus String @default("") + industry String @default("") + annualRevenue String @default("") @db.Text + noOfEmployees String @default("") @db.Text + rating String @default("") + description String @default("") @db.Text // Address (Lead / Contact / Account) - street String @default("") @db.Text - city String @default("") @db.Text - state String @default("") @db.Text - country String @default("India") - zipCode String @default("") @db.Text + street String @default("") @db.Text + city String @default("") @db.Text + state String @default("") @db.Text + country String @default("India") + zipCode String @default("") @db.Text // Deal fields - dealName String @default("") @db.Text - dealStage String @default("") - dealAmount String @default("") @db.Text - closingDate String @default("") @db.Text - accountName String @default("") @db.Text - contactName String @default("") @db.Text - probability String @default("") @db.Text - dealType String @default("") + dealName String @default("") @db.Text + dealStage String @default("") + dealAmount String @default("") @db.Text + closingDate String @default("") @db.Text + accountName String @default("") @db.Text + contactName String @default("") @db.Text + probability String @default("") @db.Text + dealType String @default("") // Account fields - accountOwner String @default("") @db.Text - billingCity String @default("") @db.Text - billingState String @default("") @db.Text + accountOwner String @default("") @db.Text + billingCity String @default("") @db.Text + billingState String @default("") @db.Text // Task / Activity fields - subject String @default("") @db.Text - dueDate String @default("") @db.Text - priority String @default("High") - status String @default("Not Started") - whoId String @default("") @db.Text - whatId String @default("") @db.Text - whoModule String @default("Contacts") - whatModule String @default("Deals") + subject String @default("") @db.Text + dueDate String @default("") @db.Text + priority String @default("High") + status String @default("Not Started") + whoId String @default("") @db.Text + whatId String @default("") @db.Text + whoModule String @default("Contacts") + whatModule String @default("Deals") // Call log fields - callDuration String @default("") @db.Text - callDirection String @default("Outbound") - callResult String @default("") - callStartTime String @default("") @db.Text - callDescription String @default("") @db.Text + callDuration String @default("") @db.Text + callDirection String @default("Outbound") + callResult String @default("") + callStartTime String @default("") @db.Text + callDescription String @default("") @db.Text // Meeting fields - meetingStart String @default("") @db.Text - meetingEnd String @default("") @db.Text - meetingAgenda String @default("") @db.Text - participants String @default("[]") @db.Text + meetingStart String @default("") @db.Text + meetingEnd String @default("") @db.Text + meetingAgenda String @default("") @db.Text + participants String @default("[]") @db.Text // Note fields - noteTitle String @default("") @db.Text - noteContent String @default("") @db.Text - parentModule String @default("Leads") + noteTitle String @default("") @db.Text + noteContent String @default("") @db.Text + parentModule String @default("Leads") // Search / filter fields - searchTerm String @default("") @db.Text - searchField String @default("Email") - criteria String @default("") @db.Text - page Int @default(1) - perPage Int @default(10) + searchTerm String @default("") @db.Text + searchField String @default("Email") + criteria String @default("") @db.Text + page Int @default(1) + perPage Int @default(10) // Convert lead - createDeal Boolean @default(false) - overwrite Boolean @default(false) + createDeal Boolean @default(false) + overwrite Boolean @default(false) // Custom fields JSON — escape hatch for any Zoho field not listed above - customFields String @default("{}") @db.Text + customFields String @default("{}") @db.Text // Upsert - duplicateCheckField String @default("Email") + duplicateCheckField String @default("Email") - continueOnFail Boolean @default(false) + continueOnFail Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([workflowId]) @@index([nodeId]) } model HubspotNode { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String credentialId String? - credential Credential? @relation(fields: [credentialId], references: [id]) + credential Credential? @relation(fields: [credentialId], references: [id]) operation HubspotOperation @default(CREATE_CONTACT) variableName String @default("hubspot") // ── OBJECT TYPE (for generic ops) ──────────────────────── - objectType String @default("contacts") + objectType String @default("contacts") // ── RECORD IDENTIFICATION ───────────────────────────────── - recordId String @default("") @db.Text + recordId String @default("") @db.Text // ── CONTACT FIELDS ──────────────────────────────────────── - email String @default("") @db.Text - firstName String @default("") @db.Text - lastName String @default("") @db.Text - phone String @default("") @db.Text - website String @default("") @db.Text - company String @default("") @db.Text - jobTitle String @default("") @db.Text - lifecycleStage String @default("") - leadStatus String @default("") + email String @default("") @db.Text + firstName String @default("") @db.Text + lastName String @default("") @db.Text + phone String @default("") @db.Text + website String @default("") @db.Text + company String @default("") @db.Text + jobTitle String @default("") @db.Text + lifecycleStage String @default("") + leadStatus String @default("") // ── COMPANY FIELDS ──────────────────────────────────────── - companyName String @default("") @db.Text - domain String @default("") @db.Text - industry String @default("") - annualRevenue String @default("") @db.Text - numberOfEmployees String @default("") @db.Text - city String @default("") @db.Text - state String @default("") @db.Text - country String @default("India") + companyName String @default("") @db.Text + domain String @default("") @db.Text + industry String @default("") + annualRevenue String @default("") @db.Text + numberOfEmployees String @default("") @db.Text + city String @default("") @db.Text + state String @default("") @db.Text + country String @default("India") // ── DEAL FIELDS ─────────────────────────────────────────── - dealName String @default("") @db.Text - dealStage String @default("") - pipeline String @default("default") - amount String @default("") @db.Text - closeDate String @default("") @db.Text - dealType String @default("") - priority String @default("") + dealName String @default("") @db.Text + dealStage String @default("") + pipeline String @default("default") + amount String @default("") @db.Text + closeDate String @default("") @db.Text + dealType String @default("") + priority String @default("") // ── TICKET FIELDS ───────────────────────────────────────── - ticketName String @default("") @db.Text - ticketPipeline String @default("0") - ticketStatus String @default("") - ticketPriority String @default("") - ticketDescription String @default("") @db.Text - ticketSource String @default("") + ticketName String @default("") @db.Text + ticketPipeline String @default("0") + ticketStatus String @default("") + ticketPriority String @default("") + ticketDescription String @default("") @db.Text + ticketSource String @default("") // ── ACTIVITY FIELDS ─────────────────────────────────────── - noteBody String @default("") @db.Text - taskSubject String @default("") @db.Text - taskBody String @default("") @db.Text - taskStatus String @default("NOT_STARTED") - taskPriority String @default("NONE") - taskDueDate String @default("") @db.Text - callBody String @default("") @db.Text - callDuration String @default("") @db.Text - callDirection String @default("OUTBOUND") - callDisposition String @default("") - emailSubject String @default("") @db.Text - emailBody String @default("") @db.Text - emailFrom String @default("") @db.Text - emailTo String @default("") @db.Text + noteBody String @default("") @db.Text + taskSubject String @default("") @db.Text + taskBody String @default("") @db.Text + taskStatus String @default("NOT_STARTED") + taskPriority String @default("NONE") + taskDueDate String @default("") @db.Text + callBody String @default("") @db.Text + callDuration String @default("") @db.Text + callDirection String @default("OUTBOUND") + callDisposition String @default("") + emailSubject String @default("") @db.Text + emailBody String @default("") @db.Text + emailFrom String @default("") @db.Text + emailTo String @default("") @db.Text // ── ASSOCIATION FIELDS ──────────────────────────────────── - fromObjectType String @default("contacts") - fromObjectId String @default("") @db.Text - toObjectType String @default("deals") - toObjectId String @default("") @db.Text - associationType String @default("") + fromObjectType String @default("contacts") + fromObjectId String @default("") @db.Text + toObjectType String @default("deals") + toObjectId String @default("") @db.Text + associationType String @default("") // ── LIST FIELDS ─────────────────────────────────────────── - listId String @default("") @db.Text + listId String @default("") @db.Text // ── SEARCH / FILTER FIELDS ──────────────────────────────── - searchQuery String @default("") @db.Text - filterProperty String @default("") - filterOperator String @default("EQ") - filterValue String @default("") @db.Text - sortProperty String @default("createdate") - sortDirection String @default("DESCENDING") - limit Int @default(10) - after String @default("") + searchQuery String @default("") @db.Text + filterProperty String @default("") + filterOperator String @default("EQ") + filterValue String @default("") @db.Text + sortProperty String @default("createdate") + sortDirection String @default("DESCENDING") + limit Int @default(10) + after String @default("") // ── CUSTOM PROPERTIES (JSON) ────────────────────────────── // Escape hatch for any HubSpot property not listed above - customProperties String @default("{}") @db.Text + customProperties String @default("{}") @db.Text - continueOnFail Boolean @default(false) + continueOnFail Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([workflowId]) @@index([nodeId]) } model FreshdeskNode { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String credentialId String? - credential Credential? @relation(fields: [credentialId], references: [id]) + credential Credential? @relation(fields: [credentialId], references: [id]) operation FreshdeskOperation @default(CREATE_TICKET) variableName String @default("freshdesk") // ── TICKET FIELDS ───────────────────────────────────────── - subject String @default("") @db.Text - description String @default("") @db.Text - descriptionHtml String @default("") @db.Text - email String @default("") @db.Text - name String @default("") @db.Text - phone String @default("") @db.Text - mobilePhone String @default("") @db.Text - subject2 String @default("") @db.Text - ticketId String @default("") @db.Text + subject String @default("") @db.Text + description String @default("") @db.Text + descriptionHtml String @default("") @db.Text + email String @default("") @db.Text + name String @default("") @db.Text + phone String @default("") @db.Text + mobilePhone String @default("") @db.Text + subject2 String @default("") @db.Text + ticketId String @default("") @db.Text // Priority: 1=Low, 2=Medium, 3=High, 4=Urgent - priority Int @default(2) + priority Int @default(2) // Status: 2=Open, 3=Pending, 4=Resolved, 5=Closed - status Int @default(2) + status Int @default(2) // Source: 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Twitter DM, 10=WhatsApp - source Int @default(2) + source Int @default(2) // Type: Question, Incident, Problem, Feature Request, Refund Request, etc. - ticketType String @default("") + ticketType String @default("") - responderId String @default("") @db.Text - groupId String @default("") @db.Text - productId String @default("") @db.Text - companyId String @default("") @db.Text - fwdEmail String @default("") @db.Text + responderId String @default("") @db.Text + groupId String @default("") @db.Text + productId String @default("") @db.Text + companyId String @default("") @db.Text + fwdEmail String @default("") @db.Text // Tags — comma-separated list - tags String @default("") @db.Text + tags String @default("") @db.Text // Custom fields JSON — { "cf_order_id": "ORD-123" } - customFields String @default("{}") @db.Text + customFields String @default("{}") @db.Text // ── NOTE FIELDS ─────────────────────────────────────────── - noteId String @default("") @db.Text - noteBody String @default("") @db.Text - notePrivate Boolean @default(false) - noteUserId String @default("") @db.Text + noteId String @default("") @db.Text + noteBody String @default("") @db.Text + notePrivate Boolean @default(false) + noteUserId String @default("") @db.Text // ── CONTACT FIELDS ──────────────────────────────────────── - contactId String @default("") @db.Text - contactEmail String @default("") @db.Text - contactName String @default("") @db.Text - contactPhone String @default("") @db.Text - contactMobile String @default("") @db.Text - contactJobTitle String @default("") @db.Text - contactTimeZone String @default("") @db.Text - contactLanguage String @default("") @db.Text - contactTags String @default("") @db.Text - contactCompanyId String @default("") @db.Text - mergeTargetId String @default("") @db.Text + contactId String @default("") @db.Text + contactEmail String @default("") @db.Text + contactName String @default("") @db.Text + contactPhone String @default("") @db.Text + contactMobile String @default("") @db.Text + contactJobTitle String @default("") @db.Text + contactTimeZone String @default("") @db.Text + contactLanguage String @default("") @db.Text + contactTags String @default("") @db.Text + contactCompanyId String @default("") @db.Text + mergeTargetId String @default("") @db.Text // ── COMPANY FIELDS ──────────────────────────────────────── - companyName String @default("") @db.Text - companyDomain String @default("") @db.Text - companyDescription String @default("") @db.Text - companyNote String @default("") @db.Text + companyName String @default("") @db.Text + companyDomain String @default("") @db.Text + companyDescription String @default("") @db.Text + companyNote String @default("") @db.Text // ── AGENT FIELDS ────────────────────────────────────────── - agentId String @default("") @db.Text + agentId String @default("") @db.Text // ── REPLY FIELDS ────────────────────────────────────────── - replyBody String @default("") @db.Text - replyFrom String @default("") @db.Text - replyTo String @default("") @db.Text - replyCc String @default("") @db.Text - replyBcc String @default("") @db.Text + replyBody String @default("") @db.Text + replyFrom String @default("") @db.Text + replyTo String @default("") @db.Text + replyCc String @default("") @db.Text + replyBcc String @default("") @db.Text // ── SEARCH / FILTER FIELDS ──────────────────────────────── - searchQuery String @default("") @db.Text - filterBy String @default("") - orderBy String @default("created_at") - orderType String @default("desc") - page Int @default(1) - perPage Int @default(30) - updatedSince String @default("") @db.Text - includeStats Boolean @default(false) + searchQuery String @default("") @db.Text + filterBy String @default("") + orderBy String @default("created_at") + orderType String @default("desc") + page Int @default(1) + perPage Int @default(30) + updatedSince String @default("") @db.Text + includeStats Boolean @default(false) - continueOnFail Boolean @default(false) + continueOnFail Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([workflowId]) @@index([nodeId]) diff --git a/prisma/schema/nodes_flow_control.prisma b/prisma/schema/nodes_flow_control.prisma index 43b7562..94ea64b 100644 --- a/prisma/schema/nodes_flow_control.prisma +++ b/prisma/schema/nodes_flow_control.prisma @@ -91,31 +91,31 @@ model LoopNode { } model WaitNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String // Resume mode: "duration" | "until" | "webhook" - waitMode String @default("duration") + waitMode String @default("duration") // Duration mode fields - duration Int @default(30) - durationUnit String @default("minutes") + duration Int @default(30) + durationUnit String @default("minutes") // Until mode fields - untilDatetime String @default("") - timezone String @default("UTC") + untilDatetime String @default("") + timezone String @default("UTC") // Webhook mode fields - timeoutDuration Int @default(24) - timeoutUnit String @default("hours") + timeoutDuration Int @default(24) + timeoutUnit String @default("hours") continueOnTimeout Boolean @default(true) // Output - variableName String @default("wait") + variableName String @default("wait") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@ -124,12 +124,12 @@ model WaitNode { } model MergeNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String // How many input branches to wait for (2-10) - inputCount Int @default(2) + inputCount Int @default(2) // Merge strategy: // "position" — zip by index position @@ -137,13 +137,13 @@ model MergeNode { // "crossJoin" — cartesian product of all items // "keyMatch" — inner join by a key field // "keyDiff" — outer difference — items in branch1 NOT in branch2 - mergeMode String @default("combine") + mergeMode String @default("combine") // For keyMatch and keyDiff modes: // The dot-path field to match on e.g. "orderId" or "email" // Supports {{variables}} — resolved at runtime - matchKey1 String @default("") - matchKey2 String @default("") + matchKey1 String @default("") + matchKey2 String @default("") // For position mode: // "shortest" — zip to shortest branch length @@ -153,20 +153,20 @@ model MergeNode { // Branch variable name selection // branchKey1/branchKey2 — variable names of the first/second branch (e.g. "gmail", "slack") // branchKeys — comma-separated list for 3+ branch modes (e.g. "gmail,slack,notion") - branchKey1 String @default("") - branchKey2 String @default("") - branchKeys String @default("") + branchKey1 String @default("") + branchKey2 String @default("") + branchKeys String @default("") // Wait for ALL branches to complete before merging - waitForAll Boolean @default(true) + waitForAll Boolean @default(true) // Output variable name variableName String @default("merge") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([workflowId]) @@index([nodeId]) diff --git a/prisma/schema/nodes_google.prisma b/prisma/schema/nodes_google.prisma index 5ee3158..ec4b14c 100644 --- a/prisma/schema/nodes_google.prisma +++ b/prisma/schema/nodes_google.prisma @@ -4,10 +4,11 @@ // ───────────────────────────────────────────────────────────── model GmailNode { - id String @id @default(cuid()) + id String @id @default(cuid()) workflowId String - nodeId String @unique - credentialId String + nodeId String @unique + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) operation GmailOperation @default(SEND) variableName String @default("gmail") @@ -19,20 +20,20 @@ model GmailNode { isHtml Boolean @default(false) // New fields for extended operations - cc String @default("") - bcc String @default("") - replyTo String @default("") - messageId String @default("") - threadId String @default("") - searchQuery String @default("") - maxResults Int @default(10) - labelIds String @default("") - includeBody Boolean @default(true) - includeHeaders Boolean @default(false) - pageToken String @default("") - attachmentData String @default("") @db.Text - attachmentName String @default("") - attachmentMime String @default("application/octet-stream") + cc String @default("") + bcc String @default("") + replyTo String @default("") + messageId String @default("") + threadId String @default("") + searchQuery String @default("") + maxResults Int @default(10) + labelIds String @default("") + includeBody Boolean @default(true) + includeHeaders Boolean @default(false) + pageToken String @default("") + attachmentData String @default("") @db.Text + attachmentName String @default("") + attachmentMime String @default("application/octet-stream") // New fields for extended operations attachmentId String @default("") @@ -60,7 +61,8 @@ model GoogleSheetsNode { sheetName String @default("Sheet1") range String @default("A:Z") rowData Json @default("[]") - credentialId String @default("") + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) variableName String @default("googleSheets") headerRow Boolean @default(true) rowNumber String @default("") @@ -89,6 +91,7 @@ model GoogleDriveNode { nodeId String @unique workflowId String credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) operation GoogleDriveOperation @default(UPLOAD_FILE) folderId String? fileId String? @@ -98,5 +101,5 @@ model GoogleDriveNode { maxResults Int @default(10) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id]) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) } diff --git a/prisma/schema/nodes_messaging.prisma b/prisma/schema/nodes_messaging.prisma index 4b818ca..c9e6131 100644 --- a/prisma/schema/nodes_messaging.prisma +++ b/prisma/schema/nodes_messaging.prisma @@ -4,45 +4,46 @@ // ───────────────────────────────────────────────────────────── model SlackNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String - credentialId String? - operation SlackOperation @default(MESSAGE_SEND_WEBHOOK) - variableName String @default("slack") - channel String @default("") - message String @default("") @db.Text - threadTs String @default("") - messageTs String @default("") - searchQuery String @default("") - channelName String @default("") - channelTopic String @default("") - channelPurpose String @default("") - userId String @default("") - emoji String @default("") - fileComment String @default("") - webhookUrl String @default("") - blockKit String @default("") @db.Text - botName String @default("") - iconEmoji String @default("") - unfurlLinks Boolean @default(true) - channelTypes String @default("public_channel,private_channel") - limit Int @default(100) - excludeArchived Boolean @default(true) - isPrivate Boolean @default(false) - filename String @default("") - fileType String @default("") - title String @default("") - initialComment String @default("") - email String @default("") - statusText String @default("") - statusEmoji String @default("") - statusExpiration String @default("") - sendAt String @default("") - content String @default("") @db.Text - fileId String @default("") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + 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) + variableName String @default("slack") + channel String @default("") + message String @default("") @db.Text + threadTs String @default("") + messageTs String @default("") + searchQuery String @default("") + channelName String @default("") + channelTopic String @default("") + channelPurpose String @default("") + userId String @default("") + emoji String @default("") + fileComment String @default("") + + blockKit String @default("") @db.Text + botName String @default("") + iconEmoji String @default("") + unfurlLinks Boolean @default(true) + channelTypes String @default("public_channel,private_channel") + limit Int @default(100) + excludeArchived Boolean @default(true) + isPrivate Boolean @default(false) + filename String @default("") + fileType String @default("") + title String @default("") + initialComment String @default("") + email String @default("") + statusText String @default("") + statusEmoji String @default("") + statusExpiration String @default("") + sendAt String @default("") + content String @default("") @db.Text + fileId String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@ -55,6 +56,7 @@ model WhatsAppNode { 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 @@ -67,7 +69,7 @@ model WhatsAppNode { reactionMsgId String @default("") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id]) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) } model NotionNode { @@ -75,6 +77,7 @@ model NotionNode { nodeId String @unique workflowId String credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) operation NotionOperation @default(QUERY_DATABASE) databaseId String @default("") pageId String @default("") @@ -96,56 +99,57 @@ model NotionNode { } model Msg91Node { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) operation Msg91Operation @default(SEND_OTP) variableName String @default("msg91") - mobile String @default("") @db.Text - senderId String @default("") - flowId String @default("") - smsVariables String @default("{}") @db.Text - message String @default("") @db.Text - route String @default("4") + mobile String @default("") @db.Text + senderId String @default("") + flowId String @default("") + smsVariables String @default("{}") @db.Text + message String @default("") @db.Text + route String @default("4") - bulkData String @default("[]") @db.Text + bulkData String @default("[]") @db.Text - scheduleTime String @default("") + scheduleTime String @default("") - otpTemplateId String @default("") - otpExpiry Int @default(10) - otpLength Int @default(6) - otpValue String @default("") - retryType String @default("text") + otpTemplateId String @default("") + otpExpiry Int @default(10) + otpLength Int @default(6) + otpValue String @default("") + retryType String @default("text") whatsappTemplate String @default("") whatsappLang String @default("en") whatsappParams String @default("[]") @db.Text integratedNumber String @default("") - mediaType String @default("image") - mediaUrl String @default("") - mediaCaption String @default("") + mediaType String @default("image") + mediaUrl String @default("") + mediaCaption String @default("") - voiceMessage String @default("") + voiceMessage String @default("") - toEmail String @default("") - subject String @default("") - emailBody String @default("") @db.Text - fromEmail String @default("") - fromName String @default("") + toEmail String @default("") + subject String @default("") + emailBody String @default("") @db.Text + fromEmail String @default("") + fromName String @default("") - requestId String @default("") + requestId String @default("") continueOnFail Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([workflowId]) @@index([nodeId]) diff --git a/prisma/schema/nodes_utility.prisma b/prisma/schema/nodes_utility.prisma index 9057f61..70b2758 100644 --- a/prisma/schema/nodes_utility.prisma +++ b/prisma/schema/nodes_utility.prisma @@ -9,21 +9,22 @@ model AINode { nodeId String @unique workflowId String credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) provider AIProvider operation AIOperation @default(CHAT) variableName String @default("ai") - model String @default("") + model String @default("") - systemPrompt String @default("") @db.Text - userPrompt String @default("") @db.Text + systemPrompt String @default("") @db.Text + userPrompt String @default("") @db.Text - temperature Float @default(0.7) - maxTokens Int @default(1000) - topP Float @default(1.0) - frequencyPenalty Float @default(0.0) - presencePenalty Float @default(0.0) + temperature Float @default(0.7) + maxTokens Int @default(1000) + topP Float @default(1.0) + frequencyPenalty Float @default(0.0) + presencePenalty Float @default(0.0) responseFormat String @default("text") jsonSchema String @default("") @db.Text @@ -31,8 +32,8 @@ model AINode { toolsJson String @default("[]") @db.Text toolChoice String @default("auto") - imageUrl String @default("") - imageUrls String @default("[]") @db.Text + imageUrl String @default("") + imageUrls String @default("[]") @db.Text imageDetail String @default("auto") historyKey String @default("") @@ -66,9 +67,9 @@ model AINode { } model MediaUploadNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String source MediaUploadSource @default(URL) inputField String @default("") @@ -77,6 +78,7 @@ model MediaUploadNode { // For Google Drive source credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) variableName String @default("media") continueOnFail Boolean @default(false) @@ -91,8 +93,8 @@ model MediaUploadNode { } model SortNode { - id String @id @default(cuid()) - nodeId String @unique + id String @id @default(cuid()) + nodeId String @unique workflowId String // Operation mode @@ -100,37 +102,37 @@ model SortNode { // "SORT_KEYS" — sort the keys of an object // "REVERSE" — reverse array order // "SHUFFLE" — random order (Fisher-Yates) - operation String @default("SORT_ARRAY") + operation String @default("SORT_ARRAY") // JSON-serialized array of SortKey objects: // [{ field: string, direction: "asc"|"desc", type: "auto"|"string"| // "number"|"date"|"boolean", nulls: "first"|"last", // caseSensitive: boolean, locale: string, natural: boolean }] - sortKeys String @default("[]") @db.Text + sortKeys String @default("[]") @db.Text // Input path — dot-notation path to the array inside context // e.g. "googleSheets.rows" or "razorpayTrigger.payload.items" // Empty string = use the entire previous node output - inputPath String @default("") + inputPath String @default("") // Variable name for output - variableName String @default("sort") + variableName String @default("sort") // Whether to sort in-place (mutate) or return new array // Always false in executor — immutable by default // Kept for future UI hint - inPlace Boolean @default(false) + inPlace Boolean @default(false) - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model FilterNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String operation String @default("FILTER_ARRAY") inputArray String @default("") @db.Text @@ -144,8 +146,8 @@ model FilterNode { continueOnFail Boolean @default(false) // FILTER_OBJECT_KEYS specific: - keyFilterMode String @default("key_name") - keepMatching Boolean @default(true) + keyFilterMode String @default("key_name") + keepMatching Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -157,51 +159,51 @@ model FilterNode { } model AggregateNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String // Primary operation - operation String @default("COUNT") + operation String @default("COUNT") // Dot-notation path to the input array - inputPath String @default("") @db.Text + inputPath String @default("") @db.Text // Dot-notation field within each item to aggregate - field String @default("") @db.Text + field String @default("") @db.Text // For GROUP_BY: field to group items by groupByField String @default("") // For PIVOT: row field, column field, value field, value operation - pivotRowField String @default("") - pivotColField String @default("") - pivotValueField String @default("") - pivotValueOp String @default("SUM") + pivotRowField String @default("") + pivotColField String @default("") + pivotValueField String @default("") + pivotValueOp String @default("SUM") // For CONCATENATE: separator string (default ", ") - separator String @default(", ") + separator String @default(", ") // For PERCENTILE: the percentile to compute (0–100) - percentile Float @default(90) + percentile Float @default(90) // For COUNT: optional filter condition (JSON FilterCondition[]) - countFilter String @default("") @db.Text + countFilter String @default("") @db.Text // For MULTI: JSON array of AggregateOperation objects - multiOps String @default("[]") @db.Text + multiOps String @default("[]") @db.Text // For GROUP_BY with nested aggregation: - groupAggOps String @default("[]") @db.Text + groupAggOps String @default("[]") @db.Text // Output options - variableName String @default("aggregate") - includeInput Boolean @default(false) - sortOutput Boolean @default(true) - topN Int @default(0) - nullHandling String @default("exclude") - roundDecimals Int @default(2) - continueOnFail Boolean @default(false) + variableName String @default("aggregate") + includeInput Boolean @default(false) + sortOutput Boolean @default(true) + topN Int @default(0) + nullHandling String @default("exclude") + roundDecimals Int @default(2) + continueOnFail Boolean @default(false) workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@ -213,95 +215,95 @@ model AggregateNode { } model PostgresNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String // The operation to perform - operation String @default("EXECUTE_QUERY") + operation String @default("EXECUTE_QUERY") // Credential reference credentialId String? // For visual query builder (SELECT, UPDATE, DELETE, COUNT, EXISTS, UPSERT): - tableName String @default("") // target table - schemaName String @default("public") + tableName String @default("") // target table + schemaName String @default("public") // SELECT options: // JSON array of column names to return. [] = SELECT * - selectColumns String @default("[]") @db.Text + selectColumns String @default("[]") @db.Text // JSON array of WhereCondition objects whereConditions String @default("[]") @db.Text // JSON array of OrderBy objects: [{ column, direction }] - orderBy String @default("[]") @db.Text + orderBy String @default("[]") @db.Text // LIMIT rows. 0 = no limit - limitRows Int @default(0) + limitRows Int @default(0) // OFFSET for pagination - offsetRows Int @default(0) + offsetRows Int @default(0) // JOIN configuration: JSON array of JoinClause objects - joins String @default("[]") @db.Text + joins String @default("[]") @db.Text // INSERT / UPSERT: // JSON object or template string that resolves to row data - insertData String @default("") @db.Text + insertData String @default("") @db.Text // For UPSERT: column(s) to check for conflict - conflictColumns String @default("[]") @db.Text + conflictColumns String @default("[]") @db.Text // For UPSERT: columns to update on conflict. [] = all non-conflict columns updateOnConflict String @default("[]") @db.Text // UPDATE: // JSON object of { column: newValue } — supports template expressions - updateData String @default("") @db.Text + updateData String @default("") @db.Text // INSERT_MANY: // Template path to array of row objects: "{{filter.items}}" - insertManyPath String @default("") @db.Text + insertManyPath String @default("") @db.Text // Column names to insert. [] = infer from first row insertManyColumns String @default("[]") @db.Text // EXECUTE_QUERY: // Raw SQL with $1, $2... placeholders - query String @default("") @db.Text + query String @default("") @db.Text // JSON array of query parameters [value1, value2...] - queryParams String @default("[]") @db.Text + queryParams String @default("[]") @db.Text // EXECUTE_TRANSACTION: // JSON array of { query, params } objects — run atomically transactionStatements String @default("[]") @db.Text // EXECUTE_FUNCTION: - functionName String @default("") - functionArgs String @default("[]") @db.Text // JSON array + functionName String @default("") + functionArgs String @default("[]") @db.Text // JSON array // FULL_TEXT_SEARCH: searchColumn String @default("") searchQuery String @default("") - searchLanguage String @default("english") // pg language: "simple", "english", etc. + searchLanguage String @default("english") // pg language: "simple", "english", etc. searchLimit Int @default(10) // JSON_PATH_QUERY: - jsonColumn String @default("") - jsonPath String @default("") // e.g. '$.payment.amount' + jsonColumn String @default("") + jsonPath String @default("") // e.g. '$.payment.amount' // JSON_SET_FIELD: - jsonSetColumn String @default("") - jsonSetPath String @default("") // e.g. '{payment,status}' - jsonSetValue String @default("") // new value (supports template) + jsonSetColumn String @default("") + jsonSetPath String @default("") // e.g. '{payment,status}' + jsonSetValue String @default("") // new value (supports template) // CREATE_TABLE: // JSON array of ColumnDefinition objects - columnDefinitions String @default("[]") @db.Text + columnDefinitions String @default("[]") @db.Text createTableIfNotExists Boolean @default(true) // Output options - variableName String @default("postgres") - returnData Boolean @default(true) // whether to return affected rows - continueOnFail Boolean @default(false) + variableName String @default("postgres") + returnData Boolean @default(true) // whether to return affected rows + continueOnFail Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull) @@index([workflowId]) diff --git a/prisma/schema/triggers.prisma b/prisma/schema/triggers.prisma index 3df5cda..8692f9f 100644 --- a/prisma/schema/triggers.prisma +++ b/prisma/schema/triggers.prisma @@ -5,14 +5,22 @@ // ───────────────────────────────────────────────────────────── model WebhookTrigger { - id String @id @default(cuid()) - workflowId String @unique - webhookId String @unique @default(uuid()) - secretToken String - isActive Boolean @default(true) - httpMethod HttpMethod @default(POST) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + workflowId String @unique + webhookId String @unique @default(uuid()) + // DEPRECATED — plaintext, kept for backward-compat until data migration is complete. + // TODO: Once all rows have secretTokenEncrypted set and secretTokenLegacy IS NULL, + // create a follow-up migration to: DROP COLUMN secretTokenLegacy and set + // secretTokenEncrypted NOT NULL. + secretTokenLegacy String? @map("secretToken") // legacy plaintext (pre-encryption rows) + + // AES-256 encrypted webhook secret. Prefer this over secretTokenLegacy. + // Populated by new saves and by `npm run migrate:webhook-secrets`. + secretTokenEncrypted String? // encrypted; null until migrated + isActive Boolean @default(true) + httpMethod HttpMethod @default(POST) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@ -38,46 +46,46 @@ model ScheduleTrigger { } model ErrorTriggerNode { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String - variableName String @default("errorTrigger") + variableName String @default("errorTrigger") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([nodeId]) @@index([workflowId]) } model RazorpayTrigger { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String - webhookId String @unique @default(uuid()) + webhookId String @unique @default(uuid()) // DEPRECATED — plaintext, kept for backward-compat until data migration is complete. // TODO: Once all rows have webhookSecretEncrypted set and webhookSecret IS NULL, // create a follow-up migration to: DROP COLUMN webhookSecret and set // webhookSecretEncrypted NOT NULL. - webhookSecret String? // legacy plaintext (pre-encryption rows) + webhookSecret String? // legacy plaintext (pre-encryption rows) // AES-256 encrypted webhook secret. Prefer this over webhookSecret. // Populated by new saves and by `npm run migrate:razorpay-secrets`. - webhookSecretEncrypted String? // encrypted; null until migrated + webhookSecretEncrypted String? // encrypted; null until migrated - activeEvents String @default("[]") @db.Text - variableName String @default("razorpayTrigger") - isActive Boolean @default(true) + activeEvents String @default("[]") @db.Text + variableName String @default("razorpayTrigger") + isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([nodeId]) @@index([workflowId]) @@ -85,33 +93,33 @@ model RazorpayTrigger { } model WhatsAppTrigger { - id String @id @default(cuid()) - nodeId String @unique - workflowId String + id String @id @default(cuid()) + nodeId String @unique + workflowId String - webhookId String @unique @default(uuid()) + webhookId String @unique @default(uuid()) // DEPRECATED — plaintext, kept for backward-compat until data migration is complete. // TODO: Once all rows have verifyTokenEncrypted set and verifyToken IS NULL, // create a follow-up migration to: DROP COLUMN verifyToken and set // verifyTokenEncrypted NOT NULL. - verifyToken String? // legacy plaintext (pre-encryption rows) + verifyToken String? // legacy plaintext (pre-encryption rows) // AES-256 encrypted verify token. Prefer this over verifyToken. // Populated by new saves and by `npm run migrate:whatsapp-tokens`. - verifyTokenEncrypted String? // encrypted; null until migrated + verifyTokenEncrypted String? // encrypted; null until migrated - phoneNumberId String @default("") - activeEvents String @default("[]") @db.Text - messageTypes String @default("[]") @db.Text - ignoreOwnMessages Boolean @default(true) - variableName String @default("whatsappTrigger") - isActive Boolean @default(true) + phoneNumberId String @default("") + activeEvents String @default("[]") @db.Text + messageTypes String @default("[]") @db.Text + ignoreOwnMessages Boolean @default(true) + variableName String @default("whatsappTrigger") + isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([nodeId]) @@index([workflowId]) diff --git a/scripts/migrate-webhook-secrets.ts b/scripts/migrate-webhook-secrets.ts new file mode 100644 index 0000000..f571eda --- /dev/null +++ b/scripts/migrate-webhook-secrets.ts @@ -0,0 +1,68 @@ +/** + * scripts/migrate-webhook-secrets.ts + * + * One-time data migration: encrypts all plaintext secretTokenLegacy values + * in WebhookTrigger and stores them in secretTokenEncrypted. + * + * Run: npm run migrate:webhook-secrets + * + * Safe to re-run — skips rows that already have secretTokenEncrypted set. + */ +import { PrismaClient } from "../src/generated/prisma" +import { encrypt } from "../src/lib/encryption" + +const prisma = new PrismaClient() + +async function main() { + 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 + }, + }) + migrated++ + console.log(` ✓ Migrated trigger ${trigger.id} (workflow: ${trigger.workflowId})`) + } catch (err) { + failed++ + console.error(` ✗ Failed trigger ${trigger.id}:`, err) + } + } + + console.log( + `\nMigration complete: ${migrated} migrated, ${skipped} skipped (empty secret), ${failed} failed` + ) + + if (failed > 0) { + console.error("Some triggers failed to migrate. Check errors above.") + process.exit(1) + } +} + +main() + .catch((err) => { + console.error("Fatal error:", err) + process.exit(1) + }) + .finally(() => prisma.$disconnect()) diff --git a/src/app/api/auth/custom-signup/route.ts b/src/app/api/auth/custom-signup/route.ts index 0456ef5..c496b09 100644 --- a/src/app/api/auth/custom-signup/route.ts +++ b/src/app/api/auth/custom-signup/route.ts @@ -81,8 +81,7 @@ export async function POST(req: NextRequest) { } catch (error) { console.error("Signup error:", error) return NextResponse.json({ - error: "Internal server error", - details: error instanceof Error ? error.message : String(error) + error: "Internal server error" }, { status: 500 }) } } diff --git a/src/features/executions/components/google-sheets/dialog.tsx b/src/features/executions/components/google-sheets/dialog.tsx index bee2583..abd3b93 100644 --- a/src/features/executions/components/google-sheets/dialog.tsx +++ b/src/features/executions/components/google-sheets/dialog.tsx @@ -132,7 +132,7 @@ export const GoogleSheetsDialog = ({ // Pre-fill from DB config when loaded useEffect(() => { if (config) { - setCredentialId(config.credentialId) + setCredentialId(config.credentialId || "") setOperation(config.operation as GoogleSheetsOp) setVariableName(config.variableName || "googleSheets") setSpreadsheetId(config.spreadsheetId) diff --git a/src/features/executions/components/slack/dialog.tsx b/src/features/executions/components/slack/dialog.tsx index 3b7a5f0..990fdd4 100644 --- a/src/features/executions/components/slack/dialog.tsx +++ b/src/features/executions/components/slack/dialog.tsx @@ -43,7 +43,6 @@ export interface SlackFormValues { channelPurpose?: string userId?: string emoji?: string - webhookUrl?: string blockKit?: string botName?: string iconEmoji?: string @@ -280,7 +279,6 @@ export const SlackDialog = ({ ) const [slackUserId, setSlackUserId] = useState(defaultValues.userId || "") const [emoji, setEmoji] = useState(defaultValues.emoji || "") - const [webhookUrl, setWebhookUrl] = useState(defaultValues.webhookUrl || "") const [blockKit, setBlockKit] = useState(defaultValues.blockKit || "") const [botName, setBotName] = useState(defaultValues.botName || "") const [iconEmoji, setIconEmoji] = useState(defaultValues.iconEmoji || "") @@ -340,7 +338,6 @@ export const SlackDialog = ({ setChannelPurpose(config.channelPurpose) setSlackUserId(config.userId) setEmoji(config.emoji) - setWebhookUrl(config.webhookUrl) setBlockKit(config.blockKit) setBotName(config.botName) setIconEmoji(config.iconEmoji) @@ -380,7 +377,6 @@ export const SlackDialog = ({ setChannelPurpose(defaultValues.channelPurpose || "") setSlackUserId(defaultValues.userId || "") setEmoji(defaultValues.emoji || "") - setWebhookUrl(defaultValues.webhookUrl || "") setBlockKit(defaultValues.blockKit || "") setBotName(defaultValues.botName || "") setIconEmoji(defaultValues.iconEmoji || "") @@ -442,7 +438,6 @@ export const SlackDialog = ({ channelPurpose, userId: slackUserId, emoji, - webhookUrl, blockKit, botName, iconEmoji, @@ -482,7 +477,6 @@ export const SlackDialog = ({ channelPurpose, userId: slackUserId, emoji, - webhookUrl, blockKit, botName, iconEmoji, diff --git a/src/features/executions/components/slack/executor.ts b/src/features/executions/components/slack/executor.ts index bdf2a8c..fed07b5 100644 --- a/src/features/executions/components/slack/executor.ts +++ b/src/features/executions/components/slack/executor.ts @@ -208,7 +208,6 @@ export const slackExecutor: NodeExecutor = async ({ const channelPurpose = resolveTemplate(config.channelPurpose, context) const slackUserId = resolveTemplate(config.userId, context) const emoji = resolveTemplate(config.emoji, context) - const webhookUrl = resolveTemplate(config.webhookUrl, context) const blockKit = resolveTemplate(config.blockKit, context) const botName = resolveTemplate(config.botName, context) const iconEmojiVal = resolveTemplate(config.iconEmoji, context) @@ -232,8 +231,7 @@ export const slackExecutor: NodeExecutor = async ({ // ── Message Operations ── case SlackOperation.MESSAGE_SEND_WEBHOOK: { - const url = - creds?.type === "webhook" ? creds.webhookUrl : webhookUrl + const url = creds?.type === "webhook" ? creds.webhookUrl : "" if (!url) { throw new NonRetriableError( "Slack: webhook URL is required for MESSAGE_SEND_WEBHOOK" diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts index 4bcb424..db78931 100644 --- a/src/lib/auth-client.ts +++ b/src/lib/auth-client.ts @@ -2,5 +2,5 @@ import { createAuthClient } from "better-auth/react" export const authClient = createAuthClient({ plugins:[], - baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000" + baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || (typeof window !== "undefined" ? window.location.origin : "http://localhost:3000") }) \ No newline at end of file diff --git a/src/lib/auth.ts b/src/lib/auth.ts index e6af4dd..1102312 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -38,6 +38,6 @@ import prisma from "./db" } }, plugins:[], - baseURL: process.env.BETTER_AUTH_URL, + baseURL: process.env.BETTER_AUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "https://nodebase.mayanksaraswal.in", secret: process.env.BETTER_AUTH_SECRET }); \ No newline at end of file diff --git a/src/lib/webhook-secret.ts b/src/lib/webhook-secret.ts new file mode 100644 index 0000000..16230f3 --- /dev/null +++ b/src/lib/webhook-secret.ts @@ -0,0 +1,51 @@ +import { encrypt, decrypt } from "@/lib/encryption" + +// ─── TODO (future cleanup) ──────────────────────────────────────────────────── +// Once all WebhookTrigger rows have been migrated (secretTokenLegacy IS NULL for +// every row), do the following in a new PR: +// 1. Create a migration: ALTER COLUMN "secretTokenEncrypted" SET NOT NULL; +// DROP COLUMN "secretTokenLegacy"; +// 2. Remove the `webhookSecretLegacy` parameter and branch below. +// 3. Remove the console.warn call. +// 4. Delete scripts/migrate-webhook-secrets.ts. +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Encrypt a webhook secret for storage in `secretTokenEncrypted`. + * Always call this before persisting a new or updated secret. + */ +export function encryptWebhookSecret(plaintext: string): string { + return encrypt(plaintext) +} + +/** + * Decrypt a WebhookTrigger secret. + * + * Supports two formats: + * 1. New format — AES-256 ciphertext stored in `secretTokenEncrypted` + * 2. Legacy format — plaintext stored in `secretTokenLegacy` (pre-migration rows) + * + * Returns the plaintext secret. + */ +export function decryptWebhookSecret( + secretTokenEncrypted: string | null | undefined, + secretTokenLegacy: string | null | undefined +): string { + // Prefer the new encrypted field + if (secretTokenEncrypted) { + return decrypt(secretTokenEncrypted) + } + + // Fall back to legacy plaintext (present on rows not yet migrated) + if (secretTokenLegacy) { + console.warn( + "[WebhookTrigger] Using legacy plaintext secretToken — " + + "run `npm run migrate:webhook-secrets` to encrypt it." + ) + return secretTokenLegacy + } + + throw new Error( + "WebhookTrigger: No webhook secret configured." + ) +} diff --git a/src/server/routers/slack.router.ts b/src/server/routers/slack.router.ts index bc75f16..e43a3dc 100644 --- a/src/server/routers/slack.router.ts +++ b/src/server/routers/slack.router.ts @@ -46,7 +46,6 @@ const upsertInput = z.object({ channelPurpose: z.string().default(""), userId: z.string().default(""), emoji: z.string().default(""), - webhookUrl: z.string().default(""), blockKit: z.string().default(""), botName: z.string().default(""), iconEmoji: z.string().default(""), @@ -106,7 +105,6 @@ export const slackRouter = createTRPCRouter({ channelPurpose: input.channelPurpose, userId: input.userId, emoji: input.emoji, - webhookUrl: input.webhookUrl, blockKit: input.blockKit, botName: input.botName, iconEmoji: input.iconEmoji, diff --git a/src/server/routers/webhook-trigger.router.ts b/src/server/routers/webhook-trigger.router.ts index b168288..78f6461 100644 --- a/src/server/routers/webhook-trigger.router.ts +++ b/src/server/routers/webhook-trigger.router.ts @@ -2,6 +2,7 @@ import { createTRPCRouter, protectedProcedure } from "@/trpc/init"; import prisma from "@/lib/db"; import { randomUUID } from "crypto"; import { z } from "zod"; +import { encryptWebhookSecret } from "@/lib/webhook-secret"; export const webhookTriggerRouter = createTRPCRouter({ getByWorkflowId: protectedProcedure @@ -18,7 +19,7 @@ export const webhookTriggerRouter = createTRPCRouter({ return prisma.webhookTrigger.create({ data: { workflowId: input.workflowId, - secretToken: randomUUID(), + secretTokenEncrypted: encryptWebhookSecret(randomUUID()), }, }); }), From 6e5fe19016069ab4b181cd4c3cd38774ae7484b8 Mon Sep 17 00:00:00 2001 From: Mayank Saini Date: Thu, 4 Jun 2026 19:49:12 +0530 Subject: [PATCH 3/6] feat: implement GitHub integration with support for workflows, deployments, and triggers --- prisma/schema/core.prisma | 3 + prisma/schema/enums.prisma | 261 +++++++++++ prisma/schema/nodes_github.prisma | 75 +++ .../api/webhooks/github/[webhookId]/route.ts | 123 +++++ .../credentials/components/credentials.tsx | 4 +- .../executions/components/github/actions.ts | 16 + .../components/github/api-client.ts | 109 +++++ .../github/components/generic-fields.tsx | 134 ++++++ .../github/components/issue-fields.tsx | 183 ++++++++ .../github/components/operation-groups.ts | 346 ++++++++++++++ .../github/components/repo-fields.tsx | 210 +++++++++ .../github/components/workflow-fields.tsx | 190 ++++++++ .../executions/components/github/dialog.tsx | 240 ++++++++++ .../executions/components/github/executor.ts | 123 +++++ .../components/github/executors/issues-prs.ts | 326 +++++++++++++ .../components/github/executors/repository.ts | 372 +++++++++++++++ .../github/executors/search-misc.ts | 440 ++++++++++++++++++ .../components/github/executors/users-orgs.ts | 338 ++++++++++++++ .../components/github/executors/workflows.ts | 236 ++++++++++ .../executions/components/github/node.tsx | 63 +++ .../executions/components/github/types.ts | 34 ++ .../executions/lib/executor-registry.ts | 6 +- .../components/github-trigger/dialog.tsx | 163 +++++++ .../components/github-trigger/executor.ts | 29 ++ .../components/github-trigger/node.tsx | 46 ++ src/inngest/channels/github.ts | 10 + src/inngest/functions.ts | 2 + src/server/routers/github-trigger.router.ts | 81 ++++ src/server/routers/github.router.ts | 131 ++++++ src/trpc/routers/_app.ts | 4 + 30 files changed, 4295 insertions(+), 3 deletions(-) create mode 100644 prisma/schema/nodes_github.prisma create mode 100644 src/app/api/webhooks/github/[webhookId]/route.ts create mode 100644 src/features/executions/components/github/actions.ts create mode 100644 src/features/executions/components/github/api-client.ts create mode 100644 src/features/executions/components/github/components/generic-fields.tsx create mode 100644 src/features/executions/components/github/components/issue-fields.tsx create mode 100644 src/features/executions/components/github/components/operation-groups.ts create mode 100644 src/features/executions/components/github/components/repo-fields.tsx create mode 100644 src/features/executions/components/github/components/workflow-fields.tsx create mode 100644 src/features/executions/components/github/dialog.tsx create mode 100644 src/features/executions/components/github/executor.ts create mode 100644 src/features/executions/components/github/executors/issues-prs.ts create mode 100644 src/features/executions/components/github/executors/repository.ts create mode 100644 src/features/executions/components/github/executors/search-misc.ts create mode 100644 src/features/executions/components/github/executors/users-orgs.ts create mode 100644 src/features/executions/components/github/executors/workflows.ts create mode 100644 src/features/executions/components/github/node.tsx create mode 100644 src/features/executions/components/github/types.ts create mode 100644 src/features/triggers/components/github-trigger/dialog.tsx create mode 100644 src/features/triggers/components/github-trigger/executor.ts create mode 100644 src/features/triggers/components/github-trigger/node.tsx create mode 100644 src/inngest/channels/github.ts create mode 100644 src/server/routers/github-trigger.router.ts create mode 100644 src/server/routers/github.router.ts diff --git a/prisma/schema/core.prisma b/prisma/schema/core.prisma index da94ceb..a687cf9 100644 --- a/prisma/schema/core.prisma +++ b/prisma/schema/core.prisma @@ -44,6 +44,8 @@ model workflow { cashfreeNodes CashfreeNode[] aggregateNodes AggregateNode[] postgresNodes PostgresNode[] + githubNodes GitHubNode[] + githubTriggerNodes GitHubTriggerNode[] userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) } @@ -62,6 +64,7 @@ model Credential { freshdeskNodes FreshdeskNode[] cashfreeNodes CashfreeNode[] postgresNodes PostgresNode[] + githubNodes GitHubNode[] createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt diff --git a/prisma/schema/enums.prisma b/prisma/schema/enums.prisma index 650f788..0770169 100644 --- a/prisma/schema/enums.prisma +++ b/prisma/schema/enums.prisma @@ -50,6 +50,8 @@ enum NodeType { CASHFREE_TRIGGER AGGREGATE POSTGRES + GITHUB + GITHUB_TRIGGER // In a future sprint: ZOHO_CRM_TRIGGER // Registers a Zoho Notification API webhook for lead/contact/deal events. // Ref: https://www.zoho.com/crm/developer/docs/api/v6/notifications/overview.html @@ -131,6 +133,8 @@ enum CredentialType { FRESHDESK CASHFREE POSTGRES + GITHUB + GITHUB_APP } enum GmailOperation { @@ -516,3 +520,260 @@ enum CashfreeOperation { // ── WEBHOOKS ────────────────────────────────────────────── VERIFY_WEBHOOK_SIGNATURE } + +enum GitHubOperation { + // Repository Operations + REPOSITORY_CREATE + REPOSITORY_GET + REPOSITORY_LIST + REPOSITORY_UPDATE + REPOSITORY_DELETE + REPOSITORY_FORK + REPOSITORY_LIST_CONTRIBUTORS + REPOSITORY_LIST_LANGUAGES + REPOSITORY_TRANSFER + REPOSITORY_DISPATCH + REPOSITORY_CHECK_COLLABORATOR + REPOSITORY_LIST_EVENTS + + // Issue Operations + ISSUE_CREATE + ISSUE_GET + ISSUE_UPDATE + ISSUE_CLOSE + ISSUE_REOPEN + ISSUE_LIST + ISSUE_ADD_LABELS + ISSUE_REMOVE_LABEL + ISSUE_CREATE_COMMENT + ISSUE_LIST_COMMENTS + ISSUE_UPDATE_COMMENT + ISSUE_DELETE_COMMENT + ISSUE_ASSIGN + ISSUE_UNASSIGN + + // Pull Request Operations + PULL_REQUEST_CREATE + PULL_REQUEST_GET + PULL_REQUEST_LIST + PULL_REQUEST_UPDATE + PULL_REQUEST_MERGE + PULL_REQUEST_CLOSE + PULL_REQUEST_REOPEN + PULL_REQUEST_LIST_REVIEWS + PULL_REQUEST_CREATE_REVIEW + PULL_REQUEST_REQUEST_REVIEWERS + PULL_REQUEST_LIST_COMMITS + PULL_REQUEST_LIST_FILES + PULL_REQUEST_CREATE_REVIEW_COMMENT + + // File Operations + FILE_GET + FILE_CREATE + FILE_UPDATE + FILE_DELETE + FILE_LIST + FILE_GET_CONTENTS + FILE_CREATE_OR_UPDATE + + // Branch Operations + BRANCH_LIST + BRANCH_GET + BRANCH_CREATE + BRANCH_DELETE + BRANCH_RENAME + BRANCH_SET_PROTECTION + + // Release Operations + RELEASE_CREATE + RELEASE_GET + RELEASE_LIST + RELEASE_UPDATE + RELEASE_DELETE + RELEASE_UPLOAD_ASSET + + // Workflow Operations + WORKFLOW_LIST + WORKFLOW_GET + WORKFLOW_DISPATCH + WORKFLOW_RUN_LIST + WORKFLOW_RUN_GET + WORKFLOW_RUN_CANCEL + WORKFLOW_RUN_RE_RUN + WORKFLOW_RUN_LIST_JOBS + + // Gist Operations + GIST_CREATE + GIST_GET + GIST_LIST + GIST_UPDATE + GIST_DELETE + GIST_STAR + + // Search Operations + SEARCH_REPOSITORIES + SEARCH_ISSUES + SEARCH_PULL_REQUESTS + SEARCH_CODE + SEARCH_USERS + SEARCH_COMMITS + SEARCH_TOPICS + + // User Operations + USER_GET + USER_GET_CURRENT + USER_LIST_REPOS + USER_LIST_FOLLOWERS + USER_LIST_FOLLOWING + + // Organization Operations + ORG_GET + ORG_LIST + ORG_LIST_MEMBERS + ORG_LIST_TEAMS + ORG_LIST_REPOS + ORG_CREATE_REPO + ORG_INVITE_MEMBER + ORG_REMOVE_MEMBER + + // Discussion Operations + DISCUSSION_CREATE + DISCUSSION_GET + DISCUSSION_LIST + DISCUSSION_UPDATE + DISCUSSION_DELETE + + // Codespaces (NEW) + CODESPACES_LIST + CODESPACES_GET + CODESPACES_CREATE + CODESPACES_START + CODESPACES_STOP + CODESPACES_DELETE + CODESPACES_UPDATE + CODESPACES_EXPORT + CODESPACES_LIST_FOR_REPO + CODESPACES_LIST_MACHINE_TYPES + CODESPACES_LIST_SECRETS + CODESPACES_CREATE_SECRET + + // Copilot (NEW) + COPILOT_LIST_SEATS + COPILOT_ADD_SEAT + COPILOT_REMOVE_SEAT + COPILOT_GET_SETTINGS + COPILOT_UPDATE_SETTINGS + COPILOT_LIST_SPACES + COPILOT_GET_SPACE + COPILOT_CREATE_SPACE + COPILOT_UPDATE_SPACE + COPILOT_DELETE_SPACE + COPILOT_LIST_SPACE_RESOURCES + COPILOT_ADD_SPACE_RESOURCE + COPILOT_REMOVE_SPACE_RESOURCE + COPILOT_GET_AGENT_POLICY + COPILOT_UPDATE_AGENT_POLICY + + // Rulesets (NEW) + RULESET_LIST + RULESET_GET + RULESET_CREATE + RULESET_UPDATE + RULESET_DELETE + RULESET_LIST_RULE_EVALUATIONS + RULESET_GET_RULE_SUITE + RULESET_LIST_ORG_RULESETS + + // Environments (NEW) + ENVIRONMENT_LIST + ENVIRONMENT_GET + ENVIRONMENT_CREATE + ENVIRONMENT_UPDATE + ENVIRONMENT_DELETE + ENVIRONMENT_LIST_SECRETS + ENVIRONMENT_GET_SECRET + ENVIRONMENT_CREATE_SECRET + ENVIRONMENT_DELETE_SECRET + ENVIRONMENT_LIST_PROTECTION_RULES + + // Secrets (NEW) + SECRET_LIST_REPO + SECRET_GET_REPO + SECRET_CREATE_REPO + SECRET_DELETE_REPO + SECRET_LIST_ORG + SECRET_GET_ORG + SECRET_CREATE_ORG + SECRET_DELETE_ORG + SECRET_GET_PUBLIC_KEY + SECRET_GET_ORG_PUBLIC_KEY + SECRET_LIST_SELECTED_REPOS + SECRET_SET_SELECTED_REPOS + + // Deployments (NEW) + DEPLOYMENT_LIST + DEPLOYMENT_GET + DEPLOYMENT_CREATE + DEPLOYMENT_LIST_STATUSES + DEPLOYMENT_GET_STATUS + DEPLOYMENT_CREATE_STATUS + DEPLOYMENT_DELETE + DEPLOYMENT_SET_STATUS + + // Packages (NEW) + PACKAGE_LIST_FOR_USER + PACKAGE_LIST_FOR_ORG + PACKAGE_GET + PACKAGE_DELETE + PACKAGE_RESTORE + PACKAGE_LIST_VERSIONS + PACKAGE_GET_VERSION + PACKAGE_DELETE_VERSION + PACKAGE_RESTORE_VERSION + PACKAGE_LIST_DOCKER_IMAGES + + // Attestations (NEW) + ATTESTATION_LIST_FOR_REPO + ATTESTATION_GET + ATTESTATION_CREATE + ATTESTATION_LIST_FOR_ORG + ATTESTATION_VERIFY + + // Agent Tasks (NEW) + AGENT_TASK_LIST + AGENT_TASK_GET + AGENT_TASK_CREATE + AGENT_TASK_UPDATE + AGENT_TASK_CANCEL + AGENT_TASK_GET_LOGS + + // Artifacts (NEW) + ARTIFACT_LIST + ARTIFACT_GET + ARTIFACT_DELETE + ARTIFACT_DOWNLOAD + ARTIFACT_UPLOAD + ARTIFACT_GET_ARCHIVE_URL + + // Projects V2 (NEW) + PROJECT_V2_LIST + PROJECT_V2_GET + PROJECT_V2_CREATE + PROJECT_V2_UPDATE + PROJECT_V2_DELETE + PROJECT_V2_LIST_ITEMS + PROJECT_V2_ADD_ITEM + PROJECT_V2_UPDATE_ITEM + PROJECT_V2_DELETE_ITEM + PROJECT_V2_LIST_FIELDS + PROJECT_V2_CREATE_FIELD + PROJECT_V2_UPDATE_FIELD_VALUE + + // Security Advisories (NEW) + ADVISORY_LIST + ADVISORY_GET + ADVISORY_CREATE + ADVISORY_UPDATE + ADVISORY_LIST_REPOS_AFFECTED + ADVISORY_REPORT +} diff --git a/prisma/schema/nodes_github.prisma b/prisma/schema/nodes_github.prisma new file mode 100644 index 0000000..b4dbffa --- /dev/null +++ b/prisma/schema/nodes_github.prisma @@ -0,0 +1,75 @@ +// ───────────────────────────────────────────────────────────── +// Nodebase — Prisma Schema: GitHub Nodes +// GitHubNode, GitHubTriggerNode +// ───────────────────────────────────────────────────────────── + +model GitHubNode { + id String @id @default(cuid()) + nodeId String @unique + + workflowId String + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + credentialId String? + credential Credential? @relation(fields: [credentialId], references: [id]) + + operation GitHubOperation @default(USER_GET_CURRENT) + + // Common Fields (Templated) + owner String @default("") + repo String @default("") + branch String @default("") + filePath String @default("") + fileContent String @default("") @db.Text + commitMessage String @default("") + + // Issue / PR Fields + issueNumber String @default("") + pullNumber String @default("") + title String @default("") + body String @default("") @db.Text + state String @default("") + labels String @default("") // JSON string array + assignees String @default("") // JSON string array + headBranch String @default("") + baseBranch String @default("") + + // Workflow / Event Fields + workflowId_github String @default("") // Renamed to avoid collision with Nodebase workflowId + eventType String @default("") + clientPayload String @default("") @db.Text + + // Search / Filter Fields + searchQuery String @default("") + perPage Int @default(30) + + // Other fields specific to releases, codespaces, rulesets, etc. + tagName String @default("") + releaseName String @default("") + draft Boolean @default(false) + prerelease Boolean @default(false) + + // Generic JSON options for all other specific flags and options + options Json @default("{}") + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt +} + +model GitHubTriggerNode { + id String @id @default(cuid()) + nodeId String @unique + + workflowId String + workflow workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) + + owner String @default("") + repo String @default("") + events String @default("[\"push\"]") // JSON string array of GitHub webhook events + + webhookId String? // The ID of the webhook created in GitHub, to allow cleanup + webhookSecret String? // The secret used to verify the incoming payload + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt +} diff --git a/src/app/api/webhooks/github/[webhookId]/route.ts b/src/app/api/webhooks/github/[webhookId]/route.ts new file mode 100644 index 0000000..a67b14c --- /dev/null +++ b/src/app/api/webhooks/github/[webhookId]/route.ts @@ -0,0 +1,123 @@ +import { sendWorkflowExecution } from "@/inngest/utils"; +import prisma from "@/lib/db"; +import { claimIdempotencyKey } from "@/lib/redis"; +import crypto from "crypto"; +import { NextRequest, NextResponse } from "next/server"; + +function verifyGitHubSignature( + secret: string, + payload: string, + signature: string +): boolean { + if (!signature || !secret) return false; + try { + const hmac = crypto.createHmac("sha256", secret); + const digest = "sha256=" + hmac.update(payload).digest("hex"); + return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature)); + } catch (error) { + return false; + } +} + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ webhookId: string }> } +) { + try { + const contentLength = request.headers.get("content-length"); + if (contentLength && parseInt(contentLength) > 10_000_000) { + return NextResponse.json({ error: "Payload too large" }, { status: 413 }); + } + + const { webhookId } = await params; + + if (!webhookId) { + return NextResponse.json( + { success: false, error: "Missing webhookId" }, + { status: 400 } + ); + } + + const webhookTrigger = await prisma.gitHubTriggerNode.findFirst({ + where: { webhookId }, + }); + + if (!webhookTrigger) { + return NextResponse.json( + { success: false, error: "Webhook not found" }, + { status: 404 } + ); + } + + const rawTextBody = await request.text().catch(() => ""); + const signature = request.headers.get("x-hub-signature-256") || ""; + + // Verify signature if a secret is configured + if (webhookTrigger.webhookSecret) { + const isValid = verifyGitHubSignature( + webhookTrigger.webhookSecret, + rawTextBody, + signature + ); + if (!isValid) { + return NextResponse.json( + { success: false, error: "Invalid signature" }, + { status: 401 } + ); + } + } + + let body = {}; + try { + if (rawTextBody) { + body = JSON.parse(rawTextBody); + } + } catch { + // Keep empty + } + + const githubEvent = request.headers.get("x-github-event") || "unknown"; + const githubDelivery = request.headers.get("x-github-delivery") || ""; + + // GitHub's delivery ID acts as a natural idempotency key + const idempotencyKey = `github-webhook:${webhookId}:${githubDelivery}`; + const isFirstDelivery = await claimIdempotencyKey(idempotencyKey, 3600); // 1 hour TTL + + if (!isFirstDelivery) { + console.log( + `[GitHub Webhook] Duplicate delivery ${githubDelivery} discarded for workflow ${webhookTrigger.workflowId}` + ); + return NextResponse.json({ + success: true, + status: "duplicate_discarded", + }); + } + + const receivedAt = new Date().toISOString(); + + await sendWorkflowExecution({ + workflowId: webhookTrigger.workflowId, + inngestId: idempotencyKey, + initialData: { + github: { + event: githubEvent, + deliveryId: githubDelivery, + body, + receivedAt, + }, + }, + }); + + return NextResponse.json({ + success: true, + status: "accepted", + receivedAt, + }); + } catch (error) { + console.error("GitHub webhook trigger error: ", error); + return NextResponse.json( + { success: false, error: "Failed to process webhook" }, + { status: 500 } + ); + } +} diff --git a/src/features/credentials/components/credentials.tsx b/src/features/credentials/components/credentials.tsx index 2e0aae3..80ed049 100644 --- a/src/features/credentials/components/credentials.tsx +++ b/src/features/credentials/components/credentials.tsx @@ -153,7 +153,9 @@ const credentialsLogos: Record = { [CredentialType.HUBSPOT]: "/logos/hubspot.svg", [CredentialType.FRESHDESK]: "/logos/freshdesk.svg", [CredentialType.CASHFREE]: "/logos/cashfree.svg", - [CredentialType.POSTGRES]: "/logos/postgres.svg", + [CredentialType.POSTGRES]: "/logos/postgresql.svg", + [CredentialType.GITHUB]: "/logos/github.svg", + [CredentialType.GITHUB_APP]: "/logos/github.svg", } export const CredentialsItem = ({ diff --git a/src/features/executions/components/github/actions.ts b/src/features/executions/components/github/actions.ts new file mode 100644 index 0000000..fe171cb --- /dev/null +++ b/src/features/executions/components/github/actions.ts @@ -0,0 +1,16 @@ +"use server" + +import { githubChannel } from "@/inngest/channels/github" +import { inngest } from "@/inngest/client" +import { getSubscriptionToken, Realtime } from "@inngest/realtime" + +export type GitHubToken = Realtime.Token, ["status"]> + +export async function fetchGitHubRealtimeToken(): Promise { + const token = await getSubscriptionToken(inngest, { + channel: githubChannel(), + topics: ["status"] + }) + + return token +} diff --git a/src/features/executions/components/github/api-client.ts b/src/features/executions/components/github/api-client.ts new file mode 100644 index 0000000..e0af79f --- /dev/null +++ b/src/features/executions/components/github/api-client.ts @@ -0,0 +1,109 @@ +import { NonRetriableError } from "inngest"; + +interface GitHubClientOptions { + accessToken: string; + baseUrl?: string; +} + +interface RequestOptions { + method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; + body?: Record; + headers?: Record; +} + +export class GitHubClient { + private baseUrl: string; + private headers: Record; + + constructor(options: GitHubClientOptions) { + this.baseUrl = options.baseUrl || "https://api.github.com"; + this.headers = { + Authorization: `Bearer ${options.accessToken}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2026-03-10", + }; + } + + async request( + endpoint: string, + options: RequestOptions = {} + ): Promise { + const url = `${this.baseUrl}${endpoint}`; + const response = await fetch(url, { + method: options.method || "GET", + headers: { ...this.headers, ...options.headers }, + body: options.body ? JSON.stringify(options.body) : undefined, + }); + + // Handle rate limiting + if (response.status === 403 || response.status === 429) { + const remaining = response.headers.get("x-ratelimit-remaining"); + if (remaining === "0") { + const resetTime = response.headers.get("x-ratelimit-reset"); + throw new NonRetriableError( + `GitHub API rate limit exceeded. Resets at ${resetTime}` + ); + } + } + + if (!response.ok) { + const error = await response.json().catch(() => ({})); + throw new NonRetriableError( + this.getErrorMessage(response.status, error) + ); + } + + // Handle 204 No Content + if (response.status === 204) { + return {} as T; + } + + // For raw contents (like file downloads) + const contentType = response.headers.get("content-type"); + if (contentType && !contentType.includes("application/json")) { + return response.text() as any; + } + + return response.json(); + } + + private getErrorMessage(status: number, error: any): string { + const message = error.message || error.error || "Unknown error"; + const hints: Record = { + "Not Found": "Repository or resource not found. Check owner/repo.", + "Bad credentials": "Invalid GitHub token. Check your credential.", + "Repository creation failed": "Failed to create repository.", + "Reference already exists": "Branch or tag already exists.", + }; + if (error.errors && Array.isArray(error.errors)) { + hints["Validation Failed"] = `Validation error: ${JSON.stringify(error.errors)}`; + } + return hints[message] || `GitHub API error (${status}): ${message}`; + } + + // Pagination helper + async *paginate( + endpoint: string, + perPage: number = 30 + ): AsyncGenerator { + let page = 1; + while (true) { + const separator = endpoint.includes("?") ? "&" : "?"; + const url = `${endpoint}${separator}per_page=${perPage}&page=${page}`; + const response = await fetch(`${this.baseUrl}${url}`, { + headers: this.headers, + }); + + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) break; + + yield data; + + // Check Link header for next page + const linkHeader = response.headers.get("link"); + if (!linkHeader?.includes('rel="next"')) break; + + page++; + } + } +} diff --git a/src/features/executions/components/github/components/generic-fields.tsx b/src/features/executions/components/github/components/generic-fields.tsx new file mode 100644 index 0000000..8938fab --- /dev/null +++ b/src/features/executions/components/github/components/generic-fields.tsx @@ -0,0 +1,134 @@ +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { GitHubConfig } from "../types" + +interface GenericFieldsProps { + values: Partial + setValues: (values: Partial) => void +} + +export function GenericFields({ values, setValues }: GenericFieldsProps) { + const op = values.operation || "" + + const isSearch = op.startsWith("SEARCH_") + const isUser = op.startsWith("USER_") + const isOrg = op.startsWith("ORG_") + const needsOwnerRepo = op.startsWith("CODESPACES_") || op.startsWith("RULESET_") || + op.startsWith("ENVIRONMENT_") || op.startsWith("SECRET_") || + op.startsWith("PACKAGE_") || op.startsWith("ATTESTATION_") || + op.startsWith("PROJECT_V2_") || op.startsWith("ADVISORY_") + const needsOrgOnly = op.startsWith("COPILOT_") || isOrg + + return ( +
+ {/* Owner / Org field — shown for most ops */} + {(needsOwnerRepo || needsOrgOnly || isUser) && ( +
+ + setValues({ ...values, owner: e.target.value })} + /> +
+ )} + + {/* Repo field — for operations that need a repo context */} + {needsOwnerRepo && ( +
+ + setValues({ ...values, repo: e.target.value })} + /> +
+ )} + + {/* Search query */} + {isSearch && ( +
+ + setValues({ ...values, searchQuery: e.target.value })} + /> +

+ Uses GitHub search syntax. See docs for query qualifiers. +

+
+ )} + + {/* Title — for create operations */} + {(op.includes("CREATE") || op.includes("INVITE") || op.includes("REPORT")) && ( +
+ + setValues({ ...values, title: e.target.value })} + /> +
+ )} + + {/* Body — for create/update operations */} + {(op.includes("CREATE") || op.includes("UPDATE") || op.includes("REPORT")) && ( +
+ +