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

[OTE-760] implement comlink affiliate metadata endpoint #2243

Merged
merged 8 commits into from
Sep 13, 2024

Conversation

jerryfan01234
Copy link
Contributor

@jerryfan01234 jerryfan01234 commented Sep 12, 2024

Changelist

replace /affiliates/metadata stub with real logic

Test Plan

[Describe how this PR was tested (if applicable)]

Author/Reviewer Checklist

  • If this PR has changes that result in a different app state given the same prior state and transaction list, manually add the state-breaking label.
  • If the PR has breaking postgres changes to the indexer add the indexer-postgres-breaking label.
  • If this PR isn't state-breaking but has changes that modify behavior in PrepareProposal or ProcessProposal, manually add the label proposal-breaking.
  • If this PR is one of many that implement a specific feature, manually label them all feature:[feature-name].
  • If you wish to for mergify-bot to automatically create a PR to backport your change to a release branch, manually add the label backport/[branch-name].
  • Manually add any of the following labels: refactor, chore, bug.

Summary by CodeRabbit

  • New Features

    • Introduced a new configuration parameter for volume eligibility threshold.
    • Enhanced the affiliate metadata retrieval process, including checks for wallet existence, volume eligibility, and affiliate status.
  • Bug Fixes

    • Improved error handling for cases where wallet addresses or usernames are not found.
  • Tests

    • Expanded test coverage for the affiliate metadata functionality, validating various scenarios and ensuring reliability.

Copy link

linear bot commented Sep 12, 2024

Copy link
Contributor

coderabbitai bot commented Sep 12, 2024

Warning

Rate limit exceeded

@jerryfan01234 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 36 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

Files that changed from the base of the PR and between 7d36faa and b68f364.

Walkthrough

The changes implemented in this pull request enhance the functionality of the GET /metadata endpoint in the affiliates controller. They introduce new tests to validate various scenarios for retrieving affiliate metadata based on wallet addresses. Additionally, a new configuration parameter for volume eligibility is added, and the logic for the getMetadata method is refined to include checks for wallet existence, volume eligibility, and affiliate status, improving error handling and dynamic data retrieval.

Changes

Files Change Summary
indexer/services/comlink/__tests__/controllers/api/v4/affiliates-controller.test.ts Added comprehensive tests for the GET /metadata endpoint, covering various scenarios for affiliate metadata retrieval.
indexer/services/comlink/src/config.ts Introduced VOLUME_ELIGIBILITY_THRESHOLD configuration parameter for volume eligibility checks.
indexer/services/comlink/src/controllers/api/v4/affiliates-controller.ts Modified getMetadata method to validate wallet existence, check volume eligibility, and retrieve referral codes dynamically.

Possibly related PRs

  • [OTE-793] modify affiliate api stubs #2234: This PR updates tests for the GET /metadata endpoint, which directly relates to the new tests added in this PR and includes response structure updates for isVolumeEligible and isAffiliate.
  • [OTE-730] add test to check register affiliate auth #2235: This PR focuses on testing the authentication process for registering affiliates, which is related to the overall affiliate functionality that includes the changes made in this PR regarding affiliate metadata retrieval.

Poem

🐇 In the meadow where the data flows,
New tests sprout like bright, blooming rows.
With thresholds set and checks in place,
Our metadata finds its rightful space.
Hooray for changes, let the rabbits cheer,
For clearer paths and code that's dear! 🌼


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 anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Outside diff range and nitpick comments (1)
indexer/services/comlink/src/config.ts (1)

63-65: LGTM! Consider adding a comment to clarify the purpose of the threshold.

The addition of the VOLUME_ELIGIBILITY_THRESHOLD parameter to the configuration looks good. It allows for flexibility in adjusting the threshold value without modifying the code, which is a good practice.

To improve the clarity of the code, consider adding a comment to explain the purpose and unit of the threshold value. For example:

// The minimum volume required for an affiliate to be eligible for rewards (in USD).
VOLUME_ELIGIBILITY_THRESHOLD: parseInteger({ default: 10_000 }),

Also, were there any specific considerations for choosing the default value of 10,000? It would be helpful to understand the rationale behind this choice.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 5acd685 and 21481ec.

Files selected for processing (3)
  • indexer/services/comlink/tests/controllers/api/v4/affiliates-controller.test.ts (1 hunks)
  • indexer/services/comlink/src/config.ts (1 hunks)
  • indexer/services/comlink/src/controllers/api/v4/affiliates-controller.ts (3 hunks)
Additional comments not posted (7)
indexer/services/comlink/__tests__/controllers/api/v4/affiliates-controller.test.ts (7)

33-46: LGTM!

The test case is correctly implemented and checks the expected response. It also uses the beforeEach and afterEach hooks to set up and tear down the test data.


48-55: LGTM!

The test case is correctly implemented and checks the expected 404 response for a non-existent wallet address.


57-75: LGTM!

The test case is correctly implemented and checks the expected response with isVolumeEligible set to false when the wallet has a total volume of 0. It also updates the wallet data to set up the test scenario.


77-95: LGTM!

The test case is correctly implemented and checks the expected response with isVolumeEligible set to true when the wallet has a total volume of 100000. It also updates the wallet data to set up the test scenario.


97-109: LGTM!

The test case is correctly implemented and checks the expected response with isAffiliate set to false when the AffiliateReferredUsersTable is empty. It relies on the table being empty to set up the test scenario.


111-127: LGTM!

The test case is correctly implemented and checks the expected response with isAffiliate set to true when the AffiliateReferredUsersTable has an entry for the wallet address. It creates the entry to set up the test scenario.


129-137: LGTM!

The test case is correctly implemented and checks the expected 500 response when the wallet does not have a subaccount username. It creates the wallet without a subaccount username to set up the test scenario.

Comment on lines 41 to 91
@Query() address: string,
): Promise<AffiliateMetadataResponse> {
// simulate a delay
await new Promise((resolve) => setTimeout(resolve, 100));
// Check that the address exists
const walletRow = await WalletTable.findById(address);
if (!walletRow) {
throw new NotFoundError(`Wallet with address ${address} not found`);
}
const isVolumeEligible = Number(walletRow.totalVolume) >= config.VOLUME_ELIGIBILITY_THRESHOLD;

// Check if the address is an affiliate (has referred users)
const referredUserRows = await AffiliateReferredUsersTable.findByAffiliateAddress(address);
const isAffiliate = referredUserRows !== undefined ? referredUserRows.length > 0 : false;

// Get referral code (subaccount 0 username)
const subaccountRows = await SubaccountTable.findAll(
{
address,
subaccountNumber: 0,
},
[],
);
// No need to check subaccountRows.length > 1 as subaccountNumber is unique for an address
if (subaccountRows.length === 0) {
throw new UnexpectedServerError(`Subaccount 0 not found for address ${address}`);
}
const subaccountId = subaccountRows[0].id;

const usernameRows = await SubaccountUsernamesTable.findAll(
{
subaccountId: [subaccountId],
},
[],
);
// No need to check usernameRows.length > 1 as subAccountId is unique (foreign key constraint)
if (usernameRows.length === 0) {
throw new UnexpectedServerError(`Username not found for subaccount ${subaccountId}`);
}
const referralCode = usernameRows[0].username;

return {
referralCode: 'TempCode123',
isVolumeEligible: true,
isAffiliate: false,
referralCode,
isVolumeEligible,
isAffiliate,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM with suggestions!

The getMetadata method correctly retrieves the metadata for a given address. The logic for checking wallet existence, volume eligibility, and affiliate status is implemented correctly. The referral code is also retrieved correctly from the subaccount username. The error handling is appropriate and informative.

However, I have a few suggestions for improvement:

  1. Consider optimizing the database queries by using joins or subqueries to reduce the number of round trips to the database.
  2. Consider breaking down the method into smaller, reusable functions to improve modularity and readability.
  3. Consider extracting the database queries into a separate data access layer to improve separation of concerns.
  4. Consider performing input validation and sanitization on the address parameter to prevent potential SQL injection attacks.

}
const subaccountId = subaccountRows[0].id;

const usernameRows = await SubaccountUsernamesTable.findAll(
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of doing multiple awaits, can we do promise.All instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Im not too famliar with ts, but the output of these functions is processed (for errors) and then used in subsequent functions. Would this be suitable for promise.all? My understanding is that promise.all should not be used for such cases?

Copy link
Contributor

@affanv14 affanv14 Sep 13, 2024

Choose a reason for hiding this comment

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

Linked the wrong line
but cant we combine


const walletRow = await WalletTable.findById(address);
const referredUserRows = await AffiliateReferredUsersTable.findByAffiliateAddress(address);
    const subaccountRows = await SubaccountTable.findAll(
      {
        address,
        subaccountNumber: 0,
      },
      [],
    );

into one promise.all?
since all of these are independent and make db queries

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can do!

@@ -60,6 +60,9 @@ export const configSchema = {
// vaults table is added.
EXPERIMENT_VAULTS: parseString({ default: '' }),
EXPERIMENT_VAULT_MARKETS: parseString({ default: '' }),

// Affiliates config
VOLUME_ELIGIBILITY_THRESHOLD: parseInteger({ default: 10_000 }),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you verify volume is stored in dollars and not quote quantums?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe the quantum conversion is made before writing to fills db

    fill_amount = dydx_trim_scale(dydx_from_jsonlib_long(event_data->'fillAmount') *
                                  power(10, perpetual_market_record."atomicResolution")::numeric);
    maker_price = dydx_trim_scale(dydx_from_jsonlib_long(maker_order->'subticks') *
                                  power(10, perpetual_market_record."quantumConversionExponent" +
                                                     asset_record."atomicResolution" -
                                                     perpetual_market_record."atomicResolution")::numeric);

This screenshot also suggests it is converted

image

Comment on lines 75 to 85
if (usernameRows.length === 0) {
throw new UnexpectedServerError(`Username not found for subaccount ${subaccountId}`);
}
const referralCode = usernameRows[0].username;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we log this metric?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean logging to logs eg. in logging.info OR logging as metric?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe for a future PR... but I meant sending this up to dd

@jerryfan01234 jerryfan01234 force-pushed the affiliates-comlink-metadata branch from 21481ec to a495b25 Compare September 13, 2024 15:58
@jerryfan01234 jerryfan01234 force-pushed the affiliates-comlink-metadata branch from a495b25 to 7d36faa Compare September 13, 2024 15:59
Comment on lines 58 to 64
const subaccountRows = await SubaccountTable.findAll(
{
address,
subaccountNumber: 0,
},
[],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to add this too to the above promise.all statement?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 21481ec and 7d36faa.

Files selected for processing (2)
  • indexer/services/comlink/tests/controllers/api/v4/affiliates-controller.test.ts (1 hunks)
  • indexer/services/comlink/src/controllers/api/v4/affiliates-controller.ts (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • indexer/services/comlink/tests/controllers/api/v4/affiliates-controller.test.ts
Additional comments not posted (2)
indexer/services/comlink/src/controllers/api/v4/affiliates-controller.ts (2)

48-84: Error handling looks good!

The error handling in the getMetadata method is appropriate and informative. The method throws specific errors with clear error messages in the following scenarios:

  • NotFoundError if the wallet is not found.
  • UnexpectedServerError if subaccount 0 is not found for the address.
  • UnexpectedServerError if the username is not found for the subaccount.

This helps in identifying and debugging issues effectively.


87-91: LGTM!

The return statement in the getMetadata method correctly provides the required metadata information. The referralCode, isVolumeEligible, and isAffiliate values are dynamically retrieved based on the database queries and conditions, replacing the previous hardcoded placeholders. This enhances the functionality of the method and provides accurate metadata for the given address.

Comment on lines 43 to 64
const [walletRow, referredUserRows] = await Promise.all([
WalletTable.findById(address),
AffiliateReferredUsersTable.findByAffiliateAddress(address),
]);

// Check that the address exists
if (!walletRow) {
throw new NotFoundError(`Wallet with address ${address} not found`);
}

// Check if the address is an affiliate (has referred users)
const isVolumeEligible = Number(walletRow.totalVolume) >= config.VOLUME_ELIGIBILITY_THRESHOLD;
const isAffiliate = referredUserRows !== undefined ? referredUserRows.length > 0 : false;

// Get referral code (subaccount 0 username)
const subaccountRows = await SubaccountTable.findAll(
{
address,
subaccountNumber: 0,
},
[],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Optimize database queries by using Promise.all.

The getMetadata method currently performs multiple independent database queries sequentially. To improve performance, consider combining the following queries using Promise.all:

const walletRow = await WalletTable.findById(address);
const referredUserRows = await AffiliateReferredUsersTable.findByAffiliateAddress(address);
const subaccountRows = await SubaccountTable.findAll(
  {
    address,
    subaccountNumber: 0,
  },
  [],
);

By executing these queries in parallel using Promise.all, you can reduce the overall execution time of the method.

Apply this diff to combine the queries:

- const walletRow = await WalletTable.findById(address);
- const referredUserRows = await AffiliateReferredUsersTable.findByAffiliateAddress(address);
+ const [walletRow, referredUserRows, subaccountRows] = await Promise.all([
+   WalletTable.findById(address),
+   AffiliateReferredUsersTable.findByAffiliateAddress(address),
+   SubaccountTable.findAll(
+     {
+       address,
+       subaccountNumber: 0,
+     },
+     [],
+   ),
+ ]);
- const subaccountRows = await SubaccountTable.findAll(
-   {
-     address,
-     subaccountNumber: 0,
-   },
-   [],
- );

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

Successfully merging this pull request may close these issues.

2 participants