Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/bull_mq_integration #2735

Open
wants to merge 17 commits into
base: dev
Choose a base branch
from
Open

feature/bull_mq_integration #2735

wants to merge 17 commits into from

Conversation

Blokh
Copy link
Collaborator

@Blokh Blokh commented Sep 29, 2024

feat: added redis defaults

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Redis configuration options for enhanced queue management.
    • Added support for webhook job processing with queuing capabilities.
    • Implemented a new BullMqModule for managing job queues.
    • Integrated alert functionalities into the Business Report module.
    • Enhanced the ability to manage incoming and outgoing webhooks with new services.
    • Added a new BaseQueueWorkerService for structured job processing.
    • Introduced a configuration object for Redis connection settings.
    • Added new queue configurations for better job management.
  • Bug Fixes

    • Improved error handling and logging for webhook requests.
  • Documentation

    • Updated environment variable configurations to include Redis and queue settings.
  • Chores

    • Added new scripts for managing Redis Docker containers in the setup process.

@Blokh Blokh requested a review from alonp99 September 29, 2024 12:54
@Blokh Blokh self-assigned this Sep 29, 2024
Copy link

changeset-bot bot commented Sep 29, 2024

⚠️ No Changeset found

Latest commit: 3193904

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Sep 29, 2024

Walkthrough

The changes in this pull request introduce several new environment variables for the workflows-service, enhancing its configuration for Redis and job queue management. A new Docker Compose file for Redis is added, along with updates to the package.json to include scripts for managing the Redis container and new dependencies for job processing. The application module is modified to integrate BullMQ for job queue management, and new services for handling incoming and outgoing webhooks are created. Overall, the updates enhance the service's capabilities for processing jobs and managing webhooks.

Changes

File Path Change Summary
services/workflows-service/.env.example Added new environment variables: REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, IS_WORKER_SERVICE, QUEUE_SYSTEM_ENABLED.
services/workflows-service/docker-compose.redis.yml Introduced new Redis service configuration, including volume and network setup.
services/workflows-service/package.json Modified setup script and added new scripts for Redis management and worker functionality. Included new dependencies for BullMQ integration.
services/workflows-service/src/app.module.ts Added BullMqModule, IncomingWebhooksModule, and OutgoingWebhooksModule; removed WebhooksModule.
services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts Added BaseQueueWorkerService class with methods for job handling and initialization.
services/workflows-service/src/bull-mq/bull-mq.module.ts Introduced BullMqModule for BullMQ integration, registered job queues, and set up Bull Board.
services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts Added IncomingWebhookQueueService class for processing incoming webhook jobs.
services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts Added OutgoingWebhookQueueService class for managing outgoing webhook jobs with error handling and retries.
services/workflows-service/src/env.ts Updated serverEnvSchema with new properties for Redis and queue management.
services/workflows-service/src/worker-app.module.ts Introduced WorkerAppModule for configuring worker services and validation.
services/workflows-service/src/worker-main.ts Added main entry point for the worker application.
services/workflows-service/src/workflow/workflow.module.ts Imported BullMqModule and OutgoingWebhooksModule into WorkflowModule.
services/workflows-service/src/events/document-changed-webhook-caller.ts Updated DocumentChangedWebhookCaller to use queuing for webhook requests based on environment variable.
services/workflows-service/src/events/workflow-completed-webhook-caller.ts Modified WorkflowCompletedWebhookCaller to enqueue webhook requests based on environment variable.
services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts Updated WorkflowStateChangedWebhookCaller to use queuing for webhook requests based on environment variable.
services/workflows-service/src/business-report/business-report.module.ts Added AlertModule to BusinessReportModule.
services/workflows-service/src/redis/const/redis-config.ts Introduced REDIS_CONFIG for managing Redis connection settings.
services/workflows-service/src/workflow/workflow.service.unit.test.ts Updated unit tests for WorkflowService and DocumentChangedWebhookCaller to accommodate new constructor parameters.

Possibly related PRs

  • blokh/feat/business report alerts #2311: This PR enhances the business report handling system, which may relate to the new environment variables added in the main PR for configuring Redis, as both involve managing configurations for services.
  • Limit reports generation per customer #2582: This PR introduces a limit on report generation per customer, which is directly related to the management of business reports and could connect with the new configurations for handling reports in the main PR.
  • Documents block sort #2730: This PR focuses on sorting document blocks, which may relate to the overall management of document data, potentially connecting with the new environment variables that enhance document handling capabilities in the main PR.
  • Feature/implement ocr button #2731: This PR implements an OCR button, enhancing document processing capabilities, which aligns with the new configurations introduced in the main PR for managing document-related services.
  • Updated swagger schema of get workflow by id endpoint #2762: This PR updates the Swagger schema for the workflow by ID endpoint, which may relate to the overall service management and configuration changes introduced in the main PR.
  • Additional swagger documentations #2767: This PR adds additional Swagger documentation, which could relate to the overall enhancements in service management and configuration introduced in the main PR.

Suggested labels

enhancement, Review effort [1-5]: 4

Suggested reviewers

  • liorzam
  • Omri-Levy

🐰 In the meadow, changes bloom,
Redis whispers, dispelling gloom.
Queues are set, the workers play,
Webhooks dance in a joyful sway.
With each job, a hop, a cheer,
A brighter path, the future's near! 🌼


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b20a326 and 3193904.

📒 Files selected for processing (1)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Outside diff range and nitpick comments (2)
services/workflows-service/docker-compose.redis.yml (1)

3-16: Approve Redis service configuration with a minor suggestion.

The Redis service configuration is well-structured and follows good practices:

  • Using the Alpine-based image for a smaller footprint
  • Exposing the port via an environment variable for flexibility
  • Mounting a volume for data persistence
  • Setting a password for security
  • Enabling append-only mode for data durability
  • Using a custom network for isolation

However, there's a minor redundancy in the configuration.

Consider removing the redundant environment variables:

 environment:
-  - REDIS_PASSWORD=${REDIS_PASSWORD}
-  - REDIS_PORT=${REDIS_PORT}

These variables are already used in the configuration and don't need to be explicitly set in the environment section.

services/workflows-service/package.json (1)

8-8: LGTM! Consider adding a wait for Redis.

The addition of npm run docker:redis to the setup script is a good change, ensuring Redis is started as part of the setup process. This aligns well with the PR objectives of introducing Redis defaults.

Consider adding a wait for Redis to be ready, similar to how you wait for the database:

-    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis &&  npm run db:reset && npm run seed",
+    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis && wait-on tcp:6379 &&  npm run db:reset && npm run seed",

This ensures that Redis is fully ready before proceeding with subsequent steps.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between df66c39 and 4632b91.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • services/workflows-service/.env.example (1 hunks)
  • services/workflows-service/docker-compose.redis.yml (1 hunks)
  • services/workflows-service/package.json (4 hunks)
  • services/workflows-service/src/app.module.ts (2 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
🔇 Additional comments (11)
services/workflows-service/docker-compose.redis.yml (2)

1-1: LGTM: Docker Compose version is appropriate.

The chosen Docker Compose version (3.8) is suitable for modern Docker environments and provides access to recent features.


24-26: LGTM: Network configuration is appropriate.

The custom bridge network 'app-network' is well-defined and follows best practices:

  • It provides isolation for the Redis service.
  • The configuration is simple and suitable for most use cases.

This setup allows for easy communication between services while maintaining network separation from other Docker networks.

services/workflows-service/.env.example (1)

38-40: ⚠️ Potential issue

Improve Redis configuration for security and consistency

While adding Redis configuration is a good step for integrating BullMQ, there are a few points to consider:

  1. Security: Using 'password' as the default Redis password in the .env.example file is a security risk. Consider using a placeholder like '' instead.

  2. Configuration:

    • The Redis port (7381) is non-standard. If there's no specific reason for this, consider using the default Redis port 6379.
    • 'local' as the host might be confusing. Consider using 'localhost' or '127.0.0.1' for clarity.
  3. Consistency: To maintain consistency with other database configurations in this file, consider using uppercase for the host (e.g., 'LOCALHOST' instead of 'local').

Here's a suggested improvement:

-REDIS_HOST=local
-REDIS_PORT=7381
-REDIS_PASSWORD=password
+REDIS_HOST=LOCALHOST
+REDIS_PORT=6379
+REDIS_PASSWORD=<your-redis-password>

To ensure these Redis settings are used consistently across the project, run the following script:

This script will help identify where the Redis configuration is used and if there are any hardcoded Redis connections that should be using these environment variables instead.

✅ Verification successful

Redis configuration verified and recommendation applied

The Redis configurations are correctly utilized across the project without any hardcoded connections, ensuring security best practices are followed.

Recommendation:

  • Update the .env.example to use placeholders for sensitive information and standardize configuration values:

    -REDIS_HOST=local
    -REDIS_PORT=7381
    -REDIS_PASSWORD=password
    +REDIS_HOST=LOCALHOST
    +REDIS_PORT=6379
    +REDIS_PASSWORD=<your-redis-password>
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for Redis configuration usage across the project

# Search for Redis configuration usage
echo "Searching for Redis configuration usage:"
rg --type-add 'config:*.{js,ts,json,yaml,yml}' --type config 'REDIS_(HOST|PORT|PASSWORD)'

# Check for any hardcoded Redis configurations
echo "Checking for hardcoded Redis configurations:"
rg --type-add 'code:*.{js,ts}' --type code 'redis.(createClient|connect)\('

Length of output: 1142

services/workflows-service/src/env.ts (2)

93-98: Summary of Redis configuration changes

The addition of Redis-related environment variables is a positive step towards integrating Redis with the workflows service. The implementation is generally sound, with room for minor improvements in validation and flexibility.

Key points:

  1. New variables added: REDIS_HOST, REDIS_PASSWORD, and REDIS_PORT.
  2. Suggestions made for enhancing validation and flexibility.
  3. Verification needed to ensure proper usage of these variables across the project.

Overall, these changes lay the groundwork for Redis integration, which aligns with the PR objective of introducing enhancements related to Redis defaults within the project.


93-98: Verify the usage of new Redis environment variables

The addition of Redis configuration variables suggests that Redis integration is being added to the workflows service. However, there are no other changes visible in this file or mentioned in the context that show how these variables will be used.

To ensure these variables are properly utilized, please run the following script to check for their usage across the project:

This script will help verify that the new environment variables are being used appropriately in the project and that Redis is being properly integrated.

services/workflows-service/src/app.module.ts (2)

51-51: LGTM: BullModule import added correctly.

The import statement for BullModule from '@nestjs/bullmq' is correctly placed and uses the appropriate package for NestJS integration with BullMQ.


130-136: BullModule configuration looks good, but consider some improvements.

The BullModule configuration is correctly placed and uses environment variables for Redis connection details, which is a good practice. However, there are a few points to consider:

  1. Consider adding default values or validation for REDIS_PORT and REDIS_PASSWORD. This will prevent potential issues if these environment variables are not set.

  2. It's recommended to add error handling for the Redis connection to avoid silent failures. You could wrap the BullModule configuration in a try-catch block or use a custom provider to handle connection errors.

  3. Ensure that these environment variables (REDIS_HOST, REDIS_PORT, REDIS_PASSWORD) are properly documented and set in your deployment environments.

Here's a suggested improvement:

BullModule.forRootAsync({
  useFactory: (configService: ConfigService) => ({
    connection: {
      host: configService.get('REDIS_HOST', 'localhost'),
      port: configService.get('REDIS_PORT', 6379),
      password: configService.get('REDIS_PASSWORD'),
    },
  }),
  inject: [ConfigService],
}),

Let's verify the usage of these environment variables:

services/workflows-service/package.json (4)

33-34: Great addition of Redis management scripts!

The new docker:redis and docker:redis:down scripts are well-implemented:

  1. They use a separate Docker Compose file, promoting modularity.
  2. The --wait flag ensures the Redis service is ready before the command completes.
  3. The --volumes flag in the down command ensures proper cleanup.

These scripts align well with the PR objectives and provide convenient commands for managing the Redis service.


57-57: Excellent choice for Redis-based queue integration!

The addition of @nestjs/bullmq is a great choice for integrating Redis-based queues with NestJS. This aligns perfectly with the PR objectives of improving Redis integration and enhancing message queuing capabilities.


Line range hint 1-184: Overall, excellent changes for Redis and BullMQ integration!

The modifications to package.json effectively support the PR objectives:

  1. The setup script now includes Redis initialization.
  2. New scripts for managing the Redis service have been added.
  3. Dependencies for @nestjs/bullmq and bullmq have been included.

These changes provide a solid foundation for integrating Redis and improving message queuing in the workflows service. Great job on maintaining consistency and following best practices!


84-84: Good addition of BullMQ core library.

The inclusion of bullmq is necessary and complements the @nestjs/bullmq dependency well. This further supports the PR objectives of enhancing Redis integration and message queuing capabilities.

Please ensure that the versions of @nestjs/bullmq and bullmq are compatible. You can verify this by checking the peer dependencies of @nestjs/bullmq:

If there are any version mismatches, consider updating the versions to ensure compatibility.

Comment on lines 17 to 23
volumes:
redis-data:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: size=100m
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 29, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Reconsider volume configuration for data persistence.

The current volume configuration uses a tmpfs (in-memory filesystem) with a 100MB limit. While this provides fast I/O, it has some potential drawbacks:

  1. Data volatility: All data will be lost when the container stops.
  2. Size limitation: 100MB might be restrictive depending on your data volume.

Consider the following alternatives:

  1. For true persistence, use a named volume without tmpfs:

    volumes:
      redis-data:
        driver: local
  2. If tmpfs is required for performance, consider increasing the size limit and be aware of the data loss risk:

    volumes:
      redis-data:
        driver: local
        driver_opts:
          type: tmpfs
          device: tmpfs
          o: size=1g  # Adjust size as needed
  3. For a balance of persistence and performance, consider using the local driver with a bind mount to an SSD on the host:

    volumes:
      redis-data:
        driver: local
        driver_opts:
          type: none
          o: bind
          device: /path/on/host/redis-data

Choose the option that best fits your persistence and performance requirements.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good comment, i think "local" is the choice here for local development

Copy link
Contributor

Choose a reason for hiding this comment

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

@alonp99, glad you agree. I've noted that using driver: local is the choice for local development.

(_/)
(•ᴥ•)
/ 🍃


✏️ Learnings added
Learnt from: alonp99
PR: ballerine-io/ballerine#2735
File: services/workflows-service/docker-compose.redis.yml:17-23
Timestamp: 2024-09-30T07:58:41.803Z
Learning: For Redis volumes in local development, prefer using `driver: local` in Docker Compose configurations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 93 to 98
REDIS_HOST: z.string(),
REDIS_PASSWORD: z.string(),
REDIS_PORT: z
.string()
.default('6379')
.transform(value => Number(value)),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider enhancing Redis configuration variables

The addition of Redis-related environment variables is a good step towards integrating Redis with the workflows service. However, consider the following improvements:

  1. Add validation for REDIS_PORT to ensure it's within a valid range (e.g., 1024-65535).
  2. Make REDIS_PASSWORD optional, as not all Redis configurations require authentication.
  3. Add comments explaining the purpose of these new variables for better maintainability.

Here's a suggested implementation:

  // Redis configuration for BullMQ integration
  REDIS_HOST: z.string().describe('Redis server hostname'),
  REDIS_PASSWORD: z.string().optional().describe('Redis server password (if required)'),
  REDIS_PORT: z
    .string()
    .default('6379')
    .transform(value => {
      const port = Number(value);
      if (isNaN(port) || port < 1024 || port > 65535) {
        throw new Error('REDIS_PORT must be a number between 1024 and 65535');
      }
      return port;
    })
    .describe('Redis server port'),

This implementation adds descriptions, makes the password optional, and includes port number validation.

version: '3.8'
services:
redis:
image: redis:alpine
Copy link
Collaborator

@MatanYadaev MatanYadaev Sep 29, 2024

Choose a reason for hiding this comment

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

Is it for local only? Because the Alpine image is less performant than the Debian image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes it's local only

Comment on lines 20 to 23
driver_opts:
type: tmpfs
device: tmpfs
o: size=100m
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you explain this configuration?

Copy link
Collaborator Author

@Blokh Blokh Sep 29, 2024

Choose a reason for hiding this comment

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

the maximum size of the redis cache that can be set in your local computer under the file of tmp file system ( it will reset after restart )
this is not really an issue - as it is a queue system...
in any matter we don't want to keep a lot of information in our open source system at our customers

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did you create a new docker-compose file and didn't use the existing one?

@@ -5,7 +5,7 @@
"description": "workflow-service",
"scripts": {
"spellcheck": "cspell \"*\"",
"setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run db:reset && npm run seed",
"setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis && npm run db:reset && npm run seed",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't you need to run docker:redis:down as well?

@@ -30,6 +30,8 @@
"db:reset:dev:with-data": "npm run db:reset:dev && npm run db:data-migration:migrate && npm run db:data-sync",
"db:init": "npm run db:migrate-dev -- --name 'initial version' && npm run db:migrate-up seed",
"prisma:generate": "prisma generate",
"docker:redis": "docker compose -f docker-compose.redis.yml up -d --wait",
Copy link
Collaborator

@MatanYadaev MatanYadaev Sep 29, 2024

Choose a reason for hiding this comment

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

If you run this command this way, the REDIS_PORT variable won't be loaded from the dotenv. Is it part of the requirements?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

from what i checked it worked

REDIS_PASSWORD: z.string(),
REDIS_PORT: z
.string()
.default('6379')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to set it as the default?

Suggested change
.default('6379')
.default('7381')

@liorzam
Copy link
Collaborator

liorzam commented Sep 29, 2024

Can i share with you some bull services that might help you to work with their queues?

@@ -35,3 +35,6 @@ WEB_UI_SDK_URL=http://localhost:5202
#HASHING_KEY_SECRET="$2b$10$FovZTB91/QQ4Yu28nvL8e."
HASHING_KEY_SECRET_BASE64=JDJiJDEwJDNFeWtwWEs4QkdiczlRaWFwLkM4Vk8=
NOTION_API_KEY=secret
REDIS_HOST=local
Copy link
Collaborator

Choose a reason for hiding this comment

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

localhost

@@ -0,0 +1,26 @@
version: '3.8'
services:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is it located on a new docker image? Our service has to use redis in order to run ...
you can place it with a dependency and when Redis is ready start workflow service

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why cant we use the same docker-compose-db.yml ?

we can use same like this

version: '3'
services:
  db:
    image: sibedge/postgres-plv8:15.3-3.1.7
    ports:
      - ${DB_PORT}:5432
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres15:/var/lib/postgresql/data
  redis:
    image: redis:alpine
    ports:
      - '${REDIS_PORT}:6379'
    volumes:
      - redis-data:/data
    command: >
      --requirepass ${REDIS_PASSWORD}
      --appendonly yes
    environment:
      - REDIS_PASSWORD=${REDIS_PASSWORD}
      - REDIS_PORT=${REDIS_PORT}
volumes:
  postgres15: ~
  redis-data:
    driver: local

Bring only redis using :

This Line
can be replaced with the following

docker:redis: docker compose -f docker-compose.db.yml up -d redis --wait
docker:redis:down: docker compose -f docker-compose.db.yml down -v redis,

@@ -78,6 +81,7 @@
"ballerine-nestjs-typebox": "3.0.2-next.11",
"base64-stream": "^1.0.0",
"bcrypt": "5.1.0",
"bullmq": "^5.13.2",
Copy link
Collaborator

Choose a reason for hiding this comment

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

you should also include redis client

@@ -126,6 +127,13 @@ export const validate = async (config: Record<string, unknown>) => {
RuleEngineModule,
NotionModule,
SecretsManagerModule,
BullModule.forRoot({
connection: {
Copy link
Collaborator

@liorzam liorzam Sep 29, 2024

Choose a reason for hiding this comment

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

its wrong way to init a module in NestJS also i would recommend also to use: config.get('redis')
in case we would like to include more options like ENFORCE SSL or any other configuration parameter which supported by redis client

@@ -78,6 +81,7 @@
"ballerine-nestjs-typebox": "3.0.2-next.11",
"base64-stream": "^1.0.0",
"bcrypt": "5.1.0",
"bullmq": "^5.13.2",
Copy link
Collaborator

Choose a reason for hiding this comment

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

add bull-arena which is the UI component for bull

Comment on lines 130 to 136
BullModule.forRoot({
connection: {
host: env.REDIS_HOST || 'localhost',
port: env.REDIS_PORT,
password: env.REDIS_PASSWORD,
},
}),
Copy link
Collaborator

Choose a reason for hiding this comment

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

    BullModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        redis: configService.get('redis'),
        settings: {
          maxStalledCount: 3,
          stalledInterval: 30_000,
          guardInterval: 5000,
        },
      }),
      inject: [ConfigService],
    }),
    ```

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

🧹 Outside diff range and nitpick comments (10)
services/workflows-service/src/bull-mq/webhook/webhook.service.ts (2)

1-4: Consider moving WebhookJobData interface to a separate file

The comment on line 4 suggests moving the WebhookJobData interface to a separate file. This is a good practice for better code organization and reusability.

Consider creating a new file (e.g., webhook.types.ts) and moving the WebhookJobData interface there. Then update the import statement accordingly.


1-13: Overall implementation looks good with minor suggestions

The WebhookService implementation using NestJS and BullMQ looks correct and follows best practices. Here's a summary of the review:

  1. Consider moving the WebhookJobData interface to a separate file for better code organization.
  2. Verify that the queue name 'webhook-queue' is consistently used in your BullMQ configuration.
  3. Ensure that the job name 'webhook' matches the name expected by your webhook processor.

Additionally, consider the following improvements:

  • Add error handling in the addWebhookJob method to catch and log any errors that might occur when adding a job to the queue.
  • Consider adding options to the Queue.add() method call, such as priority or delay, if needed for your use case.

Here's a suggested improvement for error handling:

async addWebhookJob(data: WebhookJobData) {
  try {
    await this.webhookQueue.add('webhook', data);
  } catch (error) {
    // Log the error or handle it as appropriate for your application
    console.error('Failed to add webhook job:', error);
    throw error; // Re-throw if you want calling code to handle it
  }
}
services/workflows-service/src/bull-mq/bull-mq.module.ts (5)

1-18: LGTM! Consider using an enum for queue names.

The imports are comprehensive and relevant to the module's functionality. The QUEUE_NAMES constant is a good approach for defining queues.

Consider using an enum for queue names to improve type safety and maintainability:

enum QueueName {
  WEBHOOK = 'webhook-queue',
}

const QUEUE_NAMES = [
  {
    name: QueueName.WEBHOOK,
    config: {},
  },
];

24-33: LGTM! Consider adding error handling for Redis connection.

The BullModule configuration is well-structured and follows best practices by using environment variables for connection details.

Consider adding error handling for the Redis connection:

BullModule.forRootAsync({
  useFactory: () => {
    if (!env.REDIS_PASSWORD || !env.REDIS_HOST || !env.REDIS_PORT) {
      throw new Error('Redis configuration is incomplete');
    }
    return {
      connection: {
        password: env.REDIS_PASSWORD,
        host: env.REDIS_HOST,
        port: env.REDIS_PORT,
      },
    };
  },
}),

34-43: LGTM! Consider adding authentication to the Bull Board route.

The BullBoardModule configuration is well-structured and provides a good way to monitor queues.

Consider adding authentication to the Bull Board route to prevent unauthorized access to queue information:

BullBoardModule.forRoot({
  route: '/queues',
  adapter: ExpressAdapter,
  middleware: [authMiddleware], // Add your authentication middleware here
}),

45-48: LGTM! Consider using a constant for exported services.

The module providers and exports are well-defined and appropriate for the module's functionality.

For consistency and maintainability, consider using a constant for exported services:

const EXPORTED_SERVICES = [BullModule, WebhookService] as const;

@Module({
  // ... other configurations
  providers: [WebhookProcessor, WebhookService, AppLoggerService],
  exports: EXPORTED_SERVICES,
})
export class BullMqModule {}

1-48: Great implementation of BullMqModule! Consider the suggested improvements.

The BullMqModule is well-structured and follows NestJS best practices. It successfully integrates BullMQ for job and queue management, sets up Bull Board for monitoring, and provides necessary services. The use of environment variables for configuration enhances security and flexibility.

To further improve the module:

  1. Consider using an enum for queue names.
  2. Add error handling for Redis connection configuration.
  3. Implement authentication for the Bull Board route.
  4. Use a constant for exported services for consistency.

These changes will enhance type safety, error handling, security, and maintainability of the module.

As this module is crucial for job processing and queue management, ensure that it's properly integrated with other parts of the application. Consider creating a separate configuration file for BullMQ-related settings to keep the module file cleaner and more focused on its primary responsibilities.

services/workflows-service/src/bull-mq/webhook/webhook.processor.ts (2)

21-50: LGTM with a minor suggestion: Process method is well-implemented.

The process method effectively handles webhook job processing, including proper extraction of job data, setting a default timeout, and returning relevant response data on success. The error handling is good, distinguishing between Axios errors and other errors.

Consider adding more context to the error message for non-Axios errors:

- throw error;
+ throw new Error(`Unexpected error processing webhook: ${error.message}`);

This will provide more informative error messages for debugging purposes.


62-67: LGTM with a minor suggestion: Module initialization is well-implemented.

The onModuleInit method effectively sets up a listener for the 'cleaned' event on the queue, providing useful logging information about queue maintenance operations.

Consider adding error handling to the event listener:

 async onModuleInit() {
-  this.webhookQueue.on('cleaned', (jobs, type) => {
+  this.webhookQueue.on('cleaned', (jobs, type) => {
     this.logger.log(`${jobs.length} ${type} jobs have been cleaned from the webhook queue`);
   });
+  this.webhookQueue.on('error', (error) => {
+    this.logger.error(`Error in webhook queue: ${error.message}`);
+  });
 }

This will ensure that any errors in the queue are logged, improving the overall robustness of the system.

services/workflows-service/package.json (1)

8-8: LGTM with a minor suggestion.

The addition of Redis setup commands in the "setup" script is consistent with the PR objectives. However, consider adding a wait command for Redis similar to the database setup to ensure Redis is fully operational before proceeding.

You could add a wait command for Redis like this:

-    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis:down && npm run docker:redis && npm run db:reset && npm run seed",
+    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis:down && npm run docker:redis && wait-on tcp:6379 && npm run db:reset && npm run seed",

This ensures that Redis is fully operational before proceeding with the rest of the setup.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4632b91 and 44343e8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • services/workflows-service/.env.example (1 hunks)
  • services/workflows-service/package.json (4 hunks)
  • services/workflows-service/src/app.module.ts (3 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/webhook/webhook.processor.ts (1 hunks)
  • services/workflows-service/src/bull-mq/webhook/webhook.service.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • services/workflows-service/.env.example
  • services/workflows-service/src/app.module.ts
  • services/workflows-service/src/env.ts
🔇 Additional comments (10)
services/workflows-service/src/bull-mq/webhook/webhook.service.ts (3)

6-7: Class declaration looks good

The WebhookService class is correctly decorated with @Injectable(), following NestJS best practices for dependency injection.


10-12: Method implementation looks good, verify job name

The addWebhookJob method correctly adds a job to the queue using async/await syntax. The job name 'webhook' is used, which is good.

Please ensure that 'webhook' matches the job name expected by your webhook processor. Run the following script to verify:

#!/bin/bash
# Description: Verify the job name 'webhook' is consistently used across the codebase.

# Test: Search for 'webhook' job name in all TypeScript files
rg --type typescript -A 5 $'\.add\([\'"]webhook[\'"]'

8-8: Constructor looks good, verify queue name

The constructor correctly uses dependency injection for the BullMQ Queue. The queue name 'webhook-queue' is used, which is good.

Please ensure that 'webhook-queue' matches the queue name defined in your BullMQ configuration. Run the following script to verify:

✅ Verification successful

Please use the corrected script below to verify that 'webhook-queue' is consistently used across the codebase:


Queue name 'webhook-queue' is consistent across the codebase

The queue name 'webhook-queue' is correctly and consistently used in all relevant parts of the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the queue name 'webhook-queue' is consistently used across the codebase.

# Test: Search for 'webhook-queue' in all TypeScript files
rg --type typescript -A 5 $'webhook-queue'

Length of output: 93


Script:

#!/bin/bash
# Description: Verify the queue name 'webhook-queue' is consistently used across the codebase.

# Corrected Test: Search for 'webhook-queue' in all TypeScript files using the correct file type identifier.
rg --type ts -A 5 'webhook-queue'

Length of output: 1995

services/workflows-service/src/bull-mq/webhook/webhook.processor.ts (3)

1-12: LGTM: Imports and interface definition are well-structured.

The imports cover all necessary dependencies, and the WebhookJobData interface is well-defined with appropriate properties for configuring webhook jobs. The optional timeout property provides flexibility in job configuration.


14-19: LGTM: Class definition and constructor follow NestJS best practices.

The WebhookProcessor class is correctly decorated with @Processor('webhook-queue'), and the constructor uses dependency injection for the logger and queue. This approach aligns with NestJS best practices and promotes maintainability.


52-60: LGTM: Event handlers are well-implemented.

The onCompleted and onFailed event handlers are correctly decorated with @OnQueueEvent and provide appropriate logging for job outcomes. These handlers will be useful for monitoring and debugging the webhook processing system.

services/workflows-service/package.json (4)

33-34: LGTM! Addresses previous review comment.

The addition of "docker:redis" and "docker:redis:down" scripts is consistent with the PR objectives. The "docker:redis:down" script properly addresses the concern raised in the previous review comment by MatanYadaev.


87-87: LGTM! Completes BullMQ integration.

The addition of the "bullmq" dependency completes the set of required packages for BullMQ integration. The version specified (^5.13.2) allows for minor and patch updates, which is a good practice for keeping the package up-to-date while avoiding potentially breaking changes.


Line range hint 1-190: Overall assessment: LGTM with minor suggestions

The changes to package.json effectively integrate Redis and BullMQ into the project, aligning well with the PR objectives. The additions include:

  1. Updated setup script to manage Redis alongside the database.
  2. New scripts for Docker-based Redis management.
  3. New dependencies for BullMQ and Bull Board integration.

These changes provide a solid foundation for implementing job queues and monitoring in the workflow service. Consider the minor suggestions provided in the individual comments to further improve the setup process and dependency management.


55-57: LGTM! Addresses previous review comments partially.

The addition of BullMQ and Bull Board dependencies is consistent with the PR objectives. The inclusion of Bull Board packages suggests that a UI for managing Bull queues will be implemented, which is a good practice for monitoring and managing job queues.

Regarding the past review comments:

  1. The Redis client is not explicitly added, but it might be included as a sub-dependency of BullMQ. Let's verify this:

  2. For the suggestion to add bull-arena, it seems that @bull-board has been chosen instead. This is a valid alternative that provides similar functionality.

Also applies to: 60-60

@@ -35,3 +35,6 @@ WEB_UI_SDK_URL=http://localhost:5202
#HASHING_KEY_SECRET="$2b$10$FovZTB91/QQ4Yu28nvL8e."
HASHING_KEY_SECRET_BASE64=JDJiJDEwJDNFeWtwWEs4QkdiczlRaWFwLkM4Vk8=
NOTION_API_KEY=secret
REDIS_HOST=localhost
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we would want something like USE_REDIS - to change between using redis to im-mem implmenation

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

🧹 Outside diff range and nitpick comments (4)
services/workflows-service/src/bull-mq/webhook/types/types.ts (1)

3-9: LGTM: Well-structured WebhookJobData interface with minor suggestions.

The WebhookJobData interface is well-defined and covers the essential properties for a webhook job. Here are a few suggestions for potential improvements:

  1. Consider using a more specific type for the body property instead of Record<string, unknown>. This could help prevent potential runtime errors by ensuring the correct data structure at compile-time.

  2. It might be beneficial to add a comment or consider using a custom type for the timeout property to ensure it's always a positive number.

Here's an example of how you could implement these suggestions:

// You might want to define a more specific type for the body
type WebhookBody = {
  // Define your expected body structure here
};

export interface WebhookJobData {
  url: string;
  method: Method;
  headers?: Record<string, string>;
  body?: WebhookBody;
  // Ensure timeout is always positive
  timeout?: number & { __brand: 'PositiveNumber' };
}

// Helper function to create a positive number
function createPositiveNumber(value: number): number & { __brand: 'PositiveNumber' } {
  if (value <= 0) throw new Error('Timeout must be a positive number');
  return value as number & { __brand: 'PositiveNumber' };
}

This approach provides more type safety and helps prevent potential runtime errors.

services/workflows-service/src/bull-mq/consts.ts (2)

1-1: Consider using a more generic import path.

The current import statement is using a specific path to the ESM distribution file. While this works, it might be more robust to use a more generic import path.

Consider changing the import to:

-import { BaseJobOptions } from 'bullmq/dist/esm/interfaces';
+import { BaseJobOptions } from 'bullmq';

This change would make the import less dependent on the specific package structure and potentially more resilient to future updates of the bullmq package.


3-14: Approve the QUEUES constant structure with a suggestion for enhancement.

The QUEUES constant is well-structured and uses appropriate type validation. The configuration for INCOMING_WEBHOOKS_QUEUE seems suitable for handling incoming webhooks with a reasonable retry strategy.

To enhance flexibility, consider extracting the queue configuration into a separate constant. This would allow easier modification of queue settings and potential reuse. For example:

const INCOMING_WEBHOOKS_QUEUE_CONFIG = {
  name: 'webhook-queue',
  config: {
    attempts: 10,
    backoff: {
      type: 'exponential' as const,
      delay: 1000,
    },
  },
};

export const QUEUES = {
  INCOMING_WEBHOOKS_QUEUE: INCOMING_WEBHOOKS_QUEUE_CONFIG,
} satisfies Record<string, { name: string; config: BaseJobOptions }>;

This structure would make it easier to add more queues in the future and allow for easier testing and modification of individual queue configurations.

services/workflows-service/src/bull-mq/bull-mq.module.ts (1)

15-29: BullModule configuration looks good, but consider adding error handling.

The BullModule configuration correctly uses environment variables for Redis connection details, which is secure and flexible. Dynamically registering queues based on the QUEUES constant is an efficient approach.

Consider adding error handling for missing environment variables. Here's a suggested implementation:

BullModule.forRootAsync({
  useFactory: () => {
    if (!env.REDIS_PASSWORD || !env.REDIS_HOST || !env.REDIS_PORT) {
      throw new Error('Missing Redis configuration in environment variables');
    }
    return {
      connection: {
        password: env.REDIS_PASSWORD,
        host: env.REDIS_HOST,
        port: env.REDIS_PORT,
      },
    };
  },
}),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 44343e8 and 8e1ff70.

📒 Files selected for processing (7)
  • services/workflows-service/docker-compose.redis.yml (1 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/consts.ts (1 hunks)
  • services/workflows-service/src/bull-mq/webhook/types/types.ts (1 hunks)
  • services/workflows-service/src/bull-mq/webhook/webhook.processor.ts (1 hunks)
  • services/workflows-service/src/bull-mq/webhook/webhook.service.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • services/workflows-service/prisma/data-migrations
🚧 Files skipped from review as they are similar to previous changes (3)
  • services/workflows-service/docker-compose.redis.yml
  • services/workflows-service/src/bull-mq/webhook/webhook.processor.ts
  • services/workflows-service/src/bull-mq/webhook/webhook.service.ts
🔇 Additional comments (4)
services/workflows-service/src/bull-mq/webhook/types/types.ts (1)

1-1: LGTM: Appropriate import for webhook method types.

The import of Method from 'axios' is correct and necessary for defining the HTTP method types in the WebhookJobData interface.

services/workflows-service/src/bull-mq/consts.ts (1)

1-14: Overall, the implementation is solid with room for minor enhancements.

The QUEUES constant provides a clear and type-safe way to define queue configurations. The use of BullMQ for job processing aligns well with the PR objectives of integrating with Redis and improving message queuing. The exponential backoff strategy for job retries is a good choice for handling potential transient issues.

To further improve this implementation:

  1. Consider the suggestion to use a more generic import path for bullmq.
  2. Think about extracting the queue configuration into a separate constant for increased flexibility.

These changes would enhance the maintainability and scalability of the queue configuration system.

services/workflows-service/src/bull-mq/bull-mq.module.ts (2)

41-44: LGTM! Module providers and exports are well-defined.

The providers (WebhookProcessor and WebhookService) and exports (BullModule and WebhookService) are appropriately set up for the module's functionality. This configuration allows other modules to use the configured queues and webhook functionality.


1-10: LGTM! Imports look appropriate.

The imports cover all necessary modules and services for BullMQ integration. Using environment variables for configuration is a good practice.

Let's verify the contents of the QUEUES constant:

✅ Verification successful

Let's try a different approach to locate the QUEUES constant.


Let's retrieve the full definition of the QUEUES constant for further verification.


QUEUES Constant Verified Successfully

The QUEUES constant in consts.ts is correctly defined with the INCOMING_WEBHOOKS_QUEUE and appropriate configuration settings.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the contents of the QUEUES constant

# Test: Search for the QUEUES constant definition
rg --type typescript -A 10 'export const QUEUES ='

Length of output: 87


Script:

#!/bin/bash
# Description: Verify the contents of the QUEUES constant by searching all TypeScript files.

grep -r 'export const QUEUES =' --include='*.ts' .

Length of output: 127


Script:

#!/bin/bash
# Description: Get the full definition of the QUEUES constant.

grep -A 20 'export const QUEUES =' --include='*.ts' ./services/workflows-service/src/bull-mq/consts.ts

Length of output: 376

Comment on lines 30 to 39
BullBoardModule.forRoot({
route: '/queues',
adapter: ExpressAdapter,
}),
...Object.values(QUEUES).map(queue =>
BullBoardModule.forFeature({
name: queue.name,
adapter: BullAdapter,
}),
),
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

'/queues' route is not secured.

The '/queues' route in BullBoardModule is currently exposed without authentication or authorization measures. It's recommended to secure this route to prevent unauthorized access to the queue monitoring interface.

  • Implement authentication middleware for the '/queues' route.
  • Review and enhance global security configurations to ensure they protect all sensitive routes.
🔗 Analysis chain

BullBoardModule configuration is correct, but consider security implications.

The BullBoardModule is properly configured for monitoring queues, using the correct adapters and dynamically adding all queues from the QUEUES constant.

However, the '/queues' route might need to be secured in a production environment. Let's verify if there are any security measures in place:

If no security measures are found, consider adding authentication middleware to protect the '/queues' route.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for security measures on the '/queues' route

# Test: Search for middleware or guards applied to the '/queues' route
rg --type typescript -A 5 "route: '/queues'"

# Test: Search for any global guards or middleware in the main application file
rg --type typescript -A 10 'const app = await NestFactory.create'

Length of output: 190


Script:

#!/bin/bash
# Description: Check for security measures on the '/queues' route

# Test: Search for middleware or guards applied to the '/queues' route
rg "route: '/queues'" -A 5 -g "*.ts" -g "*.tsx"

# Test: Search for any global guards or middleware in the main application file
rg "const app = await NestFactory.create" -A 10 -g "*.ts" -g "*.tsx"

Length of output: 2919

@Blokh Blokh changed the title feature/bull_mq_integration feature/bull_mq_integration ( WIP ) Oct 1, 2024
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: 14

🧹 Outside diff range and nitpick comments (13)
services/workflows-service/src/bull-mq/outgoing-webhook/types/types.ts (2)

3-3: LGTM: Well-defined type alias with a suggestion for improvement.

The WebhookJobData type alias is well-defined, leveraging TypeScript's utility types to extract the parameter type from the invokeWebhook method. This approach ensures type consistency and reduces duplication, which is a good practice.

Consider adding a brief JSDoc comment to explain the purpose and usage of this type alias. For example:

/** Represents the data structure for a webhook job, matching the parameter of OutgoingWebhooksService.invokeWebhook */
export type WebhookJobData = Parameters<OutgoingWebhooksService['invokeWebhook']>[0];

1-3: Overall, excellent implementation with clear purpose and good practices.

This file demonstrates a focused and efficient approach to type definition. It leverages TypeScript's advanced type system to create a reusable type that ensures consistency with the OutgoingWebhooksService. The use of utility types and derived types is a best practice that reduces the chance of type inconsistencies as the codebase evolves.

As the project grows, consider creating a central types file or directory for shared types like this, especially if WebhookJobData is used across multiple modules. This can help maintain a single source of truth for common types and improve overall code organization.

services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.module.ts (2)

5-9: LGTM: Module declaration is well-structured. Consider adding a brief comment.

The module declaration follows NestJS best practices, correctly importing, providing, and exporting the necessary components.

Consider adding a brief comment above the @module decorator to describe the purpose of this module:

/**
 * Module for handling outgoing webhooks.
 * Provides and exports OutgoingWebhooksService for managing webhook operations.
 */
@Module({
  // ... (existing code)
})

1-10: Overall assessment: Well-implemented NestJS module.

This new OutgoingWebhooksModule is cleanly implemented, following NestJS best practices and conventions. It correctly imports dependencies, provides and exports the OutgoingWebhooksService, and maintains a clear structure. The module is well-positioned to handle outgoing webhook functionality within the application.

As the project grows, consider the following architectural points:

  1. Ensure that the OutgoingWebhooksService remains focused on outgoing webhook logic to maintain the Single Responsibility Principle.
  2. If the webhook functionality becomes more complex, consider creating separate modules for different types of webhooks or webhook-related operations.
  3. Monitor the usage of AppLoggerModule and ensure it provides adequate logging for webhook operations, especially for debugging and monitoring purposes in a production environment.
services/workflows-service/src/business-report/business-report.module.ts (1)

Line range hint 1-35: Consider addressing circular dependencies

The file contains multiple imports with eslint-disable-next-line import/no-cycle comments, including the newly added AlertModule. These indicate circular dependencies between modules, which can lead to initialization issues and make the codebase harder to maintain.

Consider refactoring the module structure to eliminate these circular dependencies. Some strategies include:

  1. Creating a shared module for common functionality.
  2. Using interfaces instead of concrete implementations where possible.
  3. Employing the mediator pattern to decouple modules.

Would you like assistance in developing a plan to address these circular dependencies?

services/workflows-service/src/webhooks/incoming/incoming-webhooks.service.ts (1)

Line range hint 22-115: Consider refactoring handleIndividualAmlHit for improved maintainability

While not directly related to the class name change, the handleIndividualAmlHit method is quite complex and handles multiple responsibilities. Consider the following improvements:

  1. Break down the method into smaller, more focused methods to improve readability and maintainability.
  2. Enhance error handling, particularly for database operations.
  3. Address the type mismatch indicated by the @ts-expect-error comment to improve type safety.

Here's a suggested refactoring approach:

async handleIndividualAmlHit({ endUserId, data }: { endUserId: string; data: IndividualAmlWebhookInput['data'] }) {
  this.logger.log('Started handling individual AML hit', { endUserId });

  const endUser = await this.getEndUser(endUserId);
  const config = await this.getCustomerConfig(endUser.projectId);
  const workflowDefinitionId = await this.getWorkflowDefinitionId(config, endUser.projectId);

  const amlHits = this.processAmlHits(data);
  await this.updateEndUserAmlHits(endUserId, amlHits);

  if (amlHits.length === 0) {
    this.logger.log('No AML hits found', { endUserId });
    return;
  }

  const workflow = await this.createOrUpdateWorkflow(workflowDefinitionId, endUser, data);
  this.logger.log(`Created workflow for AML hits`, { workflow });
}

// Implement the following methods:
// - getEndUser(endUserId: string): Promise<EndUser>
// - getCustomerConfig(projectId: string): Promise<CustomerConfig>
// - getWorkflowDefinitionId(config: CustomerConfig, projectId: string): Promise<string>
// - processAmlHits(data: IndividualAmlWebhookInput['data']): AmlHit[]
// - updateEndUserAmlHits(endUserId: string, amlHits: AmlHit[]): Promise<void>
// - createOrUpdateWorkflow(workflowDefinitionId: string, endUser: EndUser, data: IndividualAmlWebhookInput['data']): Promise<Workflow>

This refactoring improves readability, makes the code more modular, and allows for better error handling in each sub-method.

services/workflows-service/src/env.ts (1)

93-99: Overall assessment of Redis and queue system integration

The additions of Redis-related environment variables (REDIS_HOST, REDIS_PASSWORD, REDIS_PORT) and the QUEUE_SYSTEM_ENABLED flag are good steps towards integrating Redis and potentially BullMQ into the workflow service. These changes provide the necessary configuration options for connecting to a Redis instance and controlling the queue system's activation.

However, there are opportunities to enhance these additions:

  1. Add descriptions to all new variables for better maintainability.
  2. Implement stricter validation, especially for REDIS_PORT.
  3. Make REDIS_PASSWORD optional to support various Redis configurations.
  4. Use more explicit typing for QUEUE_SYSTEM_ENABLED.

These improvements will make the configuration more robust, self-documenting, and flexible for different deployment scenarios.

Consider the following architectural advice:

  1. Ensure that the Redis connection is properly handled in the application code, including connection pooling and error handling.
  2. Implement a fallback mechanism or graceful degradation if Redis or the queue system is unavailable.
  3. Plan for potential future scalability by considering Redis cluster configurations.
  4. Document the purpose and usage of the queue system in the project documentation to guide other developers on when and how to use it.
services/workflows-service/src/app.module.ts (1)

50-52: LGTM! Consider updating documentation for the new modules.

The addition of BullMqModule, IncomingWebhooksModule, and OutgoingWebhooksModule improves the modularity of the application, especially for webhook handling. This aligns well with the PR objectives for enhancing Redis integration and message queuing.

Consider updating the project documentation to reflect these new modules and their purposes, particularly the split between incoming and outgoing webhooks.

services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1)

7-7: Consider specifying a more precise generic type parameter instead of any.

Using any as the default for the generic type T reduces type safety. Consider requiring subclasses to provide a specific type to enhance type checking and prevent potential runtime errors.

services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts (1)

15-20: Implement the job processing logic in handleJob

The handleJob method contains a TODO comment indicating that the implementation for processing incoming webhook jobs is pending. Completing this method is essential to ensure that incoming webhooks are handled correctly.

Would you like assistance in implementing the logic to process the incoming webhook data? I can help generate code to handle the webhook job internally or open a GitHub issue to track this task.

services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (2)

48-48: Unused destructured variables environment and apiVersion

The variables environment and apiVersion are destructured from webhook but are not used in the sendWebhook method. Consider removing them to avoid unused variables and potential confusion.


Line range hint 85-89: Potential exposure of sensitive data in error logs

Logging data and webhook in the error handling may expose sensitive information. Consider sanitizing or omitting sensitive details from the logs to prevent possible PII leakage.

🧰 Tools
🪛 Biome

[error] 84-84: Catch clause variable type annotation must be 'any' or 'unknown' if specified.

(parse)

services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (1)

Line range hint 6-6: Fix typographical error in import path

There's a typo in the import path for WorkflowDefinitionModule:

import { WorkflowDefinitionModule } from '@/workflow-defintion/workflow-definition.module';

The directory '@/workflow-defintion' should be '@/workflow-definition'.

Apply this diff to correct the typo:

-import { WorkflowDefinitionModule } from '@/workflow-defintion/workflow-definition.module';
+import { WorkflowDefinitionModule } from '@/workflow-definition/workflow-definition.module';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 146d628 and 5b83c6e.

📒 Files selected for processing (17)
  • services/workflows-service/src/alert/alert.module.ts (3 hunks)
  • services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (2 hunks)
  • services/workflows-service/src/app.module.ts (3 hunks)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/types/types.ts (1 hunks)
  • services/workflows-service/src/business-report/business-report.module.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
  • services/workflows-service/src/redis/const/redis-config.ts (1 hunks)
  • services/workflows-service/src/redis/redis.module.ts (1 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.controller.ts (2 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (3 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.service.ts (1 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.module.ts (1 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • services/workflows-service/src/redis/redis.module.ts
🧰 Additional context used
🔇 Additional comments (31)
services/workflows-service/src/bull-mq/outgoing-webhook/types/types.ts (1)

1-1: LGTM: Import statement is correct and follows good practices.

The import statement correctly imports the OutgoingWebhooksService using a path alias, which is a good practice for maintaining clean and readable import paths.

services/workflows-service/src/redis/const/redis-config.ts (1)

1-7: Crucial configuration for Redis integration

This configuration file is a key component in achieving the PR objective of introducing Redis defaults and enhancing the integration with Redis. It provides a flexible way to configure the Redis connection while allowing for environment-specific settings.

Ensure that this configuration is properly utilized in the BullMQ integration mentioned in the PR summary. Also, consider documenting the required environment variables in the project's README or configuration guide to assist other developers in setting up the Redis connection correctly.

services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.module.ts (2)

1-3: LGTM: Imports are well-structured and follow NestJS conventions.

The imports are correctly organized, using absolute paths where appropriate. This approach enhances code readability and maintainability.


10-10: LGTM: Module export is correct.

The OutgoingWebhooksModule is properly exported, allowing it to be imported and used in other parts of the application.

services/workflows-service/src/business-report/business-report.module.ts (1)

12-12: AlertModule import added

The AlertModule has been added to the imports of the BusinessReportModule. This change appears to be intentional and aligns with the PR objectives of enhancing the project's functionality.

To ensure this addition is used correctly, let's verify its usage:

services/workflows-service/src/webhooks/incoming/incoming-webhooks.controller.ts (5)

2-2: LGTM: Import statement reorganization.

The separate import for BadRequestException improves code readability without affecting functionality.


Line range hint 1-58: Overall assessment: Refactoring improves code organization.

The changes in this file primarily involve renaming and restructuring to better separate concerns between incoming and outgoing webhooks. The main functionality of the controller remains intact, which is a positive aspect of this refactoring.

Key points:

  1. The controller and service have been renamed to specifically handle incoming webhooks.
  2. Import statements have been updated to reflect the new structure.
  3. The core logic in the amlHook method remains unchanged.

These changes improve code organization and maintainability. However, it's crucial to ensure that these refactoring changes are consistently applied across the entire codebase to prevent any potential issues.


6-6: Verify the new import path for AmlWebhookInput.

The import path change from './dtos/aml-webhook-input' to '../dtos/aml-webhook-input' suggests a project structure reorganization. While this change looks good, it's crucial to ensure that the file exists at the new location to prevent potential runtime errors.

✅ Verification successful

Import path for AmlWebhookInput is correct.

The new import path '../dtos/aml-webhook-input' has been verified, and the file exists at this location.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the existence of the AmlWebhookInput file
fd -p "src/webhooks/dtos/aml-webhook-input.ts"

Length of output: 112


22-24: Verify consistency of IncomingWebhooksController usage across the codebase.

The class renaming from WebhooksController to IncomingWebhooksController and the updated service injection in the constructor align well with the refactoring to separate incoming and outgoing webhooks. These changes improve code clarity by better reflecting the controller's specific responsibility.

However, it's crucial to ensure that these changes are consistently applied throughout the codebase to prevent any potential runtime errors or inconsistencies.

✅ Verification successful

Refactoring Verified Successfully

All references to WebhooksController and WebhooksService have been updated to IncomingWebhooksController and IncomingWebhooksService respectively. No remaining references to the old names were found, ensuring consistency across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the usage of IncomingWebhooksController across the codebase
rg --type typescript "IncomingWebhooksController"

# Verify there are no remaining references to the old WebhooksController
rg --type typescript "WebhooksController"

# Check for any remaining references to WebhooksService
rg --type typescript "WebhooksService"

Length of output: 235


Script:

#!/bin/bash
# Verify the usage of IncomingWebhooksController across the codebase
rg --type ts "IncomingWebhooksController"

# Verify there are no remaining references to the old WebhooksController
rg --type ts "WebhooksController"

# Check for any remaining references to WebhooksService
rg --type ts "WebhooksService"

Length of output: 3307


8-8: Verify the implementation of IncomingWebhooksService.

The import statement has been updated to use IncomingWebhooksService instead of WebhooksService, which aligns with the service renaming. This change looks good, but it's important to ensure that the IncomingWebhooksService is correctly implemented and contains all the necessary methods used in this controller.

✅ Verification successful

Please run the following scripts to verify the IncomingWebhooksService implementation:


IncomingWebhooksService Implementation Verified

The IncomingWebhooksService is correctly implemented and contains the handleIndividualAmlHit method as required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the existence and implementation of IncomingWebhooksService
fd -p "src/webhooks/incoming/incoming-webhooks.service.ts"
ast-grep --lang typescript --pattern $'class IncomingWebhooksService {
  $$$
  handleIndividualAmlHit($$$) {
    $$$
  }
  $$$
}'

Length of output: 235


Script:

#!/bin/bash
# Verify the existence of IncomingWebhooksService
fd 'incoming-webhooks.service.ts' src/webhooks/incoming/

# Verify the class and handleIndividualAmlHit method in IncomingWebhooksService
ast-grep --lang typescript --pattern $'class IncomingWebhooksService {
  $$$
  handleIndividualAmlHit($$$) {
    $$$
  }
  $$$
}'

Length of output: 11726

services/workflows-service/src/webhooks/incoming/incoming-webhooks.service.ts (2)

Line range hint 1-115: Summary of review

  1. The class name change from WebhooksService to IncomingWebhooksService is approved and improves clarity.
  2. A verification script has been provided to check for any references to the old class name in the codebase.
  3. While not directly related to the changes, suggestions for refactoring the handleIndividualAmlHit method have been provided to improve code maintainability and readability.

Overall, the changes look good, but please ensure to verify the impact on the rest of the codebase and consider the refactoring suggestions for future improvements.


11-11: Approve class name change and verify dependencies

The change from WebhooksService to IncomingWebhooksService improves clarity and aligns well with the file name and the service's purpose. This change enhances code readability and maintainability.

To ensure this change doesn't break existing code, please run the following script to check for any references to the old class name:

If any results are found, please update those references to use IncomingWebhooksService.

✅ Verification successful

Verified class name change and dependencies

No references to the old class name WebhooksService were found outside of OutgoingWebhooksService. The change to IncomingWebhooksService does not affect other parts of the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for references to the old class name 'WebhooksService'

# Test: Search for 'WebhooksService' in TypeScript and JavaScript files
rg --type-add 'script:*.{ts,js}' --type script 'WebhooksService'

# Test: Search for 'webhooks.service' in TypeScript and JavaScript files (common import pattern)
rg --type-add 'script:*.{ts,js}' --type script 'webhooks\.service'

Length of output: 3555

services/workflows-service/src/alert/alert.module.ts (2)

27-28: LGTM: New module imports added correctly.

The addition of BullMqModule and OutgoingWebhooksModule imports aligns with the PR objectives of enhancing Redis integration and improving webhook functionality. These modules will likely provide necessary functionality for job queue management and outgoing webhook handling.


37-38: LGTM: New modules correctly added to imports array.

The addition of BullMqModule and OutgoingWebhooksModule to the imports array is consistent with the new import statements and allows the AlertModule to utilize their functionality.

services/workflows-service/src/env.ts (1)

95-95: 🛠️ Refactor suggestion

Enhance REDIS_PORT with validation, default value, and description

The current implementation of REDIS_PORT can be improved. Consider the following enhancements:

  1. Add validation to ensure the port is within a valid range (1024-65535).
  2. Provide a default value (typically 6379 for Redis).
  3. Add a description for better maintainability.

Here's a suggested implementation:

REDIS_PORT: z
  .string()
  .default('6379')
  .transform(value => {
    const port = Number(value);
    if (isNaN(port) || port < 1024 || port > 65535) {
      throw new Error('REDIS_PORT must be a number between 1024 and 65535');
    }
    return port;
  })
  .describe('Redis server port'),

This implementation adds a default value, includes port number validation, and provides a description.

To verify if the default Redis port (6379) is already in use, you can run the following script:

services/workflows-service/src/app.module.ts (2)

90-91: LGTM! Modular approach to webhook handling.

The addition of IncomingWebhooksModule and OutgoingWebhooksModule to the imports array is consistent with the new imports and enhances the modularity of webhook handling in the application.


132-132: LGTM! Verify BullMQ configuration.

The addition of BullMqModule to the imports array is consistent with the new import and aligns with the PR objectives for improving Redis integration and message queuing.

Please ensure that the BullMQ configuration is properly set up, preferably using config.get('redis') for Redis configuration as suggested in previous comments. You can verify this by checking the BullMqModule implementation:

If the configuration is not set up as expected, consider updating it to use the ConfigService for better maintainability and consistency with other parts of the application.

services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (2)

1-5: Imports are correctly defined.

All necessary modules are imported, and the paths appear to be correct. This sets up the required dependencies for the class.


40-42: Graceful shutdown is properly implemented in onModuleDestroy.

The onModuleDestroy method correctly closes both the worker and the queue, ensuring that resources are cleaned up when the module is destroyed. This helps prevent memory leaks and other issues related to lingering connections.

services/workflows-service/src/bull-mq/bull-mq.module.ts (1)

12-45: Well-structured BullMqModule Implementation

The module is correctly set up to integrate BullMQ and Bull Board with NestJS. It dynamically registers all queues from the QUEUES constant, properly configures the Redis connection using REDIS_CONFIG, and integrates Bull Board for queue monitoring.

services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (4)

9-12: Imports are appropriate

The newly added imports (AnyRecord, env, OutgoingWebhookQueueService, OutgoingWebhooksService) are necessary for the updated functionality and are correctly included.


23-24: Updated constructor dependencies

The constructor now injects OutgoingWebhookQueueService and OutgoingWebhooksService, replacing previous dependencies. This change aligns with the new architecture using queue services for webhook handling.


50-63: Queue system integration logic

The condition if (env.QUEUE_SYSTEM_ENABLED) correctly checks whether the queue system is enabled. The implementation using outgoingQueueWebhookService.addJob adds the webhook to the queue as expected.


65-83: Direct webhook invocation fallback

When the queue system is not enabled, the code properly falls back to invoking the webhook directly using outgoingWebhookService.invokeWebhook. The response handling and error throwing are appropriately managed.

services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (7)

29-29: Import IncomingWebhooksService correctly

The import statement properly references IncomingWebhooksService from the new path.


38-38: Import IncomingWebhooksController correctly

The import statement correctly brings in IncomingWebhooksController from the updated location.


41-41: Update controllers to use IncomingWebhooksController

The controllers array now appropriately includes IncomingWebhooksController, reflecting the focus on incoming webhooks.


79-79: Add IncomingWebhooksService to providers

Including IncomingWebhooksService in the providers array ensures that it can be injected where needed.


82-82: Export IncomingWebhooksService for external use

Exporting IncomingWebhooksService allows other modules to access and utilize its functionality.


84-84: Rename module to IncomingWebhooksModule

Renaming the module to IncomingWebhooksModule accurately reflects its specialized purpose.


84-84: Verify all references to WebhooksModule are updated

Since the module has been renamed, please ensure that all imports and references to WebhooksModule throughout the codebase are updated to IncomingWebhooksModule.

Run the following script to confirm:

✅ Verification successful

All references to WebhooksModule have been successfully updated to IncomingWebhooksModule.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all references to 'WebhooksModule' have been updated.

# Test: Search for 'WebhooksModule' in the codebase, excluding the current file. Expect: No occurrences.

rg 'WebhooksModule' --type=typescript --glob '!services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts'

Length of output: 159


Script:

#!/bin/bash
# Description: Verify that all references to 'WebhooksModule' have been updated.

# Test: Search for 'WebhooksModule' in the codebase, excluding the current file. Expect: No occurrences.

rg 'WebhooksModule' --type=ts --glob '!services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts'

Length of output: 1209

Comment on lines 1 to 7
import { env } from '@/env';

export const REDIS_CONFIG = {
password: env.REDIS_PASSWORD || 'password',
host: env.REDIS_HOST || 'localhost',
port: env.REDIS_PORT || 7381,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Enhance Security and Configuration Robustness

The Redis configuration is well-structured, but there are several areas that require attention:

  1. Missing Environment Variables in .env.example Files:

    • The following .env.example files are missing REDIS_PASSWORD, REDIS_HOST, and REDIS_PORT:
      • apps/kyb-app/.env.example
      • apps/workflows-dashboard/.env.example
      • services/websocket-service/.env.example
  2. Insecure Fallback Password:

    • The fallback password 'password' is not secure for production use.
  3. Non-Standard Redis Port:

    • The default port 7381 is non-standard for Redis (typically 6379).
  4. Lack of Type Checking for Port Number:

    • There's no type checking to ensure the port number is a valid integer.

Consider the following improvements:

  1. Remove the Fallback Password and Make It a Required Environment Variable:

    - password: env.REDIS_PASSWORD || 'password',
    + password: env.REDIS_PASSWORD,
  2. Use the Standard Redis Port as the Fallback:

    - port: env.REDIS_PORT || 7381,
    + port: Number(env.REDIS_PORT) || 6379,
  3. Add Type Checking for the Port Number:

    - port: env.REDIS_PORT || 6379,
    + port: Number(env.REDIS_PORT) || 6379,
  4. Ensure All .env.example Files Include Required Redis Environment Variables:

    • Add the following lines to each .env.example file:
      REDIS_PASSWORD=
      REDIS_HOST=localhost
      REDIS_PORT=6379
🔗 Analysis chain

Enhance security and configuration robustness

The Redis configuration is well-structured, but there are a few areas for improvement:

  1. The fallback password 'password' is not secure for production use.
  2. The default port 7381 is non-standard for Redis (usually 6379).
  3. There's no type checking for the port number.

Consider the following improvements:

  1. Remove the fallback password and make it a required environment variable:
- password: env.REDIS_PASSWORD || 'password',
+ password: env.REDIS_PASSWORD,
  1. Use the standard Redis port as the fallback:
- port: env.REDIS_PORT || 7381,
+ port: env.REDIS_PORT || 6379,
  1. Add type checking for the port:
- port: env.REDIS_PORT || 6379,
+ port: Number(env.REDIS_PORT) || 6379,

To ensure these environment variables are properly documented, let's check the .env.example file:

Additionally, to verify the uniqueness of the Redis port across the project:


Further Verification Required

The previous verification steps encountered issues. To ensure accurate analysis, please execute the following scripts:

[/run_scripts]


Further Verification Required

To ensure all .env.example files include REDIS_PASSWORD, REDIS_HOST, and REDIS_PORT, please execute the following scripts:

[/run_scripts]

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify Redis environment variables in .env.example
# Expected: REDIS_PASSWORD, REDIS_HOST, and REDIS_PORT should be present

grep -E "REDIS_(PASSWORD|HOST|PORT)" .env.example

Length of output: 97


Script:

#!/bin/bash
# Description: Check for other occurrences of the non-standard Redis port
# Expected: No other occurrences of port 7381 for Redis

rg --type=yaml --type=typescript --type=javascript "7381"

Length of output: 92


Script:

#!/bin/bash
# Description: Locate all .env files in the repository
# Expected: List of .env files present

fd --type f "^\.env" || find . -name ".env*"

Length of output: 453


Script:

#!/bin/bash
# Description: Search for occurrences of port 7381 in YAML, JavaScript, and TypeScript files
# Expected: List of files containing '7381'

rg "7381" --glob "*.yaml" --glob "*.yml" --glob "*.js" --glob "*.ts"

Length of output: 572


Script:

#!/bin/bash
# Description: Verify presence of REDIS_PASSWORD, REDIS_HOST, and REDIS_PORT in all .env.example files
# Expected: List of files missing any of the required Redis environment variables

REQUIRED_VARS=("REDIS_PASSWORD" "REDIS_HOST" "REDIS_PORT")
MISSING_VARS=()

for file in $(fd --type f ".env.example" || find . -name ".env.example"); do
  for var in "${REQUIRED_VARS[@]}"; do
    if ! grep -q "^${var}=" "$file"; then
      MISSING_VARS+=("${file}: Missing ${var}")
    fi
  done
done

if [ ${#MISSING_VARS[@]} -eq 0 ]; then
  echo "All .env.example files contain the required Redis environment variables."
else
  printf "%s\n" "${MISSING_VARS[@]}"
fi

Length of output: 3030


Script:

#!/bin/bash
# Description: Confirm that the Redis port is standardized across the codebase
# Expected: Ensure that the port is set to 6379 instead of 7381 where applicable

rg "7381" --glob "*.ts" --glob "*.js" --glob "*.yaml" --glob "*.yml" | grep -v "redis-config.ts"

Length of output: 507

provide: WebhookHttpService,
useExisting: HttpService,
},
WebhookHttpService,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Review module inclusion in providers array.

The simplification of WebhookHttpService provider is good. However, including BullMqModule and OutgoingWebhooksModule in both the imports and providers arrays is unusual and potentially incorrect.

Modules are typically only included in the imports array, not in providers. The providers array is meant for services, not modules.

Consider removing BullMqModule and OutgoingWebhooksModule from the providers array:

 providers: [
   WebhookHttpService,
   AlertService,
   AlertRepository,
   AlertDefinitionRepository,
   WebhookManagerService,
   WebhookEventEmitterService,
-  BullMqModule,
-  OutgoingWebhooksModule,
   // TODO: Export to user module
   UserService,
   UserRepository,
   PasswordService,
 ],

Also applies to: 61-62

Comment on lines 17 to 19
if (env.QUEUE_SYSTEM_ENABLED !== true) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure proper type comparison for environment variable QUEUE_SYSTEM_ENABLED.

Environment variables are typically strings. Comparing env.QUEUE_SYSTEM_ENABLED directly to true (a boolean) may not yield the expected result. Consider parsing the environment variable to a boolean before the comparison.

Apply this diff to fix the comparison:

     if (env.QUEUE_SYSTEM_ENABLED !== true) {
-      return;
+      return;
     }

Or parse the environment variable:

     protected constructor(private queueName: string) {
+      const isQueueSystemEnabled = env.QUEUE_SYSTEM_ENABLED === 'true';
       this.connectionOptions = {
         ...REDIS_CONFIG,
       };

-      if (env.QUEUE_SYSTEM_ENABLED !== true) {
+      if (!isQueueSystemEnabled) {
         return;
       }

Committable suggestion was skipped due to low confidence.

Comment on lines 30 to 38
protected initializeWorker() {
this.worker = new Worker(
this.queueName,
async (job: Job<T>) => {
await this.handleJob(job);
},
{ connection: this.connectionOptions },
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add a check for the queue system being enabled in initializeWorker method.

If the queue system is disabled, initializing the worker is unnecessary and may cause unintended behavior. Add a condition to ensure the worker is only initialized when the queue system is enabled.

Apply this diff to add the check:

   protected initializeWorker() {
+    if (!this.queue) {
+      return;
+    }
     this.worker = new Worker(
       this.queueName,
       async (job: Job<T>) => {
         await this.handleJob(job);
       },
       { connection: this.connectionOptions },
     );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected initializeWorker() {
this.worker = new Worker(
this.queueName,
async (job: Job<T>) => {
await this.handleJob(job);
},
{ connection: this.connectionOptions },
);
}
protected initializeWorker() {
if (!this.queue) {
return;
}
this.worker = new Worker(
this.queueName,
async (job: Job<T>) => {
await this.handleJob(job);
},
{ connection: this.connectionOptions },
);
}

Comment on lines 25 to 27
this.worker?.on('completed', (job: Job) => {
this.logger.log(`Outgoing Webhook job ${job.id} completed successfully`);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect log messages: Replace 'Outgoing' with 'Incoming'

The logging statements in the initializeWorker method incorrectly refer to "Outgoing Webhook" jobs, whereas this service handles incoming webhooks. This could lead to confusion when monitoring logs and debugging.

Apply this diff to correct the log messages:

 this.worker?.on('completed', (job: Job) => {
-  this.logger.log(`Outgoing Webhook job ${job.id} completed successfully`);
+  this.logger.log(`Incoming Webhook job ${job.id} completed successfully`);
 });

 this.worker?.on('failed', (job, error, prev) => {
-  this.logger.error(`Outgoing Webhook job ${job?.id} failed after retries: ${error.message}`);
+  this.logger.error(`Incoming Webhook job ${job?.id} failed after retries: ${error.message}`);
 });

 this.queue?.on('cleaned', (jobs, type) => {
   this.logger.log(
-    `${jobs.length} ${type} Outgoing Webhook jobs have been cleaned from the webhook queue`,
+    `${jobs.length} ${type} Incoming Webhook jobs have been cleaned from the webhook queue`,
   );
 });

Also applies to: 29-31, 34-36

Comment on lines +28 to +31
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
...headers,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove 'Access-Control-Allow-Origin' from request headers

The 'Access-Control-Allow-Origin' header is a response header used by servers to indicate which origins are permitted to read the response. Including it in the request headers has no effect and is unnecessary. It should be removed to prevent confusion and potential misconfigurations.

Apply this diff to remove the unnecessary header:

const signedHeaders = {
  Accept: 'application/json',
  'Content-Type': 'application/json',
- 'Access-Control-Allow-Origin': '*',
  ...headers,
  ...(body && customerConfig?.webhookSharedSecret
    ? {
        'X-HMAC-Signature': sign({
          payload: body,
          key: customerConfig.webhookSharedSecret,
        }),
      }
    : {}),
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
...headers,
Accept: 'application/json',
'Content-Type': 'application/json',
...headers,

Comment on lines +55 to +56
this.logger.error(`Webhook job failed: ${isErrorWithMessage(error) && error.message}`);
throw error;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve error logging in the catch block

Using isErrorWithMessage(error) && error.message may result in logging false if isErrorWithMessage(error) returns false. This could lead to unclear error messages in your logs. Consider updating the error logging to handle cases where the error does not have a message property.

Here's a suggested improvement:

this.logger.error(
- `Webhook job failed: ${isErrorWithMessage(error) && error.message}`
+ `Webhook job failed: ${
+   isErrorWithMessage(error) ? error.message : 'An unknown error occurred.'
+ }`
);

Alternatively, if it's safe to log the entire error object (ensure no sensitive information is included):

this.logger.error('Webhook job failed:', error);

This way, you get a more comprehensive error log, which can aid in debugging.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.logger.error(`Webhook job failed: ${isErrorWithMessage(error) && error.message}`);
throw error;
this.logger.error(
`Webhook job failed: ${
isErrorWithMessage(error) ? error.message : 'An unknown error occurred.'
}`
);
throw error;


const signedHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Set 'Content-Type' header appropriately based on body

Currently, the 'Content-Type' header is set to 'application/json' unconditionally. If the body is a string that is not JSON (e.g., plain text or XML), this header may be incorrect. Consider setting the 'Content-Type' header based on the type of body or allow it to be overridden by the headers provided in requestConfig.

Here's how you might adjust the code:

const signedHeaders = {
  Accept: 'application/json',
- 'Content-Type': 'application/json',
  ...headers,
  ...(body && customerConfig?.webhookSharedSecret
    ? {
        'X-HMAC-Signature': sign({
          payload: body,
          key: customerConfig.webhookSharedSecret,
        }),
      }
    : {}),
};

+ if (!signedHeaders['Content-Type']) {
+   signedHeaders['Content-Type'] =
+     typeof body === 'string' ? 'text/plain' : 'application/json';
+ }

This change ensures that:

  • If 'Content-Type' is provided in requestConfig.headers, it will be used.
  • Otherwise, it sets 'Content-Type' based on the type of body.

Committable suggestion was skipped due to low confidence.


@common.Injectable()
export abstract class WebhookHttpService extends HttpService {}
export class WebhookHttpService {}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unused Empty Class WebhookHttpService

The class WebhookHttpService is currently empty without any methods or properties. If it's no longer needed, consider removing it to keep the codebase clean. If it's a placeholder for future development, adding a comment to indicate its intended use would be helpful.

Comment on lines 32 to 61
private async handleRetryStrategy(status: number, job: Job<WebhookJobData>) {
if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
throw new Error(`Job ${job.id} failed after reaching max attempts`);
}

let delayMs: number;

switch (status) {
case HttpStatusCode.TooManyRequests:
case HttpStatusCode.InternalServerError:
case HttpStatusCode.BadGateway:
case HttpStatusCode.ServiceUnavailable:
case HttpStatusCode.GatewayTimeout:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
break;

case HttpStatusCode.RequestTimeout:
delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
break;

case HttpStatusCode.BadRequest:
throw new Error(`Webhook job failed with status ${status}: Bad Request`);

default:
throw new Error(`Webhook job failed with status ${status}: Unexpected Error`);
}

await this.retryJob(job, delayMs);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix handling of successful response statuses in handleRetryStrategy

Currently, the handleRetryStrategy method throws an error for any status code not explicitly handled in the switch statement, including successful status codes like 200 (OK). This means that even when the webhook invocation is successful, the job will be marked as failed.

To resolve this, adjust the method to handle successful statuses appropriately. Here's the suggested change:

+      case HttpStatusCode.Ok:
+      case HttpStatusCode.Created:
+      case HttpStatusCode.Accepted:
+        // Success statuses, no action needed
+        break;

       default:
-        throw new Error(`Webhook job failed with status ${status}: Unexpected Error`);
+        delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff for unexpected errors
+        await this.retryJob(job, delayMs);
+        break;

This change ensures that successful responses do not cause the job to fail, and unexpected errors are retried with exponential backoff.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async handleRetryStrategy(status: number, job: Job<WebhookJobData>) {
if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
throw new Error(`Job ${job.id} failed after reaching max attempts`);
}
let delayMs: number;
switch (status) {
case HttpStatusCode.TooManyRequests:
case HttpStatusCode.InternalServerError:
case HttpStatusCode.BadGateway:
case HttpStatusCode.ServiceUnavailable:
case HttpStatusCode.GatewayTimeout:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
break;
case HttpStatusCode.RequestTimeout:
delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
break;
case HttpStatusCode.BadRequest:
throw new Error(`Webhook job failed with status ${status}: Bad Request`);
default:
throw new Error(`Webhook job failed with status ${status}: Unexpected Error`);
}
await this.retryJob(job, delayMs);
}
private async handleRetryStrategy(status: number, job: Job<WebhookJobData>) {
if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
throw new Error(`Job ${job.id} failed after reaching max attempts`);
}
let delayMs: number;
switch (status) {
case HttpStatusCode.TooManyRequests:
case HttpStatusCode.InternalServerError:
case HttpStatusCode.BadGateway:
case HttpStatusCode.ServiceUnavailable:
case HttpStatusCode.GatewayTimeout:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
break;
case HttpStatusCode.RequestTimeout:
delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
break;
case HttpStatusCode.BadRequest:
throw new Error(`Webhook job failed with status ${status}: Bad Request`);
case HttpStatusCode.Ok:
case HttpStatusCode.Created:
case HttpStatusCode.Accepted:
// Success statuses, no action needed
break;
default:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff for unexpected errors
await this.retryJob(job, delayMs);
break;
}
await this.retryJob(job, delayMs);
}

# Conflicts:
#	services/workflows-service/package.json
#	services/workflows-service/prisma/data-migrations
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: 11

🧹 Outside diff range and nitpick comments (4)
services/workflows-service/.env.example (1)

41-41: Consider adding a USE_REDIS variable.

The IS_WORKER_SERVICE=false flag is a good addition for determining the service type. However, as suggested in a previous comment, consider also adding a USE_REDIS variable to toggle between Redis and in-memory implementations.

Consider adding the following line:

USE_REDIS=true

This would allow for easier switching between Redis and in-memory implementations in different environments.

services/workflows-service/src/main.ts (2)

54-60: LGTM: Conditional creation of WorkerAppModule.

The addition of conditional logic to create either WorkerAppModule or AppModule based on the IS_WORKER_SERVICE environment variable is a good approach for supporting different application modes. This allows for flexibility in deployment and testing.

However, consider adding an explicit else statement for clarity:

if (env.IS_WORKER_SERVICE) {
  return await NestFactory.create(WorkerAppModule, {
    bufferLogs: true,
    snapshot: true,
  });
} else {
  const app = await NestFactory.create(AppModule, {
    // ... existing options
  });
  // ... rest of the setup
}

This makes the flow more explicit and easier to follow.


Worker service includes Sentry integration but lacks other middleware configurations.

  • services/workflows-service/src/worker-app.module.ts imports and uses SentryModule.
  • No usage of helmet or other middleware configurations found.

Consider verifying if the worker service requires additional security configurations or middleware similar to the main application. Implement them if necessary to maintain consistency and enhance security.

🔗 Analysis chain

Line range hint 61-186: Verify worker service requirements.

The current implementation bypasses all middleware and configuration setups for the worker service. While this may be intentional, it's important to verify that the worker service doesn't require any of these configurations.

Consider the following:

  1. Does the worker service need any security configurations (e.g., helmet)?
  2. Is error handling (e.g., Sentry) required for the worker service?
  3. Are there any shared configurations that both the main app and worker service should use?

If any of these are needed, consider extracting shared configurations into a separate function that can be called for both the main app and the worker service.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any usage of middleware or configurations in the worker module
rg -t typescript 'middleware|configur|helmet|sentry' src/worker-app.module.ts

Length of output: 114


Script:

#!/bin/bash
# Search for any usage of middleware or configurations in the worker module with corrected file type
rg -t ts 'middleware|configur|helmet|sentry' src/worker-app.module.ts

Length of output: 136


Script:

#!/bin/bash
# Search for TypeScript module files related to the worker service
fd -e ts -e tsx -i 'worker.*module.ts'

Length of output: 92


Script:

#!/bin/bash
# Search for middleware or configuration usages in the worker module
rg -i 'middleware|configur|helmet|sentry' services/workflows-service/src/worker-app.module.ts

Length of output: 168


Script:

#!/bin/bash
# Verify the existence of the worker module file and search for middleware or configuration usages
if [ -f services/workflows-service/src/worker-app.module.ts ]; then
  rg -i 'middleware|configur|helmet|sentry' services/workflows-service/src/worker-app.module.ts
else
  echo "File services/workflows-service/src/worker-app.module.ts does not exist."
fi

Length of output: 233

services/workflows-service/src/worker-app.module.ts (1)

2-13: Inconsistent import paths: Standardize the import statements

The import statements use both absolute paths (using @/) and relative paths (using ./). For consistency and maintainability, consider standardizing the import paths throughout the file.

Suggested Change:

Choose one import style and apply it consistently. For example, if using absolute paths with module aliases:

  • Update relative imports to use absolute paths with the appropriate alias.

Apply this diff to adjust the relative imports to use absolute paths:

-import { HealthModule } from './health/health.module';
-import { PrismaModule } from './prisma/prisma.module';
-import { hashKey } from './customer/api-key/utils';
+import { HealthModule } from '@/health/health.module';
+import { PrismaModule } from '@/prisma/prisma.module';
+import { hashKey } from '@/customer/api-key/utils';

Ensure that your project's module resolution is configured to recognize the @ alias (e.g., in the tsconfig.json file).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 5b83c6e and 606a006.

📒 Files selected for processing (8)
  • services/workflows-service/.env.example (1 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
  • services/workflows-service/src/main.ts (2 hunks)
  • services/workflows-service/src/worker-app.module.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • services/workflows-service/prisma/data-migrations
🚧 Files skipped from review as they are similar to previous changes (2)
  • services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts
🧰 Additional context used
🔇 Additional comments (8)
services/workflows-service/.env.example (2)

38-38: LGTM: Redis host configuration looks good.

The REDIS_HOST=localhost setting is appropriate for local development environments.


39-39: Verify the non-standard Redis port.

The REDIS_PORT=7381 uses a non-standard port for Redis. While this may be intentional to avoid conflicts, please ensure this port is consistent with your Docker configuration and other services that might interact with Redis.

✅ Verification successful

To further verify the Redis port configuration, please run the following script:


Redis port configuration is consistent and verified.

The REDIS_PORT=7381 is intentionally set to avoid conflicts and is consistently mapped in the Docker configuration:

  • Docker Mapping: Host port 7381 ➔ Container port 6379.
  • Application Configuration: Uses REDIS_PORT from the environment with a default of 7381.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the Redis port is consistent in the Docker configuration.

# Test: Search for the Redis port in Docker Compose files
echo "Searching for Redis port in Docker Compose files:"
rg --type yaml 'redis.*port.*7381' services/workflows-service/

Length of output: 173


Script:

#!/bin/bash
# Description: Search for all occurrences of REDIS_PORT to ensure consistency across the codebase.

echo "Searching for REDIS_PORT in the codebase:"
rg 'REDIS_PORT' .

Length of output: 531

services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (4)

1-5: LGTM: Import statements are well-organized and complete.

The import statements are logically ordered and include all necessary dependencies for the class functionality.


29-29: LGTM: Abstract method declaration is correct.

The handleJob abstract method is properly declared, ensuring that subclasses will implement the specific job handling logic.


59-61: LGTM: onModuleDestroy method is well-implemented.

The onModuleDestroy method correctly handles resource cleanup:

  • It uses Promise.all to close both worker and queue concurrently, which is efficient.
  • Optional chaining is used to safely handle cases where worker or queue might be undefined.

This implementation ensures proper resource management when the module is destroyed.


1-62: Overall assessment: Well-structured base class with minor improvements needed.

The BaseQueueWorkerService class provides a solid foundation for implementing job queue management in a NestJS application using BullMQ. It follows best practices for both NestJS (using @Injectable() and implementing OnModuleDestroy) and BullMQ (proper queue and worker setup with event handling).

Key strengths:

  1. Abstract design allowing for flexible job handling in subclasses.
  2. Proper resource management with onModuleDestroy.
  3. Comprehensive event logging for job statuses.

Areas for improvement:

  1. Environment variable parsing for boolean values.
  2. Error handling in the addJob method when the queue system is disabled.
  3. Additional check in initializeWorker to prevent initialization when the queue system is disabled.

Addressing these minor issues will further enhance the robustness and reliability of this base class. Overall, this is a well-implemented foundation for queue worker services.

services/workflows-service/src/main.ts (2)

21-21: LGTM: New import for WorkerAppModule.

The import statement for WorkerAppModule is correctly added. This new module likely contains the worker-specific functionality for the application.


Line range hint 1-186: Summary of changes and suggestions.

The changes to main.ts successfully introduce a new worker service mode, aligning with the PR objectives of enhancing Redis integration and improving application flexibility. The implementation allows for easy switching between regular and worker service modes using an environment variable.

Key points and suggestions:

  1. The import and conditional creation of WorkerAppModule are well-implemented.
  2. Consider adding an explicit else statement for clarity in the main function.
  3. Verify the requirements for the worker service, especially regarding middleware and configurations.

Overall, these changes provide a solid foundation for integrating the worker service functionality. Ensure thorough testing of both modes to guarantee proper functionality in different environments.

@@ -35,3 +35,7 @@ WEB_UI_SDK_URL=http://localhost:5202
#HASHING_KEY_SECRET="$2b$10$FovZTB91/QQ4Yu28nvL8e."
HASHING_KEY_SECRET_BASE64=JDJiJDEwJDNFeWtwWEs4QkdiczlRaWFwLkM4Vk8=
NOTION_API_KEY=secret
REDIS_HOST=localhost
REDIS_PORT=7381
REDIS_PASSWORD=password
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security concern: Weak Redis password.

The REDIS_PASSWORD=password is a significant security risk. Even though this is an example file, it's crucial to:

  1. Use a strong, unique password for each environment.
  2. Never commit actual passwords to version control.
  3. Consider using a secret management system for production environments.

Replace the current line with:

-REDIS_PASSWORD=password
+REDIS_PASSWORD=<strong-unique-password>

Also, add a comment above this line:

# IMPORTANT: Replace with a strong, unique password. Never commit the actual password.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
REDIS_PASSWORD=password
# IMPORTANT: Replace with a strong, unique password. Never commit the actual password.
REDIS_PASSWORD=<strong-unique-password>

Comment on lines 7 to 27
@Injectable()
export abstract class BaseQueueWorkerService<T = any> implements OnModuleDestroy {
protected queue?: Queue;
protected worker?: Worker;
protected connectionOptions: ConnectionOptions;

protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};

if (env.QUEUE_SYSTEM_ENABLED !== true) {
return;
}

this.queue = new Queue(queueName, { connection: this.connectionOptions });

if (env.IS_WORKER_SERVICE !== true) {
this.initializeWorker();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve environment variable comparison in the constructor.

The class structure and decorator usage are appropriate. However, there's a potential issue with the environment variable comparison:

  1. Environment variables are typically strings, so comparing env.QUEUE_SYSTEM_ENABLED directly to true (a boolean) may not yield the expected result.
  2. The same issue applies to the env.IS_WORKER_SERVICE comparison.

Consider parsing the environment variables to booleans before comparison. Apply this diff to fix the comparisons:

-    if (env.QUEUE_SYSTEM_ENABLED !== true) {
+    if (env.QUEUE_SYSTEM_ENABLED !== 'true') {
       return;
     }

     this.queue = new Queue(queueName, { connection: this.connectionOptions });

-    if (env.IS_WORKER_SERVICE !== true) {
+    if (env.IS_WORKER_SERVICE !== 'true') {
       this.initializeWorker();
     }

Alternatively, you could use a helper function to parse boolean environment variables consistently throughout the application.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Injectable()
export abstract class BaseQueueWorkerService<T = any> implements OnModuleDestroy {
protected queue?: Queue;
protected worker?: Worker;
protected connectionOptions: ConnectionOptions;
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};
if (env.QUEUE_SYSTEM_ENABLED !== true) {
return;
}
this.queue = new Queue(queueName, { connection: this.connectionOptions });
if (env.IS_WORKER_SERVICE !== true) {
this.initializeWorker();
}
}
@Injectable()
export abstract class BaseQueueWorkerService<T = any> implements OnModuleDestroy {
protected queue?: Queue;
protected worker?: Worker;
protected connectionOptions: ConnectionOptions;
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};
if (env.QUEUE_SYSTEM_ENABLED !== 'true') {
return;
}
this.queue = new Queue(queueName, { connection: this.connectionOptions });
if (env.IS_WORKER_SERVICE !== 'true') {
this.initializeWorker();
}
}

@@ -90,6 +90,17 @@ export const serverEnvSchema = {
IN_MEMORIES_SECRET_ACQUIRER_ID: z.string().optional(),
IN_MEMORIES_SECRET_PRIVATE_KEY: z.string().optional(),
IN_MEMORIES_SECRET_CONSUMER_KEY: z.string().optional(),
REDIS_HOST: z.string(),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance the REDIS_HOST configuration

While adding REDIS_HOST is a good start, consider improving it based on previous suggestions:

  1. Add a description for better maintainability.
  2. Consider adding validation to ensure it's a valid hostname or IP address.

Here's a suggested implementation:

REDIS_HOST: z.string().describe('Redis server hostname or IP address'),

If you want to add stricter validation, you could use a regex pattern:

REDIS_HOST: z
  .string()
  .regex(/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/)
  .describe('Redis server hostname or IP address'),

This regex pattern allows for valid hostnames and IP addresses.

@@ -90,6 +90,17 @@
IN_MEMORIES_SECRET_ACQUIRER_ID: z.string().optional(),
IN_MEMORIES_SECRET_PRIVATE_KEY: z.string().optional(),
IN_MEMORIES_SECRET_CONSUMER_KEY: z.string().optional(),
REDIS_HOST: z.string(),
REDIS_PASSWORD: z.string(),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make REDIS_PASSWORD optional and add a description

The current implementation makes REDIS_PASSWORD mandatory, which might not be suitable for all Redis configurations. Consider the following improvements:

  1. Make the password optional.
  2. Add a description for better maintainability.

Here's a suggested implementation:

REDIS_PASSWORD: z.string().optional().describe('Redis server password (if required)'),

This change allows for Redis configurations without authentication while still supporting those that require a password.

@@ -90,6 +90,17 @@
IN_MEMORIES_SECRET_ACQUIRER_ID: z.string().optional(),
IN_MEMORIES_SECRET_PRIVATE_KEY: z.string().optional(),
IN_MEMORIES_SECRET_CONSUMER_KEY: z.string().optional(),
REDIS_HOST: z.string(),
REDIS_PASSWORD: z.string(),
REDIS_PORT: z.string().transform(value => Number(value)),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance REDIS_PORT configuration with validation

The current implementation transforms the port to a number but lacks validation. Consider adding the following improvements:

  1. Add a description for better maintainability.
  2. Validate that the port is within a valid range (e.g., 1024-65535).
  3. Provide a default value if needed.

Here's a suggested implementation:

REDIS_PORT: z
  .string()
  .default('6379')
  .transform(value => {
    const port = Number(value);
    if (isNaN(port) || port < 1024 || port > 65535) {
      throw new Error('REDIS_PORT must be a number between 1024 and 65535');
    }
    return port;
  })
  .describe('Redis server port'),

This implementation adds a description, includes port number validation, and provides a default value of 6379 (the standard Redis port).

Comment on lines 96 to 99
QUEUE_SYSTEM_ENABLED: z
.string()
.transform(value => Boolean(value))
.default('false'),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance QUEUE_SYSTEM_ENABLED with description and explicit typing

The implementation of QUEUE_SYSTEM_ENABLED is good, but it can be improved:

  1. Add a description for better maintainability.
  2. Use more explicit typing for better clarity.

Here's a suggested implementation:

QUEUE_SYSTEM_ENABLED: z
  .enum(['true', 'false'])
  .default('false')
  .transform(value => value === 'true')
  .describe('Enable or disable the queue system'),

This implementation adds a description, uses an enum for explicit typing, and simplifies the boolean transformation.

Comment on lines 100 to 103
IS_WORKER_SERVICE: z
.string()
.transform(value => Boolean(value))
.default('false'),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance IS_WORKER_SERVICE configuration

The IS_WORKER_SERVICE configuration can be improved similarly to QUEUE_SYSTEM_ENABLED:

  1. Add a description for better maintainability.
  2. Use more explicit typing for better clarity.

Here's a suggested implementation:

IS_WORKER_SERVICE: z
  .enum(['true', 'false'])
  .default('false')
  .transform(value => value === 'true')
  .describe('Indicates whether this instance is a worker service'),

This implementation adds a description, uses an enum for explicit typing, and simplifies the boolean transformation, maintaining consistency with the QUEUE_SYSTEM_ENABLED configuration.

if (!result.success) {
const errors = result.error.errors.map(zodIssue => ({
message: `❌ ${zodIssue.message}`,
path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message excepts array
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in comment: 'excepts' should be 'expects'

There's a typographical error in the comment. The word "excepts" should be "expects" to accurately convey the intended meaning.

Apply this diff to correct the typo:

-  path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message excepts array
+  path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message expects array
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message excepts array
path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message expects array

Comment on lines +15 to +38
export const validate = async (config: Record<string, unknown>) => {
const zodEnvSchema = z
.object(serverEnvSchema)
.refine(data => data.HASHING_KEY_SECRET || data.HASHING_KEY_SECRET_BASE64, {
message: 'At least one of HASHING_KEY_SECRET or HASHING_KEY_SECRET_BASE64 should be present',
path: ['HASHING_KEY_SECRET', 'HASHING_KEY_SECRET_BASE64'],
});

const result = zodEnvSchema.safeParse(config);

if (!result.success) {
const errors = result.error.errors.map(zodIssue => ({
message: `❌ ${zodIssue.message}`,
path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message excepts array
}));

throw new Error(JSON.stringify(errors, null, 2));
}

// validate salt value
await hashKey('check salt value');

return result.data;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential Issue: validate function should be synchronous with ConfigModule

The validate function passed to ConfigModule.forRoot() is defined as an async function. However, NestJS ConfigModule expects a synchronous function for validation. Using an async function may lead to unexpected behavior or errors during the configuration process since the configuration loading is synchronous.

Suggested Change:

Refactor the validate function to be synchronous. If hashKey is an asynchronous operation, consider performing the salt validation elsewhere in the application where asynchronous operations are allowed.

Apply this diff to make the validate function synchronous:

-export const validate = async (config: Record<string, unknown>) => {
+export const validate = (config: Record<string, unknown>) => {
   const zodEnvSchema = z
     .object(serverEnvSchema)
     .refine(data => data.HASHING_KEY_SECRET || data.HASHING_KEY_SECRET_BASE64, {
       message: 'At least one of HASHING_KEY_SECRET or HASHING_KEY_SECRET_BASE64 should be present',
       path: ['HASHING_KEY_SECRET', 'HASHING_KEY_SECRET_BASE64'],
     });

   const result = zodEnvSchema.safeParse(config);

   if (!result.success) {
     const errors = result.error.errors.map(zodIssue => ({
       message: `❌ ${zodIssue.message}`,
       path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message expects array
     }));

     throw new Error(JSON.stringify(errors, null, 2));
   }

-  // validate salt value
-  await hashKey('check salt value');

   return result.data;
 };

Then, perform the salt value validation after the application has initialized, possibly in the onModuleInit lifecycle hook of a suitable module.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const validate = async (config: Record<string, unknown>) => {
const zodEnvSchema = z
.object(serverEnvSchema)
.refine(data => data.HASHING_KEY_SECRET || data.HASHING_KEY_SECRET_BASE64, {
message: 'At least one of HASHING_KEY_SECRET or HASHING_KEY_SECRET_BASE64 should be present',
path: ['HASHING_KEY_SECRET', 'HASHING_KEY_SECRET_BASE64'],
});
const result = zodEnvSchema.safeParse(config);
if (!result.success) {
const errors = result.error.errors.map(zodIssue => ({
message: `❌ ${zodIssue.message}`,
path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message excepts array
}));
throw new Error(JSON.stringify(errors, null, 2));
}
// validate salt value
await hashKey('check salt value');
return result.data;
};
export const validate = (config: Record<string, unknown>) => {
const zodEnvSchema = z
.object(serverEnvSchema)
.refine(data => data.HASHING_KEY_SECRET || data.HASHING_KEY_SECRET_BASE64, {
message: 'At least one of HASHING_KEY_SECRET or HASHING_KEY_SECRET_BASE64 should be present',
path: ['HASHING_KEY_SECRET', 'HASHING_KEY_SECRET_BASE64'],
});
const result = zodEnvSchema.safeParse(config);
if (!result.success) {
const errors = result.error.errors.map(zodIssue => ({
message: `❌ ${zodIssue.message}`,
path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message excepts array
}));
throw new Error(JSON.stringify(errors, null, 2));
}
return result.data;
};

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

🧹 Outside diff range and nitpick comments (5)
services/workflows-service/src/worker-main.ts (1)

6-19: LGTM: Well-structured worker setup with a minor suggestion.

The workerMain function follows NestJS best practices for setting up a worker application. It correctly initializes the application, sets up logging, and applies middleware for context management.

Consider adding error handling to catch and log any initialization errors:

 const workerMain = async () => {
-  const app = await NestFactory.create(WorkerAppModule, {
-    bufferLogs: true,
-    snapshot: true,
-  });
+  try {
+    const app = await NestFactory.create(WorkerAppModule, {
+      bufferLogs: true,
+      snapshot: true,
+    });
 
-  const logger = app.get(AppLoggerService);
+    const logger = app.get(AppLoggerService);
 
-  app.useLogger(logger);
-  app.use(new ClsMiddleware({}).use);
-  logger.log('Worker started');
+    app.useLogger(logger);
+    app.use(new ClsMiddleware({}).use);
+    logger.log('Worker started');
 
-  return app;
+    return app;
+  } catch (error) {
+    console.error('Failed to initialize worker:', error);
+    throw error;
+  }
 };

This change will ensure that any initialization errors are properly logged and propagated.

services/workflows-service/package.json (3)

14-14: LGTM: New worker script added.

The addition of a separate worker script is a good practice for handling background jobs. This separation of concerns can improve the application's scalability and maintainability.

Consider adding a comment or updating the documentation to explain the purpose and usage of this new worker script.


35-36: LGTM: Redis management scripts added.

The addition of scripts for managing the Redis container is a good practice. It allows for easy setup and teardown of the Redis service during development and testing.

Consider adding a docker:redis:logs script to easily view Redis logs, which can be helpful for debugging:

"docker:redis:logs": "docker compose -f docker-compose.redis.yml logs -f"

57-59: LGTM: Bull Board dependencies added for queue monitoring.

The addition of Bull Board dependencies (@bull-board/api, @bull-board/express, @bull-board/nestjs) is a good choice for monitoring BullMQ queues. This will provide valuable insights into job processing and queue status.

Consider adding documentation on how to access and use the Bull Board UI in your project's README or developer documentation.

services/workflows-service/src/events/workflow-completed-webhook-caller.ts (1)

113-126: Remove unnecessary 'await' keyword before return

Using return await in an async function is redundant because it adds an extra microtask without any benefit. Consider removing the await keyword to improve performance.

Apply this diff to simplify the return statement:

-            return await this.outgoingWebhookQueueService.addJob({
+            return this.outgoingWebhookQueueService.addJob({
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 606a006 and e7d416b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • services/workflows-service/package.json (4 hunks)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
  • services/workflows-service/src/events/document-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-completed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/worker-app.module.ts (1 hunks)
  • services/workflows-service/src/worker-main.ts (1 hunks)
  • services/workflows-service/src/workflow/workflow.module.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts
  • services/workflows-service/src/env.ts
  • services/workflows-service/src/worker-app.module.ts
🧰 Additional context used
🔇 Additional comments (16)
services/workflows-service/src/worker-main.ts (1)

1-4: LGTM: Imports are appropriate for the worker functionality.

The imports cover the necessary modules for creating a NestJS worker application, including logging, context management, and the main application module.

services/workflows-service/src/workflow/workflow.module.ts (3)

50-50: LGTM: BullMqModule import added correctly.

The import statement for BullMqModule is properly formatted and uses the correct path aliasing. This addition aligns with the PR objective of integrating BullMQ for Redis-based job queue management.


69-69: LGTM: BullMqModule correctly added to imports.

The BullMqModule has been properly added to the imports array of the WorkflowModule. This addition is necessary for integrating BullMQ functionality into the Workflow service and aligns with the PR objectives.


Line range hint 50-69: Verify BullMqModule configuration and usage.

The integration of BullMqModule into the WorkflowModule aligns well with the PR objectives. However, to ensure a complete implementation:

  1. Verify that BullMqModule is properly configured, possibly in a separate configuration file.
  2. Check if there are any other files in the Workflow service that need to be updated to use BullMQ functionality.
  3. Consider adding unit tests to verify the integration of BullMqModule.

To help with verification, you can run the following script:

This script will help identify if BullMqModule is properly configured and used within the Workflow service, and if there are any new unit tests related to the BullMQ integration.

services/workflows-service/package.json (3)

8-8: LGTM: Setup script updated to include Redis management.

The setup script has been appropriately modified to include Redis management. It now includes both docker:redis:down and docker:redis, addressing the previous comment by MatanYadaev. The order of operations is logical, ensuring a clean setup environment.


16-16: LGTM: Production worker script added.

The new "worker:prod" script appropriately runs database migrations before starting the worker application from the compiled source. This ensures that the database schema is up-to-date in the production environment before the worker starts processing jobs.


62-62: LGTM: BullMQ dependencies added for job queue functionality.

The addition of @nestjs/bullmq and bullmq dependencies is appropriate for implementing job queue functionality in the project. This addresses the need for robust background job processing.

Regarding past comments:

  1. The Redis client is typically included with BullMQ, so it doesn't need to be added separately.
  2. Bull Board (added earlier) is a more modern alternative to bull-arena, effectively addressing the suggestion for a UI component.

Also applies to: 89-89

services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (2)

13-14: Approved: Necessary imports added

The imports for env and OutgoingWebhookQueueService are correctly added and necessary for the new functionality.


26-26: Approved: Injected 'OutgoingWebhookQueueService' into the constructor

The OutgoingWebhookQueueService is properly injected into the constructor to enable queueing of webhook requests.

services/workflows-service/src/events/workflow-completed-webhook-caller.ts (3)

113-126: ⚠️ Potential issue

Ensure webhook headers are included when queuing requests

In the queue-enabled scenario, the requestConfig has an empty headers object. In the direct webhook call, headers like X-Authorization and X-HMAC-Signature are included. Verify that these headers are added when the webhook is processed from the queue to maintain consistent authentication.

Run the following script to check if headers are set in the queued webhook processing:

#!/bin/bash
# Description: Search for header assignment using 'webhookSharedSecret' in queue processing code

fd -e ts 'outgoing-webhook' | xargs rg 'headers.*webhookSharedSecret'

29-29: Ensure 'OutgoingWebhookQueueService' is provided in the module's providers array

The OutgoingWebhookQueueService is injected into the constructor. Make sure it is included in the module's providers array to enable proper dependency injection.

Run the following script to check if OutgoingWebhookQueueService is registered in the module:

#!/bin/bash
# Description: Search for module files that provide 'OutgoingWebhookQueueService'

fd -e ts 'module.ts' | xargs rg 'providers\s*:\s*\[.*OutgoingWebhookQueueService.*\]'

15-16: Verify imports for 'env' and 'OutgoingWebhookQueueService' are correctly resolved

The imports for env and OutgoingWebhookQueueService have been added. Ensure that these modules are properly exported and there are no issues with module resolution.

Run the following script to confirm that the imports are valid:

services/workflows-service/src/events/document-changed-webhook-caller.ts (4)

16-17: LGTM!

The new imports are correctly added and paths are accurate.


39-39: LGTM!

Dependency injection of OutgoingWebhookQueueService in the constructor is properly implemented.


178-201: LGTM!

The payload for the webhook is constructed correctly with all necessary fields included.


202-215: Ensure headers are correctly included in queued webhook requests

When env.QUEUE_SYSTEM_ENABLED is true, the webhook request is enqueued with empty headers in requestConfig. In the direct HTTP POST request, headers include X-Authorization and X-HMAC-Signature. Please verify that these headers are added when the queued job is processed to ensure proper authentication and signature verification.

Run the following script to verify that the OutgoingWebhookQueueService or its associated job processor includes the necessary headers:

return app;
};

module.exports = workerMain();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider exporting the function instead of its execution result.

The current export might cause issues if the module is imported synchronously, as it immediately executes the async function. This approach also doesn't allow for error handling or customization by the importing module.

Consider changing the export to:

-module.exports = workerMain();
+module.exports = workerMain;

This change allows the importing module to control when and how the worker is initialized, and provides better error handling capabilities.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
module.exports = workerMain();
module.exports = workerMain;

@@ -89,6 +92,21 @@ export class WorkflowStateChangedWebhookCaller {
data: data.runtimeData.context,
};

if (env.QUEUE_SYSTEM_ENABLED) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure 'QUEUE_SYSTEM_ENABLED' is explicitly compared to 'true'

Environment variables are strings, so the condition if (env.QUEUE_SYSTEM_ENABLED) may not work as intended. If env.QUEUE_SYSTEM_ENABLED is set to 'false' or any non-empty string, the condition will evaluate as truthy. Consider explicitly comparing the value to 'true'.

Apply this diff to fix the condition:

-if (env.QUEUE_SYSTEM_ENABLED) {
+if (env.QUEUE_SYSTEM_ENABLED === 'true') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (env.QUEUE_SYSTEM_ENABLED) {
if (env.QUEUE_SYSTEM_ENABLED === 'true') {

Comment on lines 95 to 109
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob({
requestConfig: {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
},
customerConfig: {
webhookSharedSecret,
},
});
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential errors from 'addJob' to prevent unhandled exceptions

The addJob method may throw an error, and currently, it's not wrapped in a try-catch block. This could lead to unhandled exceptions if an error occurs while adding the job to the queue. Consider wrapping the addJob call in a try-catch block to handle any potential errors and maintain consistency with error handling in the rest of the method.

Apply this diff to wrap the addJob call in a try-catch block:

 if (env.QUEUE_SYSTEM_ENABLED === 'true') {
+  try {
     await this.outgoingWebhookQueueService.addJob({
       requestConfig: {
         url,
         method: 'POST',
         headers: {},
         body: payload,
         timeout: 15_000,
       },
       customerConfig: {
         webhookSharedSecret,
       },
     });
+    return;
+  } catch (error) {
+    this.logger.error('Failed to add job to queue', { error });
+    alertWebhookFailure(error);
+  }
+  return;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob({
requestConfig: {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
},
customerConfig: {
webhookSharedSecret,
},
});
}
if (env.QUEUE_SYSTEM_ENABLED === 'true') {
try {
await this.outgoingWebhookQueueService.addJob({
requestConfig: {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
},
customerConfig: {
webhookSharedSecret,
},
});
return;
} catch (error) {
this.logger.error('Failed to add job to queue', { error });
alertWebhookFailure(error);
}
return;
}

Comment on lines 113 to 126
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob({
requestConfig: {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
},
customerConfig: {
webhookSharedSecret,
},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the evaluation of 'QUEUE_SYSTEM_ENABLED' environment variable

Environment variables are strings, so env.QUEUE_SYSTEM_ENABLED may not behave as expected in a boolean context. If the variable is set to 'false', it will still be truthy. Explicitly compare the value to ensure correct behavior.

Apply this diff to properly evaluate the environment variable:

-          if (env.QUEUE_SYSTEM_ENABLED) {
+          if (env.QUEUE_SYSTEM_ENABLED === 'true') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob({
requestConfig: {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
},
customerConfig: {
webhookSharedSecret,
},
});
}
if (env.QUEUE_SYSTEM_ENABLED === 'true') {
return await this.outgoingWebhookQueueService.addJob({
requestConfig: {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
},
customerConfig: {
webhookSharedSecret,
},
});
}

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: 8

🧹 Outside diff range and nitpick comments (3)
services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (1)

109-111: Consider adding logging after adding the job to the queue

To maintain consistency and aid in debugging, consider logging a message after successfully adding a job to the queue. This mirrors the logging behavior in the direct webhook invocation.

You could implement the logging as follows:

if (env.QUEUE_SYSTEM_ENABLED) {
  const job = await this.outgoingWebhookQueueService.addJob(webhookArgs);
  this.logger.log('Job added to queue', { jobId: job.id });
  return;
}
services/workflows-service/src/events/document-changed-webhook-caller.ts (2)

16-16: Consistent Environment Variable Management

The import import { env } from '@/env'; introduces env for environment variables. Ensure that the env object is populated correctly and consistently throughout the application. Inconsistent usage might lead to environment variables not being read as expected.

Consider centralizing environment variable access to maintain consistency.


Line range hint 221-234: Enhance Error Logging for Webhook Invocation

In the catch block, the error is logged, but without detailed error information. Providing more context can aid in debugging.

Consider logging additional error details:

this.logger.error('Failed to send webhook', {
  id,
  message: error?.message,
  stack: error?.stack,
  responseData: error?.response?.data,
});

This includes error stack traces and any response data from failed HTTP requests.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e7d416b and ab614af.

📒 Files selected for processing (5)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/events/document-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-completed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/workflow/workflow.module.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/workflows-service/src/workflow/workflow.module.ts
🧰 Additional context used
🔇 Additional comments (11)
services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (2)

1-9: LGTM: Imports and decorator are appropriate.

The imports cover all necessary dependencies for BullMQ, NestJS, and custom configurations. The @Injectable() decorator is correctly used to make the class available for dependency injection in NestJS.


66-68: LGTM: Proper resource cleanup in onModuleDestroy.

The onModuleDestroy method correctly implements the OnModuleDestroy interface, ensuring proper cleanup of both the worker and queue resources. The use of Promise.all is an efficient way to handle multiple asynchronous operations.

services/workflows-service/src/events/workflow-completed-webhook-caller.ts (3)

12-12: LGTM: New dependencies and constructor parameters.

The addition of new imports and constructor parameters for OutgoingWebhookQueueService and OutgoingWebhooksService is consistent with the changes in the sendWebhook method. This approach follows good dependency injection principles, enhancing modularity and testability.

Also applies to: 15-17, 30-31


86-126: LGTM: Improved webhook payload and flexible sending mechanism.

The changes to the sendWebhook method are well-structured:

  1. The new payload object is more comprehensive and organized.
  2. The webhookArgs object encapsulates request and customer configurations cleanly.
  3. The introduction of a queue system option allows for better handling of high loads or network issues.
  4. Error handling and logging are preserved, maintaining robustness.

These changes enhance the flexibility and reliability of the webhook sending process.


Line range hint 132-138: LGTM: Improved webhook invocation using a dedicated service.

The replacement of the direct Axios call with outgoingWebhooksService.invokeWebhook is a positive change:

  1. It promotes better separation of concerns by delegating the webhook invocation to a dedicated service.
  2. The preservation of result logging maintains good observability.

This change enhances the modularity and maintainability of the code.

services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (3)

12-14: Imports are correctly added

The new imports for env, OutgoingWebhookQueueService, and OutgoingWebhooksService are appropriate and necessary for the new functionality introduced.


26-27: Constructor parameters are appropriately updated

Including outgoingWebhookQueueService and outgoingWebhooksService in the constructor aligns with the required dependencies for handling outgoing webhooks.


96-107: 'webhookArgs' object is constructed correctly

The webhookArgs object encapsulates the necessary request and customer configurations, and using as const ensures the object is read-only, which is appropriate for preventing unintended mutations.

services/workflows-service/src/events/document-changed-webhook-caller.ts (3)

17-18: Ensure New Services Are Registered in Module

The OutgoingWebhookQueueService and OutgoingWebhooksService are newly imported services. Please verify that these services are properly registered in the module's providers array to ensure they are correctly injected, preventing any runtime UndefinedDependencyError.

Consider adding them to the module if they are not already included.


40-41: Confirm Constructor Injection Order

With the addition of OutgoingWebhookQueueService and OutgoingWebhooksService in the constructor, ensure that the parameter order aligns with the expected injection order, especially if any parameters are optional or if the project relies on parameter indices for injection.

Double-check the dependency injection to prevent any mismatches.


205-215: Review Usage of Type Assertion with as const

The webhookArgs object is asserted with as const. This makes all properties readonly and can prevent modifications later in the code.

Ensure that making webhookArgs immutable is intentional and doesn't affect subsequent operations that may require modifying this object.

Comment on lines 42 to 64
protected initializeWorker() {
this.worker = new Worker(
this.queueName,
async (job: Job<T>) => {
await this.handleJob(job);
},
{ connection: this.connectionOptions },
);

this.worker?.on('completed', (job: Job) => {
this.logger.log(`Webhook job ${job.id} completed successfully`);
});

this.worker?.on('failed', (job, error, prev) => {
this.logger.error(
`Webhook job ${job?.id} failed after in queue: ${this.queue?.name} retries: ${error.message}`,
);
});

this.queue?.on('cleaned', (jobs, type) => {
this.logger.log(`${jobs.length} ${type} jobs have been cleaned from the webhook queue`);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add queue system check in initializeWorker and LGTM on event listeners.

The event listeners for job completion, failure, and queue cleaning are well implemented. However, we should add a check to ensure the queue system is enabled before initializing the worker.

Apply this diff to add the check:

   protected initializeWorker() {
+    if (!this.queue) {
+      this.logger.warn('Attempted to initialize worker while queue system is disabled');
+      return;
+    }
     this.worker = new Worker(
       // ... rest of the method
     );
     // ... event listeners
   }

This change prevents attempting to initialize a worker when the queue system is disabled.

The event listener setup is well-implemented, providing good logging for job statuses and queue cleaning.

Committable suggestion was skipped due to low confidence.

Comment on lines 38 to 40
async addJob(jobData: T, jobOptions = {}): Promise<void> {
await this.queue?.add(this.queueName, jobData, jobOptions);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve error handling in addJob method.

The current implementation silently fails when the queue is undefined. Implement explicit error handling to make the behavior more predictable and easier to debug.

Apply this diff to improve the addJob method:

   async addJob(jobData: T, jobOptions = {}): Promise<void> {
-    await this.queue?.add(this.queueName, jobData, jobOptions);
+    if (!this.queue) {
+      this.logger.warn('Attempted to add job while queue system is disabled');
+      throw new Error('Queue system is disabled. Cannot add job.');
+    }
+    try {
+      await this.queue.add(this.queueName, jobData, jobOptions);
+    } catch (error) {
+      this.logger.error(`Failed to add job to queue: ${error.message}`);
+      throw error;
+    }
   }

This change ensures that callers are aware when jobs cannot be added due to a disabled queue system and provides better error logging.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async addJob(jobData: T, jobOptions = {}): Promise<void> {
await this.queue?.add(this.queueName, jobData, jobOptions);
}
async addJob(jobData: T, jobOptions = {}): Promise<void> {
if (!this.queue) {
this.logger.warn('Attempted to add job while queue system is disabled');
throw new Error('Queue system is disabled. Cannot add job.');
}
try {
await this.queue.add(this.queueName, jobData, jobOptions);
} catch (error) {
this.logger.error(`Failed to add job to queue: ${error.message}`);
throw error;
}
}

Comment on lines 14 to 34
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};

if (!env.QUEUE_SYSTEM_ENABLED) {
return;
}

this.queue = new Queue(queueName, {
connection: this.connectionOptions,
defaultJobOptions: {
...Object.entries(QUEUES).find(([_, queueOptions]) => queueOptions.name === queueName)?.[1]
.config,
},
});

if (env.IS_WORKER_SERVICE !== true) {
this.initializeWorker();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve environment variable checks and add error handling.

  1. Environment variable checks:
    • Convert env.QUEUE_SYSTEM_ENABLED and env.IS_WORKER_SERVICE to booleans for comparison.
  2. Add error handling for queue initialization.

Apply this diff to improve the constructor:

   protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
     this.connectionOptions = {
       ...REDIS_CONFIG,
     };

-    if (!env.QUEUE_SYSTEM_ENABLED) {
+    if (env.QUEUE_SYSTEM_ENABLED !== 'true') {
       return;
     }

-    this.queue = new Queue(queueName, {
-      connection: this.connectionOptions,
-      defaultJobOptions: {
-        ...Object.entries(QUEUES).find(([_, queueOptions]) => queueOptions.name === queueName)?.[1]
-          .config,
-      },
-    });
+    try {
+      this.queue = new Queue(queueName, {
+        connection: this.connectionOptions,
+        defaultJobOptions: {
+          ...Object.entries(QUEUES).find(([_, queueOptions]) => queueOptions.name === queueName)?.[1]
+            ?.config,
+        },
+      });
+    } catch (error) {
+      this.logger.error(`Failed to initialize queue: ${error.message}`);
+      return;
+    }

-    if (env.IS_WORKER_SERVICE !== true) {
+    if (env.IS_WORKER_SERVICE === 'true') {
       this.initializeWorker();
     }
   }

These changes ensure proper boolean comparisons for environment variables and add error handling for queue initialization.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};
if (!env.QUEUE_SYSTEM_ENABLED) {
return;
}
this.queue = new Queue(queueName, {
connection: this.connectionOptions,
defaultJobOptions: {
...Object.entries(QUEUES).find(([_, queueOptions]) => queueOptions.name === queueName)?.[1]
.config,
},
});
if (env.IS_WORKER_SERVICE !== true) {
this.initializeWorker();
}
}
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};
if (env.QUEUE_SYSTEM_ENABLED !== 'true') {
return;
}
try {
this.queue = new Queue(queueName, {
connection: this.connectionOptions,
defaultJobOptions: {
...Object.entries(QUEUES).find(([_, queueOptions]) => queueOptions.name === queueName)?.[1]
?.config,
},
});
} catch (error) {
this.logger.error(`Failed to initialize queue: ${error.message}`);
return;
}
if (env.IS_WORKER_SERVICE === 'true') {
this.initializeWorker();
}
}

Comment on lines +127 to +129
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve the evaluation of 'QUEUE_SYSTEM_ENABLED' environment variable.

The current check if (env.QUEUE_SYSTEM_ENABLED) may not behave as expected. Environment variables are strings, so even if env.QUEUE_SYSTEM_ENABLED is set to 'false', it will still be truthy.

To ensure correct behavior, explicitly compare the value:

-    if (env.QUEUE_SYSTEM_ENABLED) {
+    if (env.QUEUE_SYSTEM_ENABLED === 'true') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}
if (env.QUEUE_SYSTEM_ENABLED === 'true') {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}

Comment on lines +180 to +202
const payload = {
id,
eventName: 'workflow.context.document.changed',
apiVersion,
timestamp: new Date().toISOString(),
assignee: data.assignee
? {
id: data.assignee.id,
firstName: data.assignee.firstName,
lastName: data.assignee.lastName,
email: data.assignee.email,
}
: null,
assignedAt: data.assignedAt,
workflowCreatedAt: data.updatedRuntimeData.createdAt,
workflowResolvedAt: data.updatedRuntimeData.resolvedAt,
workflowDefinitionId: data.updatedRuntimeData.workflowDefinitionId,
workflowRuntimeId: data.updatedRuntimeData.id,
ballerineEntityId: data.entityId,
correlationId: data.correlationId,
environment,
data: data.updatedRuntimeData.context,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid Exposing Sensitive Data in Webhook Payload

The payload includes detailed assignee information and the entire data.updatedRuntimeData.context. There is a risk of exposing sensitive or personally identifiable information (PII).

Review the payload to ensure that only non-sensitive, necessary data is sent. Consider redacting or excluding sensitive fields.

Comment on lines +185 to +193
assignee: data.assignee
? {
id: data.assignee.id,
firstName: data.assignee.firstName,
lastName: data.assignee.lastName,
email: data.assignee.email,
}
: null,
assignedAt: data.assignedAt,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle Potential Undefined Assignee Properties

When constructing the assignee object, the code assumes that properties like id, firstName, lastName, and email are available. If any of these are undefined, it could lead to unexpected issues.

Add checks or default values to handle cases where these properties might be missing.

Comment on lines +217 to +220
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Explicit Comparison for Environment Variable

The condition if (env.QUEUE_SYSTEM_ENABLED) relies on the truthiness of env.QUEUE_SYSTEM_ENABLED. If this variable is a string (e.g., 'false'), this condition might not behave as intended.

Use an explicit comparison to ensure correct evaluation:

-if (env.QUEUE_SYSTEM_ENABLED) {
+if (env.QUEUE_SYSTEM_ENABLED === 'true') {

This change ensures that the queue system is enabled only when the variable is set to 'true'.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}
if (env.QUEUE_SYSTEM_ENABLED === 'true') {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}

Comment on lines +187 to +191
id: data.assignee.id,
firstName: data.assignee.firstName,
lastName: data.assignee.lastName,
email: data.assignee.email,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify Assignee Object with Destructuring

You can streamline the assignee object creation using object destructuring if all properties are guaranteed to exist.

-assignee: data.assignee
-  ? {
-      id: data.assignee.id,
-      firstName: data.assignee.firstName,
-      lastName: data.assignee.lastName,
-      email: data.assignee.email,
-    }
-  : null,
+assignee: data.assignee
+  ? (({ id, firstName, lastName, email }) => ({ id, firstName, lastName, email }))(data.assignee)
+  : null,

Ensure that no additional unintended properties are included.

Committable suggestion was skipped due to low confidence.

@@ -5,13 +5,15 @@
"description": "workflow-service",
"scripts": {
"spellcheck": "cspell \"*\"",
"setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run db:reset && npm run seed",
"setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis:down && npm run docker:redis && npm run db:reset && npm run seed",
Copy link
Collaborator

@pratapalakshmi pratapalakshmi Oct 15, 2024

Choose a reason for hiding this comment

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

I think this is better

"setup": "cp .env.example .env && npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis:down && npm run docker:redis && npm run db:reset && npm run seed",

# Conflicts:
#	services/workflows-service/package.json
#	services/workflows-service/prisma/data-migrations
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

🧹 Outside diff range and nitpick comments (2)
services/workflows-service/src/redis/const/redis-config.ts (1)

1-9: Update .env.example files with Redis configuration

While the Redis configuration in this file looks good, it's important to ensure that all .env.example files in the project are updated to include the necessary Redis environment variables. This helps developers set up the project correctly.

Please add the following lines to each .env.example file in the project:

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=

This will ensure that all required Redis configuration options are documented and available for setup.

services/workflows-service/src/business-report/business-report.module.ts (1)

Line range hint 1-35: Consider refactoring to reduce circular dependencies.

While the current implementation works, there are several forwardRef uses and eslint-disable comments for import/no-cycle, indicating potential circular dependencies. This could lead to maintenance challenges in the future.

Consider the following suggestions:

  1. Evaluate if all these circular dependencies are necessary. Can some be resolved by restructuring the code?
  2. If circular dependencies are unavoidable, consider creating a separate file for shared interfaces or types to break the cycles.
  3. Document the reasons for circular dependencies, if they must remain, to aid future maintenance.

To help identify the extent of circular dependencies, you can run the following command:

#!/bin/bash
# Description: Identify potential circular dependencies

# Test: Search for forwardRef and import/no-cycle in the project
rg -e "forwardRef" -e "import/no-cycle" services/workflows-service/src

This will help in assessing the overall architecture and identifying areas for potential refactoring.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between dbf0374 and 216bd60.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • services/workflows-service/package.json (4 hunks)
  • services/workflows-service/src/business-report/business-report.module.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
  • services/workflows-service/src/redis/const/redis-config.ts (1 hunks)
  • services/workflows-service/src/workflow/workflow.module.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • services/workflows-service/package.json
  • services/workflows-service/src/env.ts
  • services/workflows-service/src/workflow/workflow.module.ts
🧰 Additional context used
🔇 Additional comments (3)
services/workflows-service/src/redis/const/redis-config.ts (1)

1-2: LGTM: Appropriate imports for Redis configuration

The imports are well-chosen for the file's purpose. Using environment variables (env) for configuration is a good practice, and importing RedisOptions ensures type safety for the Redis configuration.

services/workflows-service/src/business-report/business-report.module.ts (2)

Line range hint 1-35: Overall, the changes look good and align with the PR objectives.

The addition of the AlertModule to the BusinessReportModule is straightforward and consistent with the existing code structure. It enhances the module's capabilities without introducing any apparent issues.

While there are some architectural considerations regarding circular dependencies, these are pre-existing and not introduced by this change. The current modifications serve the PR's purpose of improving Redis integration and enhancing the service's functionality.


12-12: LGTM: AlertModule integration looks good.

The addition of the AlertModule import and its inclusion in the module's imports array is appropriate. This change allows the BusinessReportModule to utilize functionalities provided by the AlertModule, which aligns with the PR's objective of enhancing the service's capabilities.

A few observations:

  1. The import is correctly placed with other module imports.
  2. The forwardRef is used, which is good practice to avoid circular dependencies.
  3. The eslint-disable comment is consistent with other similar imports in this file.

To ensure this change doesn't introduce any unintended side effects, let's verify the AlertModule's usage:

✅ Verification successful

Verified: AlertModule is correctly integrated without introducing circular dependencies. No issues found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any direct usage of AlertModule in BusinessReportModule

# Test: Search for AlertModule usage in business-report files
rg "AlertModule" services/workflows-service/src/business-report

Length of output: 296

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

🧹 Outside diff range and nitpick comments (2)
services/workflows-service/src/workflow/workflow.service.unit.test.ts (2)

185-186: Consider using more specific types for new parameters

The addition of two new parameters of type any to the DocumentChangedWebhookCaller constructor might reduce type safety. Consider the following suggestions:

  1. If possible, use more specific types instead of any to improve type checking and code clarity.
  2. If the exact types are not known in the test environment, consider using unknown instead of any as a safer alternative.
  3. Add comments explaining the purpose of these new parameters to improve code readability.

Example:

new DocumentChangedWebhookCaller(
  // ... other parameters ...
  {} as unknown, // TODO: Replace with actual type and explain its purpose
  {} as unknown, // TODO: Replace with actual type and explain its purpose
);

Enhance Type Safety in WorkflowService Unit Tests

The WorkflowService constructor is being invoked with multiple parameters cast as any, which reduces type safety and may lead to potential runtime errors.

Suggestions for improvement:

  • Use Specific Types: Replace any with the actual types or appropriate mock implementations for each dependency.

    service = new WorkflowService(
      workflowDefinitionRepo as WorkflowDefinitionRepository,
      workflowRuntimeDataRepo as WorkflowRuntimeDataRepository,
      endUserRepo as EndUserRepository,
      businessReportService as BusinessReportService,
      {} as EndUserService, // Replace with a proper mock or specific type
      businessRepo as BusinessRepository,
      businessService as BusinessService,
      entityRepo as EntityRepository,
      customerService as CustomerService,
      {} as EventEmitter, // Replace with a proper mock or specific type
      eventEmitter as EventEmitter,
      testingModule.get(AppLoggerService),
      projectScopeService as ProjectScopeService,
      userService as UserService,
      salesforceService as SalesforceService,
      workflowTokenService as WorkflowTokenService,
      uiDefinitionService as UIDefinitionService,
      {} as RiskRuleService, // Replace with a proper mock or specific type
      riskRuleService as RiskRuleService,
      ruleEngineService as RuleEngineService,
      {} as AdditionalService, // Replace with a proper mock or specific type
      {} as AnotherService, // Replace with a proper mock or specific type
      {} as ExtraService, // Replace with a proper mock or specific type
    );
  • Add Descriptive Comments: Include comments explaining the purpose of each mocked dependency to improve code readability and maintainability.

  • Group Related Dependencies: Consider grouping related parameters into configuration objects to reduce constructor complexity.

Implementing these improvements will enhance the reliability of your tests and ensure that they accurately reflect the application's behavior.

🔗 Analysis chain

Line range hint 189-214: Improve type safety and clarity in WorkflowService constructor

The addition of multiple parameters of type any to the WorkflowService constructor raises some concerns:

  1. Using any type reduces type safety and can lead to potential runtime errors.
  2. The purpose of these new parameters is not clear from the context.

Suggestions for improvement:

  1. Replace any with more specific types or unknown if the exact types are not known in the test environment.
  2. Add comments explaining the purpose of each new parameter.
  3. Consider grouping related parameters into objects to reduce the number of constructor arguments.

Example:

service = new WorkflowService(
  workflowDefinitionRepo as WorkflowDefinitionRepo, // Use actual type instead of any
  // ... other existing parameters ...
  {} as unknown, // TODO: Replace with actual type and explain its purpose
  {} as unknown, // TODO: Replace with actual type and explain its purpose
  {} as unknown, // TODO: Replace with actual type and explain its purpose
);

Additionally, could you provide more context on the purpose of these new parameters? This information would help in understanding the changes and ensuring they align with the PR objectives.

To verify the impact of these changes, let's check for any related modifications in the WorkflowService implementation:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for WorkflowService constructor in the implementation file
rg -A 10 "class WorkflowService" services/workflows-service/src/workflow/workflow.service.ts

Length of output: 172


Script:

#!/bin/bash
# Search for the WorkflowService constructor in the implementation file
rg 'constructor\(' services/workflows-service/src/workflow/workflow.service.ts

Length of output: 95


Script:

#!/bin/bash
# Retrieve the constructor signature with surrounding lines for context
rg -C 5 'constructor\(' services/workflows-service/src/workflow/workflow.service.ts

Length of output: 550

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 216bd60 and ef46137.

📒 Files selected for processing (1)
  • services/workflows-service/src/workflow/workflow.service.unit.test.ts (1 hunks)
🧰 Additional context used
🔇 Additional comments (1)
services/workflows-service/src/workflow/workflow.service.unit.test.ts (1)

Line range hint 1-238: Ensure test coverage remains comprehensive with constructor changes

While the changes to the DocumentChangedWebhookCaller and WorkflowService constructors don't directly modify the test cases, it's important to consider their potential impact on the overall test coverage:

  1. Verify that the existing tests still cover all critical paths and edge cases with these new parameters.
  2. Consider adding new test cases that specifically exercise the functionality related to these new parameters, if applicable.
  3. Ensure that the mock objects and setup in the beforeEach block accurately represent the new dependencies and their behavior.

To help ensure comprehensive test coverage, let's check for any new methods or properties added to the WorkflowService that might need testing:

#!/bin/bash
# Search for new public methods or properties in WorkflowService
rg "public \w+\(|public \w+:" services/workflows-service/src/workflow/workflow.service.ts

This will help identify any new public interfaces that might require additional test cases.

* feat: added dlq logic for each queue

* updated queue worker listener logic

* feat: added pausing and resuming the workers on destroy

* feat: set queue worker logic functionaloity

* feat: finalized functionality of base queue and optional dlq

* feat: added webhook handling logic
@Blokh Blokh changed the title feature/bull_mq_integration ( WIP ) feature/bull_mq_integration Oct 22, 2024
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: 7

🧹 Outside diff range and nitpick comments (4)
services/workflows-service/src/bull-mq/consts.ts (2)

1-1: Consider using a more general import path.

The current import is very specific:

import { BaseJobOptions } from 'bullmq/dist/esm/interfaces';

Consider using a more general import path to make the code more resilient to potential changes in the library's structure:

import { BaseJobOptions } from 'bullmq';

This change might make the code more maintainable in the long run.


3-14: LGTM! Consider using constants for magic numbers.

The structure and configuration of the DEFAULT queue look good. The use of a dead letter queue and an exponential backoff strategy are excellent practices for robust job processing.

To improve code readability and maintainability, consider extracting magic numbers into named constants. For example:

const DEFAULT_ATTEMPTS = 15;
const DEFAULT_BACKOFF_DELAY = 1000;

export const QUEUES = {
  DEFAULT: {
    // ...
    config: {
      attempts: DEFAULT_ATTEMPTS,
      backoff: {
        type: 'exponential',
        delay: DEFAULT_BACKOFF_DELAY,
      },
    },
    // ...
  },
};

This makes it easier to understand the significance of these values and adjust them if needed.

services/workflows-service/src/bull-mq/bull-mq.module.ts (2)

1-40: LGTM! Consider using type aliases for better readability.

The imports and utility functions are well-structured and handle both regular queues and dead-letter queues (DLQs) effectively. The code is type-safe and follows good practices.

For improved readability, consider creating a type alias for (typeof QUEUES)[keyof typeof QUEUES]. For example:

type QueueConfig = (typeof QUEUES)[keyof typeof QUEUES];

function composeQueueAndDlqBoard(queue: QueueConfig) {
  // ...
}

const composeInitiateQueueWithDlq = (queue: QueueConfig) => {
  // ...
};

This would make the function signatures more concise and easier to understand.


45-58: LGTM! Consider adding error handling for Redis connection.

The BullModule configuration is well-structured, using asynchronous setup and centralized Redis configuration. The registration of multiple queues, including DLQs, is handled efficiently.

Consider adding error handling for the Redis connection. For example:

BullModule.forRootAsync({
  useFactory: async () => {
    try {
      // Test the Redis connection here
      return {
        connection: {
          ...REDIS_CONFIG,
        },
      };
    } catch (error) {
      console.error('Failed to connect to Redis:', error);
      throw error; // or handle it as appropriate for your application
    }
  },
}),

This would provide more robust error handling and potentially easier debugging of Redis connection issues.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ef46137 and b20a326.

📒 Files selected for processing (9)
  • apps/backoffice-v2/src/lib/blocks/components/EditableDetails/EditableDetails.tsx (1 hunks)
  • apps/backoffice-v2/src/lib/blocks/hooks/useDocumentBlocks/useDocumentBlocks.tsx (1 hunks)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/consts.ts (1 hunks)
  • services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/types.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/workflows-service/src/env.ts
🧰 Additional context used
🪛 Biome
apps/backoffice-v2/src/lib/blocks/hooks/useDocumentBlocks/useDocumentBlocks.tsx

[error] 412-412: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts

[error] 27-37: This code is unreachable

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)

🔇 Additional comments (19)
services/workflows-service/src/bull-mq/types.ts (1)

1-4: LGTM! Consider the optionality of fields.

The TJobPayloadMetadata type is well-defined and follows TypeScript best practices. It provides a clear structure for job payload metadata, which will enhance type safety and clarity in job processing.

Consider if either projectId or customerName should be required fields. If so, you might want to remove the optional modifier (?) from that field. To help verify the usage of this type across the codebase, you can run the following script:

This will help you determine if these fields are consistently used and if making either of them required would be appropriate.

✅ Verification successful

Verified: Optional fields are appropriately used.

The TJobPayloadMetadata type correctly uses optional fields, allowing flexibility where metadata may not be necessary. No changes are required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of TJobPayloadMetadata type across the codebase

# Search for usage of TJobPayloadMetadata
echo "Searching for usage of TJobPayloadMetadata:"
rg "TJobPayloadMetadata" --type ts

# Check if projectId or customerName are always provided when using this type
echo "Checking if projectId and customerName are always provided:"
rg "TJobPayloadMetadata.*=.*\{" -A 3 --type ts

Length of output: 2858

services/workflows-service/src/bull-mq/consts.ts (3)

15-34: Please clarify the configuration differences between queues.

The INCOMING_WEBHOOKS_QUEUE and OUTGOING_WEBHOOKS_QUEUE configurations are similar to the DEFAULT queue, which is good for consistency. However, there are a few differences that might benefit from explanation:

  1. These queues don't have a dlq (dead letter queue) property. Is this intentional? If so, how will failed jobs be handled for these queues?

  2. The attempts value is set to 10 for these queues, while it's 15 for the DEFAULT queue. Can you explain the reasoning behind this difference?

Providing comments in the code to explain these design decisions would improve maintainability and help future developers understand the rationale behind these configurations.


35-35: LGTM! Good use of type validation.

The use of the satisfies clause to validate the QUEUES object against a Record type is an excellent practice. It ensures type safety while still allowing for type inference. This approach will catch any future modifications that don't adhere to the expected structure, enhancing code reliability and maintainability.


1-35: Overall, well-structured and type-safe queue configurations.

This new file introduces well-organized and type-safe configurations for job queues, which is crucial for robust job processing. The use of TypeScript features, particularly the satisfies clause for type validation, is commendable.

Key points from the review:

  1. Consider using a more general import path for BaseJobOptions.
  2. Extract magic numbers into named constants for better readability.
  3. Clarify the reasoning behind configuration differences between queues (e.g., absence of dlq and different attempts values).

These minor improvements and clarifications will further enhance the maintainability and understandability of the code. Great job on implementing these queue configurations!

services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts (3)

1-7: Imports look good and well-structured.

The imports are correctly specified and seem to cover all the necessary dependencies for the IncomingWebhookQueueService class. The use of absolute imports (starting with '@/') indicates a well-organized project structure.


9-9: Well-defined custom type for incoming webhook jobs.

The TJobsWebhookIncoming type effectively combines the job data (IncomingWebhookData) with metadata (TJobPayloadMetadata). This structure provides a clear and type-safe way to handle incoming webhook jobs.


11-15: Well-structured class definition and constructor.

The IncomingWebhookQueueService class is correctly decorated with @Injectable() for dependency injection. It extends BaseQueueWorkerService with the appropriate generic type, promoting code reuse. The constructor properly initializes the base class with the queue name and logger, following good practices for dependency injection and inheritance.

services/workflows-service/src/bull-mq/bull-mq.module.ts (3)

41-44: LGTM! Module structure and exports are well-defined.

The module structure is well-organized, with appropriate imports, providers, and exports. Exporting both BullModule and OutgoingWebhookQueueService allows for flexible usage of the queue functionality throughout the application.

Also applies to: 66-70


1-70: Overall, excellent implementation with a few points to address.

The BullMqModule is well-structured and implements job queue management effectively using BullMQ. The code is clean, type-safe, and follows good practices for NestJS module development.

Key points to address:

  1. Security: Implement authentication for the '/queues' route to prevent unauthorized access to the Bull Board interface.
  2. Error Handling: Consider adding error handling for the Redis connection in the BullModule configuration.
  3. Type Definitions: For improved readability, consider creating a type alias for the queue configuration type.

These improvements will enhance the robustness and maintainability of the module. Great work overall!


59-65: ⚠️ Potential issue

Secure the '/queues' route to prevent unauthorized access.

The BullBoardModule configuration is correct, but the '/queues' route is currently exposed without authentication or authorization measures. This could potentially allow unauthorized access to sensitive queue information.

To address this security concern:

  1. Implement authentication middleware for the '/queues' route. You can use NestJS guards or middleware for this purpose.

  2. Consider moving the Bull Board setup to a separate admin module that's only loaded in certain environments or behind additional security layers.

Example of adding a guard:

import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Module({
  imports: [
    // ... other imports
    BullBoardModule.forRoot({
      route: '/queues',
      adapter: ExpressAdapter,
    }),
  ],
})
export class BullMqModule {}

// In your app.module.ts or main.ts
app.use('/queues', AuthGuard('your-auth-strategy'));

This will ensure that only authenticated users can access the queue monitoring interface.

services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (3)

1-21: LGTM: Imports and class declaration are well-structured.

The imports are appropriate for the functionality, and the class is properly decorated with @Injectable(). The constructor correctly initializes the logger and webhookService.


71-79: LGTM: retryJob method is well-implemented.

The retryJob method correctly handles the retry logic by:

  1. Calculating the next attempt number.
  2. Logging the retry information.
  3. Updating the job progress.
  4. Moving the job to a delayed state for the next attempt.

This implementation ensures proper tracking and scheduling of retried jobs.


1-80: ⚠️ Potential issue

Address potential SSRF vulnerability in webhook processing.

While the overall structure of the OutgoingWebhookQueueService is well-implemented, there's a potential security concern:

The service doesn't appear to validate the webhook URLs or methods before making requests. This could lead to Server-Side Request Forgery (SSRF) vulnerabilities if untrusted input is provided in the job data.

To address this, consider implementing URL and method validation in the handleJob method or in the OutgoingWebhooksService.invokeWebhook method. Here's a script to verify if such validation exists:

If no validation is found, consider adding it to ensure that only allowed URLs and HTTP methods are processed. For example:

private validateWebhookData(data: WebhookJobData) {
  const allowedDomains = ['example.com', 'api.example.com']; // Configure as needed
  const allowedMethods = ['GET', 'POST', 'PUT', 'DELETE']; // Configure as needed

  const url = new URL(data.url);
  if (!allowedDomains.includes(url.hostname)) {
    throw new Error('Invalid webhook URL');
  }

  if (!allowedMethods.includes(data.method.toUpperCase())) {
    throw new Error('Invalid HTTP method');
  }
}

async handleJob(job: Job<TJobArgs>) {
  this.validateWebhookData(job.data.jobData);
  // ... rest of the method
}

This validation helps prevent potential SSRF attacks by ensuring that only trusted domains and methods are used in webhook requests.

🧰 Tools
🪛 Biome

[error] 27-37: This code is unreachable

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)

services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (5)

1-11: LGTM: Imports and class declaration are well-structured.

The imports cover all necessary dependencies, and the class declaration follows NestJS best practices. The use of generics for job data types provides good flexibility.


50-50: LGTM: Abstract method is well-defined.

The handleJob abstract method is correctly defined with proper use of generics and inclusion of metadata in the job type.


69-142: LGTM: Listener methods are well-implemented.

The worker and queue listener methods are comprehensive and handle various scenarios effectively. The use of a dead letter queue for permanently failed jobs is a good practice. The error handling and logging in these methods provide valuable insights into job processing.


144-165: LGTM: Lifecycle methods are well-implemented.

The onModuleDestroy and onModuleInit methods handle resource management appropriately. The pause, close, and resume operations on the queue are well-handled, with good error logging if the queue fails to resume. This ensures proper cleanup and initialization of resources during the module lifecycle.


1-166: Overall, the BaseQueueWorkerService is well-implemented with a few suggestions for improvement.

The class provides a robust foundation for queue worker services, handling various aspects of queue management, job processing, error handling, and lifecycle management. The use of generics, abstract methods, and comprehensive listener implementations are commendable.

Key points:

  1. Consider improving the environment variable check in the constructor.
  2. Add error handling for queue initialization.
  3. Enhance the addJob method with explicit error handling.
  4. Add a queue system check in the initializeWorker method.

These minor improvements will further enhance the reliability and robustness of the service.

apps/backoffice-v2/src/lib/blocks/components/EditableDetails/EditableDetails.tsx (1)

76-76: Enhancement: Format values consistently using getValue

The introduction of const value = getValue(children); ensures that the Detail component formats the display values appropriately based on their type. This change enhances data consistency and improves the user interface by handling different value types such as dates, date-times, and booleans effectively.

Comment on lines +17 to +22
async handleJob(job: Job<TJobsWebhookIncoming>) {
this.logger.log(`Processing webhook job ${job.id}`);

const { service: workingService, payload } = job.data.jobData;
// TODO - handle the invoking webhook job internally
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement the TODO and add error handling.

The handleJob method has a good start with logging and data extraction. However, there are a few points to address:

  1. The TODO comment indicates that the core functionality is not yet implemented. This needs to be completed to handle the incoming webhook job.
  2. There's no error handling. Consider wrapping the job processing in a try-catch block to handle potential errors.
  3. The method doesn't indicate job completion or failure. Use job.updateProgress(), job.moveToCompleted(), or job.moveToFailed() as appropriate.

Here's a suggested structure for improvement:

async handleJob(job: Job<TJobsWebhookIncoming>) {
  this.logger.log(`Processing webhook job ${job.id}`);

  try {
    const { service: workingService, payload } = job.data.jobData;
    
    // TODO: Implement the core logic to handle the incoming webhook job
    // ...

    // Update progress as needed
    // await job.updateProgress(progress);

    // Mark job as completed
    await job.moveToCompleted('Job processed successfully');
  } catch (error) {
    this.logger.error(`Error processing job ${job.id}: ${error.message}`);
    await job.moveToFailed({ message: 'Job processing failed' });
  }
}

Would you like me to provide a more detailed implementation for handling the incoming webhook job?

Comment on lines 23 to 38
async handleJob(job: Job<TJobArgs>) {
this.logger.log(`Processing webhook job ${job.id}`);

throw new Error('Method not implemented.');
const response = await this.webhookService.invokeWebhook({
...job.data.jobData,
});

this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);

if (response.status >= 200 && response.status < 300) {
return;
}

await this.handleRetryStrategy(response.status, job);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove the throw statement to make the implementation reachable.

The handleJob method contains a throw statement that makes the rest of the method unreachable. This is likely unintended as the actual implementation seems to be present after the throw statement.

Remove the throw statement to make the implementation reachable:

async handleJob(job: Job<TJobArgs>) {
  this.logger.log(`Processing webhook job ${job.id}`);

-  throw new Error('Method not implemented.');
  const response = await this.webhookService.invokeWebhook({
    ...job.data.jobData,
  });

  this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);

  if (response.status >= 200 && response.status < 300) {
    return;
  }

  await this.handleRetryStrategy(response.status, job);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async handleJob(job: Job<TJobArgs>) {
this.logger.log(`Processing webhook job ${job.id}`);
throw new Error('Method not implemented.');
const response = await this.webhookService.invokeWebhook({
...job.data.jobData,
});
this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);
if (response.status >= 200 && response.status < 300) {
return;
}
await this.handleRetryStrategy(response.status, job);
}
async handleJob(job: Job<TJobArgs>) {
this.logger.log(`Processing webhook job ${job.id}`);
const response = await this.webhookService.invokeWebhook({
...job.data.jobData,
});
this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);
if (response.status >= 200 && response.status < 300) {
return;
}
await this.handleRetryStrategy(response.status, job);
}
🧰 Tools
🪛 Biome

[error] 27-37: This code is unreachable

... because this statement will throw an exception beforehand

(lint/correctness/noUnreachable)

Comment on lines +40 to +69
private async handleRetryStrategy(status: number, job: Job<TJobArgs>) {
if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
throw new Error(`Job ${job.id} failed after reaching max attempts`);
}

let delayMs: number;

switch (status) {
case HttpStatusCode.TooManyRequests:
case HttpStatusCode.InternalServerError:
case HttpStatusCode.BadGateway:
case HttpStatusCode.ServiceUnavailable:
case HttpStatusCode.GatewayTimeout:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
break;

case HttpStatusCode.RequestTimeout:
delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
break;

case HttpStatusCode.BadRequest:
throw new Error(`Webhook job failed with status ${status}: Bad Request`);

default:
throw new Error(`Webhook job failed with status ${status}: Unexpected Error`);
}

await this.retryJob(job, delayMs);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve handling of HTTP status codes in handleRetryStrategy.

The current implementation of handleRetryStrategy has a few areas for improvement:

  1. Successful status codes (200-299) are not explicitly handled.
  2. The default case throws an error, which might not be the best approach for unexpected status codes.

Consider updating the method as follows:

private async handleRetryStrategy(status: number, job: Job<TJobArgs>) {
  if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
    this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
    throw new Error(`Job ${job.id} failed after reaching max attempts`);
  }

  let delayMs: number;

  switch (status) {
+   case HttpStatusCode.Ok:
+   case HttpStatusCode.Created:
+   case HttpStatusCode.Accepted:
+     // Success statuses, no action needed
+     return;

    case HttpStatusCode.TooManyRequests:
    case HttpStatusCode.InternalServerError:
    case HttpStatusCode.BadGateway:
    case HttpStatusCode.ServiceUnavailable:
    case HttpStatusCode.GatewayTimeout:
      delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
      break;

    case HttpStatusCode.RequestTimeout:
      delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
      break;

    case HttpStatusCode.BadRequest:
      throw new Error(`Webhook job failed with status ${status}: Bad Request`);

    default:
-     throw new Error(`Webhook job failed with status ${status}: Unexpected Error`);
+     this.logger.warn(`Unexpected status code ${status} for job ${job.id}. Applying exponential backoff.`);
+     delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff for unexpected errors
+     break;
  }

  await this.retryJob(job, delayMs);
}

This update ensures that successful responses are handled correctly and unexpected status codes are retried with exponential backoff instead of throwing an error.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async handleRetryStrategy(status: number, job: Job<TJobArgs>) {
if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
throw new Error(`Job ${job.id} failed after reaching max attempts`);
}
let delayMs: number;
switch (status) {
case HttpStatusCode.TooManyRequests:
case HttpStatusCode.InternalServerError:
case HttpStatusCode.BadGateway:
case HttpStatusCode.ServiceUnavailable:
case HttpStatusCode.GatewayTimeout:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
break;
case HttpStatusCode.RequestTimeout:
delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
break;
case HttpStatusCode.BadRequest:
throw new Error(`Webhook job failed with status ${status}: Bad Request`);
default:
throw new Error(`Webhook job failed with status ${status}: Unexpected Error`);
}
await this.retryJob(job, delayMs);
}
private async handleRetryStrategy(status: number, job: Job<TJobArgs>) {
if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
throw new Error(`Job ${job.id} failed after reaching max attempts`);
}
let delayMs: number;
switch (status) {
case HttpStatusCode.Ok:
case HttpStatusCode.Created:
case HttpStatusCode.Accepted:
// Success statuses, no action needed
return;
case HttpStatusCode.TooManyRequests:
case HttpStatusCode.InternalServerError:
case HttpStatusCode.BadGateway:
case HttpStatusCode.ServiceUnavailable:
case HttpStatusCode.GatewayTimeout:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
break;
case HttpStatusCode.RequestTimeout:
delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
break;
case HttpStatusCode.BadRequest:
throw new Error(`Webhook job failed with status ${status}: Bad Request`);
default:
this.logger.warn(`Unexpected status code ${status} for job ${job.id}. Applying exponential backoff.`);
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff for unexpected errors
break;
}
await this.retryJob(job, delayMs);
}

Comment on lines +56 to +67
protected initializeWorker() {
this.worker = new Worker(
this.queueName,
async (job: Job<{ jobData: T; metadata: TJobPayloadMetadata }>) => {
await this.handleJob(job);
},
{ connection: this.connectionOptions },
);

this.addWorkerListeners();
this.addQueueListeners();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add queue system check in initializeWorker.

Consider adding a check to ensure the queue system is enabled before initializing the worker:

   protected initializeWorker() {
+    if (!this.queue) {
+      this.logger.warn('Attempted to initialize worker while queue system is disabled');
+      return;
+    }
     this.worker = new Worker(
       // ... rest of the method
     );
     this.addWorkerListeners();
     this.addQueueListeners();
   }

This change prevents attempting to initialize a worker when the queue system is disabled.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected initializeWorker() {
this.worker = new Worker(
this.queueName,
async (job: Job<{ jobData: T; metadata: TJobPayloadMetadata }>) => {
await this.handleJob(job);
},
{ connection: this.connectionOptions },
);
this.addWorkerListeners();
this.addQueueListeners();
}
protected initializeWorker() {
if (!this.queue) {
this.logger.warn('Attempted to initialize worker while queue system is disabled');
return;
}
this.worker = new Worker(
this.queueName,
async (job: Job<{ jobData: T; metadata: TJobPayloadMetadata }>) => {
await this.handleJob(job);
},
{ connection: this.connectionOptions },
);
this.addWorkerListeners();
this.addQueueListeners();
}

Comment on lines +52 to +54
async addJob(jobData: T, metadata: TJobPayloadMetadata = {}, jobOptions = {}): Promise<void> {
await this.queue?.add(this.queueName, { metadata, jobData }, jobOptions);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve error handling in addJob method.

Consider adding explicit error handling when the queue is undefined:

   async addJob(jobData: T, metadata: TJobPayloadMetadata = {}, jobOptions = {}): Promise<void> {
-    await this.queue?.add(this.queueName, { metadata, jobData }, jobOptions);
+    if (!this.queue) {
+      this.logger.warn('Attempted to add job while queue system is disabled');
+      throw new Error('Queue system is disabled. Cannot add job.');
+    }
+    try {
+      await this.queue.add(this.queueName, { metadata, jobData }, jobOptions);
+    } catch (error) {
+      this.logger.error(`Failed to add job to queue: ${error.message}`);
+      throw error;
+    }
   }

This change ensures that callers are aware when jobs cannot be added due to a disabled queue system and provides better error logging.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async addJob(jobData: T, metadata: TJobPayloadMetadata = {}, jobOptions = {}): Promise<void> {
await this.queue?.add(this.queueName, { metadata, jobData }, jobOptions);
}
async addJob(jobData: T, metadata: TJobPayloadMetadata = {}, jobOptions = {}): Promise<void> {
if (!this.queue) {
this.logger.warn('Attempted to add job while queue system is disabled');
throw new Error('Queue system is disabled. Cannot add job.');
}
try {
await this.queue.add(this.queueName, { metadata, jobData }, jobOptions);
} catch (error) {
this.logger.error(`Failed to add job to queue: ${error.message}`);
throw error;
}
}

Comment on lines +17 to +48
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};

if (!env.QUEUE_SYSTEM_ENABLED) {
return;
}

const currentQueue = Object.entries(QUEUES).find(
([_, queueOptions]) => queueOptions.name === queueName,
);

if (!currentQueue) {
throw new Error(`Queue configuration of ${queueName} not found in QUEUES`);
}

const queueConfig = currentQueue[1];
this.queue = new Queue(queueName, {
connection: this.connectionOptions,
defaultJobOptions: {
...queueConfig.config,
},
});

this.deadLetterQueue =
'dlq' in queueConfig
? new Queue(queueConfig.dlq, { connection: this.connectionOptions })
: undefined;

this.initializeWorker();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve environment variable check and consider error handling for queue initialization.

  1. The environment variable check should compare to 'true' as a string:
-    if (!env.QUEUE_SYSTEM_ENABLED) {
+    if (env.QUEUE_SYSTEM_ENABLED !== 'true') {
  1. Consider adding error handling for queue initialization:
+    try {
       this.queue = new Queue(queueName, {
         connection: this.connectionOptions,
         defaultJobOptions: {
           ...queueConfig.config,
         },
       });
+    } catch (error) {
+      this.logger.error(`Failed to initialize queue: ${error.message}`);
+      return;
+    }

These changes will ensure proper boolean comparison for the environment variable and add robustness to queue initialization.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};
if (!env.QUEUE_SYSTEM_ENABLED) {
return;
}
const currentQueue = Object.entries(QUEUES).find(
([_, queueOptions]) => queueOptions.name === queueName,
);
if (!currentQueue) {
throw new Error(`Queue configuration of ${queueName} not found in QUEUES`);
}
const queueConfig = currentQueue[1];
this.queue = new Queue(queueName, {
connection: this.connectionOptions,
defaultJobOptions: {
...queueConfig.config,
},
});
this.deadLetterQueue =
'dlq' in queueConfig
? new Queue(queueConfig.dlq, { connection: this.connectionOptions })
: undefined;
this.initializeWorker();
}
protected constructor(protected queueName: string, protected readonly logger: AppLoggerService) {
this.connectionOptions = {
...REDIS_CONFIG,
};
if (env.QUEUE_SYSTEM_ENABLED !== 'true') {
return;
}
const currentQueue = Object.entries(QUEUES).find(
([_, queueOptions]) => queueOptions.name === queueName,
);
if (!currentQueue) {
throw new Error(`Queue configuration of ${queueName} not found in QUEUES`);
}
const queueConfig = currentQueue[1];
try {
this.queue = new Queue(queueName, {
connection: this.connectionOptions,
defaultJobOptions: {
...queueConfig.config,
},
});
} catch (error) {
this.logger.error(`Failed to initialize queue: ${error.message}`);
return;
}
this.deadLetterQueue =
'dlq' in queueConfig
? new Queue(queueConfig.dlq, { connection: this.connectionOptions })
: undefined;
this.initializeWorker();
}

@@ -409,6 +409,7 @@ export const useDocumentBlocks = ({
}

if (ocrResult?.parsedData?.[title]) {
debugger;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove the debugger statement to prevent unintended pauses in execution.

The debugger; statement on line 412 can cause the application to halt unexpectedly, which is undesirable in production code.

Apply this diff to remove the debugger statement:

-                              debugger;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
debugger;
🧰 Tools
🪛 Biome

[error] 412-412: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

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.

5 participants