Skip to content

Conversation

@Sithaarth24
Copy link
Contributor

@Sithaarth24 Sithaarth24 commented Oct 15, 2025

Summary by CodeRabbit

  • New Features

    • Background/async workflow steps for non-blocking executions.
    • Automatic agent creation for AI agent tools.
    • Public read-access for templates, steps, tools, and executions.
    • Hard-delete support for workflow tools.
    • Tool execution records now include start/completion timestamps.
  • Bug Fixes

    • More accurate permission checks when listing, viewing, and executing items.
    • Consistent error responses (404 for missing, 400 for invalid).
  • Refactor

    • Centralized, validated data access with null-safe returns and schema-driven parsing.
    • Batch step execution support and standardized ID/type handling.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Sithaarth24, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request undertakes a significant refactoring effort to improve the architecture of database interactions related to workflows and workflow tools. It moves direct Drizzle ORM queries from the server/api/workflow.ts file into new, specialized wrapper functions located in server/db/workflow.ts and server/db/workflowTool.ts. This change centralizes data access patterns, making the API layer cleaner, more focused on business logic, and less coupled to the underlying database implementation. The introduction of these wrapper functions promotes consistency, reduces redundancy, and sets the stage for easier testing and future enhancements of data operations.

Highlights

  • Centralized DB Operations: Direct Drizzle ORM queries within server/api/workflow.ts have been replaced with calls to dedicated wrapper functions, centralizing database access logic.
  • New Workflow DB Utilities: A comprehensive set of new functions has been introduced in server/db/workflow.ts for managing workflow templates, steps, and executions.
  • New Workflow Tool DB Utilities: New functions have been added in server/db/workflowTool.ts for handling workflow tools and their executions.
  • Improved Code Structure: This refactoring enhances code readability, maintainability, and reusability by abstracting database access logic from the API layer.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces direct DB queries in workflow APIs with centralized service/DB helpers, adds permission-checked public fetchers, zod-based result normalization, batch create/update helpers for executions/steps/tools, agent-wiring for AI tools, and moves execution start to async/non-blocking flows.

Changes

Cohort / File(s) Summary of changes
API layer — service-backed access
server/api/workflow.ts
Replaced inline table queries with service/DB helper calls (e.g., getAccessibleWorkflowTemplates, getWorkflowTemplateByIdWithPublicCheck, createWorkflowExecution, createWorkflowStepExecutionsFromSteps, createToolExecution); added null-safe checks, standardized ID casting, automatic agent creation/wiring for AI tools, and async/non-blocking execution responses (202).
Workflow DB accessors / execution persistence
server/db/workflow.ts
Added permission-aware fetcher getWorkflowTemplateByIdWithPublicCheck, non-checked getters getWorkflowExecutionById and getWorkflowStepTemplateById, batch inserter createWorkflowStepExecutionsFromSteps, updated createWorkflowExecution to accept optional status, renamed/repurposed listing to getAccessibleWorkflowExecutions, and converted many returns to zod-parsed normalized types.
Workflow tool access helpers
server/db/workflowTool.ts
Added getWorkflowToolByIdWithChecks, getAccessibleWorkflowTools, and deleteWorkflowTool; normalized tool and tool-execution returns with zod schemas, added workspace/user-scoped listings, ensured startedAt/completedAt propagation, and switched deletes to hard delete with boolean result.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API as server/api/workflow.ts
  participant DBH as Service/DB helpers
  participant DB

  rect rgba(240,248,255,0.9)
  note right of Client: List templates
  Client->>API: GET /workflows/templates
  API->>DBH: getAccessibleWorkflowTemplates(workspaceId, userId)
  DBH->>DB: query templates
  DB-->>DBH: templates
  DBH-->>API: normalized templates
  API-->>Client: 200 OK
  end

  rect rgba(245,255,245,0.9)
  note right of Client: Get template (public check)
  Client->>API: GET /workflows/templates/:id
  API->>DBH: getWorkflowTemplateByIdWithPublicCheck(id, workspaceId, userId)
  DBH->>DB: fetch template, steps, tools, access
  DB-->>DBH: entities or null
  alt found
    DBH-->>API: populated template
    API-->>Client: 200 OK
  else not found
    API-->>Client: 404 Not Found
  end
  end

  rect rgba(255,250,240,0.9)
  note right of Client: Execute workflow (async)
  Client->>API: POST /workflows/:id/execute
  API->>DBH: getWorkflowTemplateByIdWithPublicCheck(id, workspaceId, userId)
  DBH->>DB: fetch template, steps, tools
  DB-->>DBH: entities
  API->>DBH: createWorkflowExecution(status="queued")
  DBH->>DB: insert execution
  DB-->>DBH: execution
  DBH-->>API: execution
  API-->>Client: 202 Accepted
  Note over API,DBH: Background: create step executions, tool executions, agent creation
  API->>DBH: createWorkflowStepExecutionsFromSteps(...) (async)
  DBH->>DB: insert step executions & tool executions
  DB-->>DBH: results
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • zereraz
  • junaid-shirur
  • kalpadhwaryu
  • devesh-juspay

Poem

In my burrow I patch and hop with cheer,
Templates lined up, tools all near,
Agents nuzzle configs, steps begin to run,
Background carrots fetched — a job well done,
Hooray — workflows bloom beneath the sun. 🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "refactor: replace workflow db queries with wrapper functions" accurately and clearly summarizes the primary refactoring effort across the changeset. The title correctly identifies that the main change involves replacing direct database queries with centralized wrapper functions across the workflow module (in server/api/workflow.ts, server/db/workflow.ts, and server/db/workflowTool.ts). The title is specific, concise, follows conventional commit format, and would allow reviewers scanning history to immediately understand the core objective without ambiguity or vagueness.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/workflow-db-wrappers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request is a great refactoring effort to abstract database queries into wrapper functions, which significantly improves code structure and maintainability. However, I've identified a critical syntax error that would cause a crash, a couple of performance regressions where bulk database operations were replaced with multiple individual calls, and a data loss issue where timestamps are no longer being saved. I've also pointed out some minor areas for code cleanup.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
server/api/workflow.ts (1)

640-644: Correct mixed status enums

  • In the API response (responseData.rootStepExecution, line ~642), use WorkflowStatus.COMPLETED instead of ToolExecutionStatus.COMPLETED.
  • In every .set({ status: … }) call that updates a workflow step (where you currently use raw "failed" or ToolExecutionStatus.*), replace with the corresponding WorkflowStatus.* enum.
  • Keep all ToolExecutionStatus.* usages in tool-execution payloads (e.g. in createToolExecution) unchanged.
🧹 Nitpick comments (9)
server/db/workflowTool.ts (1)

71-86: Consider Zod-parse for runtime validation consistency.

Elsewhere (e.g., workflow templates) results are parsed via schema. For parity and safer shape guarantees, parse with selectWorkflowToolSchema (if available) instead of raw casts.

server/db/workflow.ts (2)

192-218: Guard optional status to preserve DB defaults.

If data.status is undefined, avoid sending it to ensure the column default applies.

-  const [execution] = await trx
-    .insert(workflowExecution)
-    .values({
-      workflowTemplateId: data.workflowTemplateId,
-      userId: data.userId,
-      workspaceId: data.workspaceId,
-      name: data.name,
-      description: data.description,
-      metadata: data.metadata || {},
-      status: data.status,
-    })
+  const values: any = {
+    workflowTemplateId: data.workflowTemplateId,
+    userId: data.userId,
+    workspaceId: data.workspaceId,
+    name: data.name,
+    description: data.description,
+    metadata: data.metadata || {},
+  }
+  if (data.status !== undefined) values.status = data.status
+  const [execution] = await trx.insert(workflowExecution).values(values)
     .returning()

240-253: Tiny cleanup: redundant and(...) wrapper.

Single predicate doesn’t need and(). Optional.

-  const [execution] = await trx
-    .select()
-    .from(workflowExecution)
-    .where(and(
-      eq(workflowExecution.id, id),
-    ))
+  const [execution] = await trx
+    .select()
+    .from(workflowExecution)
+    .where(eq(workflowExecution.id, id))
     .limit(1)
server/api/workflow.ts (6)

1081-1110: Use createToolExecution wrapper instead of raw inserts.

Keeps defaults, types, and shaping consistent; removes duplicate insert error handling paths.

-      let toolExecutionRecord
-      try {
-        const [execution] = await db
-          .insert(toolExecution)
-          .values({
-            workflowToolId: tool.id,
-            workflowExecutionId: executionId,
-            status: "failed",
-            result: toolResult.result,
-            startedAt: new Date(),
-            completedAt: new Date(),
-          })
-          .returning()
-        toolExecutionRecord = execution
-      } catch (dbError) {
-        ...
-        const [execution] = await db
-          .insert(toolExecution)
-          .values({ ... })
-          .returning()
-        toolExecutionRecord = execution
-      }
+      const toolExecutionRecord = await createToolExecution(db, {
+        workflowToolId: tool.id,
+        workflowExecutionId: executionId,
+        status: ToolExecutionStatus.FAILED,
+        result: toolResult.result,
+      })
@@
-    let toolExecutionRecord
-    try {
-      const [execution] = await db
-        .insert(toolExecution)
-        .values({
-          workflowToolId: tool.id,
-          workflowExecutionId: executionId,
-          status: ToolExecutionStatus.COMPLETED,
-          result: toolResult.result,
-          startedAt: new Date(),
-          completedAt: new Date(),
-        })
-        .returning()
-      toolExecutionRecord = execution
-    } catch (dbError) {
-      ...
-      try {
-        const [execution] = await db
-          .insert(toolExecution)
-          .values({ ... })
-          .returning()
-        toolExecutionRecord = execution
-      } catch (secondError) {
-        const [execution] = await db
-          .insert(toolExecution)
-          .values({ ... })
-          .returning()
-        toolExecutionRecord = execution
-      }
-    }
+    const toolExecutionRecord = await createToolExecution(db, {
+      workflowToolId: tool.id,
+      workflowExecutionId: executionId,
+      status: ToolExecutionStatus.COMPLETED,
+      result: toolResult.result,
+    })

Also applies to: 1142-1153, 1165-1180, 1187-1204


2841-2850: Prefer wrappers for tool/step creation.

Use createWorkflowTool and createWorkflowStepTemplate to keep behavior consistent with the refactor goal and centralize defaults.

-    const [newTool] = await db
-      .insert(workflowTool)
-      .values({
-        type: requestData.tool.type,
-        workspaceId: user.workspaceId,
-        userId: user.id,
-        value: requestData.tool.value,
-        config: requestData.tool.config || {},
-      })
-      .returning()
+    const newTool = await createWorkflowTool(db, {
+      type: requestData.tool.type,
+      workspaceId: user.workspaceId,
+      userId: user.id,
+      value: requestData.tool.value,
+      config: requestData.tool.config || {},
+    })
@@
-    const [newStep] = await db
-      .insert(workflowStepTemplate)
-      .values({
-        workflowTemplateId: templateId,
-        name: requestData.stepName,
-        description: requestData.stepDescription || `Step ${stepOrder}`,
-        type: requestData.stepType || "automated",
-        parentStepId: null,
-        prevStepIds: isFirstStep ? [] : [],
-        nextStepIds: [],
-        toolIds: [newTool.id],
-        timeEstimate: requestData.timeEstimate || 300,
-        metadata: {
-          icon: getStepIcon(requestData.tool.type),
-          step_order: stepOrder,
-          ...requestData.metadata,
-        },
-      })
-      .returning()
+    const newStep = await createWorkflowStepTemplate(db, {
+      workflowTemplateId: templateId,
+      name: requestData.stepName,
+      description: requestData.stepDescription || `Step ${stepOrder}`,
+      type: requestData.stepType || StepType.AUTOMATED,
+      parentStepId: undefined,
+      prevStepIds: [],
+      nextStepIds: [],
+      toolIds: [newTool.id],
+      timeEstimate: requestData.timeEstimate || 300,
+      metadata: {
+        icon: getStepIcon(requestData.tool.type),
+        step_order: stepOrder,
+        ...requestData.metadata,
+      },
+    })

Also applies to: 2863-2881


2307-2316: Also refactor complex template tool/step inserts to wrappers.

Aligns with the PR objective and reduces duplication.

-      const [createdTool] = await db
-        .insert(workflowTool)
-        .values({
-          type: tool.type,
-          workspaceId: user.workspaceId,
-          userId: user.id,
-          value: processedValue,
-          config: processedConfig,
-        })
-        .returning()
+      const createdTool = await createWorkflowTool(db, {
+        type: tool.type,
+        workspaceId: user.workspaceId,
+        userId: user.id,
+        value: processedValue,
+        config: processedConfig,
+      })
@@
-      const [createdStep] = await db
-        .insert(workflowStepTemplate)
-        .values({
-          workflowTemplateId: templateId,
-          name: stepData.name,
-          description: stepData.description || "",
-          type: stepData.type === "form_submission" || stepData.type === "manual" ? "manual" : "automated",
-          timeEstimate: 180,
-          metadata: { ... },
-          prevStepIds: [],
-          nextStepIds: [],
-          toolIds: [],
-        })
-        .returning()
+      const createdStep = await createWorkflowStepTemplate(db, {
+        workflowTemplateId: templateId,
+        name: stepData.name,
+        description: stepData.description || "",
+        type: (stepData.type === "form_submission" || stepData.type === "manual") ? StepType.MANUAL : StepType.AUTOMATED,
+        timeEstimate: 180,
+        metadata: {
+          icon: stepData.metadata?.icon,
+          step_order: stepData.metadata?.step_order,
+          schema_version: stepData.metadata?.schema_version,
+          user_instructions: stepData.metadata?.user_instructions,
+          ai_model: stepData.metadata?.ai_model,
+          automated_description: stepData.metadata?.automated_description,
+          position: node.position,
+          ...stepData.config,
+        },
+        prevStepIds: [],
+        nextStepIds: [],
+        toolIds: [],
+      })

Also applies to: 2343-2364


2673-2678: Nice: access-check before update. Consider wrapper for the update itself.

updateWorkflowTool(trx, id, data) would keep updatedAt handling and return typing consistent inside the transaction.


2889-2895: Optional: no need to pass updatedAt to wrapper.

updateWorkflowTemplate already sets updatedAt. Passing it is redundant.

Also applies to: 3041-3047


599-606: Safer metadata merge.

Avoid as Object in spreads. Prefer nullish-coalescing; keeps typing cleaner.

-          ...(rootStepExecution.metadata as Object || {}),
+          ...(rootStepExecution.metadata ?? {}),
@@
-            metadata: {
-              ...(step.metadata as Object || {}),
+            metadata: {
+              ...(step.metadata ?? {}),

Also applies to: 3105-3107

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35d1b9c and cc40b03.

📒 Files selected for processing (3)
  • server/api/workflow.ts (49 hunks)
  • server/db/workflow.ts (7 hunks)
  • server/db/workflowTool.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
server/db/workflowTool.ts (2)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/schema/workflows.ts (2)
  • SelectWorkflowTool (242-242)
  • workflowTool (115-133)
server/api/workflow.ts (2)
server/db/workflow.ts (11)
  • getAccessibleWorkflowTemplates (94-112)
  • getWorkflowStepTemplatesByTemplateId (179-190)
  • getWorkflowTemplateByIdWithPublicCheck (58-78)
  • getWorkflowStepTemplateById (166-177)
  • createWorkflowExecution (193-219)
  • createWorkflowStepExecution (290-322)
  • getWorkflowExecutionById (240-253)
  • getWorkflowExecutionByIdWithChecks (221-238)
  • createWorkflowTemplate (27-55)
  • updateWorkflowTemplate (114-129)
  • getWorkflowTemplateById (81-92)
server/db/workflowTool.ts (6)
  • getWorkflowToolsByIds (100-113)
  • getWorkflowToolById (39-50)
  • createToolExecution (147-167)
  • getAccessibleWorkflowTools (71-86)
  • createWorkflowTool (15-37)
  • getWorkflowToolByIdWithChecks (52-69)
server/db/workflow.ts (2)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/schema/workflows.ts (6)
  • workflowTemplate (63-87)
  • selectWorkflowTemplateSchema (213-213)
  • SelectWorkflowStepTemplate (239-241)
  • workflowStepTemplate (90-112)
  • SelectWorkflowExecution (243-245)
  • workflowExecution (136-161)
🔇 Additional comments (4)
server/db/workflowTool.ts (1)

52-69: Access-checked fetch looks correct.

Query is scoped by id, workspaceId, and userId; returns normalized shape. LGTM.

server/api/workflow.ts (3)

77-89: Good move: centralized DB access via wrappers.

Imports align API with the new service-layer helpers. Keeps authorization and shaping consistent.

Also applies to: 90-97


153-157: LGTM: using access-checked/list helpers.

These calls correctly scope by workspace/user and simplify the API logic.

Also applies to: 162-166, 178-182, 226-231, 237-241, 243-247


1298-1305: LGTM: secure execution fetches.

Using getWorkflowExecutionByIdWithChecks for status/details is correct.

Also applies to: 1329-1335

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
server/api/workflow.ts (1)

639-641: Use WorkflowStatus for workflow/step statuses; avoid raw strings.

Several places set workflow/step status with ToolExecutionStatus or "failed". Use WorkflowStatus for these fields to keep types and enum domains consistent.

-          status: ToolExecutionStatus.COMPLETED,
+          status: WorkflowStatus.COMPLETED,
@@
-          status: "failed",
+          status: WorkflowStatus.FAILED,
@@
-          status: ToolExecutionStatus.COMPLETED,
+          status: WorkflowStatus.COMPLETED,
@@
-          status: "failed",
+          status: WorkflowStatus.FAILED,
@@
-          status: "failed",
+          status: WorkflowStatus.FAILED,
@@
-            status: ToolExecutionStatus.COMPLETED,
+            status: WorkflowStatus.COMPLETED,

Lines affected:

  • Response shaping: Line 640 (rootStepExecution status)
  • Completion checks/updates: Lines 858-862, 903-907, 976-983
  • Failure paths: Lines 1112-1116, 1123-1127
  • Final completion: Lines 1271-1277

Also applies to: 858-862, 903-907, 976-983, 1112-1116, 1123-1127, 1271-1277

♻️ Duplicate comments (2)
server/api/workflow.ts (1)

572-585: Tool execution wrapper drops startedAt/completedAt; extend API and pass timestamps.

Refactor replaced inline inserts that set timestamps with createToolExecution, but wrapper doesn’t accept or set startedAt/completedAt, losing data.

Proposed wrapper change (server/db/workflowTool.ts):

-export const createToolExecution = async (
+export const createToolExecution = async (
   trx: TxnOrClient,
   data: {
     workflowToolId: string
     workflowExecutionId: string
     status?: ToolExecutionStatus
     result?: any
+    startedAt?: Date
+    completedAt?: Date
   },
 ): Promise<SelectToolExecution> => {
   const [execution] = await trx
     .insert(toolExecution)
     .values({
       workflowToolId: data.workflowToolId,
       workflowExecutionId: data.workflowExecutionId,
       status: data.status || ToolExecutionStatus.PENDING,
       result: data.result,
+      startedAt: data.startedAt ?? new Date(),
+      completedAt: data.completedAt ?? (data.status === ToolExecutionStatus.COMPLETED ? new Date() : null),
     })
     .returning()
   return execution as SelectToolExecution
}

Then pass timestamps at call sites:

-      toolExecutionRecord = await createToolExecution(
+      toolExecutionRecord = await createToolExecution(
         db,
         {
           workflowToolId: rootStepTool.id,
           workflowExecutionId: execution.id,
           status: ToolExecutionStatus.COMPLETED,
           result: {
             formData: processedFormData,
             submittedAt: new Date().toISOString(),
             submittedBy: "api",
             autoCompleted: true,
           },
+          startedAt: new Date(),
+          completedAt: new Date(),
         }
       )
-    const toolExecutionRecord = await createToolExecution(
+    const toolExecutionRecord = await createToolExecution(
       db,
       {
         workflowToolId: formTool.id,
         workflowExecutionId: stepExecution.workflowExecutionId,
         status: ToolExecutionStatus.COMPLETED,
         result: {
           formData: formData,
           submittedAt: new Date().toISOString(),
           submittedBy: "demo",
         }
+        ,startedAt: new Date()
+        ,completedAt: new Date()
       }
     )

Also applies to: 1598-1610

server/db/workflow.ts (1)

159-170: Make PK fetch nullable and limit(1); drop meaningless orderBy.

Current code can return undefined while the type is non-null and orders by createdAt on an equality. Return null and cap to 1 row.

Apply:

-export const getWorkflowStepTemplateById = async(
-  trx: TxnOrClient,
-  id: string,
-): Promise<SelectWorkflowStepTemplate> => {
+export const getWorkflowStepTemplateById = async (
+  trx: TxnOrClient,
+  id: string,
+): Promise<SelectWorkflowStepTemplate | null> => {
   const [step] = await trx
     .select()
     .from(workflowStepTemplate)
-    .where(eq(workflowStepTemplate.id, id))
-    .orderBy(workflowStepTemplate.createdAt)
+    .where(eq(workflowStepTemplate.id, id))
+    .limit(1)
 
-  return step as SelectWorkflowStepTemplate
+  return step ? (step as SelectWorkflowStepTemplate) : null
 }
🧹 Nitpick comments (3)
server/db/workflow.ts (1)

233-245: Avoid and() with a single predicate.

Minor clarity: use eq(...) directly.

-  const [execution] = await trx
-    .select()
-    .from(workflowExecution)
-    .where(and(
-      eq(workflowExecution.id, id),
-    ))
+  const [execution] = await trx
+    .select()
+    .from(workflowExecution)
+    .where(eq(workflowExecution.id, id))
     .limit(1)
server/api/workflow.ts (2)

1077-1106: Prefer createToolExecution wrapper over direct inserts for consistency.

These direct inserts bypass the wrapper, duplicating logic and risking divergence (timestamps, defaults, result sanitation).

  • Replace the three db.insert(toolExecution) blocks with createToolExecution(trx/db, {...}) calls.
  • If wrapper needs to handle sanitization, add an optional sanitizeResult flag or pre-sanitize the payload before passing.

Also applies to: 1138-1150, 1161-1200


2303-2313: Use wrappers (or add bulk variants) for tool/step template creates.

Direct inserts here can be replaced with createWorkflowTool/createWorkflowStepTemplate, or introduce bulk wrappers for performance and consistency.

  • For many tools/steps, add createWorkflowTools/createWorkflowStepTemplates(trx, data[]) to batch.
  • Keeps updatedAt/default handling centralized and consistent.

Also applies to: 2338-2361

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc40b03 and 15d4ce1.

📒 Files selected for processing (2)
  • server/api/workflow.ts (50 hunks)
  • server/db/workflow.ts (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/db/workflow.ts (2)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/schema/workflows.ts (9)
  • SelectWorkflowTemplate (236-238)
  • workflowTemplate (63-87)
  • selectWorkflowTemplateSchema (213-213)
  • SelectWorkflowStepTemplate (239-241)
  • workflowStepTemplate (90-112)
  • SelectWorkflowExecution (243-245)
  • workflowExecution (136-161)
  • SelectWorkflowStepExecution (246-248)
  • workflowStepExecution (164-190)
server/api/workflow.ts (2)
server/db/workflow.ts (11)
  • getAccessibleWorkflowTemplates (87-105)
  • getWorkflowStepTemplatesByTemplateId (172-183)
  • getWorkflowTemplateByIdWithPublicCheck (51-71)
  • getWorkflowStepTemplateById (159-170)
  • createWorkflowExecution (186-212)
  • createWorkflowStepExecutions (317-353)
  • getWorkflowExecutionById (233-246)
  • getWorkflowExecutionByIdWithChecks (214-231)
  • createWorkflowTemplate (20-48)
  • updateWorkflowTemplate (107-122)
  • getWorkflowTemplateById (74-85)
server/db/workflowTool.ts (6)
  • getWorkflowToolsByIds (100-113)
  • getWorkflowToolById (39-50)
  • createToolExecution (147-167)
  • getAccessibleWorkflowTools (71-86)
  • createWorkflowTool (15-37)
  • getWorkflowToolByIdWithChecks (52-69)
🔇 Additional comments (4)
server/db/workflow.ts (2)

73-85: Public getter LGTM.

Null-safe and schema-validated return looks good.


317-353: Great: bulk step-execution insert wrapper.

Eliminates N inserts and aligns with PR goals. Thanks for adding the empty-array fast path too.

server/api/workflow.ts (2)

153-157: Nice adoption of accessors and bulk insert.

Swapping to getAccessibleWorkflowTemplates/getWorkflowStepTemplatesByTemplateId and createWorkflowStepExecutions removes N queries and aligns with the PR’s goals.

Also applies to: 439-454, 726-742


2921-2929: Handle potential nulls from getWorkflowTemplateById().

These calls can return null; add a null-check (404 or fallback) before using in response.

  • Add guard after fetching updatedTemplate; return 404 if missing (deleted concurrently).

Also applies to: 3107-3115

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
server/db/workflow.ts (1)

159-170: Fix the missing null check (critical issue from past review still unresolved).

The function returns step as SelectWorkflowStepTemplate without checking if step exists. When no row is found, step is undefined, but the return type promises SelectWorkflowStepTemplate | null. This violates the type contract and can cause runtime errors in calling code.

Apply this diff to add the null check:

 export const getWorkflowStepTemplateById = async(
   trx: TxnOrClient,
   id: string,
 ): Promise<SelectWorkflowStepTemplate | null> => {
   const [step] = await trx
     .select()
     .from(workflowStepTemplate)
     .where(eq(workflowStepTemplate.id, id))
     .limit(1)
 
-  return step as SelectWorkflowStepTemplate
+  return step ? (step as SelectWorkflowStepTemplate) : null
 }
🧹 Nitpick comments (1)
server/db/workflow.ts (1)

233-246: Simplify: Remove unnecessary and() wrapper.

The and() wraps a single condition. You can pass eq() directly to .where() for cleaner code.

Apply this diff:

 export const getWorkflowExecutionById = async (
   trx: TxnOrClient,
   id: string,
 ): Promise<SelectWorkflowExecution | null> => {
   const [execution] = await trx
     .select()
     .from(workflowExecution)
-    .where(and(
-      eq(workflowExecution.id, id),
-    ))
+    .where(eq(workflowExecution.id, id))
     .limit(1)
 
   return execution ? (execution as SelectWorkflowExecution) : null
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15d4ce1 and 2783be3.

📒 Files selected for processing (2)
  • server/db/workflow.ts (8 hunks)
  • server/db/workflowTool.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/db/workflowTool.ts (2)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/schema/workflows.ts (2)
  • SelectWorkflowTool (242-242)
  • workflowTool (115-133)
server/db/workflow.ts (2)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/schema/workflows.ts (9)
  • SelectWorkflowTemplate (236-238)
  • workflowTemplate (63-87)
  • selectWorkflowTemplateSchema (213-213)
  • SelectWorkflowStepTemplate (239-241)
  • workflowStepTemplate (90-112)
  • SelectWorkflowExecution (243-245)
  • workflowExecution (136-161)
  • SelectWorkflowStepExecution (246-248)
  • workflowStepExecution (164-190)
🔇 Additional comments (5)
server/db/workflowTool.ts (2)

51-68: LGTM!

The permission-checked helper correctly validates workspace and user ownership before returning the tool. The null handling and type casting are consistent with the existing codebase pattern.


70-85: LGTM!

The helper correctly fetches tools accessible to the user within the workspace. The ordering and type casting are consistent with the codebase pattern.

server/db/workflow.ts (3)

74-85: LGTM!

The non-checked variant correctly fetches templates by ID without permission validation. The null check and schema parsing are properly implemented.


195-207: LGTM!

The optional status parameter is correctly added. When undefined, the database default (WorkflowStatus.DRAFT) will be used automatically.


317-353: LGTM!

The batch insert helper is correctly implemented. The empty array guard, field mapping with defaults, and efficient batch operation are all properly handled.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (3)
server/db/workflowTool.ts (2)

42-53: Add null check before parsing to prevent runtime crash.

If no row is found, tool will be undefined, and selectWorkflowToolSchema.parse(undefined) will throw an error. Since the return type is Promise<SelectWorkflowTool | null>, you should return null when the row doesn't exist.

Apply this diff to fix the issue:

 export const getWorkflowToolById = async (
   trx: TxnOrClient,
   id: string,
 ): Promise<SelectWorkflowTool | null> => {
   const [tool] = await trx
     .select()
     .from(workflowTool)
     .where(eq(workflowTool.id, id))
     .limit(1)
 
-  return selectWorkflowToolSchema.parse(tool)
+  return tool ? selectWorkflowToolSchema.parse(tool) : null
 }

172-183: Add null check before parsing to prevent runtime crash.

Same pattern – execution can be undefined if no row is found, causing parse(undefined) to throw.

Apply this diff:

 export const getToolExecutionById = async (
   trx: TxnOrClient,
   id: string,
 ): Promise<SelectToolExecution | null> => {
   const [execution] = await trx
     .select()
     .from(toolExecution)
     .where(eq(toolExecution.id, id))
     .limit(1)
 
-  return selectToolExecutionSchema.parse(execution)
+  return execution ? selectToolExecutionSchema.parse(execution) : null
 }
server/db/workflow.ts (1)

118-150: Fix return type and schema mismatch.

The function returns Promise<InsertWorkflowStepTemplate> and parses with insertWorkflowStepTemplateSchema, but .insert(...).returning() returns a full database row, which should match SelectWorkflowStepTemplate. The Insert schema is for input data, not database output.

Apply this diff:

 export const createWorkflowStepTemplate = async (
   trx: TxnOrClient,
   data: {
     workflowTemplateId: string
     name: string
     description?: string
     type: StepType
     parentStepId?: string
     prevStepIds?: string[]
     nextStepIds?: string[]
     toolIds?: string[]
     timeEstimate?: number
     metadata?: any
   },
-): Promise<InsertWorkflowStepTemplate> => {
+): Promise<SelectWorkflowStepTemplate> => {
   const [step] = await trx
     .insert(workflowStepTemplate)
     .values({
       workflowTemplateId: data.workflowTemplateId,
       name: data.name,
       description: data.description,
       type: data.type,
       parentStepId: data.parentStepId,
       prevStepIds: data.prevStepIds || [],
       nextStepIds: data.nextStepIds || [],
       toolIds: data.toolIds || [],
       timeEstimate: data.timeEstimate || 0,
       metadata: data.metadata || {},
     })
     .returning()
 
-  return insertWorkflowStepTemplateSchema.parse(step)
+  return selectWorkflowStepTemplateSchema.parse(step)
 }
♻️ Duplicate comments (1)
server/db/workflow.ts (1)

152-163: [UNRESOLVED] Add null check before parsing to prevent runtime crash.

This issue was previously flagged and marked as "resolved in recent commit", but the fix is not present in the current code. If no row is found, step will be undefined and parse(undefined) will throw.

Apply this diff (as originally suggested):

 export const getWorkflowStepTemplateById = async (
   trx: TxnOrClient,
   id: string,
 ): Promise<SelectWorkflowStepTemplate | null> => {
   const [step] = await trx
     .select()
     .from(workflowStepTemplate)
     .where(eq(workflowStepTemplate.id, id))
     .limit(1)
 
-  return selectWorkflowStepTemplateSchema.parse(step)
+  return step ? selectWorkflowStepTemplateSchema.parse(step) : null
 }
🧹 Nitpick comments (1)
server/api/workflow.ts (1)

596-596: Consider simplifying metadata spread pattern.

The ...(rootStepExecution.metadata as Object || {}) pattern works, but as Object is unnecessary since the || {} fallback handles null/undefined. This pattern appears in several places (lines 836, 3098).

For clarity, consider:

-          ...(rootStepExecution.metadata as Object || {}),
+          ...(rootStepExecution.metadata || {}),

The || {} already provides type safety by ensuring a spreadable object.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2783be3 and d0bfb96.

📒 Files selected for processing (3)
  • server/api/workflow.ts (50 hunks)
  • server/db/workflow.ts (16 hunks)
  • server/db/workflowTool.ts (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
server/api/workflow.ts (2)
server/db/workflow.ts (10)
  • getAccessibleWorkflowTemplates (80-98)
  • getWorkflowStepTemplatesByTemplateId (165-176)
  • getWorkflowTemplateByIdWithPublicCheck (58-78)
  • getWorkflowStepTemplateById (152-163)
  • createWorkflowExecution (179-205)
  • createWorkflowStepExecutions (310-346)
  • getWorkflowExecutionById (226-239)
  • getWorkflowExecutionByIdWithChecks (207-224)
  • createWorkflowTemplate (27-55)
  • updateWorkflowTemplate (100-115)
server/db/workflowTool.ts (6)
  • getWorkflowToolsByIds (103-116)
  • getWorkflowToolById (42-53)
  • createToolExecution (150-170)
  • getAccessibleWorkflowTools (74-89)
  • createWorkflowTool (18-40)
  • getWorkflowToolByIdWithChecks (55-72)
server/db/workflowTool.ts (2)
server/db/schema/workflows.ts (4)
  • selectWorkflowToolSchema (216-216)
  • SelectWorkflowTool (242-242)
  • workflowTool (115-133)
  • selectToolExecutionSchema (222-222)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/workflow.ts (2)
server/db/schema/workflows.ts (10)
  • selectWorkflowTemplateSchema (213-213)
  • insertWorkflowStepTemplateSchema (225-226)
  • workflowStepTemplate (90-112)
  • selectWorkflowStepTemplateSchema (214-215)
  • selectWorkflowExecutionSchema (217-218)
  • SelectWorkflowExecution (243-245)
  • workflowExecution (136-161)
  • selectWorkflowStepExecutionSchema (219-221)
  • SelectWorkflowStepExecution (246-248)
  • workflowStepExecution (164-190)
server/types.ts (1)
  • TxnOrClient (319-319)
🔇 Additional comments (2)
server/api/workflow.ts (1)

77-96: LGTM – clean service-layer integration.

The import reorganization and adoption of centralized DB wrapper functions (createWorkflowTemplate, getAccessibleWorkflowTemplates, createWorkflowExecution, etc.) significantly improves maintainability and consistency.

server/db/workflow.ts (1)

310-346: LGTM – efficient bulk insert with proper validation.

The new createWorkflowStepExecutions function correctly handles batch inserts and validates the array result via z.array(selectWorkflowStepExecutionSchema).parse(stepExecutions). This addresses the performance concern raised in past reviews about N individual inserts.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
server/db/workflowTool.ts (1)

55-72: LGTM! Past null check issue resolved.

The function now correctly checks for null before parsing and returns null when no matching record exists, as suggested in previous reviews.

server/db/workflow.ts (1)

152-163: LGTM! Past issues fully resolved.

The function now correctly:

  • Uses .limit(1) for single-result queries
  • Returns Promise<SelectWorkflowStepTemplate | null>
  • Checks for null before parsing

All concerns from previous reviews have been addressed.

🧹 Nitpick comments (1)
server/db/workflow.ts (1)

226-239: Consider simplifying single-condition query.

The and() wrapper with a single condition is unnecessary.

Apply this diff:

 export const getWorkflowExecutionById = async (
   trx: TxnOrClient,
   id: string,
 ): Promise<SelectWorkflowExecution | null> => {
   const [execution] = await trx
     .select()
     .from(workflowExecution)
-    .where(and(
-      eq(workflowExecution.id, id),
-    ))
+    .where(eq(workflowExecution.id, id))
     .limit(1)
 
   return execution ? selectWorkflowExecutionSchema.parse(execution) : null
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0bfb96 and 465843a.

📒 Files selected for processing (2)
  • server/db/workflow.ts (16 hunks)
  • server/db/workflowTool.ts (11 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/db/workflowTool.ts (2)
server/db/schema/workflows.ts (4)
  • selectWorkflowToolSchema (216-216)
  • SelectWorkflowTool (242-242)
  • workflowTool (115-133)
  • selectToolExecutionSchema (222-222)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/workflow.ts (2)
server/db/schema/workflows.ts (11)
  • selectWorkflowTemplateSchema (213-213)
  • insertWorkflowStepTemplateSchema (225-226)
  • SelectWorkflowStepTemplate (239-241)
  • workflowStepTemplate (90-112)
  • selectWorkflowStepTemplateSchema (214-215)
  • selectWorkflowExecutionSchema (217-218)
  • SelectWorkflowExecution (243-245)
  • workflowExecution (136-161)
  • selectWorkflowStepExecutionSchema (219-221)
  • SelectWorkflowStepExecution (246-248)
  • workflowStepExecution (164-190)
server/types.ts (1)
  • TxnOrClient (319-319)
🔇 Additional comments (5)
server/db/workflowTool.ts (2)

74-89: LGTM!

The function correctly implements access control by filtering on both workspaceId and userId, and consistently parses results using the zod schema array validator.


39-39: LGTM! Consistent zod parsing patterns.

All functions consistently apply zod schema validation:

  • Single-result functions check for null before parsing
  • Array-result functions use z.array(schema).parse()
  • Return types correctly reflect nullable results

This aligns with the PR's objective to normalize database query results through schema validation.

Also applies to: 52-52, 100-100, 115-115, 132-132, 169-169, 182-182, 195-195, 208-208, 225-225

server/db/workflow.ts (3)

132-132: Verify: Insert schema used for parsing DB result.

The function parses the returned database row with insertWorkflowStepTemplateSchema rather than selectWorkflowStepTemplateSchema. Insert schemas validate input data before insertion, while Select schemas parse complete database rows (including auto-generated fields like id, createdAt, etc.).

This pattern differs from other create functions in this file (e.g., createWorkflowTemplate, createWorkflowExecution) which use Select schemas for parsing returned rows.

Please verify this is intentional. If not, apply this diff:

-): Promise<InsertWorkflowStepTemplate> => {
+): Promise<SelectWorkflowStepTemplate> => {
   const [step] = await trx
     .insert(workflowStepTemplate)
     .values({
       workflowTemplateId: data.workflowTemplateId,
       name: data.name,
       description: data.description,
       type: data.type,
       parentStepId: data.parentStepId,
       prevStepIds: data.prevStepIds || [],
       nextStepIds: data.nextStepIds || [],
       toolIds: data.toolIds || [],
       timeEstimate: data.timeEstimate || 0,
       metadata: data.metadata || {},
     })
     .returning()
 
-  return insertWorkflowStepTemplateSchema.parse(step)
+  return selectWorkflowStepTemplateSchema.parse(step)
 }

Also applies to: 149-149


310-346: LGTM! Well-implemented batch operation.

The function correctly:

  • Handles empty input arrays with early return
  • Maps input data to insert values with appropriate defaults
  • Uses batch insert for efficiency
  • Consistently validates results with zod array schema

This aligns with the PR's goal of adding batch create helpers for workflow executions/steps.


54-54: LGTM! Consistent zod validation throughout.

All database operations consistently apply schema validation:

  • Create operations parse returned rows with Select schemas
  • Update operations check for null before parsing
  • Array queries use z.array(schema).parse()
  • Access-controlled functions filter by workspace/user IDs
  • New status parameter in createWorkflowExecution extends functionality appropriately

This thorough schema validation aligns with the PR's goal of centralizing and standardizing database query patterns.

Also applies to: 57-78, 97-97, 114-114, 175-175, 188-188, 204-204, 223-223, 255-255, 272-272, 307-307, 358-358, 371-371, 401-401

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (5)
server/api/workflow.ts (4)

625-629: Fix enum/status mismatches (workflow vs tool).

You’re setting workflow execution status using ToolExecutionStatus and raw strings. Use WorkflowStatus for workflow_execution rows and ToolExecutionStatus only for tool_execution.

Apply these targeted fixes:

  • Line 627: rootStepExecution status should be WorkflowStatus.COMPLETED (not ToolExecutionStatus).
  • Lines 831-835, 979-983: use WorkflowStatus.FAILED (not "failed").
  • Lines 876-881, 949-957, 1246-1251: use WorkflowStatus.COMPLETED (not ToolExecutionStatus.COMPLETED).

Example patch for one block (replicate similarly in other spots):

-          status: ToolExecutionStatus.COMPLETED,
+          status: WorkflowStatus.COMPLETED,
-          status: "failed",
+          status: WorkflowStatus.FAILED,

Also applies to: 831-835, 876-881, 949-957, 1246-1251


1380-1392: Add authorization checks for step-execution reads/updates.

These endpoints fetch/update workflowStepExecution by id without verifying workspace/user ownership via workflowExecution. A user guessing a stepId could read/mutate another user’s execution.

  • SubmitWorkflowFormApi: Lines 1380-1392 and 1501-1513
  • GetFormDefinitionApi: Lines 3183-3191
  • UpdateWorkflowStepExecutionApi: Lines 3125-3135
  • CompleteWorkflowStepExecutionApi: Lines 3154-3161

Recommended: introduce a DB helper and use it here:

// server/db/workflow.ts
export const getWorkflowStepExecutionByIdWithChecks = async (
  trx: TxnOrClient, id: string, workspaceId: number, userId: number
) => {
  const [row] = await trx.select({ se: workflowStepExecution })
    .from(workflowStepExecution)
    .innerJoin(
      workflowExecution,
      eq(workflowExecution.id, workflowStepExecution.workflowExecutionId)
    )
    .where(and(
      eq(workflowExecution.workspaceId, workspaceId),
      eq(workflowExecution.userId, userId),
      eq(workflowStepExecution.id, id),
    ))
    .limit(1)
  return row ? selectWorkflowStepExecutionSchema.parse(row.se) : null
}

Then replace direct stepExecution selects/updates to first fetch via this helper and bail with 404 if null; only then perform the update.

Also applies to: 1501-1513, 3183-3191, 3125-3135, 3154-3161


123-137: Avoid logging attachment IDs at info level.

Attachment IDs may be sensitive. Downgrade to debug and/or redact.

- Logger.info(`Processing field ${key} with attachment ID: ${file.attachmentId}`)
+ Logger.debug(`Processing field ${key} with attachment ID: ****${String(file.attachmentId).slice(-4)}`)

1049-1079: Refactor raw database inserts to use wrapper functions for consistency and to prevent logic drift.

The review comment is accurate. Raw db.insert(toolExecution) and db.insert(workflowTool) calls exist at all identified locations:

  • toolExecution inserts: lines 1051, 1065, 1112, 1135, 1157
  • workflowTool inserts: lines 2279, 2810

The proposed wrapper functions (createToolExecution, markToolExecutionFailed, markToolExecutionCompleted, createWorkflowTool) do not yet exist in the file, confirming they need to be created before refactoring these calls.

Consolidate these duplicated insert patterns into dedicated wrapper functions to ensure Zod parsing and validation are applied consistently across all tool execution and creation flows.

server/db/workflow.ts (1)

339-347: Add with-checks helpers to support API auth fixes.

Consider adding:

  • getWorkflowStepExecutionByIdWithChecks (used by API mutations)
  • updateWorkflowStepExecutionWithChecks (enforce workspace/user in WHERE)

I can draft these if you want them in this PR.

Also applies to: 349-360

🧹 Nitpick comments (2)
server/api/workflow.ts (2)

1840-1841: Hard-coded timezone in emails.

Use user/workspace timezone or UTC to avoid incorrect timestamps.

- ${new Date().toLocaleString("en-US", {timeZone: "Asia/Kolkata"})}
+ ${new Date().toISOString()}

Or inject workspace/user tz from context.


584-591: Defensive spread for metadata.

Spreading metadata with “as Object” can misbehave for non-objects. Guard first.

- ...(rootStepExecution.metadata as Object || {}),
+ ...(typeof rootStepExecution.metadata === 'object' && rootStepExecution.metadata ? rootStepExecution.metadata : {}),

Apply similarly at lines 808-820 and 3073-3076.

Also applies to: 808-820, 3073-3076

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 465843a and 0d16301.

📒 Files selected for processing (3)
  • server/api/workflow.ts (50 hunks)
  • server/db/workflow.ts (15 hunks)
  • server/db/workflowTool.ts (11 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
server/db/workflow.ts (2)
server/db/schema/workflows.ts (11)
  • selectWorkflowTemplateSchema (213-213)
  • InsertWorkflowStepTemplate (254-256)
  • insertWorkflowStepTemplateSchema (225-226)
  • workflowStepTemplate (90-112)
  • selectWorkflowStepTemplateSchema (214-215)
  • selectWorkflowExecutionSchema (217-218)
  • SelectWorkflowExecution (243-245)
  • workflowExecution (136-161)
  • selectWorkflowStepExecutionSchema (219-221)
  • SelectWorkflowStepExecution (246-248)
  • workflowStepExecution (164-190)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/workflowTool.ts (2)
server/db/schema/workflows.ts (4)
  • selectWorkflowToolSchema (216-216)
  • SelectWorkflowTool (242-242)
  • workflowTool (115-133)
  • selectToolExecutionSchema (222-222)
server/types.ts (1)
  • TxnOrClient (319-319)
server/api/workflow.ts (2)
server/db/workflow.ts (10)
  • getAccessibleWorkflowTemplates (80-97)
  • getWorkflowStepTemplatesByTemplateId (164-174)
  • getWorkflowTemplateByIdWithPublicCheck (58-78)
  • getWorkflowStepTemplateById (151-162)
  • createWorkflowExecution (177-203)
  • createWorkflowStepExecutionsFromSteps (307-335)
  • getWorkflowExecutionById (224-237)
  • getWorkflowExecutionByIdWithChecks (205-222)
  • createWorkflowTemplate (27-55)
  • updateWorkflowTemplate (99-114)
server/db/workflowTool.ts (6)
  • getWorkflowToolsByIds (100-112)
  • getWorkflowToolById (41-52)
  • createToolExecution (146-170)
  • getAccessibleWorkflowTools (73-87)
  • createWorkflowTool (17-39)
  • getWorkflowToolByIdWithChecks (54-71)
🔇 Additional comments (3)
server/db/workflowTool.ts (1)

145-170: LGTM: tool execution wrapper supports timestamps.

createToolExecution now accepts startedAt/completedAt; aligns with API usage and prior review.

server/db/workflow.ts (2)

151-162: LGTM: getWorkflowStepTemplateById now limits(1) and returns null.

Correct fix; removes undefined risk and aligns with parsed return.


307-335: Nice: bulk create of step executions.

createWorkflowStepExecutionsFromSteps prevents N inserts and returns parsed rows.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
server/db/workflow.ts (1)

117-149: Return type mismatch: should return SelectWorkflowStepTemplate.

The function uses .returning() which provides the full inserted row including generated fields (id, createdAt, updatedAt), but the return type is Promise<InsertWorkflowStepTemplate>. Insert types represent input data, while Select types represent complete rows from the database.

Apply this diff:

 export const createWorkflowStepTemplate = async (
   trx: TxnOrClient,
   data: {
     workflowTemplateId: string
     name: string
     description?: string
     type: StepType
     parentStepId?: string
     prevStepIds?: string[]
     nextStepIds?: string[]
     toolIds?: string[]
     timeEstimate?: number
     metadata?: any
   },
-): Promise<InsertWorkflowStepTemplate> => {
+): Promise<SelectWorkflowStepTemplate> => {
   const [step] = await trx
     .insert(workflowStepTemplate)
     .values({
       workflowTemplateId: data.workflowTemplateId,
       name: data.name,
       description: data.description,
       type: data.type,
       parentStepId: data.parentStepId,
       prevStepIds: data.prevStepIds || [],
       nextStepIds: data.nextStepIds || [],
       toolIds: data.toolIds || [],
       timeEstimate: data.timeEstimate || 0,
       metadata: data.metadata || {},
     })
     .returning()

-  return insertWorkflowStepTemplateSchema.parse(step)
+  return selectWorkflowStepTemplateSchema.parse(step)
 }
🧹 Nitpick comments (2)
server/db/workflow.ts (2)

224-237: Remove redundant and() wrapper.

The and() combinator is meant for multiple conditions; with a single condition, pass eq() directly to where().

Apply this diff:

 export const getWorkflowExecutionById = async (
   trx: TxnOrClient,
   id: string,
 ): Promise<SelectWorkflowExecution | null> => {
   const [execution] = await trx
     .select()
     .from(workflowExecution)
-    .where(and(
-      eq(workflowExecution.id, id),
-    ))
+    .where(eq(workflowExecution.id, id))
     .limit(1)

   return execution ? selectWorkflowExecutionSchema.parse(execution) : null
 }

307-335: Minor: clarify comment about status default.

Line 319 explicitly sets WorkflowStatus.DRAFT rather than relying on the table's default. The comment "Default status from table schema" is slightly misleading.

Consider updating the comment to be more precise:

     name: step.name,
     type: step.type,
-    status: WorkflowStatus.DRAFT, // Default status from table schema
+    status: WorkflowStatus.DRAFT, // Set initial status
     parentStepId: step.parentStepId,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d16301 and fc9e225.

📒 Files selected for processing (2)
  • server/db/workflow.ts (15 hunks)
  • server/db/workflowTool.ts (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/db/workflowTool.ts (2)
server/db/schema/workflows.ts (5)
  • selectWorkflowToolSchema (216-216)
  • SelectWorkflowTool (242-242)
  • workflowTool (115-133)
  • toolExecution (193-210)
  • selectToolExecutionSchema (222-222)
server/types.ts (1)
  • TxnOrClient (319-319)
server/db/workflow.ts (2)
server/db/schema/workflows.ts (10)
  • selectWorkflowTemplateSchema (213-213)
  • insertWorkflowStepTemplateSchema (225-226)
  • workflowStepTemplate (90-112)
  • selectWorkflowStepTemplateSchema (214-215)
  • selectWorkflowExecutionSchema (217-218)
  • SelectWorkflowExecution (243-245)
  • workflowExecution (136-161)
  • selectWorkflowStepExecutionSchema (219-221)
  • SelectWorkflowStepExecution (246-248)
  • workflowStepExecution (164-190)
server/types.ts (1)
  • TxnOrClient (319-319)
🔇 Additional comments (3)
server/db/workflowTool.ts (1)

1-259: LGTM! Clean implementation of DB wrappers with proper validation.

All previously flagged issues have been resolved:

  • Correct Zod import form (import { z } from "zod")
  • Null checks before parsing in all getters
  • Consistent schema validation throughout

The new permission-checked helpers (getWorkflowToolByIdWithChecks, getAccessibleWorkflowTools) properly filter by workspace and user, and the parsing ensures type safety across all operations.

server/db/workflow.ts (2)

151-162: LGTM! Previous issues resolved.

The function now correctly:

  • Uses .limit(1) for single-row fetch
  • Removes unnecessary .orderBy() on primary key lookup
  • Returns null when no row found
  • Includes null check before parsing

58-78: LGTM! Consistent implementation across all workflow operations.

The new and updated functions properly implement:

  • Permission-checked access via getWorkflowTemplateByIdWithPublicCheck
  • Consistent zod parsing with null safety
  • Array normalization in updateWorkflowStepExecution using Array.from()
  • Proper handling of optional parameters (status in createWorkflowExecution)

Also applies to: 80-97, 177-203, 239-253, 273-305, 362-390

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
server/api/workflow.ts (1)

3193-3247: CRITICAL: Implement authentication and workspace validation to prevent IDOR vulnerability.

This endpoint has no user context extraction and performs no workspace validation, allowing any authenticated user to access step definitions, form configurations, and tool details from any workspace by simply providing a stepId UUID.

Attack vector:

// Attacker from Workspace A obtains stepId from Workspace B
GET /api/v1/workflow/form/{stepId_from_workspace_B}
// Returns form definition and tool config without authorization check

Impact:

  • Cross-workspace data leakage
  • Exposure of proprietary form definitions and workflow logic
  • Potential exposure of sensitive configuration data in tool configs

Required fixes:

  1. Extract user context:
 export const GetFormDefinitionApi = async (c: Context) => {
   try {
+    const user = await getUserFromJWT(db, c.get(JwtPayloadKey))
     const stepId = c.req.param("stepId")
  1. Validate workspace ownership through the execution chain:
     const stepExecution = stepExecutions[0]
+    
+    // Validate workspace access through workflowExecution
+    const execution = await getWorkflowExecutionByIdWithChecks(
+      db,
+      stepExecution.workflowExecutionId,
+      user.workspaceId,
+      user.id
+    )
+    
+    if (!execution) {
+      throw new HTTPException(403, { message: "Access denied" })
+    }
+    
     const stepTemplate = await getWorkflowStepTemplateById(
  1. Use workspace-scoped tool fetcher:
-    const formTool = await getWorkflowToolById(
+    const formTool = await getWorkflowToolByIdWithChecks(
       db,
       toolIds[0],
+      user.workspaceId,
+      user.id
     )
🧹 Nitpick comments (1)
server/api/workflow.ts (1)

343-350: Consider workspace-scoped tool fetcher for defense in depth.

While the template is validated with getWorkflowTemplateByIdWithPublicCheck at line 311, using getWorkflowToolById without workspace checks creates a theoretical vulnerability if stepTemplate.toolIds could reference cross-workspace tools (via DB manipulation or bugs elsewhere).

Recommendation:

-      const toolResult = await getWorkflowToolById(
+      const toolResult = await getWorkflowToolByIdWithChecks(
         db,
         rootStep.toolIds[0],
+        user.workspaceId,
+        user.id
       )

This provides defense in depth and aligns with the principle of validating resources at point of use, not just relying on upstream validation.

Note: This same pattern appears at lines 1407-1410, 1527-1530, 1547-1550 in SubmitWorkflowFormApi. Consider applying this refactor consistently across all tool fetch locations where user context is available.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc9e225 and 713c4f3.

📒 Files selected for processing (1)
  • server/api/workflow.ts (50 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/api/workflow.ts (2)
server/db/workflow.ts (10)
  • getAccessibleWorkflowTemplates (80-97)
  • getWorkflowStepTemplatesByTemplateId (164-174)
  • getWorkflowTemplateByIdWithPublicCheck (58-78)
  • getWorkflowStepTemplateById (151-162)
  • createWorkflowExecution (177-203)
  • createWorkflowStepExecutionsFromSteps (307-335)
  • getWorkflowExecutionById (224-237)
  • getWorkflowExecutionByIdWithChecks (205-222)
  • createWorkflowTemplate (27-55)
  • updateWorkflowTemplate (99-114)
server/db/workflowTool.ts (6)
  • getWorkflowToolsByIds (101-113)
  • getWorkflowToolById (42-53)
  • createToolExecution (147-171)
  • getAccessibleWorkflowTools (74-88)
  • createWorkflowTool (17-39)
  • getWorkflowToolByIdWithChecks (55-72)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants