Skip to content

Conversation

@gandalf-repo
Copy link

Summary

Add more resources and operations to Todoist node

Screen.Recording.2025-08-03.at.15.40.57.mov

Related Linear tickets, Github issues, and Community forum posts

Review / Merge checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with release/backport (if the PR is an urgent fix that needs to be backported)

This is a cloned PR for AI code review benchmarking.

Summary

Related Linear tickets, Github issues, and Community forum posts

Review / Merge checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with release/backport (if the PR is an urgent fix that needs to be backported)

Original PR: n8n-io/n8n#17925
Author: mutdmour

## Summary

Add more resources and operations to Todoist node

https://github.com/user-attachments/assets/4bbee79f-ea0e-4c20-9e34-e00add862cd9

<!--
Describe what the PR does and how to test.
Photos and videos are recommended.
-->

## Related Linear tickets, Github issues, and Community forum posts

<!--
Include links to **Linear ticket** or Github issue or Community forum post.
Important in order to close *automatically* and provide context to reviewers.
https://linear.app/n8n/issue/
-->
<!-- Use "closes #<issue-number>", "fixes #<issue-number>", or "resolves #<issue-number>" to automatically close issues when the PR is merged. -->

## Review / Merge checklist

- [ ] PR title and summary are descriptive. ([conventions](../blob/master/.github/pull_request_title_conventions.md)) <!--
   **Remember, the title automatically goes into the changelog.
   Use `(no-changelog)` otherwise.**
-->
- [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created.
- [ ] Tests included. <!--
   A bug is not considered fixed, unless a test is added to prevent it from happening again.
   A feature is not complete without tests.
-->
- [ ] PR Labeled with `release/backport` (if the PR is an urgent fix that needs to be backported)

---
This is a cloned PR for AI code review benchmarking.
@refacto-bot
Copy link

refacto-bot bot commented Aug 11, 2025

🤖 Refacto.AI: PR Summary & Reviewer Guide

🎯 1. The Goal (Why was this PR raised?)

This PR aims to enhance the Todoist Node by adding more resources and operations. It expands the node's functionality to support additional Todoist API features beyond the existing task operations.

🗺️ 2. The Changes (What was implemented?)

This section provides a walkthrough of the changes, connecting them to the PR's goals.

File Name Implementation Details Goal / Requirement Addressed
packages/nodes-base/utils/types.ts (Added) Added a new utility file with type assertion functions for parameter validation, including functions like assertIsString, assertIsNumber, and assertIsNodeParameters. Add type validation utilities
packages/nodes-base/utils/tests/types.test.ts (Added) Added comprehensive test suite for the new type assertion functions. Test new type validation utilities
packages/nodes-base/nodes/Todoist/v2/Service.ts (Modified) Expanded the service class to support new operation types (projects, sections, comments, labels) with corresponding handlers and type guards. Add support for new resources
packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts (Modified) Enhanced existing handlers and added new handlers for projects, sections, comments, and labels operations. Added parameter validation using the new type assertion utilities. Implement handlers for new resources
packages/nodes-base/nodes/Todoist/v2/TodoistV2.node.ts (Modified) Updated the node definition to include new resources and operations in the UI. Added color options and new parameter fields for the new resources. Add UI support for new resources
packages/nodes-base/nodes/Todoist/GenericFunctions.ts (Modified) Modified the todoistSyncRequest function to support custom endpoints. Support additional API endpoints
packages/nodes-base/nodes/Todoist/v2/test/TodoistV2.node.test.ts (Added) Added integration tests for the enhanced Todoist node with mock data for all new resources and operations. Test new node functionality
packages/nodes-base/nodes/Todoist/v2/test/OperationHandler.test.ts (Added) Added unit tests for all operation handlers including the new resource operations. Test operation handlers
packages/nodes-base/nodes/Todoist/v2/test/workflow.json (Added) Added a test workflow configuration file to test the node with all operations. Support testing

🤔 3. Key Areas for Human Review

Here are the most important areas to focus your review on, including specific testing instructions.

Area of Concern: Type Validation Implementation

  • File: packages/nodes-base/utils/types.ts
  • Why: This is a new core utility file that introduces type validation functions used throughout the Todoist node. Any issues here could affect the robustness of parameter validation across the node.
  • Testing Instruction: Review the implementation of assertIsNodeParameters and verify it correctly validates different parameter types, especially when handling optional parameters and complex nested objects.

Area of Concern: Service Class Architecture

  • File: packages/nodes-base/nodes/Todoist/v2/Service.ts (Lines 44-187)
  • Why: The service class has been significantly refactored to support multiple resource types. The architecture changes how operations are dispatched.
  • Testing Instruction: Test each resource type (task, project, section, comment, label) with at least one operation to ensure the correct handler is called and proper type checking is enforced.

Area of Concern: Operation Handler Implementation

  • File: packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts (Lines 562-979)
  • Why: This file contains extensive new code for handling operations on different resources. The implementation includes complex parameter validation and API interactions.
  • Testing Instruction: Test operations that involve parameter type conversion (like string to number) and verify that error handling works correctly when invalid parameters are provided.

Area of Concern: Node UI Definition

  • File: packages/nodes-base/nodes/Todoist/v2/TodoistV2.node.ts (Lines 206-1482)
  • Why: Significant additions to the node UI definition with many new parameters and options. Ensuring these are correctly defined is critical for user experience.
  • Testing Instruction: In the n8n UI, create a workflow with the Todoist node and verify that all resources and their operations display correctly with appropriate fields. Test especially the resource switching behavior to ensure parameters update correctly.

@gandalf-repo
Copy link
Author

/do-refacto

@refacto-bot
Copy link

refacto-bot bot commented Aug 11, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@refacto-bot
Copy link

refacto-bot bot commented Aug 11, 2025

Code Review: Todoist Node Type Safety Implementation

👍 Well Done
Strong Type Validation Framework

Comprehensive type assertion utilities improve parameter validation and prevent runtime errors.

Resource Organization

Well-structured operation types with type guards enhance code maintainability and reliability.

📌 Files Processed
  • packages/nodes-base/utils/types.ts
  • packages/nodes-base/nodes/Todoist/v2/Service.ts
  • packages/nodes-base/utils/tests/types.test.ts
  • packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts
  • packages/nodes-base/nodes/Todoist/v2/test/TodoistV2.node.test.ts
  • packages/nodes-base/nodes/Todoist/v2/test/OperationHandler.test.ts
  • packages/nodes-base/nodes/Todoist/v2/test/workflow.json
  • packages/nodes-base/nodes/Todoist/v2/TodoistV2.node.ts
  • packages/nodes-base/nodes/Todoist/GenericFunctions.ts
📝 Additional Comments
packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts (3)
String Concatenation Injection Risk

User-controlled input is passed directly to FormatDueDatetime without validation. This could lead to injection vulnerabilities if FormatDueDatetime doesn't properly sanitize inputs.

if (options.dueDateTime) {
  // Validate datetime format before processing
  const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/;
  if (!dateTimeRegex.test(options.dueDateTime)) {
    throw new Error(`Invalid datetime format for dueDateTime: ${options.dueDateTime}`);
  }
  body.due_datetime = FormatDueDatetime(options.dueDateTime);
}

Standards:

  • CWE-20
  • OWASP-A03
Unchecked Type Conversion

The parseInt operation doesn't handle invalid string formats. If options.priority contains a non-numeric string, it will return NaN without error, potentially causing downstream failures when the API rejects the invalid value.

priority: (() => {
  if (typeof options.priority === 'string') {
    const parsed = parseInt(options.priority, 10);
    if (isNaN(parsed)) {
      throw new ApplicationError(`Invalid priority value: ${options.priority}`);
    }
    return parsed;
  }
  return options.priority ?? 1;
})()

Standards:

  • ISO-IEC-25010-Reliability-Maturity
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
Redundant Type Assertions

Redundant type assertions are used after the assertIsNodeParameters validation already guarantees types. These unnecessary casts add cognitive overhead and can hide potential type issues.

389: body.content = updateFields.content;
393: body.priority = typeof updateFields.priority === 'string' ? parseInt(updateFields.priority, 10) : updateFields.priority;
397: body.description = updateFields.description;
401: body.due_datetime = FormatDueDatetime(updateFields.dueDateTime);
405: body.due_string = updateFields.dueString;

Standards:

  • ISO-IEC-25010-Performance-Maintainability
  • Google-Code-Health
packages/nodes-base/nodes/Todoist/v2/Service.ts (1)
Operation Type Duplication

Multiple operation arrays contain duplicated values like 'create', 'delete', etc. This creates maintenance overhead when adding common operations across resources and increases risk of inconsistency.

// Define common operations
const COMMON_OPERATIONS = ['create', 'delete', 'get', 'getAll', 'update'] as const;

// Define resource-specific operations extending common ones
const TASK_OPERATIONS = [
  ...COMMON_OPERATIONS,
  'close',
  'reopen',
  'move',
  'sync',
  'quickAdd',
] as const;

const PROJECT_OPERATIONS = [
  ...COMMON_OPERATIONS,
  'archive',
  'unarchive',
  'getCollaborators',
] as const;

Standards:

  • Clean-Code-DRY
  • Clean-Code-Maintainability
  • Refactoring-Extract-Common

Comment on lines 159 to +163
project_id: projectId,
priority: options.priority! ? parseInt(options.priority as string, 10) : 1,
priority:
typeof options.priority === 'string'
? parseInt(options.priority, 10)
: (options.priority ?? 1),
Copy link

Choose a reason for hiding this comment

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

Type Conversion Error in Priority Handling

The priority field handling has inconsistent type conversion. When options.priority is a number, it's used directly, but when it's a string, it's parsed to integer. This creates inconsistent behavior if options.priority is undefined (returns 1) vs null (returns null).

Suggested change
project_id: projectId,
priority: options.priority! ? parseInt(options.priority as string, 10) : 1,
priority:
typeof options.priority === 'string'
? parseInt(options.priority, 10)
: (options.priority ?? 1),
priority: options.priority !== undefined
? (typeof options.priority === 'string'
? parseInt(options.priority, 10)
: options.priority)
: 1,
Standards
  • Algorithm-Correctness-Type-Conversion
  • Logic-Verification-Null-Handling

Comment on lines +116 to +118
const content = ctx.getNodeParameter('content', itemIndex);
assertIsString('content', content);

Copy link

Choose a reason for hiding this comment

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

Missing Error Handling in Content Parameter

The assertIsString function throws errors without specific handling. If content parameter is invalid, the error propagates without proper context, making debugging difficult and reducing system reliability.

Suggested change
const content = ctx.getNodeParameter('content', itemIndex);
assertIsString('content', content);
try {
const content = ctx.getNodeParameter('content', itemIndex);
assertIsString('content', content);
} catch (error) {
throw new ApplicationError(
'Invalid content parameter: Must be a string',
{ cause: error }
);
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Reliability-Recoverability

Comment on lines +173 to +187
export function isProjectOperationType(operation: string): operation is ProjectOperationType {
return PROJECT_OPERATIONS.includes(operation as ProjectOperationType);
}

export function isSectionOperationType(operation: string): operation is SectionOperationType {
return SECTION_OPERATIONS.includes(operation as SectionOperationType);
}

export function isCommentOperationType(operation: string): operation is CommentOperationType {
return COMMENT_OPERATIONS.includes(operation as CommentOperationType);
}

export function isLabelOperationType(operation: string): operation is LabelOperationType {
return LABEL_OPERATIONS.includes(operation as LabelOperationType);
}
Copy link

Choose a reason for hiding this comment

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

Type Guard Duplication

Type guard functions follow identical pattern with duplicated logic. This violates DRY principle and makes maintenance harder when adding new resource types or modifying type guard behavior.

Suggested change
export function isProjectOperationType(operation: string): operation is ProjectOperationType {
return PROJECT_OPERATIONS.includes(operation as ProjectOperationType);
}
export function isSectionOperationType(operation: string): operation is SectionOperationType {
return SECTION_OPERATIONS.includes(operation as SectionOperationType);
}
export function isCommentOperationType(operation: string): operation is CommentOperationType {
return COMMENT_OPERATIONS.includes(operation as CommentOperationType);
}
export function isLabelOperationType(operation: string): operation is LabelOperationType {
return LABEL_OPERATIONS.includes(operation as LabelOperationType);
}
// Generic type guard factory
function createOperationTypeGuard<T extends string>(operations: readonly T[]) {
return (operation: string): operation is T => {
return operations.includes(operation as T);
};
}
// Create type guards using the factory
export const isProjectOperationType = createOperationTypeGuard(PROJECT_OPERATIONS);
export const isSectionOperationType = createOperationTypeGuard(SECTION_OPERATIONS);
export const isCommentOperationType = createOperationTypeGuard(COMMENT_OPERATIONS);
export const isLabelOperationType = createOperationTypeGuard(LABEL_OPERATIONS);
Standards
  • Clean-Code-DRY
  • Refactoring-Extract-Method
  • Design-Pattern-Factory

Comment on lines +1631 to +1645
if (!isProjectOperationType(operation)) {
throw new NodeOperationError(
this.getNode(),
`Invalid operation '${operation}' for project resource`,
);
}
responseData = await service.executeProject(this, operation, i);
} else if (resource === 'section') {
if (!isSectionOperationType(operation)) {
throw new NodeOperationError(
this.getNode(),
`Invalid operation '${operation}' for section resource`,
);
}
responseData = await service.executeSection(this, operation, i);
Copy link

Choose a reason for hiding this comment

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

Type Guard Inconsistency for Task Resource

The task resource doesn't have type guard validation unlike other resources. This inconsistency could allow invalid operations to be passed to executeTask, potentially causing runtime failures.

Suggested change
if (!isProjectOperationType(operation)) {
throw new NodeOperationError(
this.getNode(),
`Invalid operation '${operation}' for project resource`,
);
}
responseData = await service.executeProject(this, operation, i);
} else if (resource === 'section') {
if (!isSectionOperationType(operation)) {
throw new NodeOperationError(
this.getNode(),
`Invalid operation '${operation}' for section resource`,
);
}
responseData = await service.executeSection(this, operation, i);
if (resource === 'task') {
if (!isTaskOperationType(operation)) {
throw new NodeOperationError(
this.getNode(),
`Invalid operation '${operation}' for task resource`
);
}
responseData = await service.executeTask(this, operation, i);
} else if (resource === 'project') {
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

Comment on lines +101 to +115
async executeProject(
ctx: Context,
operation: ProjectOperationType,
itemIndex: number,
): Promise<TodoistResponse> {
return await this.projectHandlers[operation].handleOperation(ctx, itemIndex);
}

async executeSection(
ctx: Context,
operation: SectionOperationType,
itemIndex: number,
): Promise<TodoistResponse> {
return await this.sectionHandlers[operation].handleOperation(ctx, itemIndex);
}
Copy link

Choose a reason for hiding this comment

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

Service Method Duplication

Multiple execute methods with identical structure for different resource types. This creates code duplication and makes adding new resource types require repetitive boilerplate code.

Suggested change
async executeProject(
ctx: Context,
operation: ProjectOperationType,
itemIndex: number,
): Promise<TodoistResponse> {
return await this.projectHandlers[operation].handleOperation(ctx, itemIndex);
}
async executeSection(
ctx: Context,
operation: SectionOperationType,
itemIndex: number,
): Promise<TodoistResponse> {
return await this.sectionHandlers[operation].handleOperation(ctx, itemIndex);
}
// Generic execute method for any resource type
private async executeOperation<T extends string>(
ctx: Context,
operation: T,
itemIndex: number,
handlers: Record<string, OperationHandler>
): Promise<TodoistResponse> {
return await handlers[operation].handleOperation(ctx, itemIndex);
}
// Resource-specific methods using the generic execute
async executeTask(
ctx: Context,
operation: TaskOperationType,
itemIndex: number,
): Promise<TodoistResponse> {
return this.executeOperation(ctx, operation, itemIndex, this.handlers);
}
async executeProject(
ctx: Context,
operation: ProjectOperationType,
itemIndex: number,
): Promise<TodoistResponse> {
return this.executeOperation(ctx, operation, itemIndex, this.projectHandlers);
}
Standards
  • Clean-Code-DRY
  • Refactoring-Extract-Method
  • Design-Pattern-Template-Method

Co-authored-by: do-refacto[bot] <198860923+do-refacto[bot]@users.noreply.github.com>
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.

2 participants