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

Add Affinity CRM #499

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

Conversation

mit-27
Copy link
Contributor

@mit-27 mit-27 commented Jun 14, 2024

Objects

Third party object Name -> Unified Object

  • Person -> Contact
  • Organization -> Company
  • Opportunity -> Deal
  • Note -> Note
  • User

closes #448

/claim #448

Summary by CodeRabbit

  • New Features

    • Introduced the AffinityService for managing company, contact, deal, note, and user data integration with the Affinity CRM system.
    • Added methods for adding and synchronizing companies, contacts, deals, notes, and users.
    • Implemented data mapping capabilities through new mapper classes for companies, contacts, deals, and notes.
    • Expanded the available CRM connectors to include Affinity.
  • Enhancements

    • Extended existing data types to include new Affinity variants for better integration with the Affinity service.
    • Added a new column crm_affinity to the connector_sets table for improved CRM integration capabilities.
  • Bug Fixes

    • Improved error handling during API interactions for better reliability and traceability.
  • Documentation

    • Updated type definitions and interfaces to enhance clarity and maintainability.

@rflihxyz
Copy link
Contributor

Qovery Preview

Qovery can create a Preview Environment for this PR.
To trigger its creation, please post a comment with one of the following command.

Command Blueprint environment
/qovery preview 783d0240-ec38-4387-a9a9-8e225f1bd3e1 dev
/qovery preview {all|UUID1,UUID2,...} To preview multiple environments

This comment has been generated from Qovery AI 🤖.
Below, a word from its wisdom :

Don’t Sacrifice Readability, you are going to debug it in 6 months

Copy link

changeset-bot bot commented Jun 14, 2024

⚠️ No Changeset found

Latest commit: 9cbd823

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

This PR includes no changesets

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

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

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

Copy link

vercel bot commented Jun 14, 2024

@mit-27 is attempting to deploy a commit to the Panora Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Jun 14, 2024

Walkthrough

Walkthrough

The changes introduce a comprehensive integration with the Affinity CRM system, adding new services, mappers, and data types for managing contacts, deals, companies, notes, and users. This integration enhances the CRM's ability to interact with Affinity's API, allowing for the addition and synchronization of various entities while maintaining backward compatibility with existing functionalities.

Changes

Files Change Summary
packages/api/src/@core/utils/types/original/original.crm.ts Added new input and output types prefixed with "Affinity" for contacts, deals, companies, engagements, notes, and users.
packages/api/src/crm/company/company.module.ts, packages/api/src/crm/contact/contact.module.ts, packages/api/src/crm/deal/deal.module.ts, packages/api/src/crm/note/note.module.ts, packages/api/src/crm/user/user.module.ts Added AffinityService and related mappers to the list of providers and exports in respective module files.
packages/api/scripts/init.sql, packages/api/scripts/seed.sql Introduced a new column crm_affinity to the connector_sets table and modified the seed script to accommodate this new column.
packages/shared/src/connectors/enum.ts Added AFFINITY to the CrmConnectors enum to support integration with the Affinity platform.

Assessment against linked issues

Objective Addressed Explanation
Add integration with Affinity (CRM) (#448)
Manage contacts through Affinity API (#448)
Manage deals through Affinity API (#448)
Manage companies through Affinity API (#448)
Manage notes through Affinity API (#448)

Possibly related PRs

  • feat:Add integration with Close CRM #484: The changes in this PR involve adding new types and integrations related to the "Close" CRM, which includes modifications to the original.crm.ts file, similar to the main PR's focus on enhancing type definitions for "Affinity" entities in the same file.
  • Quick fix for project_connectors #482: This PR includes changes to the original.crm.ts file, which may relate to the type definitions and structures being modified in the main PR, indicating a potential overlap in the type system enhancements for CRM integrations.

Tip

OpenAI O1 model for chat
  • We have deployed OpenAI's latest O1 model for chat.
  • OpenAI claims that this model has superior reasoning capabilities than their GPT-4o model.
  • Please share any feedback with us in the discussions post.

Recent review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 5a60903 and 9cbd823.

Files selected for processing (8)
  • packages/api/scripts/init.sql (1 hunks)
  • packages/api/scripts/seed.sql (1 hunks)
  • packages/api/src/@core/utils/types/original/original.crm.ts (11 hunks)
  • packages/api/src/crm/company/company.module.ts (3 hunks)
  • packages/api/src/crm/contact/contact.module.ts (2 hunks)
  • packages/api/src/crm/deal/deal.module.ts (2 hunks)
  • packages/api/src/crm/note/note.module.ts (2 hunks)
  • packages/shared/src/connectors/enum.ts (1 hunks)
Additional comments not posted (24)
packages/shared/src/connectors/enum.ts (1)

9-9: LGTM!

The addition of the AFFINITY entry to the CrmConnectors enum is a valid and consistent change. It expands the set of available CRM connectors without affecting the functionality of other connectors. The naming convention and enum structure remain intact.

packages/api/scripts/seed.sql (2)

5-8: LGTM!

The changes to the INSERT INTO connector_sets statement are consistent with the addition of the new crm_affinity column. The values being inserted for the new column are set to TRUE for all rows, which seems appropriate. The modified statement has no syntax errors or logical issues.


5-5: Verify the schema change.

The addition of the crm_affinity column to the connector_sets table is necessary to match the values being inserted. The column name suggests that it is related to the Affinity CRM integration, which aligns with the PR objectives.

To ensure the schema change has been properly applied, please run the following script:

Verification successful

Schema change verified successfully.

The crm_affinity column has been properly added to the connector_sets table. This change is reflected in both the table definition and the insert statement:

  • The column is defined in packages/api/scripts/init.sql as crm_affinity boolean NULL,.
  • It's included in the INSERT INTO statement in packages/api/scripts/seed.sql.

The schema change has been implemented correctly and consistently across the SQL files.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of the `crm_affinity` column in the `connector_sets` table.

# Test: Check if the `crm_affinity` column exists in the `connector_sets` table.
# Expect: The column should be present in the table schema.
echo "SELECT column_name FROM information_schema.columns WHERE table_name='connector_sets' AND column_name='crm_affinity';" | psql <database_name>

Length of output: 289


Script:

#!/bin/bash
# Search for the connector_sets table definition in SQL files
rg -t sql "CREATE TABLE.*connector_sets" -A 50

# Search for any ALTER TABLE statements that might have added the crm_affinity column
rg -t sql "ALTER TABLE.*connector_sets.*ADD.*crm_affinity"

# Search for INSERT statements into connector_sets that include crm_affinity
rg -t sql "INSERT INTO connector_sets.*crm_affinity"

Length of output: 3954

packages/api/src/crm/note/note.module.ts (2)

25-26: LGTM!

The imports for AffinityNoteMapper and AffinityService are correctly added, which is a necessary step for integrating with Affinity CRM.


54-55: LGTM!

The AffinityService and AffinityNoteMapper are correctly added to the providers array, making them available for dependency injection within the NoteModule. This is consistent with the imports and necessary for integrating with Affinity CRM.

packages/api/src/crm/deal/deal.module.ts (2)

25-26: LGTM!

The imports for AffinityDealMapper and AffinityService are correctly placed and follow the existing import structure. They suggest a new integration with Affinity CRM is being added.


55-56: LGTM!

Adding the AffinityService and AffinityDealMapper to the list of providers is a necessary step for integrating the Affinity CRM functionality into the existing system. The providers are correctly added and follow the existing structure.

packages/api/src/crm/company/company.module.ts (2)

25-26: LGTM!

The imports for AffinityCompanyMapper and AffinityService are correctly specified and follow the existing import pattern in the file. They are necessary for integrating the Affinity CRM functionality into the module.


56-57: Looks good!

Adding AffinityService and AffinityCompanyMapper to the list of providers in the module is necessary for making them available for dependency injection. The additions follow the existing pattern of providing services and mappers for other CRM platforms and are consistent with the import statements and the overall module structure.

packages/api/src/crm/contact/contact.module.ts (2)

26-27: LGTM!

The imports for AffinityContactMapper and AffinityService are correctly specified and follow the existing import pattern in the file.


57-58: LGTM!

The addition of AffinityService and AffinityContactMapper to the providers array is consistent with the existing pattern in the module and indicates that the ContactModule is being extended to support integration with the Affinity CRM system.

The AffinityService is likely responsible for interacting with the Affinity API, while the AffinityContactMapper is likely responsible for mapping Affinity contact data to the internal contact data structure.

packages/api/src/@core/utils/types/original/original.crm.ts (12)

2-6: LGTM!

The imports for the new "Affinity" related types are correctly structured and follow the existing pattern. The imported types are used later in the file to expand the existing type unions.


173-179: Looks good!

The addition of AffinityCompanyInput and CloseContactInput types to the OriginalContactInput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new input types related to "Affinity" and "Close" CRM systems without introducing any type mismatches or conflicts.


185-191: Approved.

The addition of AffinityDealInput type to the OriginalDealInput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new input type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


Line range hint 197-203: Change looks good.

The addition of AffinityCompanyInput type to the OriginalCompanyInput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new input type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


Line range hint 215-222: Looks good to me.

The addition of AffinityNoteInput type to the OriginalNoteInput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new input type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


230-231: LGTM.

The addition of AttioTaskInput type to the OriginalTaskInput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new input type related to "Attio" CRM system without introducing any type mismatches or conflicts.


244-249: Approved.

The addition of AffinityUserInput type to the OriginalUserInput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new input type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


Line range hint 264-270: Looks good.

The addition of AffinityContactInput type to the OriginalContactOutput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new output type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


Line range hint 274-280: Change looks good.

The addition of AffinityDealOutput type to the OriginalDealOutput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new output type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


Line range hint 284-290: LGTM.

The addition of AffinityCompanyOutput type to the OriginalCompanyOutput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new output type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


302-308: Approved.

The addition of AffinityNoteOutput type to the OriginalNoteOutput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new output type related to "Affinity" CRM system without introducing any type mismatches or conflicts.


331-337: Looks good to me.

The addition of AffinityUserOutput type to the OriginalUserOutput union type is consistent with the overall pattern of the file. This change expands the union to accommodate new output type related to "Affinity" CRM system without introducing any type mismatches or conflicts.

packages/api/scripts/init.sql (1)

559-559: LGTM!

The new crm_affinity column is appropriately named and typed. Allowing NULL values provides flexibility.


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

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

@mit-27
Copy link
Contributor Author

mit-27 commented Jun 19, 2024

Hello @naelob and @rflihxyz, I cannot sign in to Affinity CRM (Every time I try to log in it always sends me a 'request to demo' link not the sign-in link ). Can I get credentials so I can test the connector?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between ccd4c8e and 95e2ddb.

Files selected for processing (21)
  • packages/api/src/@core/utils/types/original/original.crm.ts (11 hunks)
  • packages/api/src/crm/company/company.module.ts (1 hunks)
  • packages/api/src/crm/company/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/company/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/company/services/affinity/types.ts (1 hunks)
  • packages/api/src/crm/contact/contact.module.ts (1 hunks)
  • packages/api/src/crm/contact/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/contact/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/contact/services/affinity/types.ts (1 hunks)
  • packages/api/src/crm/deal/deal.module.ts (1 hunks)
  • packages/api/src/crm/deal/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/deal/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/deal/services/affinity/types.ts (1 hunks)
  • packages/api/src/crm/note/note.module.ts (1 hunks)
  • packages/api/src/crm/note/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/note/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/note/services/affinity/types.ts (1 hunks)
  • packages/api/src/crm/user/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/user/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/user/services/affinity/types.ts (1 hunks)
  • packages/api/src/crm/user/user.module.ts (1 hunks)
Additional context used
Learnings (3)
packages/api/src/crm/contact/services/affinity/types.ts (1)
Learnt from: developerdhruv
PR: panoratech/Panora#481
File: packages/api/src/crm/contact/services/attio/index.ts:32-38
Timestamp: 2024-06-05T06:18:55.940Z
Learning: Focus reviews on the `affinity` folder within the CRM services as per user's request.
packages/api/src/crm/contact/services/affinity/index.ts (1)
Learnt from: developerdhruv
PR: panoratech/Panora#481
File: packages/api/src/crm/contact/services/attio/index.ts:32-38
Timestamp: 2024-06-05T06:18:55.940Z
Learning: Focus reviews on the `affinity` folder within the CRM services as per user's request.
packages/api/src/crm/deal/services/affinity/index.ts (1)
Learnt from: developerdhruv
PR: panoratech/Panora#481
File: packages/api/src/crm/contact/services/attio/index.ts:32-38
Timestamp: 2024-06-05T06:18:55.940Z
Learning: Focus reviews on the `affinity` folder within the CRM services as per user's request.
Biome
packages/api/src/crm/user/services/affinity/index.ts

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

packages/api/src/crm/company/services/affinity/mappers.ts

[error] 77-77: This let declares a variable that is only assigned once.

'opts' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)

packages/api/src/crm/note/services/affinity/index.ts

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

packages/api/src/crm/company/services/affinity/index.ts

[error] 23-23: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

packages/api/src/crm/contact/services/affinity/index.ts

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

packages/api/src/crm/deal/services/affinity/index.ts

[error] 21-21: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

Additional comments not posted (62)
packages/api/src/crm/user/services/affinity/types.ts (4)

1-5: Well-defined User Interface

The AffinityUser interface is well-defined and encapsulates the user-related data effectively with references to Tenant, User, and Grant interfaces. This structure should facilitate easy mapping and manipulation of user data from Affinity CRM.


7-11: Tenant Interface Review

The Tenant interface is concise and includes essential fields such as id, name, and subdomain. This should adequately represent the tenant information in the context of Affinity CRM.


13-18: User Interface Review

The User interface includes basic but essential user information fields. It is well-structured for representing individual user details within the system.


20-24: Grant Interface Review

The Grant interface captures authorization details effectively. The inclusion of type, scope, and createdAt fields are appropriate for managing access control and audit trails.

packages/api/src/crm/deal/services/affinity/types.ts (4)

1-7: Comprehensive Deal Interface

The AffinityDeal interface is comprehensive and well-structured, covering essential aspects of a deal such as associated persons, organizations, and list entries. This should facilitate effective management and retrieval of deal data.


9-16: ListEntry Interface Review

The ListEntry interface is detailed and includes necessary fields for tracking the creation and association of list entries within deals. This is crucial for maintaining the integrity and traceability of list operations.


18-23: Deal Input Type Review

The AffinityDealInput type is well-defined, allowing flexibility with optional fields for person and organization IDs. This design supports various scenarios for deal creation and updates, making it versatile.


25-25: Output Type for Deals

The AffinityDealOutput type, being a partial type of AffinityDeal, is appropriate for operations where not all deal data needs to be returned, such as in search results or partial updates.

packages/api/src/crm/note/services/affinity/types.ts (3)

1-18: Detailed Note Interface

The AffinityNote interface is exceptionally detailed, covering a wide range of fields that are essential for a comprehensive representation of notes within the CRM system. This should greatly enhance the functionality and usability of the note management system.


20-28: Note Input Type Review

The AffinityNoteInput type is well-structured, providing flexibility with optional fields for associating notes with persons, organizations, and opportunities. This design is beneficial for creating and updating notes with varying levels of detail.


31-31: Output Type for Notes

The AffinityNoteOutput type, being a partial type of AffinityNote, is suitable for scenarios where partial note data is sufficient. This could include search results or summaries, enhancing performance and reducing data transfer overhead.

packages/api/src/crm/company/services/affinity/types.ts (7)

1-12: Well-defined interface for AffinityCompany.

The interface AffinityCompany is comprehensive and well-structured, covering all necessary fields for representing a company entity in Affinity CRM.


14-20: Clear and concise definition for ListEntry.

The interface ListEntry is well-defined, with clear fields that are essential for tracking list entries within the CRM system.


22-30: Comprehensive date tracking with InteractionDates.

The interface InteractionDates effectively captures various important dates related to interactions, which is crucial for CRM functionalities.


32-40: Detailed interaction tracking in Interactions interface.

The Interactions interface is well-structured, allowing for detailed tracking of various types of interactions, each linked to an InteractionsType.


42-45: Effective structure for InteractionsType.

The interface InteractionsType is effectively designed to capture essential details of interactions, focusing on the date and involved person IDs.


47-51: Flexible input type for company data.

The type AffinityCompanyInput is well-designed, offering flexibility with optional fields, which is ideal for creating or updating records in a CRM system.


53-53: Well-suited output type for flexible data retrieval.

The type AffinityCompanyOutput, using TypeScript's Partial, is appropriately designed for functions that may return a subset of company data.

packages/api/src/crm/user/user.module.ts (1)

1-1: Review of module dependencies and structure.

The module is well-structured with clear separation of services and mappers, aligning with NestJS best practices. All necessary services and mappers appear to be correctly provided and exported, which should support the integration with Affinity CRM as described in the PR objectives.

packages/api/src/crm/deal/deal.module.ts (1)

1-1: Formatting change noted.

The addition of a blank line at the beginning of the file is a minor formatting change and does not impact the functionality of the module.

packages/api/src/crm/user/services/affinity/mappers.ts (1)

10-62: Review of AffinityUserMapper implementation.

The AffinityUserMapper class is well-implemented with clear separation of unification and de-unification logic. The use of custom field mappings is a robust approach for handling diverse data structures from different CRMs. However, the desunify method at lines 16-24 returns undefined, which might be intentional but should be confirmed or documented to avoid confusion.

Please confirm if the desunify method is intended to return undefined or if this is an oversight. Consider adding a return statement or documentation to clarify its behavior.

packages/api/src/crm/contact/contact.module.ts (1)

1-1: Approved formatting change.

The addition of a blank line at the beginning of the file is a minor change that improves readability and adheres to coding standards.

packages/api/src/crm/company/company.module.ts (1)

50-50: Approved addition of CloseCompanyMapper.

The inclusion of CloseCompanyMapper in the list of providers enhances the module's functionality by supporting new mapping capabilities. Ensure that the integration of this mapper is thoroughly tested.

Run the following script to verify the integration of CloseCompanyMapper:

Verification successful

Integration of CloseCompanyMapper verified successfully.

The CloseCompanyMapper is correctly imported and included in the list of providers in the company.module.ts file. Its implementation in mappers.ts confirms that it is properly set up and registered. No issues were found with its integration.

  • company.module.ts: Import and provider list inclusion.
  • mappers.ts: Class implementation and registration.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the integration of `CloseCompanyMapper`.

# Test: Search for the usage of `CloseCompanyMapper`. Expect: Proper instantiation and usage in the module.
rg --type typescript -A 5 $'CloseCompanyMapper'

Length of output: 81


Script:

#!/bin/bash
# Description: Verify the integration of `CloseCompanyMapper`.

# Test: Search for the usage of `CloseCompanyMapper` in TypeScript files.
rg --glob '*.ts' -A 5 'CloseCompanyMapper'

Length of output: 1714

packages/api/src/crm/company/services/affinity/mappers.ts (3)

11-15: Well-structured class registration.

The use of @Injectable() and service registration within the constructor follows best practices for modularity and reusability in NestJS applications.


17-42: Correct implementation of desunify method.

The method handles the transformation of unified company data to the Affinity CRM format efficiently, with support for custom field mappings. The use of asynchronous processing is appropriate.


45-61: Efficient handling of multiple company outputs.

The method efficiently handles both single and multiple company outputs using Promise.all, which is appropriate for asynchronous operations. Good separation of concerns by delegating to mapSingleCompanyToUnified for individual transformations.

packages/api/src/crm/note/services/affinity/index.ts (3)

13-25: Well-structured class registration.

The use of @Injectable() and service registration within the constructor follows best practices for modularity and reusability in NestJS applications.

Tools
Biome

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


26-63: Correct implementation of addNote method.

The method appropriately handles HTTP requests and errors, using axios for making requests and structured error handling. Logging and structured responses are well implemented for debugging and client interaction.


66-103: Efficient handling of note synchronization.

The method efficiently handles the synchronization of notes using axios for HTTP requests. Error handling and logging are well implemented, providing clear feedback and aiding in debugging.

packages/api/src/crm/contact/services/affinity/mappers.ts (3)

12-17: Well-structured class registration.

The use of @Injectable() and service registration within the constructor follows best practices for modularity and reusability in NestJS applications.


19-48: Correct implementation of desunify method.

The method handles the transformation of unified contact data to the Affinity CRM format efficiently, with support for custom field mappings. The use of asynchronous processing is appropriate.


51-68: Efficient handling of multiple contact outputs.

The method efficiently handles both single and multiple contact outputs using Promise.all, which is appropriate for asynchronous operations. Good separation of concerns by delegating to mapSingleContactToUnified for individual transformations.

packages/api/src/crm/company/services/affinity/index.ts (2)

28-69: Review security practices around token handling.

The method implementation for adding a company is robust, including error handling and API interaction. However, consider reviewing the security practices around decrypting the token on the fly. Ensure that the decryption process is secure and that decrypted tokens are not logged or exposed in any way.


71-107: Consistent implementation; reiterate security check.

The syncCompanies method is implemented consistently with the addCompany method, which is good for maintainability. As with the previous method, ensure that the security practices around token handling are thoroughly reviewed to prevent any potential exposure of sensitive data.

packages/api/src/crm/deal/services/affinity/mappers.ts (3)

11-14: Service registration is correctly implemented.

The constructor correctly registers the AffinityDealMapper service with the mappings registry, ensuring it can be utilized effectively within the application.


16-54: Ensure robust error handling in data mapping.

The desunify method effectively handles the conversion of deal data to the Affinity-specific format. However, given the complexity and the number of asynchronous operations, ensure that error handling is robust to gracefully manage potential failures in database calls or remote ID fetches.


56-72: Flexible and robust data mapping; ensure error handling.

The unify method is well-implemented, handling both single and multiple deal inputs, which enhances flexibility. Ensure that error handling is robust, especially when dealing with multiple asynchronous operations involved in mapping data.

packages/api/src/crm/contact/services/affinity/index.ts (2)

27-69: Review security practices around token handling.

The method implementation for adding a contact is robust, including error handling and API interaction. However, consider reviewing the security practices around decrypting the token on the fly. Ensure that the decryption process is secure and that decrypted tokens are not logged or exposed in any way.


71-114: Consistent implementation; reiterate security check.

The syncContacts method is implemented consistently with the addContact method, which is good for maintainability. As with the previous method, ensure that the security practices around token handling are thoroughly reviewed to prevent any potential exposure of sensitive data.

packages/api/src/crm/deal/services/affinity/index.ts (2)

25-83: Well-implemented method for adding deals.

The addDeal method is robust, handling API interactions and errors effectively. The use of axios for HTTP requests and the EncryptionService for decrypting tokens are appropriate and secure practices.


86-126: Effective synchronization method for deals.

The syncDeals method is effectively implemented, ensuring that deals are synchronized correctly with Affinity CRM. The error handling and logging are appropriately managed, providing clear feedback on the operation's status.

packages/api/src/crm/note/services/affinity/mappers.ts (3)

11-15: Constructor setup is appropriate.

The constructor of AffinityNoteMapper correctly registers the service and sets up necessary utilities, ensuring that the mapper is ready for use.


17-84: Robust method for converting to Affinity-specific model.

The desunify method is well-implemented, effectively handling the conversion of a unified note model to an Affinity-specific model. The handling of custom field mappings and the use of utilities to fetch remote IDs are particularly noteworthy.


87-103: Effective method for unifying notes.

The unify method effectively handles the conversion of Affinity-specific note models to the unified model. The ability to handle both single and multiple notes efficiently is commendable.

packages/api/src/@core/utils/types/original/original.crm.ts (10)

161-161: Type definition expanded for OriginalContactInput.

The inclusion of AffinityContactInput in OriginalContactInput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


171-171: Type definition expanded for OriginalDealInput.

The inclusion of AffinityDealInput in OriginalDealInput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


181-181: Type definition expanded for OriginalCompanyInput.

The inclusion of AffinityCompanyInput in OriginalCompanyInput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


199-199: Type definition expanded for OriginalNoteInput.

The inclusion of AffinityNoteInput in OriginalNoteInput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


228-228: Type definition expanded for OriginalUserInput.

The inclusion of AffinityUserInput in OriginalUserInput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


248-248: Type definition expanded for OriginalContactOutput.

The inclusion of AffinityContactOutput in OriginalContactOutput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


258-258: Type definition expanded for OriginalDealOutput.

The inclusion of AffinityDealOutput in OriginalDealOutput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


268-268: Type definition expanded for OriginalCompanyOutput.

The inclusion of AffinityCompanyOutput in OriginalCompanyOutput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


286-286: Type definition expanded for OriginalNoteOutput.

The inclusion of AffinityNoteOutput in OriginalNoteOutput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.


315-315: Type definition expanded for OriginalUserOutput.

The inclusion of AffinityUserOutput in OriginalUserOutput is a positive change, enhancing the type's flexibility and integration capabilities with Affinity CRM.

packages/api/src/crm/contact/services/affinity/types.ts (7)

1-14: Review: Interface AffinityContact

The AffinityContact interface is well-defined with appropriate types for each property. This interface seems to be designed to represent a contact within the Affinity CRM system comprehensively. It includes not only basic contact details but also relational data such as organization_ids and opportunity_ids, which are essential for CRM functionalities.

  • Correctness: The use of arrays for emails, organization_ids, opportunity_ids, and current_organization_ids is appropriate given that a contact could be associated with multiple emails and organizations.
  • Consistency: The interface is consistent internally with its naming conventions and data types.
  • Maintainability: The clear separation of concerns and explicit typing enhance maintainability.

16-21: Review: Type AffinityContactInput

The AffinityContactInput type is defined as a subset of the AffinityContact interface, intended for creating or updating contact records. It correctly uses optional properties where appropriate, such as organization_ids, allowing flexibility in the data provided during contact creation or update.

  • Correctness: The choice of required and optional fields aligns well with typical use cases where not all data might be available initially.
  • Maintainability: The use of TypeScript's partial types and optional fields aids in creating flexible and maintainable code.

23-29: Review: Interface ListEntry

The ListEntry interface represents an entry in a list within the Affinity system. It includes essential fields like id, list_id, creator_id, entity_id, and created_at.

  • Correctness: The fields chosen are appropriate for tracking the creation and ownership of list entries.
  • Maintainability: The interface is straightforward and easy to understand, which will help in maintaining the code.

31-39: Review: Interface InteractionDates

The InteractionDates interface captures various dates related to interactions with a contact. It includes fields for the first and last emails, events, chat messages, and interactions, which are crucial for CRM systems to track engagement history.

  • Correctness: The comprehensive coverage of interaction types ensures that the system can accommodate detailed tracking.
  • Maintainability: The clear naming and separation of interaction types make this interface easy to extend and maintain.

41-49: Review: Interface Interactions

The Interactions interface aggregates various types of interactions by using the InteractionsType interface for each interaction field. This design pattern ensures consistency in how interaction data is handled across different types.

  • Correctness: Using a consistent interface for different interaction types simplifies the handling of diverse interaction data.
  • Maintainability: The modular approach to interaction data enhances the clarity and maintainability of the code.

51-54: Review: Interface InteractionsType

The InteractionsType interface is succinct and focused, capturing the date of an interaction and the associated person IDs. This minimalistic approach is effective for representing interactions without unnecessary complexity.

  • Correctness: The inclusion of person_ids as an array supports scenarios where multiple contacts might be involved in a single interaction.
  • Maintainability: The simplicity of the interface aids in understanding and maintaining the code.

56-56: Review: Type AffinityContactOutput

The AffinityContactOutput type uses TypeScript's Partial utility type to create a flexible output type for Affinity contacts. This allows partial objects to be used effectively in functions that may not need full contact details.

  • Correctness: The use of Partial is appropriate for output types where not all data may be necessary.
  • Maintainability: This approach provides flexibility in handling API responses or partial data updates.
packages/api/src/crm/note/note.module.ts (1)

1-1: Review: Stylistic Change

The addition of a blank line at the beginning of the file is a minor stylistic change. It does not impact the functionality or logic of the module but may enhance readability or adhere to coding style guidelines.

Comment on lines +26 to +27
export type AffinityUserOutput = Partial<AffinityUser>;
export type AffinityUserInput = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Clarification Needed for AffinityUserInput

The AffinityUserInput type is defined as null. This is unusual as input types typically define the shape of data for operations like create or update. If this is intentional, consider adding a comment explaining its usage to avoid confusion.

Comment on lines 10 to 62
export class AffinityUserMapper implements IUserMapper {

constructor(private mappersRegistry: MappersRegistry, private utils: Utils) {
this.mappersRegistry.registerService('crm', 'user', 'affinity', this);
}

desunify(
source: UnifiedUserInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): AffinityUserInput {
return;
}

unify(
source: AffinityUserOutput | AffinityUserOutput[],
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): UnifiedUserOutput | UnifiedUserOutput[] {
if (!Array.isArray(source)) {
return this.mapSingleUserToUnified(source, customFieldMappings);
}
// Handling array of AffinityUserOutput
return source.map((user) =>
this.mapSingleUserToUnified(user, customFieldMappings),
);
}

private mapSingleUserToUnified(
user: AffinityUserOutput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): UnifiedUserOutput {
const field_mappings: { [key: string]: any } = {};
if (customFieldMappings) {
for (const mapping of customFieldMappings) {
field_mappings[mapping.slug] = user[mapping.remote_id];
}
}
return {
remote_id: String(user.user.id),
name: `${user.user?.firstName} ${user.user?.lastName}`,
email: user.user?.email,
field_mappings,
};
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Potential improvement in error handling and data validation.

While the mapping logic is generally sound, adding error handling and validation for the input data could enhance robustness, especially considering the integration with an external system like Affinity CRM. This could prevent runtime errors and ensure data integrity.

Consider implementing error checks and validations within the mapping methods to handle cases where input data might be missing or malformed.

Comment on lines 1 to 68
import { Injectable } from '@nestjs/common';
import { IUserService } from '@crm/user/types';
import { CrmObject } from '@crm/@lib/@types';
import { AffinityUserOutput } from './types';
import axios from 'axios';
import { PrismaService } from '@@core/prisma/prisma.service';
import { LoggerService } from '@@core/logger/logger.service';
import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors';
import { EncryptionService } from '@@core/encryption/encryption.service';
import { ApiResponse } from '@@core/utils/types';
import { ServiceRegistry } from '../registry.service';

@Injectable()
export class AffinityService implements IUserService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.user.toUpperCase() + ':' + AffinityService.name,
);
this.registry.registerService('close', this);
}

async syncUsers(
linkedUserId: string,
custom_properties?: string[],
): Promise<ApiResponse<AffinityUserOutput[]>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'close',
vertical: 'crm',
},
});

const resp = await axios.get(
`${connection.account_url}/auth/whoami`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});

this.logger.log(`Synced close users !`);

return {
data: resp?.data,
message: 'Affinity users retrieved',
statusCode: 200,
};
} catch (error) {
handle3rdPartyServiceError(
error,
this.logger,
'Affinity',
CrmObject.user,
ActionType.GET,
);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Approved new service implementation with a suggestion.

The AffinityService is well-implemented with proper dependency injection and error handling. Consider replacing the string concatenation in line 22 with a template literal to enhance code readability and maintainability.

Apply this diff to address the static analysis hint:

- this.logger.setContext(
-     CrmObject.user.toUpperCase() + ':' + AffinityService.name,
- );
+ this.logger.setContext(`${CrmObject.user.toUpperCase()}:${AffinityService.name}`);
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
import { Injectable } from '@nestjs/common';
import { IUserService } from '@crm/user/types';
import { CrmObject } from '@crm/@lib/@types';
import { AffinityUserOutput } from './types';
import axios from 'axios';
import { PrismaService } from '@@core/prisma/prisma.service';
import { LoggerService } from '@@core/logger/logger.service';
import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors';
import { EncryptionService } from '@@core/encryption/encryption.service';
import { ApiResponse } from '@@core/utils/types';
import { ServiceRegistry } from '../registry.service';
@Injectable()
export class AffinityService implements IUserService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.user.toUpperCase() + ':' + AffinityService.name,
);
this.registry.registerService('close', this);
}
async syncUsers(
linkedUserId: string,
custom_properties?: string[],
): Promise<ApiResponse<AffinityUserOutput[]>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'close',
vertical: 'crm',
},
});
const resp = await axios.get(
`${connection.account_url}/auth/whoami`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced close users !`);
return {
data: resp?.data,
message: 'Affinity users retrieved',
statusCode: 200,
};
} catch (error) {
handle3rdPartyServiceError(
error,
this.logger,
'Affinity',
CrmObject.user,
ActionType.GET,
);
}
}
}
import { Injectable } from '@nestjs/common';
import { IUserService } from '@crm/user/types';
import { CrmObject } from '@crm/@lib/@types';
import { AffinityUserOutput } from './types';
import axios from 'axios';
import { PrismaService } from '@@core/prisma/prisma.service';
import { LoggerService } from '@@core/logger/logger.service';
import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors';
import { EncryptionService } from '@@core/encryption/encryption.service';
import { ApiResponse } from '@@core/utils/types';
import { ServiceRegistry } from '../registry.service';
@Injectable()
export class AffinityService implements IUserService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(`${CrmObject.user.toUpperCase()}:${AffinityService.name}`);
this.registry.registerService('close', this);
}
async syncUsers(
linkedUserId: string,
custom_properties?: string[],
): Promise<ApiResponse<AffinityUserOutput[]>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'close',
vertical: 'crm',
},
});
const resp = await axios.get(
`${connection.account_url}/auth/whoami`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced close users !`);
return {
data: resp?.data,
message: 'Affinity users retrieved',
statusCode: 200,
};
} catch (error) {
handle3rdPartyServiceError(
error,
this.logger,
'Affinity',
CrmObject.user,
ActionType.GET,
);
}
}
}
Tools
Biome

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

Comment on lines +21 to +23
this.logger.setContext(
CrmObject.note.toUpperCase() + ':' + AffinityService.name,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use template literals for string operations.

Using template literals instead of string concatenation can improve readability and is the preferred style in modern JavaScript/TypeScript.

- CrmObject.note.toUpperCase() + ':' + AffinityService.name,
+ `${CrmObject.note.toUpperCase()}:${AffinityService.name}`,
Committable suggestion

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

Suggested change
this.logger.setContext(
CrmObject.note.toUpperCase() + ':' + AffinityService.name,
);
this.logger.setContext(
`${CrmObject.note.toUpperCase()}:${AffinityService.name}`,
);
Tools
Biome

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

Comment on lines +12 to +24
@Injectable()
export class AffinityService implements IDealService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.deal.toUpperCase() + ':' + AffinityService.name,
);
this.registry.registerService('affinity', this);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Use template literals for string concatenation.

The constructor logic is correct and well-structured. However, to improve code readability and align with modern JavaScript practices, consider using template literals instead of string concatenation at line 21.

- CrmObject.deal.toUpperCase() + ':' + AffinityService.name,
+ `${CrmObject.deal.toUpperCase()}:${AffinityService.name}`,
Committable suggestion

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

Suggested change
@Injectable()
export class AffinityService implements IDealService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.deal.toUpperCase() + ':' + AffinityService.name,
);
this.registry.registerService('affinity', this);
}
@Injectable()
export class AffinityService implements IDealService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
`${CrmObject.deal.toUpperCase()}:${AffinityService.name}`,
);
this.registry.registerService('affinity', this);
}
Tools
Biome

[error] 21-21: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 95e2ddb and 9e8abdd.

Files ignored due to path filters (1)
  • packages/api/swagger/swagger-spec.yaml is excluded by !**/*.yaml
Files selected for processing (20)
  • packages/api/scripts/init.sql (1 hunks)
  • packages/api/scripts/seed.sql (1 hunks)
  • packages/api/src/@core/utils/types/original/original.crm.ts (11 hunks)
  • packages/api/src/crm/company/company.module.ts (3 hunks)
  • packages/api/src/crm/company/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/company/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/contact/contact.module.ts (2 hunks)
  • packages/api/src/crm/contact/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/contact/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/deal/deal.module.ts (2 hunks)
  • packages/api/src/crm/deal/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/deal/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/note/note.module.ts (2 hunks)
  • packages/api/src/crm/note/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/note/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/user/services/affinity/index.ts (1 hunks)
  • packages/api/src/crm/user/services/affinity/mappers.ts (1 hunks)
  • packages/api/src/crm/user/user.module.ts (2 hunks)
  • packages/shared/src/connectors/enum.ts (1 hunks)
  • packages/shared/src/connectors/index.ts (1 hunks)
Additional context used
Learnings (3)
packages/api/src/crm/contact/contact.module.ts (1)
Learnt from: developerdhruv
PR: panoratech/Panora#481
File: packages/api/src/crm/contact/services/attio/index.ts:32-38
Timestamp: 2024-06-05T06:18:55.940Z
Learning: Focus reviews on the `affinity` folder within the CRM services as per user's request.
packages/api/src/crm/contact/services/affinity/index.ts (1)
Learnt from: developerdhruv
PR: panoratech/Panora#481
File: packages/api/src/crm/contact/services/attio/index.ts:32-38
Timestamp: 2024-06-05T06:18:55.940Z
Learning: Focus reviews on the `affinity` folder within the CRM services as per user's request.
packages/api/src/crm/deal/services/affinity/index.ts (1)
Learnt from: developerdhruv
PR: panoratech/Panora#481
File: packages/api/src/crm/contact/services/attio/index.ts:32-38
Timestamp: 2024-06-05T06:18:55.940Z
Learning: Focus reviews on the `affinity` folder within the CRM services as per user's request.
Biome
packages/api/src/crm/user/services/affinity/index.ts

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 58-58: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

packages/api/src/crm/company/services/affinity/mappers.ts

[error] 87-87: This let declares a variable that is only assigned once.

'opts' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)

packages/api/src/crm/note/services/affinity/index.ts

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 57-57: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)


[error] 90-90: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

packages/api/src/crm/contact/services/affinity/index.ts

[error] 23-23: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 61-61: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)


[error] 96-96: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

packages/api/src/crm/company/services/affinity/index.ts

[error] 25-25: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 62-62: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)


[error] 94-94: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

packages/api/src/crm/deal/services/affinity/index.ts

[error] 24-24: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 79-79: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)


[error] 112-112: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

Additional comments not posted (20)
packages/shared/src/connectors/index.ts (1)

1-1: Approved: Addition of 'affinity' to CRM_PROVIDERS.

The addition of 'affinity' to the CRM_PROVIDERS array is correctly implemented and aligns with the PR's objectives to integrate Affinity CRM.

packages/shared/src/connectors/enum.ts (1)

8-8: Approved: Addition of 'AFFINITY' to CrmConnectors enum.

The addition of AFFINITY = 'affinity' to the CrmConnectors enum is correctly implemented and aligns with the PR's objectives to integrate Affinity CRM.

packages/api/scripts/seed.sql (1)

4-7: Approved: Addition of 'crm_affinity' column to connector_sets.

The addition of the crm_affinity column to the connector_sets table is correctly implemented and aligns with the PR's objectives to integrate Affinity CRM. However, ensure that the default value of TRUE for all records is intended and aligns with business requirements.

packages/api/src/crm/note/note.module.ts (2)

1-2: Verify import paths and definitions.

Ensure that the paths for AffinityNoteMapper and AffinityService are correct and that these entities are properly defined and exported from their respective modules.


52-53: Approve the addition of new providers.

The addition of AffinityService and AffinityNoteMapper to the providers list is necessary for their availability throughout the module. Ensure there are no conflicts with existing services.

packages/api/src/crm/user/user.module.ts (2)

1-2: Verify import paths and definitions.

Ensure that the paths for AffinityUserMapper and AffinityService are correct and that these entities are properly defined and exported from their respective modules.


52-53: Approve the addition of new providers.

The addition of AffinityService and AffinityUserMapper to the providers list is necessary for their availability throughout the module. Ensure there are no conflicts with existing services.

packages/api/src/crm/deal/deal.module.ts (2)

1-2: Verify import paths and definitions.

Ensure that the paths for AffinityDealMapper and AffinityService are correct and that these entities are properly defined and exported from their respective modules.


53-54: Approve the addition of new providers.

The addition of AffinityService and AffinityDealMapper to the providers list is necessary for their availability throughout the module. Ensure there are no conflicts with existing services.

packages/api/src/crm/user/services/affinity/index.ts (1)

1-12: Imports and Initial Setup: Approved

All imports are relevant and correctly placed for the functionality of the AffinityService.

packages/api/src/crm/company/company.module.ts (2)

1-2: Review of import statements for new providers.

The import statements for AffinityCompanyMapper and AffinityService are correctly placed and follow the project's existing structure. This ensures that the new functionality related to Affinity CRM is properly encapsulated within its service and mapper.


62-63: Addition of new providers to the module.

The AffinityService and AffinityCompanyMapper have been correctly added to the providers array of the module. This is crucial for enabling the new Affinity CRM functionality within the company module. Ensure that these services are properly initialized and that their dependencies, if any, are available in the module.

packages/api/src/crm/company/services/affinity/mappers.ts (1)

12-96: Review of AffinityCompanyMapper implementation.

The AffinityCompanyMapper class is well-implemented with clear separation of concerns between the desunify and unify methods. These methods handle the conversion between the unified CRM model and the Affinity-specific model effectively. The use of asynchronous methods and custom field mappings are appropriate for the operations being performed.

  • The desunify method correctly handles the transformation of unified model data to the Affinity model, considering custom field mappings.
  • The unify method efficiently handles both single and multiple company data inputs, which is crucial for operations that may involve bulk data processing.

Overall, the implementation is robust and adheres to modern TypeScript practices.

Tools
Biome

[error] 87-87: This let declares a variable that is only assigned once.

'opts' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)

packages/api/src/crm/note/services/affinity/index.ts (1)

13-93: Review of AffinityService implementation.

The AffinityService class is well-implemented, providing methods for adding and syncing notes with Affinity CRM. The use of axios for HTTP requests and the handling of API responses are appropriate. The service correctly registers itself and sets the logger context, which is crucial for debugging and monitoring.

  • The addNote method correctly handles the creation of notes by making a POST request to the Affinity API.
  • The sync method efficiently retrieves notes using a GET request and handles potential multiple responses with Promise.all.

Overall, the implementation is robust, adheres to modern TypeScript practices, and correctly handles API interactions.

Tools
Biome

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 57-57: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)


[error] 90-90: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

packages/api/src/crm/contact/services/affinity/mappers.ts (1)

18-44: Verify assumptions about data presence and approve data conversion logic.

The method correctly converts unified CRM contact input into Affinity-specific format. However, it assumes that 'email_addresses' and 'phone_numbers' arrays contain at least one entry. Consider adding checks to ensure these arrays are not empty to avoid runtime errors.

packages/api/src/crm/deal/services/affinity/index.ts (1)

15-27: Use template literals for string concatenation.

The constructor logic is correct and well-structured. However, to improve code readability and align with modern JavaScript practices, consider using template literals instead of string concatenation at line 24.

- CrmObject.deal.toUpperCase() + ':' + AffinityService.name,
+ `${CrmObject.deal.toUpperCase()}:${AffinityService.name}`,
Tools
Biome

[error] 24-24: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

packages/api/src/@core/utils/types/original/original.crm.ts (2)

157-161: Addition of Affinity CRM types.

The imports for Affinity types (AffinityCompanyInput, AffinityCompanyOutput, etc.) are correctly added and follow the existing pattern of CRM service type imports. This ensures consistency in how CRM types are managed across different services.


167-167: Update to Original CRM Input and Output Types.

The addition of Affinity types to the union types for various CRM entities (Contact, Deal, Company, Note, User) is correctly implemented. Each type now includes the corresponding Affinity input or output, which aligns with the PR's objective to integrate Affinity CRM. This change enhances the system's flexibility to handle different CRM data formats and ensures that the new CRM integration can be utilized effectively.

Also applies to: 177-177, 187-187, 205-205, 234-234, 254-254, 264-264, 274-274, 292-292, 321-321

packages/api/scripts/init.sql (1)

555-555: Addition of crm_affinity column to connector_sets table.

The addition of the crm_affinity column as a boolean type that allows NULL values is appropriate for tracking CRM integration states. This column will help in managing the state of Affinity CRM integration across different connector sets. The SQL syntax is correct, and the placement of the column in the table schema is consistent with the existing structure.

packages/api/src/crm/contact/contact.module.ts (1)

1-2: Review of Affinity CRM Integration in Contact Module

The addition of AffinityContactMapper and AffinityService is correctly implemented. These services are properly imported and added to the module's providers, which is essential for their availability throughout the application.

However, it's crucial to ensure that these services are correctly instantiated, especially since they are now part of the module's exports. This integration should be tested to verify that it does not interfere with the existing functionalities of the module.

Please ensure that integration tests cover the instantiation and basic functionality of AffinityContactMapper and AffinityService to prevent any runtime issues.

Also applies to: 55-56

Comment on lines +13 to +25
@Injectable()
export class AffinityService implements IUserService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.user.toUpperCase() + ':' + AffinityService.name,
);
this.registry.registerService('affinity', this);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Class Definition and Constructor: Mostly Approved

The class is correctly set up with dependency injection and service registration. However, consider improving the logger context setup:

Replace string concatenation with template literals for better readability and maintainability:

- this.logger.setContext(
-     CrmObject.user.toUpperCase() + ':' + AffinityService.name,
- );
+ this.logger.setContext(`${CrmObject.user.toUpperCase()}:${AffinityService.name}`);
Committable suggestion

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

Suggested change
@Injectable()
export class AffinityService implements IUserService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.user.toUpperCase() + ':' + AffinityService.name,
);
this.registry.registerService('affinity', this);
}
@Injectable()
export class AffinityService implements IUserService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(`${CrmObject.user.toUpperCase()}:${AffinityService.name}`);
this.registry.registerService('affinity', this);
}
Tools
Biome

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

Comment on lines +27 to +60
async sync(data: SyncParam): Promise<ApiResponse<AffinityUserOutput[]>> {
try {
const { linkedUserId } = data;

const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});

const resp = await axios.get(
`${connection.account_url}/auth/whoami`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});

this.logger.log(`Synced close users !`);

return {
data: resp?.data,
message: 'Affinity users retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Method sync: Simplify Error Handling

The method's logic for synchronizing user data is sound. However, the catch block that rethrows the error is redundant and can be removed for clarity:

Remove the redundant catch block to simplify the error handling:

-        } catch (error) {
-            throw error;
-        }
Committable suggestion

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

Suggested change
async sync(data: SyncParam): Promise<ApiResponse<AffinityUserOutput[]>> {
try {
const { linkedUserId } = data;
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});
const resp = await axios.get(
`${connection.account_url}/auth/whoami`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced close users !`);
return {
data: resp?.data,
message: 'Affinity users retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
}
async sync(data: SyncParam): Promise<ApiResponse<AffinityUserOutput[]>> {
const { linkedUserId } = data;
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});
const resp = await axios.get(
`${connection.account_url}/auth/whoami`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced close users !`);
return {
data: resp?.data,
message: 'Affinity users retrieved',
statusCode: 200,
};
}
Tools
Biome

[error] 58-58: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

}
}

let opts: any = {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Use const for unmodified variables.

The variable opts is never reassigned and should be declared with const to prevent reassignment and better communicate its intended immutability. This change aligns with best practices for immutability in JavaScript/TypeScript.

- let opts: any = {};
+ const opts: any = {};
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
let opts: any = {};
const opts: any = {};
Tools
Biome

[error] 87-87: This let declares a variable that is only assigned once.

'opts' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)

statusCode: 201,
};
} catch (error) {
throw error;
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove redundant catch clauses.

The catch clauses that only rethrow the original error are redundant and can be removed to simplify the error handling. This will make the code cleaner and easier to maintain.

-        } catch (error) {
-            throw error;
-        }

Also applies to: 90-90

Tools
Biome

[error] 57-57: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

Comment on lines +22 to +23
CrmObject.note.toUpperCase() + ':' + AffinityService.name,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use template literals for string operations.

Using template literals instead of string concatenation can improve readability and is the preferred style in modern JavaScript/TypeScript.

- CrmObject.note.toUpperCase() + ':' + AffinityService.name,
+ `${CrmObject.note.toUpperCase()}:${AffinityService.name}`,
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
CrmObject.note.toUpperCase() + ':' + AffinityService.name,
);
`${CrmObject.note.toUpperCase()}:${AffinityService.name}`,
);
Tools
Biome

[error] 22-22: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

Comment on lines +83 to +113
async sync(data: SyncParam): Promise<ApiResponse<AffinityDealOutput[]>> {
try {
const { linkedUserId } = data;

const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});
const resp = await axios.get(
`${connection.account_url}/opportunities`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced affinity deals !`);

return {
data: resp?.data?.data,
message: 'Affinity deals retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Optimize error handling by removing redundant catch clauses.

The method logic for synchronizing deals is well-implemented. However, the catch clause at lines 112-113 is redundant as it only rethrows the error. Consider removing this to simplify the error handling.

-        } catch (error) {
-            throw error;
-        }
Committable suggestion

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

Suggested change
async sync(data: SyncParam): Promise<ApiResponse<AffinityDealOutput[]>> {
try {
const { linkedUserId } = data;
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});
const resp = await axios.get(
`${connection.account_url}/opportunities`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced affinity deals !`);
return {
data: resp?.data?.data,
message: 'Affinity deals retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
async sync(data: SyncParam): Promise<ApiResponse<AffinityDealOutput[]>> {
try {
const { linkedUserId } = data;
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});
const resp = await axios.get(
`${connection.account_url}/opportunities`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced affinity deals !`);
return {
data: resp?.data?.data,
message: 'Affinity deals retrieved',
statusCode: 200,
};
}
Tools
Biome

[error] 112-112: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

Comment on lines +28 to +80
async addDeal(
dealData: AffinityDealInput,
linkedUserId: string,
): Promise<ApiResponse<AffinityDealOutput>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});

const list_resp = await axios.get(
`${connection.account_url}/lists`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);

for (const list of list_resp.data) {
if (list.type === 8) {
dealData.list_id = list.id;
}

}

const resp = await axios.post(
`${connection.account_url}/opportunities`,
JSON.stringify(dealData),
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);

return {
data: resp?.data,
message: 'Affinity deal created',
statusCode: 201,
};
} catch (error) {
throw error;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Optimize error handling by removing redundant catch clauses.

The method logic for adding a deal is well-implemented. However, the catch clause at lines 78-80 is redundant as it only rethrows the error. Consider removing this to simplify the error handling.

-        } catch (error) {
-            throw error;
-        }
Committable suggestion

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

Suggested change
async addDeal(
dealData: AffinityDealInput,
linkedUserId: string,
): Promise<ApiResponse<AffinityDealOutput>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});
const list_resp = await axios.get(
`${connection.account_url}/lists`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
for (const list of list_resp.data) {
if (list.type === 8) {
dealData.list_id = list.id;
}
}
const resp = await axios.post(
`${connection.account_url}/opportunities`,
JSON.stringify(dealData),
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
return {
data: resp?.data,
message: 'Affinity deal created',
statusCode: 201,
};
} catch (error) {
throw error;
}
async addDeal(
dealData: AffinityDealInput,
linkedUserId: string,
): Promise<ApiResponse<AffinityDealOutput>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'affinity',
vertical: 'crm',
},
});
const list_resp = await axios.get(
`${connection.account_url}/lists`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
for (const list of list_resp.data) {
if (list.type === 8) {
dealData.list_id = list.id;
}
}
const resp = await axios.post(
`${connection.account_url}/opportunities`,
JSON.stringify(dealData),
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
return {
data: resp?.data,
message: 'Affinity deal created',
statusCode: 201,
};
}
Tools
Biome

[error] 79-79: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

Comment on lines +11 to +69
@Injectable()
export class AffinityUserMapper implements IUserMapper {
constructor(private mappersRegistry: MappersRegistry, private utils: Utils) {
this.mappersRegistry.registerService('crm', 'user', 'affinity', this);
}
desunify(
source: UnifiedCrmUserInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
) {
return;
}

unify(
source: AffinityUserOutput | AffinityUserOutput[],
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmUserOutput | UnifiedCrmUserOutput[]> {
if (!Array.isArray(source)) {
return this.mapSingleUserToUnified(
source,
connectionId,
customFieldMappings,
);
}
// Handling array of AffinityUserOutput
return Promise.all(
source.map((user) =>
this.mapSingleUserToUnified(user, connectionId, customFieldMappings),
),
);
}

private async mapSingleUserToUnified(
user: AffinityUserOutput,
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmUserOutput> {
const field_mappings: { [key: string]: any } = {};
if (customFieldMappings) {
for (const mapping of customFieldMappings) {
field_mappings[mapping.slug] = user[mapping.remote_id];
}
}
return {
remote_id: String(user.user.id),
name: `${user.user?.firstName} ${user.user?.lastName}`,
email: user.user?.email,
field_mappings,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Review of Affinity User Mapper Implementation

The AffinityUserMapper class is well-structured and follows good practices with dependency injection and service registration. The methods unify and desunify are implemented with clear logic for handling single and multiple user data transformations.

However, considering the integration with an external system, it is crucial to implement robust error handling and data validation to ensure the integrity and reliability of the data being processed. This will help in preventing runtime errors and maintaining data consistency across systems.

Consider adding error handling and validation within the unify and desunify methods to manage cases where input data might be missing or malformed. This could include checks for required fields and validation of data formats.

+ if (!source) {
+     throw new Error("Source data is required for unification.");
+ }
+ if (!connectionId) {
+     throw new Error("Connection ID is required.");
+ }

Committable suggestion was skipped due to low confidence.

Comment on lines +11 to +132
}
}
}

const result: AffinityDealInput = {
name: source.name,
list_id: 21,
...opts
};

if (customFieldMappings && source.field_mappings) {
for (const [k, v] of Object.entries(source.field_mappings)) {
const mapping = customFieldMappings.find(
(mapping) => mapping.slug === k,
);
if (mapping) {
result[mapping.remote_id] = v;
}
}
}

return result;
}

async unify(
source: AffinityDealOutput | AffinityDealOutput[],
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmDealOutput | UnifiedCrmDealOutput[]> {
if (!Array.isArray(source)) {
return await this.mapSingleDealToUnified(
source,
connectionId,
customFieldMappings,
);
}
// Handling array of AffinityDealOutput
return Promise.all(
source.map((deal) =>
this.mapSingleDealToUnified(deal, connectionId, customFieldMappings),
),
);
}

private async mapSingleDealToUnified(
deal: AffinityDealOutput,
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmDealOutput> {
const field_mappings: { [key: string]: any } = {};
if (customFieldMappings) {
for (const mapping of customFieldMappings) {
field_mappings[mapping.slug] = deal[mapping.remote_id];
}
}

let opts: any = {}

if (deal.list_entries) {
const user_id = await this.utils.getUserUuidFromRemoteId(
String(deal.list_entries[0].creator_id),
'affinity'
);
if (user_id) {
opts = {
...opts,
user_id
}
}
}

if (deal.organization_ids && deal.organization_ids.length > 0) {
const company_id = await this.utils.getCompanyUuidFromRemoteId(
String(deal.organization_ids[0]),
'affinity'
);

if (company_id) {
opts = {
...opts,
company_id
}
}
}

return {
remote_id: String(deal.id),
name: deal.name,
description: '', // Placeholder if there's no direct mapping
amount: 0,
field_mappings,
...opts
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Review of Affinity Deal Mapper Implementation

The AffinityDealMapper class is well-structured and follows good practices with dependency injection and service registration. The methods unify and desunify are implemented with clear logic for handling single and multiple deal data transformations.

Similar to the user mapper, it is crucial to implement robust error handling and data validation to ensure the integrity and reliability of the data being processed. This will help in preventing runtime errors and maintaining data consistency across systems.

Consider adding error handling and validation within the unify and desunify methods to manage cases where input data might be missing or malformed. This could include checks for required fields and validation of data formats.

+ if (!source) {
+     throw new Error("Source data is required for unification.");
+ }
+ if (!connectionId) {
+     throw new Error("Connection ID is required.");
+ }

Committable suggestion was skipped due to low confidence.

Comment on lines +11 to +185
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmNoteOutput | UnifiedCrmNoteOutput[]> {
if (!Array.isArray(source)) {
return await this.mapSingleNoteToUnified(
source,
connectionId,
customFieldMappings,
);
}

return Promise.all(
source.map((note) =>
this.mapSingleNoteToUnified(note, connectionId, customFieldMappings),
),
);
}

private async mapSingleNoteToUnified(
note: AffinityNoteOutput,
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): Promise<UnifiedCrmNoteOutput> {
const field_mappings: { [key: string]: any } = {};
if (customFieldMappings) {
for (const mapping of customFieldMappings) {
field_mappings[mapping.slug] = note[mapping.remote_id];
}
}

let opts: any = {};

if (note.creator_id) {
const user_id = await this.utils.getUserUuidFromRemoteId(
String(note.creator_id),
'affinity'
);
if (user_id) {
opts = {
...opts,
user_id
}
}
}

if (note.organization_ids && note.organization_ids.length > 0) {
const company_id = await this.utils.getCompanyUuidFromRemoteId(
String(note.organization_ids[0]),
'affinity'
);
if (company_id) {
opts = {
...opts,
company_id
}
}
}

if (note.person_ids && note.person_ids.length > 0) {
const contact_id = await this.utils.getContactUuidFromRemoteId(
String(note.person_ids[0]),
'affinity'
);
if (contact_id) {
opts = {
...opts,
contact_id
}
}
}

if (note.opportunity_ids && note.opportunity_ids.length > 0) {
const deal_id = await this.utils.getDealUuidFromRemoteId(
String(note.opportunity_ids[0]),
'affinity'
);
if (deal_id) {
opts = {
...opts,
deal_id
}
}
}



return {
remote_id: note.id,
content: note.content,
field_mappings,
...opts,
};
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Review of AffinityNoteMapper Class

The AffinityNoteMapper class is well-structured and adheres to the principles of clean code and modularity. Here are some specific observations and suggestions:

  1. Dependency Injection and Registration:

    • The constructor uses dependency injection for MappersRegistry and Utils, which is a good practice in NestJS applications. The registration of the service within the constructor ensures that the mapper is available for use immediately after instantiation.
  2. Method desunify:

    • This method converts a unified CRM note format to an Affinity-specific format. The use of async/await is appropriate given the potential delays in network requests to fetch remote IDs.
    • The method handles various fields (user_id, company_id, contact_id, deal_id) which are converted using utility methods. This separation of concerns is good, but the repeated pattern suggests that a refactoring could simplify the code.
    • Suggestion: Consider creating a helper method to handle the repeated pattern of fetching remote IDs and updating the options object.
  3. Method unify:

    • Converts from Affinity-specific formats back to a unified format. The handling of both single and multiple notes is a good practice, ensuring flexibility in usage.
    • The use of Promise.all for handling multiple notes concurrently is efficient and appropriate.
    • Similar to desunify, there's a repeated pattern in handling different IDs which could be refactored into a helper method.
  4. Error Handling:

    • The current implementation does not explicitly handle errors that might occur during the fetching of remote IDs. Given that network requests are involved, adding error handling would make the system more robust.
    • Suggestion: Add try-catch blocks around the network requests or ensure that the utility methods themselves handle exceptions gracefully.
  5. Performance Considerations:

    • The methods make multiple asynchronous calls which are necessary but could be optimized if there are multiple IDs to fetch simultaneously.
    • Suggestion: If applicable, modify the utility methods to handle batches of IDs to reduce the number of network requests.
  6. Code Documentation:

    • The code lacks comments explaining the purpose of the methods and the logic within them. While the code is relatively clear, adding documentation would improve maintainability and ease of understanding for new developers.
    • Suggestion: Add method-level and inline comments to improve code documentation.

Overall, the AffinityNoteMapper class is functionally comprehensive and aligns with the objectives of integrating with Affinity CRM. The suggestions provided aim to enhance maintainability, performance, and robustness.

Copy link

gitguardian bot commented Sep 14, 2024

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
9198067 Triggered Generic Password 5a60903 .env.example View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

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

Successfully merging this pull request may close these issues.

feat: Add integration with Affinity (CRM)
2 participants