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

wip: Full refactor of the server to swift with vapor #29

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

Dracks
Copy link
Owner

@Dracks Dracks commented Nov 5, 2024

Summary by CodeRabbit

Based on the comprehensive summary of changes, here are the high-level release notes for end-users:

Release Notes: Mr Scrooge v3.0.0

New Features

  • Enhanced bank transaction management with improved label linking and filtering.
  • New graph visualization options with customizable date ranges and grouping.
  • Expanded import functionality supporting multiple bank file formats.
  • Improved user and group management with admin controls.
  • Refined transaction rules and labeling system.
  • Introduction of a new configuration file for managing settings and tools.

Improvements

  • Performance optimizations for transaction and graph data retrieval.
  • More robust error handling and logging.
  • Enhanced API with comprehensive OpenAPI specification.
  • Improved user interface with responsive design.
  • Better pagination and cursor-based navigation.
  • Added support for Swift 6.0.

Bug Fixes

  • Fixed various edge cases in transaction import and processing.
  • Resolved issues with user authentication and session management.

Breaking Changes

  • Updated API structure and endpoints.
  • Migrated from tags to labels for transaction categorization.

Platform Support

  • Improved cross-platform compatibility.

Copy link

coderabbitai bot commented Nov 5, 2024

Walkthrough

This pull request introduces a comprehensive update to the Mr Scrooge project, spanning multiple components including React frontend, Swift backend, and various configuration files. The changes establish a new project structure, enhance type safety, improve error handling, and introduce new features for managing bank transactions, graphs, labels, and user sessions. The update includes new API implementations, data models, migration strategies, and testing infrastructure across both TypeScript and Swift codebases.

Changes

File/Group Change Summary
.coderabbit.yaml New configuration file added with settings for language, tone, and review workflows.
.env.testing New environment variable DB_URL added for SQLite in-memory testing.
.eslintrc.yml New ESLint configuration file added for TypeScript linting rules.
.github/workflows/test-swift.yml New workflow for automating Swift project testing.
.github/workflows/test-view.yml Updated workflow for Node.js package testing with pnpm.
.gitignore Modified to ignore new Swift-related files and updated existing ignore rules.
.swift-format New Swift formatting rules established.
Package.swift New Swift package file created with dependencies and targets.
src/react-view/api/client.tsx New API client context created for managing API requests.
src/react-view/contents/admin/users New components added for user management, including creating and editing users.
src/swift-server/bank-transaction New models and services for handling bank transactions and associated labels.
src/swift-server/importer New importer classes for handling various bank transaction formats.
Testing Infrastructure Comprehensive test suites added for both Swift server components and React frontend.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Frontend
    participant Backend
    participant Database

    Client->>Frontend: Login Request
    Frontend->>Backend: Authenticate Session
    Backend->>Database: Validate Credentials
    Database-->>Backend: User Authenticated
    Backend-->>Frontend: Session Token
    Frontend-->>Client: Logged In

    Client->>Frontend: Request Bank Transactions
    Frontend->>Backend: Fetch Transactions
    Backend->>Database: Query Transactions
    Database-->>Backend: Return Transactions
    Backend-->>Frontend: Transactions List
    Frontend-->>Client: Display Transactions
Loading

Possibly Related PRs

Poem

🐰 A Rabbit's Tale of Code Delight

In Swift and React, we take flight
Models dancing, types so bright
Transactions flow with pure insight

From config to test, we've built it right
Mr Scrooge's system shines tonight!

🚀✨


🪧 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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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.

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.

@Dracks
Copy link
Owner Author

Dracks commented Nov 5, 2024

@coderabbitai review

Copy link

coderabbitai bot commented Nov 5, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Dracks and others added 6 commits November 5, 2024 19:44
* updating the client to fix stuff

* Adding admin users

* Fixing coderabbit comments

* dropping old view

* preparing the creation of users

* User admin and almost all linting fixed
#37)

* Fixing with a workarround the problems of caixa-enginyers with swift 6

* Some small suggestions to improve the code
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.

🛑 Comments failed to post (110)
src/swift-server/core/helper.swift (2)

28-36: 🛠️ Refactor suggestion

Fix inconsistent type alias and simplify Dictionary extension.

  1. The Element type alias should be ValueType for consistency with the protocol.
  2. The get(_:) method is redundant since Dictionary's subscript already returns an optional.
 extension Dictionary: IndexedCollection {
     typealias IndexType = Key
-    typealias Element = Value
+    typealias ValueType = Value

     func get(_ index: Key) -> Value? {
-        return self[index]
+        self[index]
     }
 }
📝 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.

// Extend Dictionary to conform to IndexedCollection with IndexType as the Dictionary's Key
extension Dictionary: IndexedCollection {
    typealias IndexType = Key
    typealias ValueType = Value

    func get(_ index: Key) -> Value? {
        self[index]
    }
}

15-26: 🛠️ Refactor suggestion

Eliminate code duplication in Array extension.

The get(_:) method duplicates the functionality of the safe subscript. Consider reusing the safe subscript implementation.

 extension Array: IndexedCollection {
     typealias IndexType = Int
     typealias ValueType = Element

     func get(_ index: Int) -> Element? {
-        if self.count > index {
-            return self[index]
-        }
-        return nil
+        return self[safe: index]
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server-tests/factories.swift (1)

40-51: 🛠️ Refactor suggestion

Improve date handling in BankTransactionFactory.

The current implementation has two potential issues:

  1. Force unwrapping the date (!) could cause crashes if the date components are invalid
  2. Hardcoded year 2022 could make tests fragile over time

Consider this safer implementation:

     override func create(id: Int) -> BankTransaction {
-        let dateComponents = DateComponents(year: 2022, month: 2, day: 2)
-        let date = Calendar.current.date(from: dateComponents)!
+        guard let date = Calendar.current.date(byAdding: .day, value: -id, to: Date()) else {
+            fatalError("Failed to create date for BankTransaction")
+        }
         return BankTransaction(
             id: UUID(),
             groupOwnerId: UUID(),
             movementName: "movement \(id)",
             date: DateOnly(date),
             value: Double(id) + Double(id) / 100.0,
             kind: "demo"
         )
     }
📝 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.

override func create(id: Int) -> BankTransaction {
        guard let date = Calendar.current.date(byAdding: .day, value: -id, to: Date()) else {
            fatalError("Failed to create date for BankTransaction")
        }
        return BankTransaction(
            id: UUID(),
            groupOwnerId: UUID(),
            movementName: "movement \(id)",
            date: DateOnly(date),
            value: Double(id) + Double(id) / 100.0,
            kind: "demo"
        )
    }
src/swift-server/configure.swift (1)

16-18: ⚠️ Potential issue

Potential crash due to accessing out-of-range index in components

The code assumes that dbUrl will always contain a colon ":", and thus components will have at least two elements. If dbUrl is malformed and doesn't contain a colon, accessing components[1] at line 18 will cause a runtime error due to an out-of-range index.

Apply this diff to add a validation check:

 func configureDb(_ app: Application) async throws {
     var dbUrl = Environment.get("DB_URL") ?? "sqlite://db.sqlite3"
     if !dbUrl.starts(with: "sqlite://") && !dbUrl.starts(with: "postgres://") {
         print("Warning: Invalid DB_URL format. Using default.")
         dbUrl = "sqlite://db.sqlite3"
     }

+    guard dbUrl.contains(":") else {
+        throw Exception(.E10003, context: ["db_url": dbUrl])
+    }

     let components = dbUrl.split(separator: ":", maxSplits: 1)
     let dbType = components[0].lowercased()
     let filePath = String(components[1].dropFirst(2))

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/core/exception.swift (1)

40-53: ⚠️ Potential issue

Avoid exposing internal details in toJSON method.

Including sensitive information such as file, line, and potentially the entire cause object in the JSON output can lead to security risks by revealing internal implementation details. Consider omitting or sanitizing this information before sending it to clients.

Suggested change:

	func toJSON() -> [String: Any] {
		var dict: [String: Any] = [
			"errorCode": errorCode.rawValue,
			"message": errorCode.message,
-			"context": context,
-			"file": file,
-			"line": line,
		]
-		if let cause = cause {
-			dict["cause"] = cause
-		}
		return dict
	}

If additional context is necessary for the client, ensure that it is safe to expose and properly sanitized.

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

	func toJSON() -> [String: Any] {
		var dict: [String: Any] = [
			"errorCode": errorCode.rawValue,
			"message": errorCode.message,
		]
		return dict
	}
src/react-view/contents/common/transaction.context.tsx (2)

35-35: ⚠️ Potential issue

Handle cases where labels may not be found.

The labels.find(...) function may return undefined if a label with the specified id is not found. Casting it to Label without checking can lead to runtime errors. Consider handling undefined values appropriately.

Suggested change:

-        labelsComplete: rds.labelIds.map(labelId => labels.find(({ id }) => id === labelId) as Label),
+        labelsComplete: rds.labelIds
+            .map(labelId => labels.find(({ id }) => id === labelId))
+            .filter((label): label is Label => label !== undefined),

Alternatively, handle the undefined case based on your application's requirements.

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

        labelsComplete: rds.labelIds
            .map(labelId => labels.find(({ id }) => id === labelId))
            .filter((label): label is Label => label !== undefined),

56-56: ⚠️ Potential issue

Fix error handling and clean up commented code.

The commented-out ErrorHandler component references query.error, but query is not defined in this scope. If you intend to display the error, use paginator.error and remove the commented code for clarity.

Suggested change:

-        return <div>Some Error happened loading Bank Transactions </div>//<ErrorHandler error={query.error} />;
+        return <ErrorHandler error={paginator.error} />;

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/label/label-api-impl.swift (1)

65-74: 🛠️ Refactor suggestion

Implement the ApiLabels_update and ApiLabels_delete methods.

The ApiLabels_update and ApiLabels_delete methods currently return a 501 status code, indicating that they are not implemented. To provide full label management functionality, consider implementing these methods or, if not yet planned, clearly document the intended behavior.

src/swift-server/importer/importers/import-helper.swift (1)

90-95: ⚠️ Potential issue

Avoid force-unwrapping optionals to prevent runtime crashes.

In the initialization of PartialBankTransaction, the properties movementName, date, and value are force-unwrapped. Although you check for missing fields earlier, it's safer to handle these optionals explicitly to prevent potential runtime crashes.

Apply this diff to safely unwrap the optionals:

-    return PartialBankTransaction(
-        movementName: movementName!,
-        date: DateOnly(date!),
-        dateValue: dateValue.map { DateOnly($0) },
-        details: details,
-        value: value!,
-        description: description
-    )
+    guard let movementName = movementName, let date = date, let value = value else {
+        throw Exception(.E10004, context: ["invalidFields": errorFields])
+    }
+    return PartialBankTransaction(
+        movementName: movementName,
+        date: DateOnly(date),
+        dateValue: dateValue.map { DateOnly($0) },
+        details: details,
+        value: value,
+        description: description
+    )

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/importer/importer.service.swift (1)

74-76: ⚠️ Potential issue

Include created date in the cursor for consistent pagination.

When generating the next cursor, only the id is included. However, the pagination logic expects both created and id in the cursor. Omitting the created date can cause issues when parsing the cursor for the next page of results.

Apply this diff to include created in the cursor:

 let nextCursor = lastElement.flatMap {
-    cursorHandler.stringify(["id": $0.id?.uuidString ?? ""])
+    cursorHandler.stringify([
+        "created": $0.createdAt?.rfc1123 ?? "",
+        "id": $0.id?.uuidString ?? ""
+    ])
 }

Additionally, ensure that createdAt is not nil before using it.

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

		let nextCursor = lastElement.flatMap {
			cursorHandler.stringify([
				"created": $0.createdAt?.rfc1123 ?? "",
				"id": $0.id?.uuidString ?? ""
			])
		}
src/swift-server/routes.swift (2)

7-41: 🛠️ Refactor suggestion

Use a logging framework instead of print statements for error handling

The ErrorHandlerMiddleware uses print statements to log errors, which is not recommended for production environments. Consider using a logging framework like Vapor's built-in Logger to handle logging with appropriate log levels.

Refactor the middleware to use request.logger:

- print("Server Error")
- print(error.underlyingError)
- print(type(of: error.underlyingError))
+ request.logger.error("Server Error: \(error.underlyingError)")

Similarly, replace other print statements with appropriate logging methods.

Committable suggestion skipped: line range outside the PR's diff.


17-32: ⚠️ Potential issue

Avoid exposing sensitive error information in responses

When handling errors, ensure that sensitive information is not exposed to the client. The current implementation might leak internal details through error messages.

Modify the error handling to provide user-friendly error messages and log detailed errors internally:

- throw Abort(
-     .badRequest,
-     reason: String(describing: decodingError))
+ request.logger.error("Decoding Error: \(decodingError)")
+ throw Abort(.badRequest, reason: "Invalid request data")

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/importer/importers/commerz-bank.importer.swift (3)

6-8: 🛠️ Refactor suggestion

Avoid force-try when initializing regular expressions

Using try! can lead to runtime crashes if the regex pattern is invalid. Although the pattern appears correct, it's safer to handle potential initialization errors.

Replace with a do-catch block or use guard let:

let DATE_REGEX: NSRegularExpression

init() {
    do {
        DATE_REGEX = try NSRegularExpression(
            pattern: "(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})T\\d{2}:\\d{2}:\\d{2}",
            options: []
        )
    } catch {
        fatalError("Invalid regex pattern: \(error)")
    }
    // Rest of the initialization
}

81-88: ⚠️ Potential issue

Handle potential nil values safely when accessing array elements

When accessing row.get(3) and row.get(4), ensure that array bounds are respected, and handle cases where the expected indices might not exist to prevent runtime errors.

Add checks for array bounds:

guard row.count > 3, let originalMovement = row.get(3)
else {
    throw Exception(.E10012)
}

guard row.count > 4, let originalValue = row.get(4) else {
    throw Exception(.E10013)
}

95-96: 💡 Codebase verification

Add empty string validation as suggested

The current implementation only validates that originalMovement is not nil, but doesn't check for empty strings. Since splitMessage(_:) performs various string operations, an empty string could lead to unexpected behavior.

  • Add the suggested empty string check after the existing nil check at line 95:
guard !originalMovement.isEmpty else {
    throw Exception(.E10014, context: ["line": lineCounter])
}
🔗 Analysis chain

Validate the length of originalMovement before processing

Ensure that originalMovement is not empty before passing it to splitMessage(_:) to prevent unexpected errors.

Add a check:

guard !originalMovement.isEmpty else {
    throw Exception(.E10014, context: ["line": lineCounter])
}
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Get the complete implementation of the commerz-bank importer
cat src/swift-server/importer/importers/commerz-bank.importer.swift

# Search for splitMessage method definition
ast-grep --pattern 'func splitMessage($_) {
  $$$
}'

# Search for existing validations of originalMovement
rg "originalMovement" -A 2 -B 2

Length of output: 4560

src/swift-server/importer/importers/caixa-enginyers.importer.swift (2)

131-131: ⚠️ Potential issue

Correct class name typo from 'CaixaEnginiers' to 'CaixaEnginyers'

The class name CaixaEnginiersCreditImporter appears to have a typo. To maintain consistency and avoid confusion, correct the spelling to CaixaEnginyersCreditImporter.

Apply this diff to fix the class name:

-class CaixaEnginiersCreditImporter: CaixaEnginyersAbstractImporter {
+class CaixaEnginyersCreditImporter: CaixaEnginyersAbstractImporter {

Committable suggestion skipped: line range outside the PR's diff.


39-39: ⚠️ Potential issue

Safely unwrap optional value for cellData[1]

Accessing cellData[1] without checking for its existence may lead to a runtime crash if the key 1 does not exist in the dictionary. To prevent this, use optional binding to safely unwrap the value.

Apply this diff to safely access cellData[1]:

-if cellData[1] != "" {
+if let cellValue = cellData[1], !cellValue.isEmpty {

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/importer/importer-api-impl.swift (1)

85-85: ⚠️ Potential issue

Validate and sanitize file extensions in generated file paths

Directly using upload.file.extension in the file path without validation may introduce security risks, such as directory traversal or unauthorized file access. Ensure that the file extension is validated against a whitelist of allowed extensions and sanitize the input to prevent potential vulnerabilities.

Consider adding validation like:

let allowedExtensions = ["csv", "xls", "xlsx"]
let fileExtension = (upload.file.extension ?? "unknown").lowercased()
guard allowedExtensions.contains(fileExtension) else {
    throw Abort(.unsupportedMediaType, reason: "Unsupported file extension.")
}
let filePath = "\(tmpDir)/mr-scrooge-\(UUID().uuidString).\(fileExtension)"
src/react-view/contents/transaction-list/transaction-list.tsx (2)

52-57: ⚠️ Potential issue

Handle potential undefined kindRequest.result in options

When kindRequest.result is undefined, accessing data.parsers may cause an error. Ensure that the options array handles undefined values safely to prevent runtime exceptions.

Adjust the options initialization to handle undefined kindRequest.result:

{
    <Select
        placeholder="Filter by importer"
        options={[
            '',
-           ...(kindRequest.result?.data?.parsers ?? []).map((kind: {name: string}) => kind.name),
+           ...((kindRequest.result?.data?.parsers ?? []) as { name: string }[]).map((kind) => kind.name),
        ]}
        value={filters.kind}
        onChange={({ option }) => {
            setFilters({ ...filters, kind: option as string });
        }}
    />
}
📝 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.

                                options={['', ...((kindRequest.result?.data?.parsers ?? []) as { name: string }[]).map((kind) => kind.name)]}
                                value={filters.kind}
                                onChange={({ option }) => {
                                    setFilters({ ...filters, kind: option as string });
                                }}
                            />

68-70: ⚠️ Potential issue

Type consistency in label filter value

Ensure that the value and onChange handler for the label filter maintain type consistency. The value should match the valueKey, and the onChange should correctly handle the selected value.

Adjust the Select component to maintain type consistency:

<Select
    placeholder="Filter by label"
    options={labelOptions}
    labelKey="name"
    valueKey={{ key: 'id', reduce: true }}
    value={filters.label}
    onChange={({ value }) => {
-       setFilters({ ...filters, label: label as string });
+       setFilters({ ...filters, label: value as string });
    }}
/>

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/importer/importers/new-import.service.swift (1)

83-129: ⚠️ Potential issue

Correct the typo in the variable name from discarting to discarding

The variable discarting appears to be a misspelling of discarding. Renaming it will enhance code readability and prevent potential confusion.

Apply this diff to fix the typo:

-			var discarting = true
+			var discarding = true

...
-					if discarting {
+					if discarding {

...
-						discarting = true
+						discarding = true

...
-					discarting = false
+					discarding = false
📝 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.

			var discarding = true
			var previous: BankTransaction?
			var previousState: FileImportRow?

			for try await partialTransaction in source {
				let transaction = partialTransaction.toBankTransaction(
					kind: key, groupOwnerId: groupOwnerId)
				let statusTransaction = generateStatusTransaction(
					status: status, transaction: partialTransaction)

				if try await bankTransactionService.existsSimilar(
					transaction: transaction)
				{
					if discarding {
						let msg = "Repeated row, not inserted"
						status.status = .warn
						statusTransaction.message = msg
						if let previousStateValidated = previousState {
							previousStateValidated.message = msg
							try await previousStateValidated.save(
								on: db)
							previousState = nil
						}
						try await statusTransaction.save(on: db)
					} else {
						previous = transaction
						previousState = statusTransaction
						discarding = true
					}
				} else {
					if let previousValidated = previous,
						let previousStateValidated = previousState
					{
						let record =
							try await bankTransactionService
							.addTransaction(
								transaction: previousValidated)
						previousStateValidated.message =
							"Repeated row, but inserted"
						previousStateValidated.transactionId =
							try record.requireID()
						try await previousStateValidated.save(on: db)
						previous = nil
						previousState = nil
					}
					discarding = false
					let record =
src/swift-server/graph/graph-api-impl.swift (2)

94-103: 🛠️ Refactor suggestion

Return Bad Request (400) for invalid UUID instead of Not Found (404)

When the provided graphId is not a valid UUID, returning a Bad Request response is more appropriate than Not Found, as the client supplied an invalid input.

Apply this diff to change the response:

-			return .notFound(
+			return .badRequest(
📝 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.

		guard let graphId = UUID(uuidString: input.path.id) else {
			return .badRequest(
				.init(
					body: .json(
						.init(
							value2: .init(
								message: "Id is not an uuid",
								code: ApiError.API10007.rawValue))))
			)
		}

138-146: 🛠️ Refactor suggestion

Return Bad Request (400) for invalid UUID instead of Not Found (404)

Similar to the update method, when the graphId is invalid, a Bad Request response should be returned to indicate the client error.

Apply this diff to change the response:

-			return .notFound(
+			return .badRequest(
📝 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.

		guard let graphId = UUID(uuidString: input.path.id) else {
			return .badRequest(
				.init(
					body: .json(
						.init(
							message: "Graph ID is not an UUID",
							code: ApiError.API10009.rawValue))))
		}

src/swift-server/bank-transaction/bank-transaction.service.swift (1)

179-189: ⚠️ Potential issue

Correct the return value after unlinking a label

After deleting the LabelTransaction pivot, the method returns .linkNotFound, which is inaccurate since the unlinking was successful.

Add a return statement to indicate success:

         try await labelPivot.delete(on: db)
+        return .ok
     }
 }
 return .linkNotFound

This ensures the method correctly reflects the outcome of the operation.

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

		if let labelPivot = labelPivot {
			if labelPivot.linkReason == .automatic {
				labelPivot.linkReason = .manualDisabled
				try await labelPivot.save(on: db)
				return .ok
			} else {
				try await labelPivot.delete(on: db)
				return .ok
			}
		}
		return .linkNotFound
	}
src/swift-server/bank-transaction/bank-transaction-api-impl.swift (3)

62-62: 🛠️ Refactor suggestion

Implement enum for link_reason

The #warning directive on line 62 indicates a need to add an enum for link_reason. Defining an enum will enhance type safety and ensure that only valid values are used, improving code reliability and maintainability.


13-17: 🛠️ Refactor suggestion

Provide detailed error responses for invalid UUIDs

In the guard statements at lines 13-17, 71-75, and 157-166, when UUID parsing fails, the methods return a 400 status code with an undocumented payload:

return .undocumented(statusCode: 400, UndocumentedPayload())

Consider returning a structured error response with a meaningful message indicating that the provided transaction or label ID is invalid. This will improve API usability by providing clients with clear feedback on why their request failed.

Also applies to: 71-75, 157-166


31-31: ⚠️ Potential issue

Avoid force unwrapping to prevent potential crashes

In lines 31, 89, and 195, the code force unwraps the first element of transaction.list:

bankTransaction: transaction.list.first!

If transaction.list is empty, this will cause a runtime crash. To ensure stability, use optional binding:

if let firstTransaction = transaction.list.first {
    bankTransaction: firstTransaction
} else {
    // Handle the empty case appropriately
}

Or throw an error if an empty list is unexpected in this context.

Also applies to: 89-89, 195-195

src/swift-server/migrations/initial-migration.swift (1)

62-62: 🛠️ Refactor suggestion

Implement enum for link_reason in the database schema

The #warning directive on line 62 indicates an intention to use an enum for the link_reason field:

#warning("Add enum for the link_reason")

Defining an enum type for link_reason at the database level will ensure data consistency and simplify value validation in your application logic.

🧰 Tools
🪛 GitHub Actions: Test Server

[warning] 62-62: Add enum for the link_reason

src/react-view/utils/data-factory/uuid.factory.ts (1)

3-4: ⚠️ Potential issue

Fix syntax error in template string

There is an extra closing brace in the template string at line 4, which will cause a syntax error. Remove the extra brace to correct the syntax.

Apply this diff to fix the error:

 export function generateUUID(id: number, prefix: string): ApiUUID {
-    return `${prefix}-0000-${String(id)}}`;
+    return `${prefix}-0000-${String(id)}`;
 }
📝 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.

export function generateUUID(id: number, prefix: string): ApiUUID {
    return `${prefix}-0000-${String(id)}`;
config/setupTests.js (1)

6-6: 💡 Codebase verification

Uncomment reflect-metadata import - required for NestJS

The reflect-metadata import should not be commented out as it's required by NestJS framework for its dependency injection system. The application uses NestJS (imported in cli.ts), which depends on metadata reflection to function properly.

🔗 Analysis chain

Verify if reflect-metadata is still needed

The commented-out reflect-metadata import might be required if the codebase uses decorators or metadata reflection. Please verify if this dependency can be safely removed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for decorator usage in the codebase
rg '@\w+\(' --type ts

Length of output: 23


Script:

#!/bin/bash
# Check for TypeScript configuration and usage
fd tsconfig.json

# Check for any TypeScript files
fd -e ts -e tsx

# Search for reflect-metadata in all files
rg "reflect-metadata"

# Search for experimental decorators configuration
rg '"experimentalDecorators"' -A 2

Length of output: 5281


Script:

#!/bin/bash
# Search for specific decorator patterns that commonly require metadata
rg "@Injectable|@Inject|@Service|@Entity|@Column|@Repository"

# Search for any class property decorators
rg "@\w+\s+\w+[:;]"

# Look for dependency injection related imports
rg "import.*['\"]@nestjs/|import.*['\"]inversify|import.*['\"]typeorm['\"]"

Length of output: 3217

src/react-view/api/upload-file.tsx (1)

3-17: ⚠️ Potential issue

Add error handling and security measures

The current implementation lacks several important security and reliability features:

  1. Error handling for network failures
  2. File type validation
  3. File size limits
  4. CSRF protection

Here's a suggested implementation:

 export const usePostUploadFile = () => {
-    const uploadRequest = useAsyncCallback((kind: string, file: File)=>{
+    const uploadRequest = useAsyncCallback(async (kind: string, file: File)=>{
+        // Validate file type
+        const allowedTypes = ['text/csv', 'application/json'];
+        if (!allowedTypes.includes(file.type)) {
+            throw new Error(`Unsupported file type: ${file.type}`);
+        }
+
+        // Validate file size (e.g., 10MB limit)
+        const MAX_SIZE = 10 * 1024 * 1024;
+        if (file.size > MAX_SIZE) {
+            throw new Error('File size exceeds limit');
+        }
+
         const form = new FormData();
         form.append('kind', kind);
         form.append('file', file, file.name)

-        return fetch(
+        const response = await fetch(
             '/api/imports/',
             {
                 method: 'POST',
                 body: form,
+                headers: {
+                    // Add CSRF token if your framework requires it
+                    // 'X-CSRF-Token': getCsrfToken(),
+                },
-            });
+            }
+        );
+
+        if (!response.ok) {
+            throw new Error(`Upload failed: ${response.statusText}`);
+        }
+
+        return response;
     });
     return uploadRequest
 }
📝 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.

export const usePostUploadFile = () => {
    const uploadRequest = useAsyncCallback(async (kind: string, file: File)=>{
        // Validate file type
        const allowedTypes = ['text/csv', 'application/json'];
        if (!allowedTypes.includes(file.type)) {
            throw new Error(`Unsupported file type: ${file.type}`);
        }

        // Validate file size (e.g., 10MB limit)
        const MAX_SIZE = 10 * 1024 * 1024;
        if (file.size > MAX_SIZE) {
            throw new Error('File size exceeds limit');
        }

        const form = new FormData();
        form.append('kind', kind);
        form.append('file', file, file.name)

        const response = await fetch(
            '/api/imports/',
            {
                method: 'POST',
                body: form,
                headers: {
                    // Add CSRF token if your framework requires it
                    // 'X-CSRF-Token': getCsrfToken(),
                },
            }
        );

        if (!response.ok) {
            throw new Error(`Upload failed: ${response.statusText}`);
        }

        return response;
    });
    return uploadRequest
}
src/react-view/contents/admin/users/user-switcher.tsx (1)

12-17: 🛠️ Refactor suggestion

Add error handling and optimize user lookup.

The current implementation has several potential issues:

  1. No validation for the id parameter format
  2. Direct array search might be inefficient for large datasets
  3. No loading state handling

Consider this improved implementation:

 export const UserSwitcher : React.FC<UserSwitcherParams> = ({users})=>{
     const {id} = useParams()
-    const userDetails = users.find(user => user.id === id)
-    console.log(id, users)
-    return userDetails ? <EditUser user={userDetails} /> : <NotFound />
+    if (!id) {
+        return <NotFound message="User ID is required" />
+    }
+    
+    // Consider using useMemo if users array is large
+    const userDetails = React.useMemo(
+        () => users.find(user => user.id === id),
+        [users, id]
+    )
+    
+    return userDetails ? <EditUser user={userDetails} /> : <NotFound message="User not found" />
 }
📝 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.

export const UserSwitcher : React.FC<UserSwitcherParams> = ({users})=>{
    const {id} = useParams()
    if (!id) {
        return <NotFound message="User ID is required" />
    }
    
    // Consider using useMemo if users array is large
    const userDetails = React.useMemo(
        () => users.find(user => user.id === id),
        [users, id]
    )
    
    return userDetails ? <EditUser user={userDetails} /> : <NotFound message="User not found" />
}
src/react-view/contents/labels/labels-list.tsx (1)

7-15: 🛠️ Refactor suggestion

Add loading and empty states handling.

The component should handle various states gracefully:

  1. Loading state while fetching labels
  2. Empty state when no labels exist
  3. Error state if context fails

Consider this improved implementation:

 export const LabelsList: React.FC = ()=>{
     const labels = useLabelsListContext()
     const groups = useUserGroupsMap()
 
+    if (!labels || labels.length === 0) {
+        return (
+            <Box direction="row" align='center' pad='small'>
+                <Text color="dark-6">No labels found</Text>
+            </Box>
+        )
+    }
 
     return <Box direction="row" align='center' pad='small' gap='small'>
-        {labels.map((label) => (<Tag key={label.id} name={groups.get(label.groupOwnerId)?.name ?? ''} value={label.name} align="center" />))}
+        {labels.map((label) => (
+            <Tag
+                key={label.id}
+                name={groups.get(label.groupOwnerId)?.name ?? ''}
+                value={label.name}
+                align="center"
+            />
+        ))}
     </Box>
 }

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/api/client.tsx (1)

7-8: 🛠️ Refactor suggestion

Avoid type assertion in context creation.

Using type assertion with empty object is unsafe. Consider creating a dummy client or throwing an error if used outside provider.

-const ApiContext = createContext<ApiClient>({} as unknown as ApiClient);
+const ApiContext = createContext<ApiClient | null>(null);
+
+export const useApi = () => {
+    const context = React.useContext(ApiContext);
+    if (context === null) {
+        throw new Error('useApi must be used within a ProvideApi component');
+    }
+    return context;
+};
📝 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.

type ApiClient = ReturnType<typeof createFetchClient<paths>>
const ApiContext = createContext<ApiClient | null>(null);

export const useApi = () => {
    const context = React.useContext(ApiContext);
    if (context === null) {
        throw new Error('useApi must be used within a ProvideApi component');
    }
    return context;
};
src/cli.ts (1)

7-22: 🛠️ Refactor suggestion

Add signal handling for graceful shutdown

Consider implementing signal handlers for graceful shutdown.

 const bootstrap = async () => {
     const app = await NestFactory.createApplicationContext(CliModule, {});
     const logger = app.get(Logger);
     app.useLogger(logger);
 
+    const shutdown = async (signal: string) => {
+        logger.log(`Received ${signal}. Shutting down...`);
+        await app.close();
+        process.exit(0);
+    };
+
+    process.on('SIGTERM', () => shutdown('SIGTERM'));
+    process.on('SIGINT', () => shutdown('SIGINT'));
+
     try {
         await app.select(CommandModule).get(CommandService).exec();
     } catch (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.

const bootstrap = async () => {
    const app = await NestFactory.createApplicationContext(CliModule, {});
    const logger = app.get(Logger);
    app.useLogger(logger);

    const shutdown = async (signal: string) => {
        logger.log(`Received ${signal}. Shutting down...`);
        await app.close();
        process.exit(0);
    };

    process.on('SIGTERM', () => shutdown('SIGTERM'));
    process.on('SIGINT', () => shutdown('SIGINT'));

    try {
        await app.select(CommandModule).get(CommandService).exec();
        await app.close();
    } catch (error) {
        logger.error(error);
        await app.close();
        process.exit(1);
    }
};
src/swift-server/rules/new-transaction.job.swift (1)

22-27: 🛠️ Refactor suggestion

Replace print statements with structured logging

Use the context logger instead of print statements for better error tracking.

     func error(_ context: QueueContext, _ error: Error, _ payload: TransactionSummary)
         async throws
     {
-        print("Some error happened processing \(payload.id) transaction")
-        print(error)
+        context.logger.error("Failed to process transaction",
+            metadata: ["transactionId": "\(payload.id)",
+                      "error": "\(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.

func error(_ context: QueueContext, _ error: Error, _ payload: TransactionSummary)
    async throws
{
    context.logger.error("Failed to process transaction",
        metadata: ["transactionId": "\(payload.id)",
                  "error": "\(error)"])
}
src/swift-server-macros/entrypoint.swift (2)

1-4: 🛠️ Refactor suggestion

Add documentation for ServiceDependency macro

Public macros should include documentation explaining their purpose and usage.

+/// A property wrapper macro that automatically injects service dependencies.
+///
+/// Use this macro to automatically inject dependencies into your service classes:
+/// ```swift
+/// @ServiceDependency
+/// var userService: UserService
+/// ```
 @attached(accessor)
 public macro ServiceDependency() =
     #externalMacro(module: "swift_macrosMacros", type: "ServiceDependencyMacro")

7-17: 🛠️ Refactor suggestion

Document error handling macros and verify type constraints

Add comprehensive documentation for the error handling macros and consider adding compile-time verification for the StringEnumType.

+/// Creates a bad request error response with the specified message and error code.
+/// - Parameters:
+///   - msg: The error message to include in the response
+///   - code: The error code that conforms to StringEnumType
 @freestanding(expression)
 public macro BasicBadRequest<T>(msg: String, code: any StringEnumType) -> T =
     #externalMacro(module: "swift_macrosMacros", type: "BasicBadRequest")

+/// Creates a not found error response with the specified message and error code.
 @freestanding(expression)
 public macro BasicNotFound<T>(msg: String, code: any StringEnumType) -> T =
     #externalMacro(module: "swift_macrosMacros", type: "BasicNotFound")

+/// Creates a generic error response with the specified response, message, and error code.
 @freestanding(expression)
 public macro GenericErrorReturn<T>(response: String, msg: String, code: any StringEnumType) -> T =
     #externalMacro(module: "swift_macrosMacros", type: "GenericErrorReturn")
📝 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.

/// Creates a bad request error response with the specified message and error code.
/// - Parameters:
///   - msg: The error message to include in the response
///   - code: The error code that conforms to StringEnumType
@freestanding(expression)
public macro BasicBadRequest<T>(msg: String, code: any StringEnumType) -> T =
	#externalMacro(module: "swift_macrosMacros", type: "BasicBadRequest")

/// Creates a not found error response with the specified message and error code.
@freestanding(expression)
public macro BasicNotFound<T>(msg: String, code: any StringEnumType) -> T =
	#externalMacro(module: "swift_macrosMacros", type: "BasicNotFound")

/// Creates a generic error response with the specified response, message, and error code.
@freestanding(expression)
public macro GenericErrorReturn<T>(response: String, msg: String, code: any StringEnumType) -> T =
	#externalMacro(module: "swift_macrosMacros", type: "GenericErrorReturn")
src/react-view/constants.ts (1)

3-8: ⚠️ Potential issue

Add error handling for JSON parsing

The JSON parsing could fail if the script content is malformed. Consider adding try-catch for better error handling.

Apply this diff:

 export const getDataFromScript = <T>(scriptName: string, schema: z.Schema<T>): T => {
     const element : {textContent?: string | null } = document.getElementById(scriptName) ?? {};
-    const obj = JSON.parse(element.textContent ?? '{}') as unknown;
+    try {
+        const obj = JSON.parse(element.textContent ?? '{}') as unknown;
+        return schema.parse(obj);
+    } catch (error) {
+        console.error(`Failed to parse ${scriptName}:`, error);
+        throw new Error(`Invalid ${scriptName} configuration`);
+    }
-    return schema.parse(obj);
 };
📝 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.

export const getDataFromScript = <T>(scriptName: string, schema: z.Schema<T>): T => {
    const element : {textContent?: string | null } = document.getElementById(scriptName) ?? {};
    try {
        const obj = JSON.parse(element.textContent ?? '{}') as unknown;
        return schema.parse(obj);
    } catch (error) {
        console.error(`Failed to parse ${scriptName}:`, error);
        throw new Error(`Invalid ${scriptName} configuration`);
    }
};
src/react-view/contents/graphs/graph-with-rechart/enrich-graph.ts (1)

5-15: 🛠️ Refactor suggestion

Add null check for labelIds

The optional chaining on labelIds?.map is good, but the type assertion might fail if labelIds is undefined.

Apply this diff:

-    const labels = (labelIds?.map(label => labelsMap.get(label)).filter(Boolean) as Label[]);
+    const labels = labelIds 
+        ? labelIds.map(label => labelsMap.get(label)).filter((label): label is Label => !!label)
+        : [];
📝 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.

export const enrichGroup = <T extends Group>(
    { labels: labelIds, ...group }: T,
    labelsMap: Map<ApiUUID, Label>,
): EnrichedGroup<T> => {
    const labels = labelIds 
        ? labelIds.map(label => labelsMap.get(label)).filter((label): label is Label => !!label)
        : [];

    return {
        ...group,
        labels,
    };
};
src/swift-server/core/cursor-handler.swift (1)

31-35: ⚠️ Potential issue

Add validation for negative limit

The getListWithCursor function should validate that the limit is positive.

Apply this diff:

 func getListWithCursor<T>(data: [T], generateCursor: (T) -> String) -> ListWithCursor<T> {
+    guard limit > 0 else {
+        return ListWithCursor(list: [], next: nil)
+    }
     let hasMore = data.count >= limit
     return ListWithCursor(list: data, next: hasMore ? generateCursor(data.last!) : nil)
 }

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/react/react-controller.swift (2)

16-26: 🛠️ Refactor suggestion

Move configuration to environment variables.

The initialization contains hardcoded values that should be configurable:

  • debug is hardcoded to true with a TODO comment
  • environment is hardcoded to "development"

Consider using Vapor's environment configuration:

init() {
    self.logger = Logger(label: "ReactController")
    let buildInfo = BuildInfo()
    self.ctx = ReactContext(
-       debug: true,  // Todo: get from app env
+       debug: Environment.get("DEBUG")?.bool ?? false,
        staticPath: "/",
        version: buildInfo.appVersion,
-       environment: "development",
+       environment: Environment.get("ENV") ?? "development",
        decimalCount: 2
    )
}
📝 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.

init() {
		self.logger = Logger(label: "ReactController")
		let buildInfo = BuildInfo()
		self.ctx = ReactContext(
			debug: Environment.get("DEBUG")?.bool ?? false,
			staticPath: "/",
			version: buildInfo.appVersion,
			environment: Environment.get("ENV") ?? "development",
			decimalCount: 2
		)
	}

36-39: 🛠️ Refactor suggestion

Add error handling for view rendering.

The getReact method should handle potential view rendering failures gracefully.

func getReact(req: Request) async throws -> View {
    logger.info("Rendering react with context: \(self.ctx)")
-   return try await req.view.render("react", self.ctx)
+   do {
+       return try await req.view.render("react", self.ctx)
+   } catch {
+       logger.error("Failed to render react view: \(error)")
+       throw Abort(.internalServerError, reason: "Failed to render view")
+   }
}
📝 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.

func getReact(req: Request) async throws -> View {
    logger.info("Rendering react with context: \(self.ctx)")
    do {
        return try await req.view.render("react", self.ctx)
    } catch {
        logger.error("Failed to render react view: \(error)")
        throw Abort(.internalServerError, reason: "Failed to render view")
    }
}
src/main.ts (1)

26-40: 🛠️ Refactor suggestion

Enhance error handling and logging.

The webpack callback could benefit from more detailed error handling and structured logging.

webpack(WebpackConfig, (err, stats) => {
    if (err) {
-       console.log(err);
+       console.error('Fatal webpack error:', err);
+       process.exit(1);
        return;
    }

+   if (stats?.hasErrors()) {
+       console.error('Webpack compilation errors:', stats.toString({
+           errors: true,
+           errorDetails: true,
+           colors: true
+       }));
+       return;
+   }

    console.log(
        stats?.toString({
            chunks: false,
            colors: 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.

webpack(WebpackConfig, (err, stats) => {
    if (err) {
        console.error('Fatal webpack error:', err);
        process.exit(1);
        return;
    }

    if (stats?.hasErrors()) {
        console.error('Webpack compilation errors:', stats.toString({
            errors: true,
            errorDetails: true,
            colors: true
        }));
        return;
    }

    console.log(
        stats?.toString({
            chunks: false,
            colors: true,
        }),
    );
});
src/react-view/utils/providers/event-emitter.provider.tsx (1)

13-17: 🛠️ Refactor suggestion

Improve error handling and memory management.

The current implementation might leak memory if the callback throws synchronously, and the error handling could be more robust.

subscribe(event: EventTypes, callback: () => Promise<void> | void): () => void {
-   const _callback = ()=>{callback()?.catch(console.error)}
+   const _callback = async () => {
+       try {
+           await Promise.resolve(callback());
+       } catch (error) {
+           console.error('Event handler error:', error);
+       }
+   }
    this.eventEmitter.addListener(event, _callback);
    return () => {
        this.eventEmitter.removeListener(event, _callback);
    };
}
📝 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.

        const _callback = async () => {
            try {
                await Promise.resolve(callback());
            } catch (error) {
                console.error('Event handler error:', error);
            }
        }
        this.eventEmitter.addListener(event, _callback);
        return () => {
            this.eventEmitter.removeListener(event, _callback);
        };
src/react-view/utils/error-boundary/error-boundary.tsx (1)

6-20: 🛠️ Refactor suggestion

Enhance error fallback UI and security.

The Fallback component could be improved in several ways:

  1. Stack traces should not be exposed in production
  2. Error messages should be more user-friendly
  3. Accessibility could be enhanced
const Fallback: React.FC<FallbackProps> = ({ error }) => {
+   const isDevelopment = process.env.NODE_ENV === 'development';
    if (error instanceof Error) {
        return (
            <div role="alert">
-               <p>Something went wrong:</p>
+               <h2>We're sorry, something went wrong</h2>
                <pre style={{ color: 'red' }}>{error.message}</pre>
-               <pre>{error.stack}</pre>
+               {isDevelopment && <pre>{error.stack}</pre>}
+               <button onClick={() => window.location.reload()} aria-label="Reload page">
+                   Try Again
+               </button>
            </div>
        );
    } else {
        return <div role="alert">
-           <h1> An unknown alert was raised</h1>
+           <h2>Unexpected Error</h2>
+           <p>We encountered an unexpected error. Please try again later.</p>
+           <button onClick={() => window.location.reload()} aria-label="Reload page">
+               Try Again
+           </button>
        </div>
    }
};
📝 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.

const Fallback: React.FC<FallbackProps> = ({ error }) => {
    const isDevelopment = process.env.NODE_ENV === 'development';
    if (error instanceof Error) {
        return (
            <div role="alert">
                <h2>We're sorry, something went wrong</h2>
                <pre style={{ color: 'red' }}>{error.message}</pre>
                {isDevelopment && <pre>{error.stack}</pre>}
                <button onClick={() => window.location.reload()} aria-label="Reload page">
                    Try Again
                </button>
            </div>
        );
    } else {
        return <div role="alert">
            <h2>Unexpected Error</h2>
            <p>We encountered an unexpected error. Please try again later.</p>
            <button onClick={() => window.location.reload()} aria-label="Reload page">
                Try Again
            </button>
        </div>
    }
};
src/swift-server/importer/importers/n26.importer.swift (1)

11-18: 🛠️ Refactor suggestion

Add CSV header validation.

The current implementation assumes a fixed CSV structure without validating headers. Consider adding header validation to catch format changes early.

 init() {
+    let expectedHeaders = ["Date", "Value Date", "Movement", "Details", "Amount"]
     let fieldsMap = FieldsMap<Int>(
         movementName: 2,
         date: 0,
         dateValue: 1,
         details: 5,
         value: 7
     )
-    self.transformHelper = TransformHelper(fieldsMap, dateFormat: "yyyy-MM-dd")
+    self.transformHelper = TransformHelper(
+        fieldsMap,
+        dateFormat: "yyyy-MM-dd",
+        headerValidation: expectedHeaders
+    )
 }

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/contents/graphs/mocks/graph.mocks.ts (1)

9-9: 🛠️ Refactor suggestion

Avoid hardcoded array indices.

Using hardcoded indices ([4, 5, 8]) for label IDs makes the code fragile. Consider using named constants or enums.

-        labels: [4, 5, 8].map(idx => listLabelIds[idx] as ApiUUID),
+        labels: [
+            listLabelIds[LabelIndices.EXPENSES],
+            listLabelIds[LabelIndices.INCOME],
+            listLabelIds[LabelIndices.SAVINGS]
+        ] as ApiUUID[],

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/contents/graphs/data-transform/create-groups.ts (1)

19-23: ⚠️ Potential issue

Add null check for Map.get result

The hashMap.get(label) could return undefined. Add a type assertion or null check.

    return Array.from(hashMap.keys()).map(label => ({
        label,
-        value: hashMap.get(label),
+        value: hashMap.get(label)!,  // Safe because we're iterating over existing keys
        groupName: lambda.name,
    } as GenericDSGroup<K, DTInputData[]>));
📝 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.

    return Array.from(hashMap.keys()).map(label => ({
        label,
        value: hashMap.get(label)!,  // Safe because we're iterating over existing keys
        groupName: lambda.name,
    } as GenericDSGroup<K, DTInputData[]>));
src/swift-server-tests/commands-helper.swift (2)

54-56: 🛠️ Refactor suggestion

Implement error reporting functionality.

The report method is currently empty. Consider implementing proper error reporting to aid in debugging during tests.

-    func report(error: String, newLine: Bool) {
-        //
-    }
+    func report(error: String, newLine: Bool) {
+        let line = "Error: " + error + (newLine ? "\n" : "")
+        testOutputQueue.insert(line, at: 0)
+    }
📝 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.

	func report(error: String, newLine: Bool) {
		let line = "Error: " + error + (newLine ? "\n" : "")
		testOutputQueue.insert(line, at: 0)
	}

33-38: ⚠️ Potential issue

Consider thread safety for shared state.

The class is marked as @unchecked Sendable but contains mutable arrays. Consider using a lock or actor to ensure thread-safe access to testInputQueue, testOutputQueue, and userInfo.

 final class TestConsole: Console, @unchecked Sendable {
+    private let lock = NSLock()
     var testInputQueue: [String]
     var testOutputQueue: [String]
     var userInfo: [AnySendableHashable: any Sendable]
📝 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.

final class TestConsole: Console, @unchecked Sendable {
    private let lock = NSLock()
    var testInputQueue: [String]
    var testOutputQueue: [String]
    var userInfo: [AnySendableHashable: any Sendable]
src/swift-server/core/date-only.swift (2)

12-18: ⚠️ Potential issue

Handle potential nil values safely.

The main initializer force unwraps calendar components, which could crash if the date is invalid.

     init(_ date: Date) {
         let calendar = Calendar.current
         let components = calendar.dateComponents([.year, .month, .day], from: date)
-        self.year = components.year!
-        self.month = components.month!
-        self.day = components.day!
+        guard let year = components.year,
+              let month = components.month,
+              let day = components.day else {
+            fatalError("Invalid date components")
+        }
+        self.year = year
+        self.month = month
+        self.day = day
     }
📝 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.

init(_ date: Date) {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.year, .month, .day], from: date)
        guard let year = components.year,
              let month = components.month,
              let day = components.day else {
            fatalError("Invalid date components")
        }
        self.year = year
        self.month = month
        self.day = day
    }

40-46: ⚠️ Potential issue

Avoid force unwrapping in getDate method.

The getDate method force unwraps the date creation, which could crash with invalid components.

     func getDate() -> Date {
         var components = DateComponents()
         components.year = year
         components.month = month
         components.day = day
-        return Calendar.current.date(from: components)!
+        guard let date = Calendar.current.date(from: components) else {
+            fatalError("Failed to create date from components")
+        }
+        return date
     }
📝 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.

	func getDate() -> Date {
		var components = DateComponents()
		components.year = year
		components.month = month
		components.day = day
		guard let date = Calendar.current.date(from: components) else {
			fatalError("Failed to create date from components")
		}
		return date
	}
src/swift-server/label/label.service.swift (2)

6-10: 🛠️ Refactor suggestion

Add error handling for database operations

The createLabel method should include error handling for database constraints and validation.

 func createLabel(label: Label) async throws -> Label {
+    guard label.groupOwnerId != nil else {
+        throw Abort(.badRequest, reason: "Group owner ID is required")
+    }
     try await label.save(on: db)
     try await label.$groupOwner.load(on: db)
     return label
 }
📝 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.

func createLabel(label: Label) async throws -> Label {
    guard label.groupOwnerId != nil else {
        throw Abort(.badRequest, reason: "Group owner ID is required")
    }
    try await label.save(on: db)
    try await label.$groupOwner.load(on: db)
    return label
}

19-42: 🛠️ Refactor suggestion

Consider adding input validation for pagination parameters

The getAll method should validate pageQuery parameters to prevent potential issues with invalid inputs.

 func getAll(pageQuery: PageQuery = .init(), groupIds: [UUID]) async throws
     -> ListWithCursor<Label>
 {
+    guard !groupIds.isEmpty else {
+        throw Abort(.badRequest, reason: "At least one group ID is required")
+    }
+    guard pageQuery.limit > 0 && pageQuery.limit <= 100 else {
+        throw Abort(.badRequest, reason: "Page limit must be between 1 and 100")
+    }
     var query = Label.query(on: db)
📝 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.

func getAll(pageQuery: PageQuery = .init(), groupIds: [UUID]) async throws
    -> ListWithCursor<Label>
{
    guard !groupIds.isEmpty else {
        throw Abort(.badRequest, reason: "At least one group ID is required")
    }
    guard pageQuery.limit > 0 && pageQuery.limit <= 100 else {
        throw Abort(.badRequest, reason: "Page limit must be between 1 and 100")
    }
    var query = Label.query(on: db)
        .filter(\.$groupOwner.$id ~~ groupIds)

    if let cursor = pageQuery.cursor {
        let cursorData = try self.cursorHandler.parse(cursor)
        if let idString = cursorData["id"], let id = UUID(uuidString: idString) {
            query = query.filter(\.$id < id)
        }
    }

    let data =
        try await query
        .sort(\.$id, .descending)
        .limit(pageQuery.limit)
        .with(\.$groupOwner)
        .all()

    return pageQuery.getListWithCursor(data: data) { last in
        self.cursorHandler.stringify(["id": last.id!.uuidString])
    }
}
src/swift-server/bank-transaction/bank-transaction.models.swift (2)

51-66: 🛠️ Refactor suggestion

Consider adding value validation in initializer

The initializer should validate the transaction value and other critical fields.

 init(
     id: UUID? = nil, groupOwnerId: UUID, movementName: String, date: DateOnly,
     dateValue: DateOnly? = nil, details: String? = nil, value: Double, kind: String,
     description: String? = nil
 ) throws {
+    guard !movementName.isEmpty else {
+        throw Abort(.badRequest, reason: "Movement name cannot be empty")
+    }
+    guard !kind.isEmpty else {
+        throw Abort(.badRequest, reason: "Kind cannot be empty")
+    }
     self.id = id
     self.$groupOwner.id = groupOwnerId
     self.groupOwnerId = groupOwnerId
     self.movementName = movementName
     self.date = date
     self.dateValue = dateValue
     self.details = details
     self.value = value
     self.kind = kind
     self.description = description
 }

Committable suggestion skipped: line range outside the PR's diff.


22-29: ⚠️ Potential issue

Add date validation in the DateOnly conversion

The force unwrap in the date getter could crash if the stored string is invalid.

 var date: DateOnly {
     get {
-        DateOnly(_date)!
+        guard let date = DateOnly(_date) else {
+            // Log the invalid date string for debugging
+            logger.error("Invalid date string: \(_date)")
+            // Return a default date or throw an error
+            return DateOnly.distantPast
+        }
+        return date
     }
     set {
         _date = newValue.toString()
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/utils/logger/logger-console.tsx (1)

31-42: ⚠️ Potential issue

Add dependency array to useEffect hook

The useEffect hook is missing its dependency array, which could cause unnecessary re-renders and event listener recreations. Add [eventEmitter, minLevelNum] as dependencies.

-    });
+    }, [eventEmitter, minLevelNum]);
📝 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.

    React.useEffect(() => {
        const logs = LoggerList.filter(({ levelNum }) => minLevelNum <= levelNum);
        logs.forEach(log => {
            eventEmitter.on(log.eventName, log.fn);
        });

        return () => {
            logs.forEach(log => {
                eventEmitter.removeListener(log.eventName, log.fn);
            });
        };
    }, [eventEmitter, minLevelNum]);
src/react-view/contents/graphs/form/new-graph.form.tsx (2)

28-28: ⚠️ Potential issue

Avoid type assertion with runtime validation

The type assertion graphData as GraphParam could be unsafe. Consider adding runtime validation before the API call.

+            save={() => {
+                if (!graphData.groupOwnerId /* add other required fields */) {
+                    logger.error("Invalid graph data", { graphData });
+                    return;
+                }
                 newFormRequest.execute(graphData as GraphParam).then(response => {

Committable suggestion skipped: line range outside the PR's diff.


31-31: 💡 Codebase verification

Use absolute navigation paths

The codebase predominantly uses absolute paths for navigation. The relative path ../${newGraph.id} should be changed to an absolute path to match the established pattern and prevent potential issues if the routing structure changes. Based on the router configuration, consider using /graph/${newGraph.id} instead.

  • src/react-view/contents/graphs/form/new-graph.form.tsx: Replace relative path ../${newGraph.id} with absolute path
  • src/react-view/contents/graphs/graph-with-rechart/graph.tsx: Consider updating graph/${graph.id} to use an absolute path as well for consistency
🔗 Analysis chain

Use absolute navigation paths

The navigation uses a relative path which could be fragile. Consider using absolute paths for more reliable navigation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for other relative navigation paths that might need updating
rg -g '*.tsx' -g '*.ts' "navigate\(`[^/]" 

Length of output: 116


Script:

#!/bin/bash
# Search for navigation patterns
rg -g '*.tsx' -g '*.ts' 'navigate\(' -A 1

# Look for router configuration or setup
fd -e tsx -e ts router
fd -e tsx -e ts routes

# Search for relative path patterns specifically
rg -g '*.tsx' -g '*.ts' 'navigate\([`'"'"'"][.]{1,2}/'

Length of output: 1765


Script:

#!/bin/bash
# Check router configuration
cat src/react-view/contents/graphs/graph-router.tsx

Length of output: 686

config/webpack5.config.ts (1)

31-34: ⚠️ Potential issue

Fix SCSS loader configuration.

The SCSS loader configuration is incorrect. The sass loader should be sass-loader, and the order of loaders matters as they are executed from right to left.

                test: /\.scss$/i,
                exclude: [/node_modules/],
-                use: ["style-loader", "css-loader", "sass"],
+                use: ["style-loader", "css-loader", "sass-loader"],
📝 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.

                test: /\.scss$/i,
                exclude: [/node_modules/],
                use: ["style-loader", "css-loader", "sass-loader"],
            },
src/react-view/contents/admin/users/user-form.tsx (2)

33-38: 🛠️ Refactor suggestion

Add email validation pattern.

The email field should include a pattern for proper email validation.

             <TextInput
                 type='email'
                 id='email-field'
-                name='email' />
+                name='email'
+                pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
+                required />
📝 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.

            <FormField label="E-Mail" htmlFor='email-field'>
                <TextInput
                    type='email'
                    id='email-field'
                    name='email'
                    pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
                    required />
            </FormField>

16-24: 🛠️ Refactor suggestion

Add password validation pattern.

The password field should include a pattern for stronger password requirements.

         <FormField name="username" label="User name" component={TextInput} minLength={5} required/>
         <FormField label="Password" htmlFor='password-field'>
             <TextInput
                 type='password'
                 required
                 id='password-field'
                 name='password'
-               minLength={8} />
+               minLength={8}
+               pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
+               title="Password must contain at least one uppercase letter, one lowercase letter, one number and one special character" />
📝 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.

        <FormField name="username" label="User name" component={TextInput} minLength={5} required/>
        <FormField label="Password" htmlFor='password-field'>
            <TextInput
                type='password'
                required
                id='password-field'
                name='password'
                minLength={8}
                pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
                title="Password must contain at least one uppercase letter, one lowercase letter, one number and one special character" />
        </FormField>
src/react-view/contents/session/login.tsx (1)

20-22: 🛠️ Refactor suggestion

Remove redundant error logging

Based on previous feedback, the error logging in the login callback is redundant as errors from loginRequest are already handled by passing loginRequest.error to the LoginView component.

    if (loginRequest.status == 'error') {
        logger.error('Login request throw an error', loginRequest.error);
    }
    // ...
    login={credentials => {
-        loginRequest.execute(credentials).catch((error: unknown) => { 
-            if (error instanceof Error) {
-                logger.error("Login request responded an error", {error})
-            } else {
-                logger.error("Login Request unknown error", { error })
-            }
-        });
+        loginRequest.execute(credentials);
    }}

Also applies to: 34-40

src/swift-server-tests/labels.tests.swift (1)

38-65: 🛠️ Refactor suggestion

Add test cases for error scenarios

The test suite should include cases for error scenarios to ensure proper error handling.

Consider adding tests for:

  • Creating a label with invalid data
  • Creating a label with duplicate name
  • Creating a label with non-existent group ID
  • Unauthorized access attempts
src/react-view/contents/graphs/graphs.tsx (2)

31-32: 🛠️ Refactor suggestion

Enhance error handling UI

The error message is too generic and doesn't provide users with any recovery options.

Consider adding:

  • More descriptive error message
  • Retry button
  • Error details (in development mode)

37-45: ⚠️ Potential issue

Avoid using array index as key

Using array index as key in the map function can lead to rendering issues when the list changes.

-                    <GraphWrapperWithRechart
-                        key={idx}
+                    <GraphWrapperWithRechart
+                        key={graph.id}

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/contents/graphs/graph-with-rechart/graph.tsx (1)

24-24: ⚠️ Potential issue

Fix the API endpoint format

The API endpoint format /graphs/{id} appears to be using OpenAPI/Swagger template syntax, but the actual endpoint should use string interpolation.

-        return client.DELETE("/graphs/{id}", {params: {path: {id}}})
+        return client.DELETE(`/graphs/${id}`)
📝 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.

        return client.DELETE(`/graphs/${id}`)
src/react-view/contents/common/label.context.tsx (1)

53-56: ⚠️ Potential issue

Fix loading state and error handling

The loading state has a syntax error (no return statement), and error handling could be improved.

    } else if (paginator.status === "loading") {
-        <LoadingPage />;
+        return <LoadingPage />;
+    } else if (paginator.status === "error") {
+        return (
+            <div role="alert" aria-live="polite">
+                Error loading labels: {paginator.error?.message ?? 'Unknown error'}
+            </div>
+        );
    } 
-    return <div>Error on loading the labels</div>
+    return null;
📝 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.

    } else if (paginator.status === "loading") {
        return <LoadingPage />;
    } else if (paginator.status === "error") {
        return (
            <div role="alert" aria-live="polite">
                Error loading labels: {paginator.error?.message ?? 'Unknown error'}
            </div>
        );
    } 
    return null;
src/swift-server/importer/importers/file-parser.swift (2)

5-49: 🛠️ Refactor suggestion

Enhance error handling and memory management for HTML parsing.

The HTML parser has several areas for improvement:

  1. Memory management for large files
  2. More specific error handling
  3. Potential performance optimization
 func parserHtml(filePath: String, encoding: String.Encoding) -> AsyncThrowingStream<
     HTMLRowData, Error
 > {
     return AsyncThrowingStream { continuation in
         Task {
             do {
+                let fileAttributes = try FileManager.default.attributesOfItem(atPath: filePath)
+                guard let fileSize = fileAttributes[.size] as? UInt64,
+                      fileSize < 50_000_000 else { // 50MB limit
+                    throw Exception(.E10011, context: ["fileName": filePath, "size": "\(fileSize)"])
+                }
                 guard let fileData = FileManager.default.contents(atPath: filePath)
                 else {
                     throw Exception(.E10006, context: ["fileName": filePath])
                 }
+                defer { // Release memory
+                    fileData.removeAll()
+                }
                 // ... rest of the code

Committable suggestion skipped: line range outside the PR's diff.


51-74: 🛠️ Refactor suggestion

Add validation for CSV file size and improve error handling.

The CSV parser should include similar safeguards as the HTML parser.

 func parseCsv(filePath: String, delimiter: UnicodeScalar = ",") throws -> CSVReader {
     do {
+        let fileAttributes = try FileManager.default.attributesOfItem(atPath: filePath)
+        guard let fileSize = fileAttributes[.size] as? UInt64,
+              fileSize < 50_000_000 else { // 50MB limit
+            throw Exception(.E10011, context: ["fileName": filePath, "size": "\(fileSize)"])
+        }
         let stream = InputStream(fileAtPath: filePath)
         guard let stream = stream else {
             throw Exception(.E10008, context: ["fileName": filePath])
         }
+        stream.open()
+        defer { stream.close() }
         return try CSVReader(stream: stream, hasHeaderRow: true, delimiter: delimiter)
     } catch {
         // ... error handling
     }
 }
📝 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.

func parseCsv(filePath: String, delimiter: UnicodeScalar = ",") throws -> CSVReader {
    do {
        let fileAttributes = try FileManager.default.attributesOfItem(atPath: filePath)
        guard let fileSize = fileAttributes[.size] as? UInt64,
              fileSize < 50_000_000 else { // 50MB limit
            throw Exception(.E10011, context: ["fileName": filePath, "size": "\(fileSize)"])
        }
        let stream = InputStream(fileAtPath: filePath)
        guard let stream = stream else {
            throw Exception(.E10008, context: ["fileName": filePath])
        }
        stream.open()
        defer { stream.close() }
        return try CSVReader(stream: stream, hasHeaderRow: true, delimiter: delimiter)
    } catch {
        if let error = error as? Exception {
            throw error
        }
        if let error = error as? CSVError {
            switch error {
            case .cannotOpenFile:
                throw Exception(.E10010, context: ["fileName": filePath])
            default:
                throw error
            }
        }
        throw error
    }
}
src/swift-server/core/cache.swift (1)

3-40: 🛠️ Refactor suggestion

Add capacity limits and eviction policies to the cache.

The cache implementation should include configurable limits to prevent memory issues.

 final class Cache<Key: Hashable, Value> {
     private let wrapped = NSCache<WrappedKey, Entry>()
+    private let countLimit: Int
+    private let totalCostLimit: Int
 
     private let dateProvider: () -> Date
     private let entryLifetime: TimeInterval
 
     init(
         dateProvider: @escaping () -> Date = Date.init,
-        entryLifetime: TimeInterval = 12 * 60 * 60
+        entryLifetime: TimeInterval = 12 * 60 * 60,
+        countLimit: Int = 1000,
+        totalCostLimit: Int = 1024 * 1024 * 50 // 50MB
     ) {
         self.dateProvider = dateProvider
         self.entryLifetime = entryLifetime
+        self.countLimit = countLimit
+        self.totalCostLimit = totalCostLimit
+        self.wrapped.countLimit = countLimit
+        self.wrapped.totalCostLimit = totalCostLimit
     }
📝 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.

final class Cache<Key: Hashable, Value> {
    private let wrapped = NSCache<WrappedKey, Entry>()
    private let countLimit: Int
    private let totalCostLimit: Int

    private let dateProvider: () -> Date
    private let entryLifetime: TimeInterval

    init(
        dateProvider: @escaping () -> Date = Date.init,
        entryLifetime: TimeInterval = 12 * 60 * 60,
        countLimit: Int = 1000,
        totalCostLimit: Int = 1024 * 1024 * 50 // 50MB
    ) {
        self.dateProvider = dateProvider
        self.entryLifetime = entryLifetime
        self.countLimit = countLimit
        self.totalCostLimit = totalCostLimit
        self.wrapped.countLimit = countLimit
        self.wrapped.totalCostLimit = totalCostLimit
    }

    func insert(_ value: Value, forKey key: Key) {
        let date = dateProvider().addingTimeInterval(entryLifetime)
        let entry = Entry(value: value, expirationDate: date)
        wrapped.setObject(entry, forKey: WrappedKey(key))
    }

    func value(forKey key: Key) -> Value? {
        guard let entry = wrapped.object(forKey: WrappedKey(key)) else {
            return nil
        }

        guard dateProvider() < entry.expirationDate else {
            // Discard values that have expired
            removeValue(forKey: key)
            return nil
        }

        return entry.value
    }

    func removeValue(forKey key: Key) {
        wrapped.removeObject(forKey: WrappedKey(key))
    }
}
src/react-view/contents/admin/users/user-edit.tsx (1)

30-42: 🛠️ Refactor suggestion

Add loading state and error handling UI feedback.

The save operation should provide better user feedback.

     const saveUser = useAsyncCallback((id: ApiUUID, body: UpdateUserParams) => {
         return client.PUT("/users/{id}", {body, params: {path: {id}}})
     })
+    const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
     
     // ... rest of the code
     
     <Form<UpdateUserParams> value={userData} onChange={setUserData} onSubmit={()=>{
-        catchAndLog(saveUser.execute(user.id, userData), "Error updating the user", logger)
+        setErrorMessage(null);
+        catchAndLog(
+            saveUser.execute(user.id, userData)
+                .catch(error => {
+                    setErrorMessage(error.message);
+                    throw error;
+                }),
+            "Error updating the user",
+            logger
+        )
     }}>

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/contents/admin/users/new-user.tsx (1)

19-27: 🛠️ Refactor suggestion

Consider adding email validation and password requirements.

The initial state includes email and password fields without validation. For security best practices, consider:

  • Email format validation
  • Password strength requirements (length, complexity)
 const [userData, setUserData] = React.useState<CreateUserParams>({
     email: "",
     isActive: true,
     isAdmin: false,
     password: "",
     username: "",
     firstName: "",
     lastName: ""
+    }, {
+    validate: {
+        email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
+        password: (value) => value.length >= 8 && /[A-Z]/.test(value) && /[0-9]/.test(value)
+    }
 })

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/contents/admin/users/admin-user.tsx (1)

42-42: 🛠️ Refactor suggestion

Optimize user search implementation.

The current implementation filters users client-side and only when search text length > 3. Consider:

  1. Debouncing the search input
  2. Moving filtering to the server-side
-<UsersList users={searchText.length >3 ? users.filter(user => user.email.includes(searchText) || user.username.includes(searchText)) : users} />
+<UsersList users={searchText.length > 3 ? 
+    await debounce(async () => {
+        const { data } = await client.GET("/users/search", {
+            params: { query: { searchText } }
+        });
+        return data || [];
+    }, 300)() : users} 
+/>

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/contents/headers.tsx (1)

11-13: 🛠️ Refactor suggestion

Improve error handling for anonymous users.

Consider redirecting anonymous users to the login page instead of throwing an error.

-if (session.user === 'anonymous') {
-    throw Error('User should not be anonymous');
-}
+if (session.user === 'anonymous') {
+    navigate('/login', { replace: true });
+    return null;
+}

Committable suggestion skipped: line range outside the PR's diff.

src/react-view/contents/graphs/form/edit-graph.form.tsx (1)

48-50: 🛠️ Refactor suggestion

Add error handling UI for graph updates

The save operation logs errors but doesn't provide user feedback.

                save={() => {
-                    updateGraph.execute(id, graphData).catch((error: unknown) => { logger.error("Saving the graph", { error }) });
+                    updateGraph
+                        .execute(id, graphData)
+                        .catch((error: unknown) => {
+                            logger.error("Saving the graph", { error });
+                            // Show error toast/notification to user
+                            showErrorNotification("Failed to save graph changes");
+                        });
                }}

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/importer/importer.models.swift (2)

78-80: 🛠️ Refactor suggestion

Add validation for value field

The value field should have validation to prevent invalid amounts.

    @Field(key: "value")
    var value: Double
+
+    // Add validation in init
+    guard !value.isNaN && !value.isInfinite else {
+        throw ValidationError("Invalid value amount")
+    }

Committable suggestion skipped: line range outside the PR's diff.


17-19: 💡 Codebase verification

Review PII handling in description and details fields - Confirmed Issue

The codebase processes sensitive financial data from multiple banks but lacks PII protection mechanisms. Recommend:

  • Implement data sanitization for imported bank statements
  • Add PII detection and masking for sensitive transaction details
  • Document PII handling guidelines and privacy considerations
  • Consider adding configurable redaction rules for specific data patterns
🔗 Analysis chain

Review PII handling in text fields

The description and details fields might contain sensitive information. Consider implementing data sanitization or adding a warning in the documentation.

Also applies to: 75-77, 81-83

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential PII patterns in description/details fields
rg -i "password|secret|key|token|credit.?card|\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b" 

Length of output: 65627


Script:

#!/bin/bash
# Check for any data sanitization or PII handling
rg -i "sanitize|redact|mask|clean.?data|pii|sensitive|gdpr" 

# Look for documentation about data handling
fd -e md -e txt . -x cat {} \; 

Length of output: 10484

src/react-view/contents/imports/wizard/import-wizard.tsx (1)

14-16: 🛠️ Refactor suggestion

Add error handling for parser types fetch

The parser types fetch lacks error handling and loading state management.

    const client = useApi()
    const parserTypes = useAsync(()=>client.GET("/imports/parsers"), [client])
+   if (parserTypes.error) {
+       logger.error("Failed to fetch parser types", parserTypes.error);
+       return {
+           kindList: [],
+           findKind: () => 'Unknown'
+       };
+   }
    const parserList : FileParserType[] = parserTypes.result?.data?.parsers ?? [];

Committable suggestion skipped: line range outside the PR's diff.

.swift-format (1)

36-37: 🛠️ Refactor suggestion

Consider enabling force unwrap and force try restrictions

Setting NeverForceUnwrap and NeverUseForceTry to false could lead to runtime crashes. Consider enabling these rules to enforce safer code practices.

-    "NeverForceUnwrap" : false,
-    "NeverUseForceTry" : false,
+    "NeverForceUnwrap" : true,
+    "NeverUseForceTry" : 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.

    "NeverForceUnwrap" : true,
    "NeverUseForceTry" : true,
src/react-view/utils/ui/tag/input-tag.tsx (2)

76-78: ⚠️ Potential issue

Remove type assertion and use proper typing

The suggestion selection handler uses unsafe type assertions. Consider using proper typing.

-                        const { value: suggestionId, label }: { label: string; value: string } = event.suggestion;
+                        const { value: suggestionId, label } = event.suggestion as { label: string; value: string };
📝 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.

                        const { value: suggestionId, label } = event.suggestion as { label: string; value: string };
                        onAddTag({ id: suggestionId, name: label });
                        setCurrentTag('');
🧰 Tools
🪛 GitHub Check: Lint

[failure] 76-76:
Unsafe assignment of an any value


40-40: ⚠️ Potential issue

Fix template literal type issues

The key generation needs proper type handling for the template literal.

-                key={`${v}${index + 0}`}
+                key={`${v.id}_${index}`}
📝 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.

                key={`${v.id}_${index}`}
🧰 Tools
🪛 GitHub Check: Lint

[failure] 40-40:
'v' will use Object's default stringification format ('[object Object]') when stringified


[failure] 40-40:
Invalid type "ITagModel" of template literal expression


[failure] 40-40:
Invalid type "number" of template literal expression

src/react-view/contents/restricted-content.tsx (1)

16-22: 🛠️ Refactor suggestion

Remove commented-out code

The commented-out code block should be removed as it's no longer needed and can cause confusion.

src/swift-server-macrosMacros/basic-errors.swift (1)

8-104: 🛠️ Refactor suggestion

Reduce code duplication in macro implementations.

The three macro implementations (BasicBadRequest, BasicNotFound, and GenericErrorReturn) share significant code duplication in their argument validation and string generation logic.

Consider extracting common functionality into helper functions:

private func validateStringArgument(_ node: some FreestandingMacroExpansionSyntax, at index: Int) throws -> String {
    guard let value = node.arguments.dropFirst(index).first?.expression
        .as(StringLiteralExprSyntax.self)?.representedLiteralValue
    else {
        throw CustomError.message("Parameter at index \(index) should be a string")
    }
    return value
}

private func validateCodeArgument(_ node: some FreestandingMacroExpansionSyntax, at index: Int) throws -> String {
    guard let expression = node.arguments.dropFirst(index).first?.expression
        .as(MemberAccessExprSyntax.self)
    else {
        throw CustomError.message("Invalid code")
    }
    return expression.declName.baseName.text
}

private func generateResponse(type: String, message: String, code: String) -> ExprSyntax {
    """
    .\(raw: type)(
        .init(
            body: .json(
                .init(
                    message: "\(raw: message)",
                    code: "\(raw: code)"))))
    """
}

Then simplify the macro implementations:

public struct BasicBadRequest: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> ExprSyntax {
        let message = try validateStringArgument(node, at: 0)
        let code = try validateCodeArgument(node, at: 1)
        return generateResponse(type: "badRequest", message: message, code: code)
    }
}
src/react-view/api/pagination.spec.tsx (1)

27-78: 💡 Codebase verification

The review comment is accurate - test coverage needs improvement.

The implementation includes error handling, loading states, and edge case handling, but these scenarios aren't tested. The suggested test cases would help ensure the reliability of these features:

  • Error handling is implemented (see setError and error status) but not tested
  • Loading state transitions are managed but lack test coverage
  • Edge cases like empty results and null next pages are handled in code but not verified by tests

The suggested test additions in the review comment would be valuable additions to the test suite.

🔗 Analysis chain

Enhance test coverage.

The test suite is missing several important test cases:

  1. Error handling scenarios
  2. Loading state transitions
  3. Edge cases (empty results, null values)
  4. Network failure scenarios

Consider adding these test cases:

it("handles network errors gracefully", async () => {
    const cb = jest.fn()
    cb.mockRejectedValueOnce(new Error("Network error"))
    // ... test implementation
})

it("handles empty results correctly", async () => {
    const cb = jest.fn()
    cb.mockResolvedValueOnce({results: [], next: null})
    // ... test implementation
})
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check test coverage for pagination-related files
jest --coverage --collectCoverageFrom='src/react-view/api/pagination.{ts,tsx}'

Length of output: 175


Script:

#!/bin/bash
# Find and show the implementation file
fd pagination.tsx --type f --exclude '*.spec.tsx' --exec cat {}

Length of output: 67


Script:

#!/bin/bash
# Search for pagination-related files with broader patterns
fd -i pagination --type f

# Search for usePagination hook implementation
ast-grep --pattern 'export function usePagination' 
ast-grep --pattern 'export const usePagination'

Length of output: 5795

src/react-view/utils/session/session-context.tsx (2)

75-75: ⚠️ Potential issue

Remove inappropriate logging message.

The logging message contains inappropriate language. Replace with a professional message.

-    logger.info("What the fuck!", session.result)
+    logger.info("Session initialization failed", { sessionResult: session.result })
📝 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.

    logger.info("Session initialization failed", { sessionResult: session.result })

31-37: 🛠️ Refactor suggestion

Enhance logout security.

The logout implementation should clear sensitive data and handle failures gracefully.

 const logout = useAsyncCallback(async () => {
+    try {
         setSessionInfo(undefined)
         await client.DELETE('/session');
-        await session.execute().then(() => {
-            logout.reset();
-        });
+        await session.execute();
+        logout.reset();
+    } catch (error) {
+        logger.error('Logout failed', { error });
+        // Ensure session is cleared even if API call fails
+        setSessionInfo(undefined);
+    } finally {
+        // Clear any sensitive data from memory
+        window.localStorage.clear();
+        window.sessionStorage.clear();
+    }
 });

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server/graph/graph.models.swift (1)

134-136: 🛠️ Refactor suggestion

Review ID configuration for junction tables.

Using @ID(custom: "graph_id") for junction tables might not be the best practice. Consider using a composite primary key.

-    @ID(custom: "graph_id")
-    var id: UUID?
+    @ID(custom: .id)
+    var id: UUID?
+
+    // Or better, use a composite key:
+    // @CompositeID([
+    //     @Parent(key: "graph_id") var graph: GraphGroup,
+    //     @Parent(key: "label_id") var label: Label
+    // ])

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server-tests/importers/importer-commerz-bank.swift (1)

16-20: 🛠️ Refactor suggestion

Avoid force unwrapping in regex creation.

The force unwrapping of factory could cause a crash if no parser is found.

-        let regex = try Regex(factory!.fileRegex)
+        guard let factory = factory else {
+            XCTFail("No parser found")
+            return
+        }
+        let regex = try Regex(factory.fileRegex)
📝 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.

		guard let factory = factory else {
            XCTFail("No parser found")
            return
        }
		let regex = try Regex(factory.fileRegex)

		let fileTest = "DE19821450020041545900_EUR_11-08-2024_2056.csv"
		let match = try? regex.firstMatch(in: fileTest)
		XCTAssertNotNil(match)
src/react-view/contents/graphs/graph-with-rechart/view.tsx (1)

41-44: ⚠️ Potential issue

Fix O(n²) time complexity in reducer.

The spread operator in the reducer creates a new object on each iteration, leading to O(n²) time complexity:

-        ...group.value.reduce<Partial<Record<SK, ApiUUID>>>((acc, cur) => {
-            keys.add(cur.label);
-            return { ...acc, [cur.label]: cur.value };
-        }, {}),
+        ...group.value.reduce<Partial<Record<SK, ApiUUID>>>((acc, cur) => {
+            keys.add(cur.label);
+            acc[cur.label] = cur.value;
+            return acc;
+        }, {}),
📝 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.

        ...group.value.reduce<Partial<Record<SK, ApiUUID>>>((acc, cur) => {
            keys.add(cur.label);
            acc[cur.label] = cur.value;
            return acc;
        }, {}),
🧰 Tools
🪛 Biome (1.9.4)

[error] 43-43: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

src/swift-server-tests/profile.tests.swift (1)

66-112: 🛠️ Refactor suggestion

Avoid force unwrapping optionals.

The test uses force unwrapping (!) which could cause runtime crashes if the values are nil. Consider using XCTUnwrap for safer optional handling in tests.

-var userFromDb = try await User.query(on: app.db).filter(\.$id == testUser.id!)
-  .first()
-XCTAssertTrue(userFromDb!.verifyPassword(pwd: "test-password"))
+let userFromDb = try XCTUnwrap(
+  await User.query(on: app.db)
+    .filter(\.$id == try XCTUnwrap(testUser.id))
+    .first()
+)
+XCTAssertTrue(userFromDb.verifyPassword(pwd: "test-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.

func testUpdateOtherProfileAsAdmin() async throws {
    let app = try getApp()

    let headers = try await app.getHeaders(
        forUser: .init(
            username: testAdmin.username, password: "test-admin-password"))

    var newUserInfo = Components.Schemas.UpdateUserData(
        username: testUser.username,
        email: "[email protected]",
        firstName: "First naming",
        lastName: "Last naming",
        isActive: false,
        isAdmin: true,
        defaultGroupId: testGroup.id!.uuidString
    )

    let response = try await app.sendRequest(
        .PUT, "/api/users/\(testUser.requireID().uuidString)", body: newUserInfo,
        headers: headers)

    XCTAssertEqual(response.status, .ok)

    let user = try response.content.decode(Components.Schemas.UserProfile.self)
    XCTAssertEqual(user.email, "[email protected]")
    XCTAssertEqual(user.firstName, "First naming")
    XCTAssertEqual(user.lastName, "Last naming")
    XCTAssertEqual(user.isActive, false)
    XCTAssertEqual(user.isAdmin, true)
    XCTAssertEqual(user.defaultGroupId, testGroup.id!.uuidString)

    let userFromDb = try XCTUnwrap(
        await User.query(on: app.db)
            .filter(\.$id == try XCTUnwrap(testUser.id))
            .first()
    )
    XCTAssertTrue(userFromDb.verifyPassword(pwd: "test-password"))

    newUserInfo.password = "new-password"

    let response2 = try await app.sendRequest(
        .PUT, "/api/users/\(testUser.requireID().uuidString)", body: newUserInfo,
        headers: headers)

    XCTAssertEqual(response2.status, .ok)

    userFromDb = try await User.query(on: app.db).filter(\.$id == testUser.id!).first()
    XCTAssertTrue(userFromDb!.verifyPassword(pwd: "new-password"))
    XCTAssertFalse(userFromDb!.verifyPassword(pwd: "test-password"))
}
config/tsconfig.build.json (1)

2-2: 💡 Codebase verification

Fix incorrect TypeScript configuration path

The config/tsconfig.build.json extends from "../react/tsconfig.json" which doesn't exist. It should extend from the root tsconfig.json that contains the proper React/TypeScript configuration.

  • Change "extends": "../react/tsconfig.json" to "extends": "../tsconfig.json" in config/tsconfig.build.json
🔗 Analysis chain

Verify TypeScript configuration inheritance.

The configuration now extends from react/tsconfig.json instead of the root tsconfig. Ensure that all necessary compiler options are properly inherited and that this change doesn't affect any existing TypeScript builds.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the referenced tsconfig exists and contains necessary compiler options
cat ../react/tsconfig.json | jq '.compilerOptions'

# Find all TypeScript files that might be affected by this change
fd -e ts -e tsx --exclude node_modules --exclude '*.spec.ts' --exclude '*.spec.tsx'

Length of output: 4415


Script:

#!/bin/bash
# Find all tsconfig files
fd tsconfig

# If root tsconfig exists, check its content
if [ -f tsconfig.json ]; then
  echo "=== Root tsconfig.json content ==="
  cat tsconfig.json
fi

# Check the content of the build tsconfig
echo "=== config/tsconfig.build.json content ==="
cat config/tsconfig.build.json

Length of output: 945

run-and-reload.sh (1)

1-4: 🛠️ Refactor suggestion

Add shebang and shell safety options

The script should start with a shebang and include shell safety options.

+#!/usr/bin/env bash
+set -euo pipefail
+
 COMMAND="swift run"
 if [ $# -gt 0 ]; then
     COMMAND="swift $*"
 fi
📝 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.

#!/usr/bin/env bash
set -euo pipefail

COMMAND="swift run"
if [ $# -gt 0 ]; then
    COMMAND="swift $*"
fi
🧰 Tools
🪛 Shellcheck (0.10.0)

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

config/jest.json (1)

16-16: 🛠️ Refactor suggestion

Avoid absolute path for coverage directory

Using an absolute path /coverage/react-view might cause permission issues or fail in different environments.

-    "coverageDirectory": "/coverage/react-view",
+    "coverageDirectory": "<rootDir>/coverage/react-view",
📝 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.

    "coverageDirectory": "<rootDir>/coverage/react-view",
.github/workflows/test-view.yml (1)

17-18: 🛠️ Refactor suggestion

Update checkout action version

The actions/checkout@v3 is outdated. Update to v4 for both jobs.

-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4

Also applies to: 36-37

🧰 Tools
🪛 actionlint (1.7.4)

17-17: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

src/react-view/index.html (2)

23-28: ⚠️ Potential issue

Secure the configuration data exposure

The configuration data is exposed directly in the HTML as JSON. This could potentially expose sensitive information to client-side inspection.

Consider:

  1. Moving sensitive configuration to environment variables
  2. Only exposing necessary configuration data
  3. Implementing runtime configuration fetching through a secure API endpoint
-    <script id='js-constants' type="application/json">{
-        "environment": "#(environment)",
-        "version": "#(version)",
-        "decimalCount": "#(decimalCount)",
-        "debug": #if(debug): true #else: false #endif
-    }</script>
+    <script id='js-constants' type="application/json">{
+        "version": "#(version)",
+        "decimalCount": "#(decimalCount)"
+    }</script>
📝 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.

    <script id='js-constants' type="application/json">{
        "version": "#(version)",
        "decimalCount": "#(decimalCount)"
    }</script>

30-34: 🛠️ Refactor suggestion

Consider using Content Security Policy (CSP)

The webpack public path is set using inline scripts, which could be restricted by CSP.

Consider:

  1. Adding appropriate CSP headers
  2. Moving the webpack configuration to a separate JavaScript file
  3. Using defer attribute for script loading
-    <!-- WIP: Webpack -->
-    <script>
-        __webpack_public_path__ = "#(static)";
-        window.__webpack_public_path__ = __webpack_public_path__;
-    </script>
+    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
+    <script src="#(static)webpack-config.js" defer></script>

Committable suggestion skipped: line range outside the PR's diff.

.coderabbit.yaml (1)

27-28: ⚠️ Potential issue

Fix indentation for base_branches

The indentation is incorrect according to YAML specifications.

Apply this fix:

-    base_branches: 
-        - "mr-scrooge-3.0.0"
+    base_branches:
+      - "mr-scrooge-3.0.0"
📝 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.

    base_branches:
      - "mr-scrooge-3.0.0"
🧰 Tools
🪛 yamllint (1.35.1)

[error] 27-27: trailing spaces

(trailing-spaces)


[warning] 28-28: wrong indentation: expected 6 but found 8

(indentation)

src/@typespec/openapi3/openapi.yaml (5)

1-12: ⚠️ Potential issue

Add security definitions and requirements

The API specification lacks critical security configurations. For a financial application handling sensitive data, this is a significant security risk.

Add the following security configurations:

 openapi: 3.0.0
 info:
   title: Mr Scrooge API
   version: 0.0.0
+security:
+  - bearerAuth: []
+components:
+  securitySchemes:
+    bearerAuth:
+      type: http
+      scheme: bearer
+      bearerFormat: JWT

Also ensure that each endpoint is properly secured by adding security requirements:

paths:
  /your-path:
    get:
      security:
        - bearerAuth: []
🧰 Tools
🪛 checkov (3.2.334)

[HIGH] 1-1954: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)


[HIGH] 1-1954: Ensure that security operations is not empty.

(CKV_OPENAPI_5)


368-370: 🛠️ Refactor suggestion

Fix 204 response description

The delete operation returns 204 (No Content) but includes a description about headers, which is inconsistent with the HTTP specification.

Update the response description:

         '204':
-          description: 'There is no content to send for this request, but the headers may be useful. '
+          description: 'The group was successfully deleted.'
📝 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.

        '204':
          description: 'The group was successfully deleted.'
      tags:

1888-1897: ⚠️ Potential issue

Add file upload constraints

The UploadData schema lacks important constraints for file uploads, which could lead to security issues.

Add file size and content type constraints:

     UploadData:
       type: object
       required:
         - kind
         - file
       properties:
         kind:
           type: string
+          enum: [csv, ofx, qif]  # Add supported file types
         file:
           type: string
           format: byte
+          maxLength: 10485760  # Limit to 10MB
+      x-content-type: multipart/form-data
📝 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.

      type: object
      required:
        - kind
        - file
      properties:
        kind:
          type: string
          enum: [csv, ofx, qif]  # Add supported file types
        file:
          type: string
          format: byte
          maxLength: 10485760  # Limit to 10MB
      x-content-type: multipart/form-data

59-91: 🛠️ Refactor suggestion

Use JSON Patch format for PATCH operations

The comment endpoint uses PATCH but doesn't follow the JSON Patch (RFC 6902) standard, which is recommended for PATCH operations.

Update the request body schema to use JSON Patch format:

       requestBody:
         required: true
         content:
           application/json:
             schema:
-              type: object
-              properties:
-                comment:
-                  type: string
+              type: array
+              items:
+                type: object
+                required:
+                  - op
+                  - path
+                  - value
+                properties:
+                  op:
+                    type: string
+                    enum: [replace]
+                  path:
+                    type: string
+                    enum: [/comment]
+                  value:
+                    type: string
📝 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.

    patch:
      operationId: ApiBankTransactions_comment
      description: Set a comment into the transaction
      parameters:
        - name: transactionId
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/UUID'
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BankTransaction'
        '404':
          description: The server cannot find the requested resource.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundBankTransaction'
      tags:
        - bank-transactions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
                required:
                  - op
                  - path
                  - value
                properties:
                  op:
                    type: string
                    enum: [replace]
                  path:
                    type: string
                    enum: [/comment]
                  value:
                    type: string

1898-1907: ⚠️ Potential issue

Enhance authentication security

The user credentials schema lacks password requirements and the login endpoint needs rate limiting.

Add password requirements and rate limiting:

     UserCredentials:
       type: object
       required:
         - username
         - password
       properties:
         username:
           type: string
         password:
           type: string
+          minLength: 8
+          pattern: ^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$
+          description: Password must contain at least 8 characters, including uppercase, lowercase, numbers, and special characters

Also add rate limiting headers to the login endpoint response and consider implementing exponential backoff for failed attempts.

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

    UserCredentials:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
        password:
          type: string
          minLength: 8
          pattern: ^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$
          description: Password must contain at least 8 characters, including uppercase, lowercase, numbers, and special characters
src/swift-server-tests/session.tests.swift (1)

89-100: 💡 Codebase verification

Add test cases to verify Django's password validation rules

The Django backend enforces several password validation rules that should be tested:

  • Minimum length validation
  • Common password prevention
  • Numeric-only password prevention
  • User attribute similarity validation

These validation rules are configured in the Django backend (server/finances/settings_base.py), but the current test suite doesn't verify their enforcement.

🔗 Analysis chain

Add test cases for password validation rules.

The password update test verifies the basic functionality but doesn't test password validation rules (e.g., minimum length, required characters).

Would you like me to generate additional test cases for password validation scenarios?

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if password validation rules exist in the codebase
rg -A 5 'password.*validation|validate.*password'

Length of output: 1935


Script:

#!/bin/bash
# Check for Swift-specific password validation rules and test setup
rg -g '*.swift' -A 5 'password.*valid|valid.*password'

# Check test setup and configuration
fd -e swift -e yaml -e json config test setup

Length of output: 723

src/swift-server-tests/imports.tests.swift (1)

19-37: 🛠️ Refactor suggestion

Optimize Data initialization and avoid force-unwrapping.

The code uses force-unwrapped Data initialization from strings and could be optimized.

+private extension String {
+    var formData: Data {
+        Data(self.utf8)
+    }
+}

-body.append("--\(boundary)\r\n".data(using: .utf8)!)
-body.append("Content-Disposition: form-data; name=\"kind\"\r\n\r\n".data(using: .utf8)!)
-body.append("\(kind)\r\n".data(using: .utf8)!)
+body.append("--\(boundary)\r\n".formData)
+body.append("Content-Disposition: form-data; name=\"kind\"\r\n\r\n".formData)
+body.append("\(kind)\r\n".formData)

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 19-19: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)


[Warning] 21-21: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)


[Warning] 22-22: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)


[Warning] 25-25: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)


[Warning] 27-27: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)


[Warning] 29-29: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)


[Warning] 31-31: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)


[Warning] 33-33: Prefer non-optional Data(_:) initializer when converting String to Data

(non_optional_string_data_conversion)

src/swift-server-tests/app-test.swift (1)

105-108: 🛠️ Refactor suggestion

Improve error handling in setUp.

The catch block simply prints and re-throws the error. Consider adding more context about the setup failure.

-        } catch {
-            print(error)
-            throw error
+        } catch let error as TestError {
+            throw TestError(message: "Test setup failed: \(error.message ?? "Unknown error")", context: error.context)
+        } catch {
+            throw TestError(message: "Test setup failed: \(error.localizedDescription)")
         }

Committable suggestion skipped: line range outside the PR's diff.

src/swift-server-tests/importers/importer-service.tests.swift (1)

171-203: 💡 Codebase verification

Test should verify complete error message format

The test only checks for the presence of error code "E10004" but should verify the complete error message format. Other tests in the codebase (e.g., in importer-n26.tests.swift) verify the full message format "E{code}: {message}". Update the test to verify "E10004: Invalid format converting data".

🔗 Analysis chain

Verify error message format consistency.

The test checks for error code "E10004" but doesn't verify the complete error message format. Consider adding a constant for error codes and verifying the complete message structure.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other error code definitions to ensure consistency
rg -A 1 'E\d{5}'

Length of output: 15434

src/react-view/contents/graphs/form/graph.form.tsx (2)

67-73: ⚠️ Potential issue

Add error handling for graph transformation.

The updateGraph function should handle potential errors during transformation.

 const updateGraph = (data: GraphUiRepresentation) => {
+    try {
         update(uiToGraph(data) as T);
+    } catch (error) {
+        useLogger().error('Failed to transform graph data', { error, data });
+        // Consider showing an error message to the user
+    }
 };

Committable suggestion skipped: line range outside the PR's diff.


81-83: 🛠️ Refactor suggestion

Add loading state during save operation.

The save operation might be asynchronous but there's no loading state handling.

+const [isSaving, setIsSaving] = React.useState(false);
+
 onSubmit={() => {
+    setIsSaving(true);
     save();
+        .catch(error => {
+            useLogger().error('Failed to save graph', { error });
+            // Show error message to user
+        })
+        .finally(() => setIsSaving(false));
 }}

Committable suggestion skipped: line range outside the PR's diff.

* Create a migration command to migrate the data from V2 to V3

* Fixing some of coderabbit comments

* some small improvements

* some small improvements

* Adding a transaction for the migration
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: 10

🧹 Nitpick comments (23)
src/swift-server-tests/app-test.swift (6)

18-33: Add documentation for helper functions.

Consider adding documentation comments to describe the purpose, parameters, and return values of these helper functions. This will improve maintainability and help other developers understand their usage.

Example documentation format:

/// Creates a new user in the database
/// - Parameters:
///   - app: The application instance
///   - username: The user's username
///   - email: The user's email address
///   - password: The user's password
///   - defaultGroupId: The user's default group ID
///   - isAdmin: Whether the user has admin privileges
/// - Returns: The created user instance
/// - Throws: Database errors or password hashing errors
func createUser(...)

67-67: Fix comment spacing.

Add a space after the double slash for consistency with Swift style guidelines.

-//app.queues.add(NewTransactionJob())
+// app.queues.add(NewTransactionJob())
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 67-67: Prefer at least one space after slashes for comments

(comment_spacing)


59-109: Refactor setUp method for better maintainability.

The setUp method is quite large and handles multiple responsibilities. Consider breaking it down into smaller, focused methods:

override func setUp() async throws {
    try await setupApplication()
    try await setupTestGroups()
    try await setupTestUsers()
    try await setupTestLabels()
}

private func setupApplication() async throws {
    app = try await Application.make(.testing)
    try await configure(app)
    app.queues.use(.asyncTest)
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 67-67: Prefer at least one space after slashes for comments

(comment_spacing)


105-108: Use proper logging instead of print statements.

Replace the print statement with proper logging for better error tracking and consistency.

-            print(error)
+            app?.logger.error("Setup failed: \(error)")

149-149: Avoid force unwrapping in tests.

While force unwrapping in tests is common, it's better to use XCTUnwrap for safer error handling and better failure messages.

-let user = users.first!
+let user = try XCTUnwrap(users.first, "Expected to find created user")

156-201: Consider enhancing request handling robustness.

A few suggestions to improve the Application extension:

  1. Define constants for magic strings:
private enum HTTPConstants {
    static let contentType = "content-type"
    static let cookie = "cookie"
    static let setCookie = "set-cookie"
    static let jsonContentType = "application/json"
    static let vaporSession = "vapor-session="
}
  1. Add validation for cookie list:
-let cookie: String = cookiesList[0]
+guard let cookie = cookiesList.first else {
+    throw TestError(message: "No session cookie found in response")
+}
src/swift-server/graph/graph.models.swift (2)

5-7: Simplify enum declarations by omitting redundant raw string values

Since the raw string values match the case names, you can omit them to make the code cleaner.

Apply this diff:

enum GraphKind: String, Codable, CaseIterable {
-	case bar = "bar"
-	case line = "line"
-	case pie = "pie"
+	case bar
+	case line
+	case pie
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 5-5: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 6-6: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 7-7: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


11-15: Simplify enum declarations by omitting redundant raw string values

Since the raw string values match the case names, you can omit them to make the code cleaner.

Apply this diff:

enum GraphGroupType: String, Codable, CaseIterable {
-	case day = "day"
-	case labels = "labels"
-	case month = "month"
-	case sign = "sign"
-	case year = "year"
+	case day
+	case labels
+	case month
+	case sign
+	case year
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 11-11: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 12-12: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 13-13: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 14-14: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 15-15: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)

src/swift-server/configure.swift (4)

41-45: Document environment configuration and validate SQLite path.

Consider adding documentation for the DB_URL environment variable and validating the SQLite file path accessibility.

+/// Configures the database connection using the DB_URL environment variable.
+/// Format:
+/// - PostgreSQL: postgres://[user:password@]host[:port]/database
+/// - SQLite: sqlite://path/to/db.sqlite3 or sqlite://memory
 func configureDb(_ app: Application) async throws {
     let dbUrl = Environment.get("DB_URL") ?? "sqlite://db.sqlite3"
+    // Validate SQLite file path accessibility if not in-memory
+    if dbUrl.starts(with: "sqlite://") && !dbUrl.contains("memory") {
+        let path = String(dbUrl.dropFirst("sqlite://".count))
+        guard FileManager.default.isWritableFile(atPath: path) else {
+            throw Exception(.E10003, context: ["error": "SQLite database path is not writable"])
+        }
+    }
     let (dbFactory, dbId) = try getDbConfig(url: dbUrl)
     app.databases.use(dbFactory, as: dbId)
 }

79-81: Consider environment-specific queue configuration.

The queue configuration uses hard-coded values:

  • Worker count is set to 1
  • Job processing is started in-process

Consider making these configurable via environment variables:

-        app.queues.configuration.workerCount = 1
+        app.queues.configuration.workerCount = Environment.get("QUEUE_WORKERS")
+            .flatMap(Int.init) ?? 1

83-86: Enhance error handling with specific error cases.

The current catch-all error handling could be more specific to help with debugging.

-    } catch {
-        print(error)
-        throw error
+    } catch let error as DecodingError {
+        app.logger.error("Configuration error: \(error)")
+        throw error
+    } catch let error as DatabaseError {
+        app.logger.error("Database error: \(error)")
+        throw error
+    } catch {
+        app.logger.error("Unexpected error: \(error)")
+        throw error

82-82: Fix comment formatting.

Add a space after the double slash to comply with SwiftLint rules.

-//try app.queues.startScheduledJobs()
+// try app.queues.startScheduledJobs()
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 82-82: Prefer at least one space after slashes for comments

(comment_spacing)

src/swift-server/django-migration/v2-migrate.command.swift (2)

20-30: Enhance database URL validation using URI parsing.

The current validation checks if oldDbUrl starts with specific strings, which might not be robust. Using URI parsing can provide a more accurate validation of the database URL.

Consider using URI(string:) to parse and validate the URL scheme:

20      func validate() throws {
21          guard let uri = URI(string: oldDbUrl),
22                uri.scheme == "sqlite" || uri.scheme == "postgres"
23          else {
24              throw InvalidArgument(
25                  argument: "dbUrl",
26                  reason: "Invalid database URL format. Must be a valid 'sqlite://' or 'postgres://' URL"
27              )
28          }
29      }
-     guard
-         oldDbUrl.starts(with: "sqlite://")
-             || oldDbUrl.starts(with: "postgres://")
-     else {
-         throw InvalidArgument(
-             argument: "dbUrl",
-             reason:
-                 "Invalid database URL format. Must start with 'sqlite://' or 'postgres://'"
-         )
-     }
30      }

41-44: Avoid redefining application; use context.application directly.

Redefining application can be confusing and is unnecessary since context.application is already available.

Simplify by using context.application:

40          let (dbFactory, _) = try getDbConfig(url: signature.oldDbUrl)
-         let application = context.application
41          let oldDbId: DatabaseID = .init(string: "old")
-         application.databases.use(dbFactory, as: oldDbId, isDefault: false)
-         let oldDb = application.db(oldDbId)
+         context.application.databases.use(dbFactory, as: oldDbId, isDefault: false)
+         let oldDb = context.application.db(oldDbId)
src/swift-server/django-migration/django-migration.service.swift (1)

269-284: Improve efficiency when fetching tag relations.

In getRelations, using raw SQL queries may introduce SQL injection risks and is less efficient.

Use Fluent's query builder to construct the query safely:

269      func getRelations(on oldDb: SQLDatabase) async throws -> [TagsRelation] {
-         let tableName = DjangoMigrationService.OldDb.TableNames.rdsToTags.rawValue
-         let tagsList = try await oldDb.select().column(
-             SQLColumn(
-                 SQLLiteral.all,
-                 table: SQLIdentifier(tableName))
-         ).from(
-             tableName
-         ).where(
-             SQLColumn("raw_data_source_id", table: tableName), .equal,
-             SQLLiteral.numeric("\(try requireID())")
-         ).all()
-         return try tagsList.map {
-             try .init(row: $0)
-         }
+         let tableName = DjangoMigrationService.OldDb.TableNames.rdsToTags.rawValue
+         let query = oldDb.select()
+             .columns("*")
+             .from(SQLIdentifier(tableName))
+             .where("raw_data_source_id", .equal, SQLBind(try requireID()))
+         let rows = try await query.all()
+         return try rows.map { try .init(row: $0) }
      }
src/swift-server-tests/migrate-v2-command.tests.swift (1)

180-181: Correct the typo in the database URL.

In the arguments for the command, "sqlite://" is misspelled as "sqlte://", which is intentional for the test case. Ensure that this typo serves its purpose in triggering the expected error.

No action needed if intentional; otherwise, correct the typo:

180         let arguments = [
181             "v2_migrate", "sqlte://\(dbPath)", groupId.uuidString,
+         // Corrected URL if not intentional: "sqlite://\(dbPath)"
         ]
README.md (4)

12-12: Clarify the status of MultiBank QIF File support.

The "(TBD)" notation should be expanded to provide more context about when this format will be supported or what limitations currently exist.

Consider adding a brief explanation, for example:

- MultiBank QIF File (TBD)
+ MultiBank QIF File (Coming in Q2 2025 - currently in development)

16-27: Enhance migration instructions with data validation steps.

While the migration steps are clear, they lack guidance on validating the migrated data and potential rollback procedures.

Consider adding:

  1. Pre-migration data backup instructions
  2. Post-migration validation steps
  3. Rollback procedures in case of migration failures
🧰 Tools
🪛 Markdownlint (0.37.0)

27-27: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


27-27: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


23-23: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)


51-52: Clarify the softlink explanation.

The comment about ServerDebug being a softlink could be more descriptive about its purpose and setup.

- # ServerDebug is a softlink, to reuse the build from the swift run
+ # ServerDebug is a symbolic link to the debug build executable, created during 'swift run'

54-57: Improve default credentials documentation.

The default credentials (demo:demo) should be highlighted as temporary and include a security recommendation.

- 3. Create an admin user (by default will be demo:demo):
+ 3. Create an admin user (defaults to username: demo, password: demo):

Add a security note:

> **Security Note**: It's recommended to change the default credentials immediately after creation.
🧰 Tools
🪛 LanguageTool

[grammar] ~54-~54: The past participle is required after “will be”, alternatively you could omit the “be”.
Context: .... Create an admin user (by default will be demo:demo): ``` ./serverDebug create_user --...

(BE_VBP_IN)

🪛 Markdownlint (0.37.0)

55-55: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)

src/swift-server/core/error.swift (3)

3-8: Consider using a more descriptive naming convention for error codes.

The current E1XXXX format makes it difficult to quickly identify the error category.

Consider grouping errors by domain and using more descriptive prefixes:

enum ErrorCode: String, CaseIterable {
-    case E10000, E10001, E10002
+    case PARSER_NOT_FOUND = "PARSER_001"
+    case GRAPH_NO_GROUP = "GRAPH_001"
+    case TRANSFORM_INVALID_MAPPING = "TRANSFORM_001"

24-32: Enhance ErrorInfo with severity levels and error codes.

The current ErrorInfo class could be more useful with additional metadata.

Consider adding:

class ErrorInfo {
    enum Severity {
        case critical, error, warning, info
    }
    
    let code: String
    let message: String
    let severity: Severity
    let additionalInfo: String?
    
    init(code: String, message: String, severity: Severity = .error, additionalInfo: String? = nil)
}

92-104: Add logging integration to ErrorCode extension.

The current error handling doesn't include logging capabilities.

Consider adding a logging method:

extension ErrorCode {
    func log(logger: Logger) {
        logger.error("\(self.stringify())", metadata: [
            "error_code": .string(self.rawValue),
            "additional_info": .string(self.info.additionalInfo ?? "none")
        ])
    }
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50b628d and 7be33b4.

📒 Files selected for processing (11)
  • Package.swift (1 hunks)
  • README.md (2 hunks)
  • src/swift-server-tests/app-test.swift (1 hunks)
  • src/swift-server-tests/migrate-v2-command.tests.swift (1 hunks)
  • src/swift-server/bank-transaction/bank-transaction.models.swift (1 hunks)
  • src/swift-server/configure.swift (1 hunks)
  • src/swift-server/core/error.swift (1 hunks)
  • src/swift-server/core/exception.swift (1 hunks)
  • src/swift-server/django-migration/django-migration.service.swift (1 hunks)
  • src/swift-server/django-migration/v2-migrate.command.swift (1 hunks)
  • src/swift-server/graph/graph.models.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Package.swift
  • src/swift-server/core/exception.swift
🧰 Additional context used
🪛 SwiftLint (0.57.0)
src/swift-server-tests/migrate-v2-command.tests.swift

[Warning] 111-111: TODOs should be resolved (Add the validations)

(todo)

src/swift-server/configure.swift

[Warning] 82-82: Prefer at least one space after slashes for comments

(comment_spacing)

src/swift-server/graph/graph.models.swift

[Warning] 5-5: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 6-6: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 7-7: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 11-11: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 12-12: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 13-13: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 14-14: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 15-15: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)

src/swift-server-tests/app-test.swift

[Warning] 67-67: Prefer at least one space after slashes for comments

(comment_spacing)

src/swift-server/bank-transaction/bank-transaction.models.swift

[Warning] 46-46: TODOs should be resolved (change the name to comment)

(todo)

🪛 Markdownlint (0.37.0)
README.md

27-27: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


27-27: null
Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


23-23: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)


46-46: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)


50-50: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)


55-55: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)


61-61: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)


66-66: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)


71-71: null
Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 LanguageTool
README.md

[grammar] ~54-~54: The past participle is required after “will be”, alternatively you could omit the “be”.
Context: .... Create an admin user (by default will be demo:demo): ``` ./serverDebug create_user --...

(BE_VBP_IN)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Test
🔇 Additional comments (10)
src/swift-server-tests/app-test.swift (1)

1-16: LGTM! Well-structured error handling.

The imports are comprehensive and the TestError class follows Swift error handling best practices with clear property definitions.

src/swift-server/bank-transaction/bank-transaction.models.swift (2)

4-5: Verify thread safety for Sendable conformance.

The @unchecked Sendable conformance indicates manual verification of thread safety. Ensure all properties are either immutable or properly synchronized when accessed concurrently.

Consider documenting why manual Sendable conformance is necessary and how thread safety is guaranteed.


52-67: 🛠️ Refactor suggestion

Update initializer to match property changes.

The initializer needs several improvements:

  1. Remove redundant groupOwnerId assignment
  2. Rename description parameter to comment after implementing the TODO
     init(
         id: UUID? = nil, groupOwnerId: UUID, movementName: String, date: DateOnly,
         dateValue: DateOnly? = nil, details: String? = nil, value: Double, kind: String,
-        description: String? = nil
+        comment: String? = nil
     ) {
         self.id = id
         self.$groupOwner.id = groupOwnerId
-        self.groupOwnerId = groupOwnerId
         self.movementName = movementName
         self.date = date
         self.dateValue = dateValue
         self.details = details
         self.value = value
         self.kind = kind
-        self.description = description
+        self.comment = comment
     }

Likely invalid or redundant comment.

src/swift-server/graph/graph.models.swift (2)

84-89: Verify the use of 'graph_id' as both the ID and foreign key

In GraphGroup, GraphHorizontalGroup, GraphGroupLabels, and GraphHorizontalGroupLabels, the id property and the @Parent relationship are both using graph_id. This means graph_id serves as both the primary key and a foreign key. Please verify that this is intentional and aligns with your database schema design, as it may lead to unexpected behavior.

Also applies to: 109-114, 139-147, 163-171


27-67: 'Graph' model is well-defined and implements relationships correctly

The Graph model's properties and relationships are properly structured, ensuring data integrity and consistency.

src/swift-server/configure.swift (2)

1-8: LGTM! Well-organized imports.

All necessary dependencies are properly imported for database operations, templating, security, job processing, and web framework functionality.


78-83: Address TODO comment and remove or document commented code.

The configuration contains:

  1. An unaddressed TODO comment about environment configuration
  2. Commented-out code (startScheduledJobs)

Please either implement the TODO or create a tracking issue. Also, either remove the commented code or document why it's preserved.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 82-82: Prefer at least one space after slashes for comments

(comment_spacing)

src/swift-server/django-migration/v2-migrate.command.swift (1)

61-77: Ensure errors within the transaction are propagated correctly.

Exceptions thrown inside the transaction block may be swallowed if not properly handled, potentially leaving the database in an inconsistent state.

Verify that errors thrown inside the transaction are correctly propagated and cause the transaction to fail:

✅ Verification successful

Error handling in transaction block is properly implemented

The transaction block correctly propagates errors:

  • All migration operations use try await
  • No error suppression within the transaction
  • Errors are properly caught and handled in the parent scope
  • Transaction will automatically rollback on any error
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for proper error handling within the transaction block.

# Search for transaction blocks in the codebase
rg 'application\.db\.transaction' -A 10

# Verify that no catch blocks inside the transaction suppress errors

Length of output: 1122


Script:

#!/bin/bash
# Get more context lines for the transaction block
rg 'application\.db\.transaction' -B 5 -A 20

# Check for catch blocks in the migration service
rg 'catch' src/swift-server/django-migration/

# Look for migration method implementations
ast-grep --pattern 'func migrate$$$($$$) $$$'

Length of output: 2783

src/swift-server/django-migration/django-migration.service.swift (1)

417-419: ⚠️ Potential issue

Handle unknown group types gracefully in GraphGroup.

In the getGroupType method, an unhandled group value will throw an exception. Consider providing a default case or logging the invalid value.

Check if all possible group values are covered, or handle the default case appropriately.

417          default:
418              throw Exception(.E10026, context: ["group": group])
+             // Alternatively, assign a default group type
+             // return .unknown
src/swift-server-tests/migrate-v2-command.tests.swift (1)

191-193: Ensure the command handles errors correctly in test cases.

In testInvalidUrl, the command is run but the test does not assert that an error occurred or that the output contains the expected error messages.

Add assertions to confirm that the invalid URL error is handled as expected.

192      try await console.run(command, with: context)

+     // Assert that the command exited with a failure
+     XCTAssertEqual(console.exitCode, .failure)

     // XCTAssertNil(console.testOutputQueue)

Comment on lines +10 to +14
@Field(key: "group_owner_id")
var groupOwnerId: UserGroup.IDValue

@Parent(key: "group_owner_id")
var groupOwner: UserGroup
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove redundant groupOwnerId storage.

The groupOwnerId is stored twice:

  1. As a @field
  2. Through the @parent relationship

This redundancy could lead to data inconsistencies. The @parent relationship already provides access to the ID.

-    @Field(key: "group_owner_id")
-    var groupOwnerId: UserGroup.IDValue
-
     @Parent(key: "group_owner_id")
     var groupOwner: UserGroup
📝 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
@Field(key: "group_owner_id")
var groupOwnerId: UserGroup.IDValue
@Parent(key: "group_owner_id")
var groupOwner: UserGroup
@Parent(key: "group_owner_id")
var groupOwner: UserGroup

Comment on lines +46 to +48
// TODO: change the name to comment
@OptionalField(key: "description")
var description: String?
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement the TODO and rename 'description' to 'comment'.

The property name 'description' could conflict with Swift's CustomStringConvertible protocol. As noted in the TODO, rename it to 'comment'.

-    // TODO: change the name to comment
-    @OptionalField(key: "description")
-    var description: String?
+    @OptionalField(key: "comment")
+    var comment: String?
📝 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
// TODO: change the name to comment
@OptionalField(key: "description")
var description: String?
@OptionalField(key: "comment")
var comment: String?
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 46-46: TODOs should be resolved (change the name to comment)

(todo)

Comment on lines +22 to +29
var date: DateOnly {
get {
DateOnly(_date)!
}
set {
_date = newValue.toString()
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for date conversion.

The force unwrap DateOnly(_date)! could crash if the stored date string is invalid. Consider implementing proper error handling.

     var date: DateOnly {
         get {
-            DateOnly(_date)!
+            guard let date = DateOnly(_date) else {
+                // Log the error
+                return DateOnly.distantPast // or another appropriate default
+            }
+            return date
         }
         set {
             _date = newValue.toString()
         }
     }
📝 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
var date: DateOnly {
get {
DateOnly(_date)!
}
set {
_date = newValue.toString()
}
}
var date: DateOnly {
get {
guard let date = DateOnly(_date) else {
// Log the error
return DateOnly.distantPast // or another appropriate default
}
return date
}
set {
_date = newValue.toString()
}
}

Comment on lines +9 to +39
func getDbConfig(url dbUrl: String) throws -> (DatabaseConfigurationFactory, DatabaseID) {
if !dbUrl.starts(with: "sqlite://") && !dbUrl.starts(with: "postgres://") {
throw Exception(.E10019, context: ["dbUrl": dbUrl])
}

let components = dbUrl.split(separator: ":", maxSplits: 1)
let dbType = components[0].lowercased()
let filePath = String(components[1].dropFirst(2))

let sqlLogLevel: Logger.Level =
Environment.get("SQL_LOG_LEVEL").flatMap { Logger.Level(rawValue: $0) } ?? .debug

switch dbType {
case "postgres":
return (try DatabaseConfigurationFactory.postgres(url: dbUrl), .psql)
case "sqlite":
if filePath == "memory" {
return (
DatabaseConfigurationFactory.sqlite(
.memory, sqlLogLevel: sqlLogLevel), .sqlite
)
} else {
return (
DatabaseConfigurationFactory.sqlite(
.file(filePath), sqlLogLevel: sqlLogLevel), .sqlite
)
}
default:
throw Exception(.E10003, context: ["db_url": dbUrl])
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance database URL parsing robustness.

The current URL parsing implementation could be more robust:

  1. Using string splitting is fragile for URL parsing
  2. PostgreSQL connection string validation is missing
  3. Error codes (E10019, E10003) lack documentation

Consider this safer implementation:

 func getDbConfig(url dbUrl: String) throws -> (DatabaseConfigurationFactory, DatabaseID) {
-    if !dbUrl.starts(with: "sqlite://") && !dbUrl.starts(with: "postgres://") {
-        throw Exception(.E10019, context: ["dbUrl": dbUrl])
-    }
-    
-    let components = dbUrl.split(separator: ":", maxSplits: 1)
-    let dbType = components[0].lowercased()
-    let filePath = String(components[1].dropFirst(2))
+    guard let url = URL(string: dbUrl) else {
+        throw Exception(.E10019, context: ["dbUrl": dbUrl])
+    }
+    
+    let dbType = url.scheme?.lowercased()
+    guard let dbType = dbType else {
+        throw Exception(.E10019, context: ["dbUrl": dbUrl])
+    }

Also, consider extracting SQL logging configuration to a separate function for better separation of concerns.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +47 to +51
public func registerMigrations(_ app: Application) async throws {
app.migrations.add(SessionRecord.migration)
app.migrations.add(JobModelMigration())
app.migrations.add(InitialMigration())
}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Add missing JobModelMigration implementation

The migration order is correct, but JobModelMigration implementation is missing from the codebase. Please implement the missing migration or remove it if no longer needed.

🔗 Analysis chain

Verify migration dependencies and document order requirements.

Please ensure that migrations are ordered correctly based on their dependencies. Consider documenting any critical ordering requirements.

Run this script to analyze migration dependencies:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for migration dependencies in the codebase
echo "Searching for migration references..."
rg --type swift "migration|prepare|revert" -A 5 -B 5

echo "Checking for potential circular dependencies..."
ast-grep --pattern 'class $name: Migration {
  $$$
  func prepare(on database: Database) async throws {
    $$$
  }
  $$$
}'

Length of output: 4068


Script:

#!/bin/bash
# Search for SessionRecord and JobModel migrations
echo "Searching for SessionRecord migration..."
rg --type swift "SessionRecord.*migration" -A 10

echo "Searching for JobModel migration..."
rg --type swift "class JobModelMigration|struct JobModelMigration" -A 10

# Search for any references to tables/schemas that might indicate dependencies
echo "Searching for schema references..."
rg --type swift "schema\(['\"].*['\"]" --no-filename | sort -u

Length of output: 2968


Script:

#!/bin/bash
# Search for SessionRecord import and JobModel references
echo "Checking SessionRecord source..."
rg --type swift "^import.*Session|^import.*@Vapor"

echo "Searching for JobModel references..."
rg --type swift "JobModel|jobs?" -A 5 -B 5

echo "Checking for foreign key dependencies..."
rg --type swift "references|foreignKey" -A 2 -B 2

Length of output: 9995

Comment on lines +38 to +86
do {
try signature.validate()
let (dbFactory, _) = try getDbConfig(url: signature.oldDbUrl)
let application = context.application
let oldDbId: DatabaseID = .init(string: "old")
application.databases.use(dbFactory, as: oldDbId, isDefault: false)
let oldDb = application.db(oldDbId)

guard let sql = oldDb as? SQLDatabase else {
throw Exception(.E10020, context: ["oldDbUrl": signature.oldDbUrl])
}

guard
let groupOwner = try await UserGroup.query(on: application.db)
.filter(
\.$id == signature.groupOwnerId
).first()
else {
throw InvalidArgument(
argument: "groupId",
reason: "Group Owner Id not found in the DB")
}

try await application.db.transaction { transaction in
let migrator = DjangoMigrationService(
owner: try groupOwner.requireID(), app: application,
newDb: transaction,
oldDb: oldDb,
oldSqlDb: sql)

context.console.print("Starting migration...")
context.console.print("Migrating tags to labels...")
try await migrator.migrateTagsToLabels()
context.console.print("Migrating tags to rules...")
try await migrator.migrateTagsToRules()
context.console.print("Migrating transactions...")
try await migrator.migrateTransactions()
context.console.print("Migrating graphs...")
try await migrator.migrateGraphs()
}

context.console.print("Migration complete!")
} catch let error as Exception {
context.console.error(String(describing: error))
} catch let error as InvalidArgument {
context.console.error("Invalid argument: \(error.argument)")
context.console.error("Error: \(error.reason)")
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure the command exits with a non-zero status on failure.

In the run method, caught exceptions are logged but not rethrown or handled in a way that causes the command to exit with a non-zero status code. This may lead to the command signaling success even when an error has occurred.

Apply this diff to exit the application with a failure status upon encountering an error:

80      } catch let error as Exception {
81          context.console.error(String(describing: error))
+         throw ExitCode.failure
82      } catch let error as InvalidArgument {
83          context.console.error("Invalid argument: \(error.argument)")
84          context.console.error("Error: \(error.reason)")
+         throw ExitCode.failure
85      }
📝 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
do {
try signature.validate()
let (dbFactory, _) = try getDbConfig(url: signature.oldDbUrl)
let application = context.application
let oldDbId: DatabaseID = .init(string: "old")
application.databases.use(dbFactory, as: oldDbId, isDefault: false)
let oldDb = application.db(oldDbId)
guard let sql = oldDb as? SQLDatabase else {
throw Exception(.E10020, context: ["oldDbUrl": signature.oldDbUrl])
}
guard
let groupOwner = try await UserGroup.query(on: application.db)
.filter(
\.$id == signature.groupOwnerId
).first()
else {
throw InvalidArgument(
argument: "groupId",
reason: "Group Owner Id not found in the DB")
}
try await application.db.transaction { transaction in
let migrator = DjangoMigrationService(
owner: try groupOwner.requireID(), app: application,
newDb: transaction,
oldDb: oldDb,
oldSqlDb: sql)
context.console.print("Starting migration...")
context.console.print("Migrating tags to labels...")
try await migrator.migrateTagsToLabels()
context.console.print("Migrating tags to rules...")
try await migrator.migrateTagsToRules()
context.console.print("Migrating transactions...")
try await migrator.migrateTransactions()
context.console.print("Migrating graphs...")
try await migrator.migrateGraphs()
}
context.console.print("Migration complete!")
} catch let error as Exception {
context.console.error(String(describing: error))
} catch let error as InvalidArgument {
context.console.error("Invalid argument: \(error.argument)")
context.console.error("Error: \(error.reason)")
}
}
do {
try signature.validate()
let (dbFactory, _) = try getDbConfig(url: signature.oldDbUrl)
let application = context.application
let oldDbId: DatabaseID = .init(string: "old")
application.databases.use(dbFactory, as: oldDbId, isDefault: false)
let oldDb = application.db(oldDbId)
guard let sql = oldDb as? SQLDatabase else {
throw Exception(.E10020, context: ["oldDbUrl": signature.oldDbUrl])
}
guard
let groupOwner = try await UserGroup.query(on: application.db)
.filter(
\.$id == signature.groupOwnerId
).first()
else {
throw InvalidArgument(
argument: "groupId",
reason: "Group Owner Id not found in the DB")
}
try await application.db.transaction { transaction in
let migrator = DjangoMigrationService(
owner: try groupOwner.requireID(), app: application,
newDb: transaction,
oldDb: oldDb,
oldSqlDb: sql)
context.console.print("Starting migration...")
context.console.print("Migrating tags to labels...")
try await migrator.migrateTagsToLabels()
context.console.print("Migrating tags to rules...")
try await migrator.migrateTagsToRules()
context.console.print("Migrating transactions...")
try await migrator.migrateTransactions()
context.console.print("Migrating graphs...")
try await migrator.migrateGraphs()
}
context.console.print("Migration complete!")
} catch let error as Exception {
context.console.error(String(describing: error))
throw ExitCode.failure
} catch let error as InvalidArgument {
context.console.error("Invalid argument: \(error.argument)")
context.console.error("Error: \(error.reason)")
throw ExitCode.failure
}
}

Comment on lines +69 to +77
func getCondDouble() throws -> Double {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
if let d = formatter.number(from: conditional)?
.doubleValue
{
return d
}
throw Exception(.E10023, context: ["filterId": id as Any, "tagId": tagId])
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle number formatting in a locale-independent manner.

The getCondDouble method relies on NumberFormatter with the locale en_US_POSIX. This may not correctly parse numbers with different decimal separators.

Use Double(conditional) for direct conversion, or specify the expected number format explicitly:

69      func getCondDouble() throws -> Double {
-         let formatter = NumberFormatter()
-         formatter.locale = Locale(identifier: "en_US_POSIX")
-         if let d = formatter.number(from: conditional)?
-             .doubleValue
-         {
-             return d
-         }
+         if let d = Double(conditional) {
+             return d
+         }
          throw Exception(.E10023, context: ["filterId": id as Any, "tagId": tagId])
      }
📝 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
func getCondDouble() throws -> Double {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
if let d = formatter.number(from: conditional)?
.doubleValue
{
return d
}
throw Exception(.E10023, context: ["filterId": id as Any, "tagId": tagId])
func getCondDouble() throws -> Double {
if let d = Double(conditional) {
return d
}
throw Exception(.E10023, context: ["filterId": id as Any, "tagId": tagId])
}

Comment on lines +183 to +194
func migrateTagsToRules() async throws {
var tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
\.$parent.$id == nil
).all()
var tagToRuleDictId: [Int64: UUID] = [:]
while !tagsList.isEmpty {
let newIds = try await migrateTags(tagsList, withDict: &tagToRuleDictId)
tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
\.$parent.$id ~~ newIds
).all()
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Optimize recursive tag migration to prevent infinite loops.

The migrateTagsToRules method uses a while loop without a clear termination condition, which might lead to an infinite loop if there's a cyclic parent-child relationship.

Implement a mechanism to detect and handle cycles in tag hierarchy:

184      var tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
185          \.$parent.$id == nil
186      ).all()
+     var processedTagIds: Set<Int64> = []
      var tagToRuleDictId: [Int64: UUID] = [:]
      while !tagsList.isEmpty {
-         let newIds = try await migrateTags(tagsList, withDict: &tagToRuleDictId)
+         let newIds = try await migrateTags(
+             tagsList.filter { !processedTagIds.contains(try $0.requireID()) },
+             withDict: &tagToRuleDictId)
+         processedTagIds.formUnion(newIds)
          tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
              \.$parent.$id ~~ newIds
          ).all()
      }
📝 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
func migrateTagsToRules() async throws {
var tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
\.$parent.$id == nil
).all()
var tagToRuleDictId: [Int64: UUID] = [:]
while !tagsList.isEmpty {
let newIds = try await migrateTags(tagsList, withDict: &tagToRuleDictId)
tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
\.$parent.$id ~~ newIds
).all()
}
}
func migrateTagsToRules() async throws {
var tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
\.$parent.$id == nil
).all()
var processedTagIds: Set<Int64> = []
var tagToRuleDictId: [Int64: UUID] = [:]
while !tagsList.isEmpty {
let newIds = try await migrateTags(
tagsList.filter { !processedTagIds.contains(try $0.requireID()) },
withDict: &tagToRuleDictId)
processedTagIds.formUnion(newIds)
tagsList = try await OldDb.TagModel.query(on: oldDb).filter(
\.$parent.$id ~~ newIds
).all()
}
}

Comment on lines +111 to +165
// TODO: Add the validations
let graphsPage = try await app.graphService.getGraphs(
groupsId: [groupId], graphsIds: nil)
XCTAssertEqual(graphsPage.list.count, 3)

let incomeVsOutcome = graphsPage.list.first { $0.name == "Income vs outcome" }
XCTAssertNotNil(incomeVsOutcome)
if let incomeVsOutcome {
XCTAssertEqual(
incomeVsOutcome,
.init(
id: incomeVsOutcome.id, groupOwnerId: groupId.uuidString,
name: "Income vs outcome", kind: .bar,
dateRange: .oneYear,
group: .init(group: .sign),
horizontalGroup: .init(group: .month, accumulate: false)
),
"Income vs Outcome correctly transformed")
}

let compareByDay = graphsPage.list.first { $0.name == "Compare by day" }
XCTAssertNotNil(compareByDay)
if let compareByDay {
try XCTAssertEqual(
compareByDay,
.init(
id: compareByDay.id, groupOwnerId: groupId.uuidString,
name: "Compare by day", kind: .line,
labelFilterId: getLabelIdByName("Expenses")?.uuidString,
dateRange: .halfYear,
group: .init(group: .month),
horizontalGroup: .init(group: .day, accumulate: true)
),
"Compare by day graph correctly transformed")
}

let testPie = graphsPage.list.first { $0.name == "Test Pie" }
XCTAssertNotNil(testPie)

if let testPie {
try XCTAssertEqual(
testPie,
.init(
id: testPie.id, groupOwnerId: groupId.uuidString,
name: "Test Pie", kind: .pie,
dateRange: .all,
group: .init(
group: .labels,
hideOthers: true,
labels: ["With parent", "Expenses"].map {
try getLabelIdByName($0)?.uuidString ?? ""
}
)
), "Test pie graph correctly transformed")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Address the TODO: Add validations for graph transformations.

There is a TODO comment indicating that validations need to be added for the graph transformations in the test testV2MigrateCommand.

Implement assertions to validate that the migrated graphs match the expected data.

Would you like assistance in writing these validations or creating a GitHub issue to track this task?

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 111-111: TODOs should be resolved (Add the validations)

(todo)

Comment on lines +10 to +22
enum ApiError: String, StringEnumType {
case API10000, API10001, API10002, API10003, API10004, API10005, API10006, API10007,
API10008, API10009
case API10010, API10011, API10012, API10013, API10014, API10015, API10016, API10017,
API10018, API10019
case API10020, API10021, API10022, API10023, API10024, API10025, API10026, API10027,
API10028, API10029
case API10030, API10031, API10032, API10033, API10034, API10035, API10036, API10037,
API10038, API10039
case API10040, API10041, API10042, API10043, API10044, API10045, API10046, API10047,
API10048, API10049
case API10050, API10051
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consolidate error enums to avoid duplication.

Having separate ErrorCode and ApiError enums with similar patterns could lead to maintenance issues.

Consider merging them into a single enum with a domain prefix:

enum AppError: String, CaseIterable {
    // Internal errors
    case INTERNAL_PARSER_NOT_FOUND = "E10000"
    
    // API errors
    case API_INVALID_REQUEST = "API10000"
    
    var isApiError: Bool {
        return rawValue.starts(with: "API")
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants