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

404 add reputation score api endpoint #405

Merged
merged 3 commits into from
Nov 14, 2024

Conversation

Behzad-rabiei
Copy link
Member

@Behzad-rabiei Behzad-rabiei commented Nov 14, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new route for handling NFT reputation score requests at /:tokenId/:address/reputation-score.
    • Added a controller for NFT functionalities, including the ability to retrieve reputation scores based on user profiles.
    • Implemented a service to manage NFT-related operations and calculate reputation scores.
  • Enhancements

    • Added a required environment variable OCI_BACKEND_URL for configuration.
    • Improved input validation for the reputation score request parameters.
  • Documentation

    • Updated validation schemas to ensure proper structure for incoming requests.

Copy link

coderabbitai bot commented Nov 14, 2024

Walkthrough

The changes in this pull request introduce several new features and enhancements to the existing codebase. A new environment variable, OCI_BACKEND_URL, is added to the configuration schema, and an NFT controller is introduced to handle reputation score requests. A corresponding route for this functionality is established, along with validation for incoming requests. Additionally, new services for NFT management and OCI interactions are created, while existing services are updated for type safety. The overall structure remains intact, with new components integrated seamlessly into the application.

Changes

File Change Summary
src/config/index.ts Added new environment variable OCI_BACKEND_URL and exported it as ociBackendURL.
src/controllers/index.ts Imported and exported nftController.
src/controllers/nft.controller.ts Introduced getReputationScore method for handling NFT reputation score requests.
src/routes/v1/index.ts Added new route for NFT-related requests, mapping /nft to nftRoute.
src/routes/v1/nft.route.ts Created a POST route /:tokenId/:address/reputation-score to handle reputation score requests.
src/services/index.ts Imported and exported ociService.
src/services/nft.service.ts Introduced getReputationScore function for managing NFT reputation scores.
src/services/oci.service.ts Added getProfiles function for fetching user profiles from OCI backend.
src/services/platform.service.ts Updated queryPlatforms and getPlatformByFilter for enhanced type safety using FilterQuery<IPlatform>.
src/validations/index.ts Imported and exported nftValidation.
src/validations/nft.validation.ts Created validation schema for getReputationScore parameters.

Possibly related PRs

🐰 In the meadow, changes bloom,
New routes and scores dispel the gloom.
With OCI_BACKEND_URL in sight,
NFT magic takes its flight!
Hop along, let’s celebrate,
A world of code that’s truly great! 🌼✨

Warning

Rate limit exceeded

@Behzad-rabiei has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 16 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 481d4d0 and 5d39c78.


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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link

@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: 13

🧹 Outside diff range and nitpick comments (16)
src/validations/nft.validation.ts (1)

4-11: Enhance tokenId validation rules

While the current implementation validates the presence of tokenId, consider adding more specific validation rules to ensure it matches your expected format and constraints.

const getReputationScore = {
  params: Joi.object().keys({
-    tokenId: Joi.string().required(),
+    tokenId: Joi.string()
+      .pattern(/^[0-9]+$/)  // Assuming tokenId should be numeric
+      .required()
+      .messages({
+        'string.pattern.base': 'tokenId must be a numeric string'
+      }),
    address: Joi.string()
      .regex(/^0x[a-fA-F0-9]{40}$/)
      .required(),
  }),
};
src/routes/v1/nft.route.ts (1)

8-12: Consider adding specific error handling for NFT-related errors

The endpoint might encounter NFT-specific errors (e.g., invalid token, network issues). Consider implementing:

  1. A try-catch block in the controller
  2. Specific error types for NFT-related failures
  3. Consistent error response format

Example structure:

interface NFTError extends Error {
  code: string;
  statusCode: number;
}

// In controller:
try {
  // reputation score logic
} catch (error) {
  if (error instanceof NFTError) {
    return res.status(error.statusCode).json({
      code: error.code,
      message: error.message
    });
  }
  next(error);
}
src/services/oci.service.ts (2)

1-1: Consider using built-in fetch API

The node-fetch package might be unnecessary as newer Node.js versions (18+) have built-in fetch support. Consider removing this dependency if your Node.js version supports it.

-import fetch from 'node-fetch';

8-8: Add return type annotation for better type safety

The function should explicitly declare its return type for better type safety and documentation.

-async function getProfiles(address: string, chainId: number) {
+async function getProfiles(address: string, chainId: number): Promise<ProfileResponse> {

Consider adding an interface to define the expected response structure:

interface ProfileResponse {
  // Define the expected response structure here
}
src/services/nft.service.ts (2)

11-15: Enhance function documentation for better clarity.

While the basic documentation is present, it would be more helpful to include:

  • Description of the function's purpose and behavior
  • Explanation of platform body requirements
  • Possible error conditions
  • Example usage
 /**
- * get reputation score
+ * Calculates and retrieves reputation score for a given platform.
  * @param {IPlatform} PlatformBody
+ * @param {IPlatform} PlatformBody.name - Platform name (Discord or Discourse)
+ * @param {Object} [PlatformBody.metadata] - Optional metadata for the platform
  * @returns {Promise<HydratedDocument<IPlatform>>}
+ * @throws {ApiError} When platform creation fails
+ * @example
+ * const platform = await getReputationScore({
+ *   name: PlatformNames.Discord,
+ *   metadata: { /* platform specific data */ }
+ * });
  */

27-29: Handle potential race condition in saga creation.

The saga creation for Discord platforms could fail independently of platform creation. Consider using a transaction or implementing a rollback mechanism.

   if (PlatformBody.name === PlatformNames.Discord) {
-    await sagaService.createAndStartFetchMemberSaga(platform._id);
+    try {
+      await sagaService.createAndStartFetchMemberSaga(platform._id);
+    } catch (error) {
+      // Consider implementing cleanup or rollback
+      await Platform.findByIdAndDelete(platform._id);
+      throw new ApiError(
+        httpStatus.INTERNAL_SERVER_ERROR,
+        'Failed to initialize member fetching'
+      );
+    }
   }
src/config/index.ts (3)

59-59: LGTM! Consider enhancing the description.

The new environment variable is properly configured with required validation. Consider making the description more specific about its purpose (e.g., "URL for the OCI backend service used for reputation score requests").


Line range hint 76-78: Remove hardcoded credentials from comments.

These commented MongoDB connection strings contain credentials that should not be in the codebase, even if commented out. This poses a security risk.

Apply this diff to remove the sensitive information:

    serverURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}/${envVars.DB_NAME}?authSource=admin`,
    botURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}?authSource=admin`,
    dbURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}?authSource=admin`,
-    // serverURL: `mongodb://tcmongo:[email protected]:37017/Core?directConnection=true`,
-    // botURL: `mongodb://tcmongo:[email protected]:37017/?directConnection=true`,
-    // dbURL: `mongodb://tcmongo:[email protected]:37017/?directConnection=true`,

Line range hint 13-33: Fix typos in environment variable descriptions.

There are several occurrences of "clinet" instead of "client" in the environment variable descriptions.

Apply this diff to fix the typos:

-    DISCORD_CLIENT_ID: Joi.string().required().description('Discord clinet id'),
-    DISCORD_CLIENT_SECRET: Joi.string().required().description('Discord clinet secret'),
+    DISCORD_CLIENT_ID: Joi.string().required().description('Discord client id'),
+    DISCORD_CLIENT_SECRET: Joi.string().required().description('Discord client secret'),
     DISCORD_BOT_TOKEN: Joi.string().required().description('Discord bot token'),
     DISCORD_AUTHORIZE_CALLBACK_URI: Joi.string().required().description('Discord authorize callback uri'),
     DISCORD_CONNECT_CALLBACK_URI: Joi.string().required().description('Discord connect callback uri'),
     DISCORD_REQUEST_ACCESS_CALLBACK_URI: Joi.string().required().description('Discord request access callback uri'),
     TWITTER_CONNECT_CALLBACK_URI: Joi.string().required().description('Twitter connect callback uri'),
-    TWITTER_CLIENT_ID: Joi.string().required().description('Twitter clinet id'),
-    TWITTER_CLIENT_SECRET: Joi.string().required().description('Twitter clinet secret'),
+    TWITTER_CLIENT_ID: Joi.string().required().description('Twitter client id'),
+    TWITTER_CLIENT_SECRET: Joi.string().required().description('Twitter client secret'),
     GOOGLE_CONNECT_CALLBACK_URI: Joi.string().required().description('Google connect callback uri'),
-    GOOGLE_CLIENT_SECRET: Joi.string().required().description('Google clinet id'),
-    GOOGLE_CLIENT_ID: Joi.string().required().description('Google clinet secret'),
+    GOOGLE_CLIENT_SECRET: Joi.string().required().description('Google client id'),
+    GOOGLE_CLIENT_ID: Joi.string().required().description('Google client secret'),
     GITHUB_APP_ID: Joi.string().required().description('Github app id'),
     GITHUB_APP_PUBLIC_LINK: Joi.string().required().description('Github app public link'),
     GITHUB_APP_CLIENT_ID: Joi.string().required().description('Github app client id'),
     GITHUB_APP_CLIENT_SECRET: Joi.string().required().description('Github app client secret'),
     GITHUB_APP_CONNECT_CALLBACK_URI: Joi.string().required().description('Github app connect callback uri'),
     NOTION_CONNECT_CALLBACK_URI: Joi.string().required().description('Notion connect callback uri'),
-    NOTION_CLIENT_SECRET: Joi.string().required().description('Notion clinet id'),
-    NOTION_CLIENT_ID: Joi.string().required().description('Notion clinet secret'),
+    NOTION_CLIENT_SECRET: Joi.string().required().description('Notion client id'),
+    NOTION_CLIENT_ID: Joi.string().required().description('Notion client secret'),
src/services/platform.service.ts (1)

41-41: Consider typing the options parameter as well

While the filter parameter is now properly typed with FilterQuery, the options parameter could also benefit from stronger typing using mongoose's PaginateOptions type.

-const queryPlatforms = async (filter: FilterQuery<IPlatform>, options: object) => {
+const queryPlatforms = async (filter: FilterQuery<IPlatform>, options: PaginateOptions) => {

Don't forget to add the import:

import { PaginateOptions } from 'mongoose';
src/controllers/nft.controller.ts (6)

69-69: Consider making supported chain IDs configurable

The supportedChainIds array currently contains a hardcoded value [11155111]. Consider making this configurable or moving it to a constants file for better maintainability and scalability.


79-79: Improve error message clarity and fix typos

The error message in shouldProfilesExist has awkward phrasing and a typo. It should be rephrased for clarity.

Apply the following diff:

- throw new ApiError(400, 'User has no any onchain profiles');
+ throw new ApiError(400, 'User has no on-chain profiles');

85-85: Fix typos in error message

The error message contains typos and inconsistent capitalization.

Apply the following diff:

- throw new ApiError(400, "There's not any assoicated dynamic nft module to the token Id");
+ throw new ApiError(400, "There is no associated dynamic NFT module for the provided tokenId");

91-91: Clarify error message

The error message is unclear and could be improved for better understanding.

Apply the following diff:

- throw new ApiError(400, "There's not any platform connected for requested platform");
+ throw new ApiError(400, "The requested platform is not connected to the community");

97-97: Fix typo in error message

The error message in shouldProfileExist contains a typo: "oncahin" should be "on-chain".

Apply the following diff:

- throw new ApiError(400, "There's not any user oncahin profile for requested platform");
+ throw new ApiError(400, "There is no user on-chain profile for the requested platform");

25-63: Optimize loop structures for better readability

Consider using modern JavaScript iterators like for...of loops to enhance readability and maintainability of your code.

Apply the following refactor:

- for (let i = 0; i < supportedPlatforms.length; i++) {
-   const platform = await platformService.getPlatformByFilter({
-     name: supportedPlatforms[i],
+ for (const platformName of supportedPlatforms) {
+   const platform = await platformService.getPlatformByFilter({
+     name: platformName,
      community: dynamicNftModule?.community,
    });
    // ...
-   for (let j = 0; j < profiles.length; j++) {
-     const profile = profiles[j];
+   for (const profile of profiles) {
      // ...
-     if (profile.profile.provider === supportedPlatforms[i]) {
+     if (profile.profile.provider === platformName) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0e7309d and 481d4d0.

📒 Files selected for processing (11)
  • src/config/index.ts (2 hunks)
  • src/controllers/index.ts (2 hunks)
  • src/controllers/nft.controller.ts (1 hunks)
  • src/routes/v1/index.ts (2 hunks)
  • src/routes/v1/nft.route.ts (1 hunks)
  • src/services/index.ts (2 hunks)
  • src/services/nft.service.ts (1 hunks)
  • src/services/oci.service.ts (1 hunks)
  • src/services/platform.service.ts (3 hunks)
  • src/validations/index.ts (2 hunks)
  • src/validations/nft.validation.ts (1 hunks)
🔇 Additional comments (21)
src/validations/nft.validation.ts (2)

13-15: LGTM! Export structure is clean and well-organized.


4-11: Verify route integration and error handling

Let's ensure this validation schema is properly integrated with the route handler and includes appropriate error handling.

✅ Verification successful

Validation schema is properly integrated with error handling middleware

The validation schema is correctly integrated:

  • Route uses the validate middleware with nftValidation.getReputationScore
  • Validation middleware is properly imported from ../../middlewares
  • Controller accesses validated params via req.params
  • Error handling is built into the catchAsync wrapper in the controller
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the integration of validation schema with route handler

# Check for route definition using this validation
echo "Checking route definition..."
rg -l "nftValidation.getReputationScore" "src/routes"

# Check for error handling in the controller
echo "Checking error handling in controller..."
rg -l "reputation-score" "src/controllers"

Length of output: 277


Script:

#!/bin/bash
# Let's check the actual route implementation and controller
echo "Checking route implementation..."
rg -A 5 "getReputationScore" src/routes/v1/nft.route.ts

echo "\nChecking NFT controller..."
rg -A 5 "getReputationScore" src/controllers/nft.controller.ts

# Let's also verify the validation middleware usage
echo "\nChecking validation middleware..."
rg "validate" src/routes/v1/nft.route.ts

Length of output: 986

src/routes/v1/nft.route.ts (3)

1-4: LGTM! Clean and well-organized imports

The imports follow standard Express.js practices and maintain a clear separation of concerns between controllers, validations, and middlewares.


5-5: LGTM! Standard Express router initialization


14-14: LGTM! Standard router export

src/validations/index.ts (2)

11-11: LGTM! Clean and consistent implementation.

The addition of nftValidation follows the established patterns in both import and export statements, maintaining consistency with the existing codebase structure.

Also applies to: 24-24


11-11: Verify the NFT validation implementation.

Let's ensure the referenced validation file exists and contains proper validation schema for the reputation score endpoint.

✅ Verification successful

NFT validation schema is properly implemented

The validation schema for the reputation score endpoint is correctly implemented with:

  • Proper Joi validation library import
  • Schema for getReputationScore endpoint that validates:
    • tokenId as a required string
    • address as a required string with Ethereum address format (0x followed by 40 hex characters)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and implementation of nft.validation.ts

# Check if the file exists
echo "Checking for nft.validation.ts file..."
fd "nft.validation.ts" src/validations

# Verify the validation schema implementation
echo "Checking validation schema implementation..."
rg -A 10 "getReputationScore.*schema" src/validations/nft.validation.ts

# Look for proper type imports
echo "Checking type imports..."
rg "import.*from.*@/types" src/validations/nft.validation.ts

Length of output: 454


Script:

#!/bin/bash
# Let's check the actual content of the nft.validation.ts file
echo "Content of nft.validation.ts:"
cat src/validations/nft.validation.ts

# Also check for any validation schema patterns
echo -e "\nChecking for validation schema patterns:"
rg "schema.*=.*{" src/validations/nft.validation.ts

# Look for Joi or other validation library imports
echo -e "\nChecking validation library imports:"
rg "^import.*joi" -i src/validations/nft.validation.ts

Length of output: 824

src/controllers/index.ts (2)

12-12: LGTM! Clean import statement.

The import follows the established pattern and naming convention used for other controllers.


26-26: LGTM! Export statement is properly placed.

The export maintains alphabetical ordering and is consistent with the existing pattern.

Let's verify we're not missing any controllers in the exports:

✅ Verification successful

Let me check the actual content of the index.ts file to understand why we're seeing these warnings and verify if there's a different export pattern being used.


The review comment is accurate - all controllers are properly exported

The initial script generated false warnings because it was looking for individual export statements, but the index.ts file uses a grouped export pattern. All controllers found in the directory (announcement, auth, category, community, discourse, heatmap, memberActivity, module, nft, notion, platform, and user) are correctly imported and exported in the index.ts file. The export of nftController maintains alphabetical ordering and follows the established pattern.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any controller files that might not be exported

# Find all controller files
echo "Looking for controller files that might not be exported..."
fd -e ts -e js "controller" src/controllers | grep -v "index.ts" | while read -r file; do
    # Extract the controller name
    controller=$(basename "$file" | sed 's/\..*$//')
    
    # Check if it's exported in index.ts
    if ! grep -q "export.*${controller}" src/controllers/index.ts; then
        echo "Warning: $controller found but not exported in index.ts"
    fi
done

Length of output: 3486


Script:

#!/bin/bash
# Let's see the actual content of index.ts
cat src/controllers/index.ts

Length of output: 961

src/services/oci.service.ts (2)

27-29: LGTM!

The export structure is clean and follows common patterns.


1-29: Verify integration points

Let's verify the integration of this service with the rest of the system:

✅ Verification successful

Integration points verified successfully

The service is well-integrated with the system:

  • Configuration: ociBackendURL is properly defined in config/index.ts and correctly used in the service
  • Error handling: ApiError usage is consistent with the rest of the codebase, using similar error codes and message patterns
  • The custom error code 590 is used consistently across external service failures (discord, twitter, discourse)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if config.ociBackendURL is properly defined
echo "Checking configuration..."
rg "ociBackendURL" -t ts

# Check for proper error handling in consumers
echo "Checking error handling in consumers..."
rg "getProfiles.*catch" -t ts

# Check if ApiError is consistently used
echo "Checking ApiError usage patterns..."
ast-grep --pattern 'throw new ApiError($_, $_)'

Length of output: 7851

src/services/index.ts (2)

17-17: LGTM! Clean integration of the OCI service.

The import and export statements follow the existing patterns and are correctly placed.

Also applies to: 35-35


17-17: Verify the OCI service implementation.

Let's ensure the referenced service file exists and contains the expected implementation for reputation score functionality.

✅ Verification successful

Let me verify the implementation further by checking the complete OCI service implementation.


OCI service implementation verified with reputation score integration

The OCI service is correctly implemented with:

  • A getProfiles function that fetches profiles from the OCI backend
  • Proper error handling and logging
  • Integration with reputation score functionality through:
    • Usage in nft.controller.ts for fetching profiles across supported chains
    • Connection to the reputation score calculation flow via getReputationScore endpoints

The service meets the expected implementation requirements and is properly integrated with the reputation score functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify OCI service implementation and its integration with reputation score functionality

# Check if the service file exists and contains reputation-related functions
echo "Checking OCI service implementation..."
rg -l "oci.service.ts$"

# Look for reputation score related functions in the OCI service
echo "Checking reputation score functionality..."
rg -A 5 "getProfiles|getReputationScore" --type ts

Length of output: 4182


Script:

#!/bin/bash
# Check the complete implementation of oci.service.ts
echo "Checking complete OCI service implementation..."
cat src/services/oci.service.ts

Length of output: 1061

src/services/nft.service.ts (2)

1-10: LGTM! Well-structured imports with proper typing.

The imports are well-organized, using specific imports rather than whole modules, which is a good practice for tree-shaking.


33-35: LGTM! Clean and standard export pattern.

The export follows the common pattern for service modules, making it easy to import and use.

src/routes/v1/index.ts (2)

67-70: LGTM! Verify security measures for NFT endpoints

The route configuration follows the established pattern and is properly integrated. Since this handles NFT-related operations, ensure that:

  1. Input validation is implemented for token IDs and addresses
  2. Rate limiting is in place
  3. Authentication/authorization is properly configured
#!/bin/bash
# Description: Verify security measures in the NFT route implementation
# Expected: Find validation, rate limiting, and auth middleware

# Check for validation implementation
rg -A 5 "validation|validate|schema" src/validations/nft.validation.ts

# Check for authentication/middleware usage
rg -A 5 "authenticate|authorize|middleware" src/routes/v1/nft.route.ts

# Check for rate limiting
rg "rateLimiter|RateLimit" src/routes/v1/nft.route.ts

14-14: Verify the NFT route implementation

The import statement follows the established pattern. Let's verify the existence and implementation of the NFT route file.

✅ Verification successful

Import statement is correctly implemented and all dependencies exist

The import of nftRoute is properly implemented with all required dependencies present:

  • nft.route.ts exists and exports a router with the reputation score endpoint
  • Route is properly configured with validation middleware and controller
  • Supporting files (nft.validation.ts and nft.controller.ts) are implemented with the necessary logic
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and implementation of nft.route.ts
# Expected: Find the file and confirm it exports a router with the reputation score endpoint

# Check if the file exists
fd "nft.route.ts" src/routes/v1

# Verify the route implementation
rg -A 10 "router\.(get|post|put|delete).*reputation-score" src/routes/v1/nft.route.ts

Length of output: 345


Script:

#!/bin/bash
# Let's verify the NFT route implementation more thoroughly

# First, check if the file exists and see its content
fd "nft.route.ts" src/routes/v1 --exec cat {}

# Also check for any validation schemas related to NFT
fd "nft.validation.ts" src/validations --exec cat {}

# Check for the controller implementation
fd "nft.controller.ts" src/controllers --exec cat {}

Length of output: 5097

src/config/index.ts (1)

162-162: LGTM! Export follows consistent pattern.

The new configuration export is properly positioned and follows the established pattern.

src/services/platform.service.ts (3)

1-1: LGTM! Clean import addition

The FilterQuery import is properly grouped with other mongoose types, maintaining good code organization.


50-50: LGTM! Improved type safety

The filter parameter is now properly typed with FilterQuery, providing better type checking and IDE support.


Line range hint 41-50: Verify integration with reputation score endpoint

The type improvements are good, but let's verify how these platform query methods are used by the new reputation score endpoint.

✅ Verification successful

Let me gather more context about how the platform service is used in the NFT controller.


Based on the evidence gathered, I can now provide the final response:

Type improvements are justified by actual platform service usage

The platform service methods are actively used within the NFT controller's reputation score implementation:

  • getPlatformByFilter is used to fetch platform details for each supported platform when calculating reputation scores
  • The type improvements ensure type safety when integrating with the Neo4j-based reputation score calculation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how platform query methods are used in the new reputation score endpoint

# Search for usage of queryPlatforms and getPlatformByFilter in NFT-related files
echo "Searching for platform query method usage in NFT controller..."
rg -l "queryPlatforms|getPlatformByFilter" --type ts | grep -i "nft"

# Search for any reputation score related code
echo "Searching for reputation score related code..."
rg -i "reputation.?score" --type ts

Length of output: 1758


Script:

#!/bin/bash
# Check the actual usage of platform service methods in NFT controller
echo "Checking platform service usage in NFT controller..."
rg -A 5 -B 5 "getPlatformByFilter|queryPlatforms" src/controllers/nft.controller.ts

# Check the platform service usage in NFT service
echo "Checking platform service usage in NFT service..."
rg -A 5 -B 5 "getPlatformByFilter|queryPlatforms" src/services/nft.service.ts

# Check the implementation of getReputationScore
echo "Checking getReputationScore implementation..."
ast-grep --pattern 'const getReputationScore = async function (req: Request, res: Response) {
  $$$
}'

Length of output: 1426

Comment on lines +1 to +3
import Joi from 'joi';
import { PlatformNames } from '@togethercrew.dev/db';

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused import PlatformNames

The PlatformNames import from '@togethercrew.dev/db' is not used in this file.

import Joi from 'joi';
-import { PlatformNames } from '@togethercrew.dev/db';
📝 Committable suggestion

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

Suggested change
import Joi from 'joi';
import { PlatformNames } from '@togethercrew.dev/db';
import Joi from 'joi';

Comment on lines +8 to +12
router.post(
'/:tokenId/:address/reputation-score',
validate(nftValidation.getReputationScore),
nftController.getReputationScore,
);
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Consider adding authentication middleware to protect NFT reputation endpoint

The codebase has authentication middleware (auth) implemented and used across multiple routes, but it's not applied to this NFT reputation endpoint. This endpoint processes sensitive NFT data and should be protected.

  • Add authentication middleware to src/routes/v1/nft.route.ts:
router.post(
  '/:tokenId/:address/reputation-score',
  auth('admin', 'view'),  // Add this line
  validate(nftValidation.getReputationScore),
  nftController.getReputationScore,
);

The validation schema correctly validates:

  • tokenId: Required string
  • address: Required Ethereum address format (0x followed by 40 hex chars)
🔗 Analysis chain

Consider adding authentication and rate limiting middleware

This endpoint processes NFT reputation scores based on user input. Consider implementing:

  1. Authentication middleware to prevent unauthorized access
  2. Rate limiting to prevent abuse
  3. Input sanitization for the URL parameters

Let's verify the validation schema and any existing auth middleware:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for validation schema implementation
echo "Checking validation schema..."
rg -A 10 "getReputationScore" --glob "src/validations/**/*.ts"

# Check for existing auth middleware in the project
echo "Checking auth middleware..."
rg -l "auth.*middleware" --glob "src/middlewares/**/*.ts"

Length of output: 894


Script:

#!/bin/bash
# Check for any auth or rate limiting middleware in the route file
echo "Checking route middleware..."
rg -B 5 -A 5 "router\.(get|post|put|delete)" src/routes/v1/nft.route.ts

# Check for any auth middleware implementations
echo "Checking auth implementations..."
fd -e ts -e js --exec rg -l "auth" {} \; -X rg -A 5 "(middleware|auth)" {}

# Check for rate limiting implementations
echo "Checking rate limiting..."
fd -e ts -e js --exec rg -l "limit" {} \; -X rg -A 5 "(rateLimit|rateLimiter)"

Length of output: 1083


Script:

#!/bin/bash
# Check for auth implementations with corrected syntax
echo "Checking auth implementations..."
rg -l "auth" --type ts --type js | xargs rg -A 5 "(middleware|auth)"

# Check for rate limiting with corrected syntax
echo "Checking rate limiting..."
rg -l "limit" --type ts --type js | xargs rg -A 5 "(rateLimit|rateLimiter)"

# Check middleware imports in the main app setup
echo "Checking app middleware setup..."
rg -A 10 "express\(\)" --type ts --type js

Length of output: 51320

Comment on lines +21 to +24
} catch (error: any) {
logger.error(error, 'Failed to get profiles from oci backend');
throw new ApiError(590, 'Failed to get profiles from oci backend ');
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling specificity

The error handling could be more specific and informative:

  1. The magic number 590 should be a named constant
  2. Error messages should include more context
  3. Original error details should be preserved
-  } catch (error: any) {
-    logger.error(error, 'Failed to get profiles from oci backend');
-    throw new ApiError(590, 'Failed to get profiles from oci backend ');
+  } catch (error) {
+    const errorMessage = `Failed to get profiles for address ${address} on chain ${chainId}`;
+    logger.error({ error, address, chainId }, errorMessage);
+    if (error instanceof Error) {
+      throw new ApiError(
+        OCI_ERROR_CODES.PROFILE_FETCH_FAILED,
+        `${errorMessage}: ${error.message}`
+      );
+    }
+    throw new ApiError(OCI_ERROR_CODES.PROFILE_FETCH_FAILED, errorMessage);

Consider adding an error codes enum:

enum OCI_ERROR_CODES {
  PROFILE_FETCH_FAILED = 590,
  // Add other OCI-related error codes here
}

Comment on lines +10 to +14
logger.debug(`${config.ociBackendURL}/oci/profiles/${chainId}/${address}`);
const response = await fetch(`${config.ociBackendURL}/api/v1/oci/profiles/${chainId}/${address}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance URL construction and request configuration

Several improvements can be made to the request setup:

  1. The URL parameters should be properly encoded to prevent injection
  2. API version should be configurable
  3. Request timeout should be added
-    logger.debug(`${config.ociBackendURL}/oci/profiles/${chainId}/${address}`);
-    const response = await fetch(`${config.ociBackendURL}/api/v1/oci/profiles/${chainId}/${address}`, {
+    const url = new URL(`${config.ociBackendURL}/api/v1/oci/profiles/${chainId}/${encodeURIComponent(address)}`);
+    logger.debug(`Fetching profiles from: ${url.toString()}`);
+    const response = await fetch(url, {
       method: 'GET',
       headers: { 'Content-Type': 'application/json' },
+      timeout: 5000, // Add reasonable timeout
     });
📝 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
logger.debug(`${config.ociBackendURL}/oci/profiles/${chainId}/${address}`);
const response = await fetch(`${config.ociBackendURL}/api/v1/oci/profiles/${chainId}/${address}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
const url = new URL(`${config.ociBackendURL}/api/v1/oci/profiles/${chainId}/${encodeURIComponent(address)}`);
logger.debug(`Fetching profiles from: ${url.toString()}`);
const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
timeout: 5000, // Add reasonable timeout
});

Comment on lines +16 to +26
const getReputationScore = async (PlatformBody: IPlatform): Promise<HydratedDocument<IPlatform>> => {
if (PlatformBody.name === PlatformNames.Discord || PlatformBody.name === PlatformNames.Discourse) {
if (PlatformBody.metadata) {
PlatformBody.metadata = {
action: analyzerAction,
window: analyzerWindow,
...PlatformBody.metadata,
};
}
}
const platform = await Platform.create(PlatformBody);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling and input validation.

The function lacks proper error handling and input validation:

  1. Platform creation could fail silently
  2. No validation of required PlatformBody fields
  3. Metadata structure is not type-checked
 const getReputationScore = async (PlatformBody: IPlatform): Promise<HydratedDocument<IPlatform>> => {
+  if (!PlatformBody || !PlatformBody.name) {
+    throw new ApiError(httpStatus.BAD_REQUEST, 'Invalid platform data');
+  }
+
   if (PlatformBody.name === PlatformNames.Discord || PlatformBody.name === PlatformNames.Discourse) {
     if (PlatformBody.metadata) {
-      PlatformBody.metadata = {
+      const enrichedMetadata = {
         action: analyzerAction,
         window: analyzerWindow,
         ...PlatformBody.metadata,
       };
+      PlatformBody = { ...PlatformBody, metadata: enrichedMetadata };
     }
   }
-  const platform = await Platform.create(PlatformBody);
+  try {
+    const platform = await Platform.create(PlatformBody);
+  } catch (error) {
+    throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, 'Failed to create platform');
+  }

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

Comment on lines 33 to 35
const profile = profiles[j];
logger.debug({ i, j, profile, supportedPlatforms: supportedPlatforms[i] });
if (profile.profile.provider === supportedPlatforms[i]) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate the existence of the user profile

After retrieving profile, you should validate that it exists before proceeding to prevent runtime errors.

Apply the following diff:

          const profile = profiles[j];
+         shouldProfileExist(profile);
          logger.debug({ i, j, profile, supportedPlatforms: supportedPlatforms[i] });
          if (profile.profile.provider === supportedPlatforms[i]) {

Ensure that the shouldProfileExist function is defined and imported.

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

Comment on lines +20 to +21
const profiles: Array<any> = await getProfilesOnAllSupportedChains(address);
logger.debug(profiles);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate profiles after retrieval

After retrieving profiles, you should validate that profiles exist before proceeding to prevent errors when profiles are empty.

Apply the following diff:

  const profiles: Array<any> = await getProfilesOnAllSupportedChains(address);
+ shouldProfilesExist(profiles);
  logger.debug(profiles);

Ensure that the shouldProfilesExist function is correctly implemented and imported.

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

}
}
}
return repuationScore;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Send the reputation score in the HTTP response

The controller function currently returns the reputation score but does not send it in the HTTP response. In an Express controller, you should use res.json() or res.send() to send data back to the client.

Apply the following diff to send the reputation score in the response:

- return reputationScore;
+ res.json({ reputationScore });

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

}

function shouldProfilesExist(profiles: Array<any>) {
if (profiles.length < 0) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect condition in profiles existence check

The condition profiles.length < 0 is incorrect because an array's length cannot be negative. It should be profiles.length === 0 to check for an empty array.

Apply the following diff:

function shouldProfilesExist(profiles: Array<any>) {
-   if (profiles.length < 0) {
+   if (profiles.length === 0) {
      throw new ApiError(400, 'User has no on-chain profiles');
    }
}

community: dynamicNftModule?.community,
});
logger.debug({ i, platform, supportedPlatforms: supportedPlatforms[i] });
// shouldPlatformExist(platform);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Uncomment the platform existence validation

The call to shouldPlatformExist(platform); is commented out. This validation is important to ensure that the platform exists before proceeding.

Apply the following diff:

- // shouldPlatformExist(platform);
+ shouldPlatformExist(platform);

Ensure that shouldPlatformExist is defined and imported.

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

@Behzad-rabiei Behzad-rabiei merged commit 4487fff into main Nov 14, 2024
13 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (2)
src/controllers/nft.controller.ts (2)

19-21: Avoid logging sensitive user data

Logging detailed user addresses and profile information may expose sensitive data. Consider limiting the amount of sensitive information in logs.

Modify the logging statements to reduce sensitive data exposure:

- logger.debug(tokenId, address);
- logger.debug(profiles);
+ logger.debug(`Processing tokenId: ${tokenId}`);
+ logger.debug(`Retrieved ${profiles.length} profiles`);

This way, you still get useful debugging information without exposing sensitive details.


68-69: Specify the type of profiles for better type safety

Using Array<any> reduces type safety and can lead to runtime errors. Define a proper interface for profiles to enhance maintainability and catch errors at compile time.

Define an interface for profiles and update the code:

interface Profile {
  profile: {
    id: string;
    provider: string;
    // Add other relevant fields
  };
}

Then update the code:

- let profiles: Array<any> = [];
+ let profiles: Profile[] = [];
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 481d4d0 and 5d39c78.

📒 Files selected for processing (1)
  • src/controllers/nft.controller.ts (1 hunks)

const { tokenId, address } = req.params;
const supportedPlatforms = ['discord', 'discourse'];

let repuationScore;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential undefined 'repuationScore' before returning

If none of the profiles match the supported platforms, repuationScore may remain undefined, leading to unexpected behavior when returned. Initialize repuationScore to a default value and ensure it is always defined before returning.

Apply the following diff to initialize repuationScore and handle the response correctly:

- let repuationScore;
+ let repuationScore = 0;

...

- return repuationScore;
+ res.json({ reputationScore: repuationScore });

Also applies to: 64-64

Comment on lines +54 to +57
const { _fieldLookup, _fields } = reputationScoreResponse as unknown as {
_fieldLookup: Record<string, number>;
_fields: number[];
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid using internal properties of Neo4j response objects

Accessing internal properties like _fieldLookup and _fields is not recommended, as they are subject to change and not part of the public API. Use the official Neo4j driver methods to extract data from the result records.

Refactor the code to use public methods:

- const { _fieldLookup, _fields } = reputationScoreResponse as unknown as {
-   _fieldLookup: Record<string, number>;
-   _fields: number[];
- };
- repuationScore = _fields[_fieldLookup['reputation_score']];
+ const reputationScoreRecord = reputationScoreResponse.get('reputation_score');
+ repuationScore = reputationScoreRecord;

This approach uses the get method to safely access the desired field from the record.

📝 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
const { _fieldLookup, _fields } = reputationScoreResponse as unknown as {
_fieldLookup: Record<string, number>;
_fields: number[];
};
const reputationScoreRecord = reputationScoreResponse.get('reputation_score');
repuationScore = reputationScoreRecord;

Comment on lines +71 to +72
const chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]);
profiles = profiles.concat(chainProfiles);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential errors from OCI service calls

The call to ociService.getProfiles might fail or throw an exception. It's important to handle such errors to prevent the application from crashing.

Add error handling for the OCI service call:

for (let i = 0; i < supportedChainIds.length; i++) {
- const chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]);
+ let chainProfiles;
+ try {
+   chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]);
+ } catch (error) {
+   logger.error(`Failed to get profiles for chain ID ${supportedChainIds[i]}:`, error);
+   continue;
+ }
  profiles = profiles.concat(chainProfiles);
}

This ensures that a failure in one chain does not halt the entire process.

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

Comment on lines +36 to +44
const reputationScoreQuery = `
MATCH (:${NEO4J_PLATFORM_INFO[temp].member} {id: "${profile.profile.id}"})-[r:HAVE_METRICS {platformId: "${platform?.id}"}]->(a)
WITH r.date as metrics_date, r.closenessCentrality as memberScore
ORDER BY metrics_date DESC
LIMIT 1
MATCH (user:${NEO4J_PLATFORM_INFO[temp].member})-[user_r:HAVE_METRICS {platformId: "${platform?.id}", date: metrics_date}]->(user)
WITH memberScore, MAX(user_r.closenessCentrality) as maxScore
RETURN memberScore / maxScore AS reputation_score
`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent injection attacks by parameterizing Neo4j queries

Directly interpolating variables into Neo4j query strings can lead to injection vulnerabilities if any variables contain malicious input. Use parameterized queries to securely pass variables to the query.

Refactor the code to use parameterized queries:

- const reputationScoreQuery = `
-   MATCH (:${NEO4J_PLATFORM_INFO[temp].member} {id: "${profile.profile.id}"})-[r:HAVE_METRICS {platformId: "${platform?.id}"}]->(a)
-   WITH r.date as metrics_date, r.closenessCentrality as memberScore
-   ORDER BY metrics_date DESC
-   LIMIT 1
-   MATCH (user:${NEO4J_PLATFORM_INFO[temp].member})-[user_r:HAVE_METRICS {platformId: "${platform?.id}", date: metrics_date}]->(user)
-   WITH memberScore, MAX(user_r.closenessCentrality) as maxScore
-   RETURN memberScore / maxScore AS reputation_score
-   `;
+ const reputationScoreQuery = `
+   MATCH (:${NEO4J_PLATFORM_INFO[temp].member} {id: $profileId})-[r:HAVE_METRICS {platformId: $platformId}]->(a)
+   WITH r.date as metrics_date, r.closenessCentrality as memberScore
+   ORDER BY metrics_date DESC
+   LIMIT 1
+   MATCH (user:${NEO4J_PLATFORM_INFO[temp].member})-[user_r:HAVE_METRICS {platformId: $platformId, date: metrics_date}]->(user)
+   WITH memberScore, MAX(user_r.closenessCentrality) as maxScore
+   RETURN memberScore / maxScore AS reputation_score
+   `;

...

- const neo4jData = await Neo4j.read(reputationScoreQuery);
+ const neo4jData = await Neo4j.read(reputationScoreQuery, {
+   profileId: profile.profile.id,
+   platformId: platform?.id,
+ });

This refactoring ensures that variables are passed safely to the query, mitigating the risk of injection attacks.

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

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

Successfully merging this pull request may close these issues.

1 participant