From f4bb5927a5c3d88b4814ea37df35ce901278dfcb Mon Sep 17 00:00:00 2001 From: jerryfan01234 <44346807+jerryfan01234@users.noreply.github.com> Date: Sun, 1 Sep 2024 15:33:57 -0400 Subject: [PATCH] [OTE-750] Affiliates new affiliate info table (#2179) --- .../postgres/__tests__/helpers/constants.ts | 27 +++++- .../stores/affiliate-info-table.test.ts | 68 ++++++++++++++ .../affiliate-referred-users-table.test.ts | 8 +- ...1_create_affiliate_referred_users_table.ts | 2 +- ...40830165511_create_affiliate_info_table.ts | 18 ++++ .../postgres/src/helpers/db-helpers.ts | 1 + .../src/models/affiliate-info-model.ts | 76 +++++++++++++++ .../models/affiliate-referred-users-model.ts | 7 +- .../src/stores/affiliate-info-table.ts | 94 +++++++++++++++++++ .../src/types/affiliate-info-types.ts | 21 +++++ .../types/affiliate-referred-users-types.ts | 2 +- .../postgres/src/types/db-model-types.ts | 13 ++- indexer/packages/postgres/src/types/index.ts | 1 + .../postgres/src/types/query-types.ts | 4 + 14 files changed, 331 insertions(+), 11 deletions(-) create mode 100644 indexer/packages/postgres/__tests__/stores/affiliate-info-table.test.ts create mode 100644 indexer/packages/postgres/src/db/migrations/migration_files/20240830165511_create_affiliate_info_table.ts create mode 100644 indexer/packages/postgres/src/models/affiliate-info-model.ts create mode 100644 indexer/packages/postgres/src/stores/affiliate-info-table.ts create mode 100644 indexer/packages/postgres/src/types/affiliate-info-types.ts diff --git a/indexer/packages/postgres/__tests__/helpers/constants.ts b/indexer/packages/postgres/__tests__/helpers/constants.ts index 64d6c5d1fd..02ec739664 100644 --- a/indexer/packages/postgres/__tests__/helpers/constants.ts +++ b/indexer/packages/postgres/__tests__/helpers/constants.ts @@ -17,6 +17,7 @@ import * as TradingRewardAggregationTable from '../../src/stores/trading-reward- import * as TransactionTable from '../../src/stores/transaction-table'; import * as TransferTable from '../../src/stores/transfer-table'; import { + AffiliateInfoCreateObject, AffiliateReferredUsersCreateObject, AssetCreateObject, AssetPositionCreateObject, @@ -947,7 +948,7 @@ export const defaultLeaderboardPnlOneDayToUpsert: LeaderboardPnlCreateObject = { export const defaultAffiliateReferredUser: AffiliateReferredUsersCreateObject = { affiliateAddress: defaultAddress, refereeAddress: defaultAddress2, - referredAtBlock: 1, + referredAtBlock: '1', }; // ============== Persistent cache Data ============== @@ -961,3 +962,27 @@ export const defaultKV2: PersistentCacheCreateObject = { key: 'otherKey', value: 'otherValue', }; + +// ============== Affiliate Info Data ============== + +export const defaultAffiliateInfo: AffiliateInfoCreateObject = { + address: defaultAddress, + affiliateEarnings: '10.00', + referredMakerTrades: 10, + referredTakerTrades: 20, + totalReferredFees: '10.00', + totalReferredUsers: 5, + referredNetProtocolEarnings: '20.00', + firstReferralBlockHeight: '1', +}; + +export const defaultAffiliateInfo1: AffiliateInfoCreateObject = { + address: defaultAddress2, + affiliateEarnings: '11.00', + referredMakerTrades: 11, + referredTakerTrades: 21, + totalReferredFees: '11.00', + totalReferredUsers: 5, + referredNetProtocolEarnings: '21.00', + firstReferralBlockHeight: '11', +}; diff --git a/indexer/packages/postgres/__tests__/stores/affiliate-info-table.test.ts b/indexer/packages/postgres/__tests__/stores/affiliate-info-table.test.ts new file mode 100644 index 0000000000..7a8ac32bd7 --- /dev/null +++ b/indexer/packages/postgres/__tests__/stores/affiliate-info-table.test.ts @@ -0,0 +1,68 @@ +import { AffiliateInfoFromDatabase } from '../../src/types'; +import { clearData, migrate, teardown } from '../../src/helpers/db-helpers'; +import { defaultAffiliateInfo, defaultAffiliateInfo1 } from '../helpers/constants'; +import * as AffiliateInfoTable from '../../src/stores/affiliate-info-table'; + +describe('Affiliate info store', () => { + beforeAll(async () => { + await migrate(); + }); + + afterEach(async () => { + await clearData(); + }); + + afterAll(async () => { + await teardown(); + }); + + it('Successfully creates affiliate info', async () => { + await AffiliateInfoTable.create(defaultAffiliateInfo); + }); + + it('Cannot create duplicate info for duplicate address', async () => { + await AffiliateInfoTable.create(defaultAffiliateInfo); + await expect(AffiliateInfoTable.create(defaultAffiliateInfo)).rejects.toThrowError(); + }); + + it('Can upsert affiliate info multiple times', async () => { + await AffiliateInfoTable.upsert(defaultAffiliateInfo); + let info: AffiliateInfoFromDatabase | undefined = await AffiliateInfoTable.findById( + defaultAffiliateInfo.address, + ); + expect(info).toEqual(expect.objectContaining(defaultAffiliateInfo)); + + await AffiliateInfoTable.upsert(defaultAffiliateInfo1); + info = await AffiliateInfoTable.findById(defaultAffiliateInfo1.address); + expect(info).toEqual(expect.objectContaining(defaultAffiliateInfo1)); + }); + + it('Successfully finds all affiliate infos', async () => { + await Promise.all([ + AffiliateInfoTable.create(defaultAffiliateInfo), + AffiliateInfoTable.create(defaultAffiliateInfo1), + ]); + + const infos: AffiliateInfoFromDatabase[] = await AffiliateInfoTable.findAll( + {}, + [], + { readReplica: true }, + ); + + expect(infos.length).toEqual(2); + expect(infos).toEqual(expect.arrayContaining([ + expect.objectContaining(defaultAffiliateInfo), + expect.objectContaining(defaultAffiliateInfo1), + ])); + }); + + it('Successfully finds an affiliate info', async () => { + await AffiliateInfoTable.create(defaultAffiliateInfo); + + const info: AffiliateInfoFromDatabase | undefined = await AffiliateInfoTable.findById( + defaultAffiliateInfo.address, + ); + + expect(info).toEqual(expect.objectContaining(defaultAffiliateInfo)); + }); +}); diff --git a/indexer/packages/postgres/__tests__/stores/affiliate-referred-users-table.test.ts b/indexer/packages/postgres/__tests__/stores/affiliate-referred-users-table.test.ts index 0e5eb7e77c..367b26db68 100644 --- a/indexer/packages/postgres/__tests__/stores/affiliate-referred-users-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/affiliate-referred-users-table.test.ts @@ -73,12 +73,12 @@ describe('AffiliateReferredUsers store', () => { const entry1: AffiliateReferredUsersCreateObject = { affiliateAddress: 'affiliate_address1', refereeAddress: 'referee_address1', - referredAtBlock: 1, + referredAtBlock: '1', }; const entry2: AffiliateReferredUsersCreateObject = { affiliateAddress: 'affiliate_address1', refereeAddress: 'referee_address2', - referredAtBlock: 20, + referredAtBlock: '20', }; await AffiliateReferredUsersTable.create(entry1); @@ -103,12 +103,12 @@ describe('AffiliateReferredUsers store', () => { const entry1: AffiliateReferredUsersCreateObject = { affiliateAddress: 'affiliate_address1', refereeAddress: 'referee_address1', - referredAtBlock: 1, + referredAtBlock: '1', }; const entry2: AffiliateReferredUsersCreateObject = { affiliateAddress: 'affiliate_address1', refereeAddress: 'referee_address2', - referredAtBlock: 20, + referredAtBlock: '20', }; await AffiliateReferredUsersTable.create(entry1); diff --git a/indexer/packages/postgres/src/db/migrations/migration_files/20240830154741_create_affiliate_referred_users_table.ts b/indexer/packages/postgres/src/db/migrations/migration_files/20240830154741_create_affiliate_referred_users_table.ts index fe65bd9207..a438766d46 100644 --- a/indexer/packages/postgres/src/db/migrations/migration_files/20240830154741_create_affiliate_referred_users_table.ts +++ b/indexer/packages/postgres/src/db/migrations/migration_files/20240830154741_create_affiliate_referred_users_table.ts @@ -4,7 +4,7 @@ export async function up(knex: Knex): Promise { return knex.schema.createTable('affiliate_referred_users', (table) => { table.string('refereeAddress').primary().notNullable(); table.string('affiliateAddress').notNullable(); - table.integer('referredAtBlock').notNullable(); + table.bigInteger('referredAtBlock').notNullable(); // Index on affiliateAddress for faster queries table.index(['affiliateAddress']); diff --git a/indexer/packages/postgres/src/db/migrations/migration_files/20240830165511_create_affiliate_info_table.ts b/indexer/packages/postgres/src/db/migrations/migration_files/20240830165511_create_affiliate_info_table.ts new file mode 100644 index 0000000000..f409fa6961 --- /dev/null +++ b/indexer/packages/postgres/src/db/migrations/migration_files/20240830165511_create_affiliate_info_table.ts @@ -0,0 +1,18 @@ +import * as Knex from 'knex'; + +export async function up(knex: Knex): Promise { + return knex.schema.createTable('affiliate_info', (table) => { + table.string('address').primary().notNullable(); + table.decimal('affiliateEarnings').notNullable(); + table.integer('referredMakerTrades').notNullable(); + table.integer('referredTakerTrades').notNullable(); + table.decimal('totalReferredFees').notNullable(); + table.integer('totalReferredUsers').notNullable(); + table.decimal('referredNetProtocolEarnings').notNullable(); + table.bigInteger('firstReferralBlockHeight').notNullable(); + }); +} + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('affiliate_info'); +} diff --git a/indexer/packages/postgres/src/helpers/db-helpers.ts b/indexer/packages/postgres/src/helpers/db-helpers.ts index e50fa96bb2..c1a46abab2 100644 --- a/indexer/packages/postgres/src/helpers/db-helpers.ts +++ b/indexer/packages/postgres/src/helpers/db-helpers.ts @@ -30,6 +30,7 @@ const layer1Tables = [ 'compliance_status', 'affiliate_referred_users', 'persistent_cache', + 'affiliate_info', ]; /** diff --git a/indexer/packages/postgres/src/models/affiliate-info-model.ts b/indexer/packages/postgres/src/models/affiliate-info-model.ts new file mode 100644 index 0000000000..1fe37b2b61 --- /dev/null +++ b/indexer/packages/postgres/src/models/affiliate-info-model.ts @@ -0,0 +1,76 @@ +import { NonNegativeNumericPattern } from '../lib/validators'; +import UpsertQueryBuilder from '../query-builders/upsert'; +import BaseModel from './base-model'; + +export default class AffiliateInfoModel extends BaseModel { + static get tableName() { + return 'affiliate_info'; + } + + static get idColumn() { + return 'address'; + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'address', + 'affiliateEarnings', + 'referredMakerTrades', + 'referredTakerTrades', + 'totalReferredFees', + 'totalReferredUsers', + 'referredNetProtocolEarnings', + 'firstReferralBlockHeight', + ], + properties: { + address: { type: 'string' }, + affiliateEarnings: { type: 'string', pattern: NonNegativeNumericPattern }, + referredMakerTrades: { type: 'int' }, + referredTakerTrades: { type: 'int' }, + totalReferredFees: { type: 'string', pattern: NonNegativeNumericPattern }, + totalReferredUsers: { type: 'int' }, + referredNetProtocolEarnings: { type: 'string', pattern: NonNegativeNumericPattern }, + firstReferralBlockHeight: { type: 'string', pattern: NonNegativeNumericPattern }, + }, + }; + } + + /** + * A mapping from column name to JSON conversion expected. + * See getSqlConversionForDydxModelTypes for valid conversions. + * + * TODO(IND-239): Ensure that jsonSchema() / sqlToJsonConversions() / model fields match. + */ + static get sqlToJsonConversions() { + return { + address: 'string', + affiliateEarnings: 'string', + referredMakerTrades: 'int', + referredTakerTrades: 'int', + totalReferredFees: 'string', + totalReferredUsers: 'int', + referredNetProtocolEarnings: 'string', + firstReferralBlockHeight: 'string', + }; + } + + QueryBuilderType!: UpsertQueryBuilder; + + address!: string; + + affiliateEarnings!: string; + + referredMakerTrades!: number; + + referredTakerTrades!: number; + + totalReferredFees!: string; + + totalReferredUsers!: number; + + referredNetProtocolEarnings!: string; + + firstReferralBlockHeight!: string; +} diff --git a/indexer/packages/postgres/src/models/affiliate-referred-users-model.ts b/indexer/packages/postgres/src/models/affiliate-referred-users-model.ts index 1d67d48700..559e499ece 100644 --- a/indexer/packages/postgres/src/models/affiliate-referred-users-model.ts +++ b/indexer/packages/postgres/src/models/affiliate-referred-users-model.ts @@ -1,3 +1,4 @@ +import { NonNegativeNumericPattern } from '../lib/validators'; import BaseModel from './base-model'; export default class AffiliateReferredUsersModel extends BaseModel { @@ -20,7 +21,7 @@ export default class AffiliateReferredUsersModel extends BaseModel { properties: { affiliateAddress: { type: 'string' }, refereeAddress: { type: 'string' }, - referredAtBlock: { type: 'integer' }, + referredAtBlock: { type: 'string', pattern: NonNegativeNumericPattern }, }, }; } @@ -35,7 +36,7 @@ export default class AffiliateReferredUsersModel extends BaseModel { return { affiliateAddress: 'string', refereeAddress: 'string', - referredAtBlock: 'integer', + referredAtBlock: 'string', }; } @@ -43,5 +44,5 @@ export default class AffiliateReferredUsersModel extends BaseModel { refereeAddress!: string; - referredAtBlock!: number; + referredAtBlock!: string; } diff --git a/indexer/packages/postgres/src/stores/affiliate-info-table.ts b/indexer/packages/postgres/src/stores/affiliate-info-table.ts new file mode 100644 index 0000000000..3f6695592a --- /dev/null +++ b/indexer/packages/postgres/src/stores/affiliate-info-table.ts @@ -0,0 +1,94 @@ +import { QueryBuilder } from 'objection'; + +import { DEFAULT_POSTGRES_OPTIONS } from '../constants'; +import { setupBaseQuery, verifyAllRequiredFields } from '../helpers/stores-helpers'; +import Transaction from '../helpers/transaction'; +import AffiliateInfoModel from '../models/affiliate-info-model'; +import { + Options, + Ordering, + QueryableField, + QueryConfig, + AffiliateInfoColumns, + AffiliateInfoCreateObject, + AffiliateInfoFromDatabase, + AffiliateInfoQueryConfig, +} from '../types'; + +export async function findAll( + { + address, + limit, + }: AffiliateInfoQueryConfig, + requiredFields: QueryableField[], + options: Options = DEFAULT_POSTGRES_OPTIONS, +): Promise { + verifyAllRequiredFields( + { + address, + limit, + } as QueryConfig, + requiredFields, + ); + + let baseQuery: QueryBuilder = setupBaseQuery( + AffiliateInfoModel, + options, + ); + + if (address) { + baseQuery = baseQuery.where(AffiliateInfoColumns.address, address); + } + + if (options.orderBy !== undefined) { + for (const [column, order] of options.orderBy) { + baseQuery = baseQuery.orderBy( + column, + order, + ); + } + } else { + baseQuery = baseQuery.orderBy( + AffiliateInfoColumns.address, + Ordering.ASC, + ); + } + + if (limit) { + baseQuery = baseQuery.limit(limit); + } + + return baseQuery.returning('*'); +} + +export async function create( + AffiliateInfoToCreate: AffiliateInfoCreateObject, + options: Options = { txId: undefined }, +): Promise { + return AffiliateInfoModel.query( + Transaction.get(options.txId), + ).insert(AffiliateInfoToCreate).returning('*'); +} + +export async function upsert( + AffiliateInfoToUpsert: AffiliateInfoCreateObject, + options: Options = { txId: undefined }, +): Promise { + const AffiliateInfos: AffiliateInfoModel[] = await AffiliateInfoModel.query( + Transaction.get(options.txId), + ).upsert(AffiliateInfoToUpsert).returning('*'); + // should only ever be one AffiliateInfo + return AffiliateInfos[0]; +} +export async function findById( + address: string, + options: Options = DEFAULT_POSTGRES_OPTIONS, +): Promise { + const baseQuery: QueryBuilder = setupBaseQuery( + AffiliateInfoModel, + options, + ); + return baseQuery + .findById(address) + .returning('*'); +} diff --git a/indexer/packages/postgres/src/types/affiliate-info-types.ts b/indexer/packages/postgres/src/types/affiliate-info-types.ts new file mode 100644 index 0000000000..4c7f11d108 --- /dev/null +++ b/indexer/packages/postgres/src/types/affiliate-info-types.ts @@ -0,0 +1,21 @@ +export interface AffiliateInfoCreateObject { + address: string, + affiliateEarnings: string, + referredMakerTrades: number, + referredTakerTrades: number, + totalReferredFees: string, + totalReferredUsers: number, + referredNetProtocolEarnings: string, + firstReferralBlockHeight: string, +} + +export enum AffiliateInfoColumns { + address = 'address', + affiliateEarnings = 'affiliateEarnings', + referredMakerTrades = 'referredMakerTrades', + referredTakerTrades = 'referredTakerTrades', + totalReferredFees = 'totalReferredFees', + totalReferredUsers = 'totalReferredUsers', + referredNetProtocolEarnings = 'referredNetProtocolEarnings', + firstReferralBlockHeight = 'firstReferralBlockHeight', +} diff --git a/indexer/packages/postgres/src/types/affiliate-referred-users-types.ts b/indexer/packages/postgres/src/types/affiliate-referred-users-types.ts index ff3fb22a64..4ba76de49e 100644 --- a/indexer/packages/postgres/src/types/affiliate-referred-users-types.ts +++ b/indexer/packages/postgres/src/types/affiliate-referred-users-types.ts @@ -1,7 +1,7 @@ export interface AffiliateReferredUsersCreateObject { affiliateAddress: string, refereeAddress: string, - referredAtBlock: number, + referredAtBlock: string, } export enum AffiliateReferredUsersColumns { diff --git a/indexer/packages/postgres/src/types/db-model-types.ts b/indexer/packages/postgres/src/types/db-model-types.ts index cd61795d3f..a81ee86d99 100644 --- a/indexer/packages/postgres/src/types/db-model-types.ts +++ b/indexer/packages/postgres/src/types/db-model-types.ts @@ -278,10 +278,21 @@ export interface PersistentCacheFromDatabase { value: string, } +export interface AffiliateInfoFromDatabase { + address: string, + affiliateEarnings: string, + referredMakerTrades: number, + referredTakerTrades: number, + totalReferredFees: string, + totalReferredUsers: number, + referredNetProtocolEarnings: string, + firstReferralBlockHeight: string, +} + export interface AffiliateReferredUserFromDatabase { affiliateAddress: string, refereeAddress: string, - referredAtBlock: number, + referredAtBlock: string, } export type SubaccountAssetNetTransferMap = { [subaccountId: string]: diff --git a/indexer/packages/postgres/src/types/index.ts b/indexer/packages/postgres/src/types/index.ts index 79aa6a2174..8c81f46fff 100644 --- a/indexer/packages/postgres/src/types/index.ts +++ b/indexer/packages/postgres/src/types/index.ts @@ -30,4 +30,5 @@ export * from './subaccount-usernames-types'; export * from './leaderboard-pnl-types'; export * from './affiliate-referred-users-types'; export * from './persistent-cache-types'; +export * from './affiliate-info-types'; export { PositionSide } from './position-types'; diff --git a/indexer/packages/postgres/src/types/query-types.ts b/indexer/packages/postgres/src/types/query-types.ts index bb301e258b..f3435ff0b9 100644 --- a/indexer/packages/postgres/src/types/query-types.ts +++ b/indexer/packages/postgres/src/types/query-types.ts @@ -335,3 +335,7 @@ export interface LeaderboardPnlQueryConfig extends QueryConfig { export interface PersistentCacheQueryConfig extends QueryConfig { [QueryableField.KEY]?: string, } + +export interface AffiliateInfoQueryConfig extends QueryConfig { + [QueryableField.ADDRESS]?: string, +}