-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[OTE-751] create affiliate referred users table (#2177)
- Loading branch information
1 parent
1b65466
commit ffde811
Showing
11 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
indexer/packages/postgres/__tests__/stores/affiliate-referred-users-table.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { AffiliateReferredUserFromDatabase, AffiliateReferredUsersCreateObject } from '../../src/types'; | ||
import { clearData, migrate, teardown } from '../../src/helpers/db-helpers'; | ||
import { defaultAffiliateReferredUser } from '../helpers/constants'; | ||
import * as AffiliateReferredUsersTable from '../../src/stores/affiliate-referred-users-table'; | ||
|
||
describe('AffiliateReferredUsers store', () => { | ||
beforeAll(async () => { | ||
await migrate(); | ||
}); | ||
|
||
afterEach(async () => { | ||
await clearData(); | ||
}); | ||
|
||
afterAll(async () => { | ||
await teardown(); | ||
}); | ||
|
||
it('Successfully creates affiliate referee pairs', async () => { | ||
await AffiliateReferredUsersTable.create(defaultAffiliateReferredUser); | ||
await AffiliateReferredUsersTable.create({ | ||
...defaultAffiliateReferredUser, | ||
refereeAddress: 'fake_address', | ||
}); | ||
}); | ||
|
||
it('Should not allow duplicate refree address', async () => { | ||
await AffiliateReferredUsersTable.create(defaultAffiliateReferredUser); | ||
|
||
// Second creation should fail due to the duplicate refereeAddress | ||
await expect( | ||
AffiliateReferredUsersTable.create({ | ||
...defaultAffiliateReferredUser, | ||
affiliateAddress: 'another_affiliate_address', | ||
}), | ||
).rejects.toThrow(); | ||
}); | ||
|
||
it('Successfully finds all entries', async () => { | ||
const entry1: AffiliateReferredUsersCreateObject = { | ||
...defaultAffiliateReferredUser, | ||
refereeAddress: 'referee_address1', | ||
}; | ||
const entry2: AffiliateReferredUsersCreateObject = { | ||
...defaultAffiliateReferredUser, | ||
affiliateAddress: 'affiliate_address1', | ||
refereeAddress: 'referee_address2', | ||
}; | ||
|
||
await Promise.all([ | ||
AffiliateReferredUsersTable.create(defaultAffiliateReferredUser), | ||
AffiliateReferredUsersTable.create(entry1), | ||
AffiliateReferredUsersTable.create(entry2), | ||
]); | ||
|
||
const entries: AffiliateReferredUserFromDatabase[] = await AffiliateReferredUsersTable.findAll( | ||
{}, | ||
[], | ||
{ readReplica: true }, | ||
); | ||
|
||
expect(entries.length).toEqual(3); | ||
expect(entries).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining(defaultAffiliateReferredUser), | ||
expect.objectContaining(entry1), | ||
expect.objectContaining(entry2), | ||
]), | ||
); | ||
}); | ||
|
||
it('Successfully finds entries by affiliate address', async () => { | ||
const entry1: AffiliateReferredUsersCreateObject = { | ||
affiliateAddress: 'affiliate_address1', | ||
refereeAddress: 'referee_address1', | ||
referredAtBlock: 1, | ||
}; | ||
const entry2: AffiliateReferredUsersCreateObject = { | ||
affiliateAddress: 'affiliate_address1', | ||
refereeAddress: 'referee_address2', | ||
referredAtBlock: 20, | ||
}; | ||
|
||
await AffiliateReferredUsersTable.create(entry1); | ||
await AffiliateReferredUsersTable.create(entry2); | ||
|
||
const entries: AffiliateReferredUserFromDatabase[] | undefined = await AffiliateReferredUsersTable.findByAffiliateAddress('affiliate_address1'); | ||
|
||
if (entries) { | ||
expect(entries.length).toEqual(2); | ||
expect(entries).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining(entry1), | ||
expect.objectContaining(entry2), | ||
]), | ||
); | ||
} else { | ||
throw new Error('findByAffiliateAddress returned undefined, expected an array'); | ||
} | ||
}); | ||
|
||
it('Successfully finds entry by referee address', async () => { | ||
const entry1: AffiliateReferredUsersCreateObject = { | ||
affiliateAddress: 'affiliate_address1', | ||
refereeAddress: 'referee_address1', | ||
referredAtBlock: 1, | ||
}; | ||
const entry2: AffiliateReferredUsersCreateObject = { | ||
affiliateAddress: 'affiliate_address1', | ||
refereeAddress: 'referee_address2', | ||
referredAtBlock: 20, | ||
}; | ||
|
||
await AffiliateReferredUsersTable.create(entry1); | ||
await AffiliateReferredUsersTable.create(entry2); | ||
|
||
const entry: AffiliateReferredUserFromDatabase | undefined = await AffiliateReferredUsersTable.findByRefereeAddress('referee_address1'); | ||
|
||
expect(entry).toEqual(expect.objectContaining(entry1)); | ||
}); | ||
}); |
16 changes: 16 additions & 0 deletions
16
...src/db/migrations/migration_files/20240830154741_create_affiliate_referred_users_table.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import * as Knex from 'knex'; | ||
|
||
export async function up(knex: Knex): Promise<void> { | ||
return knex.schema.createTable('affiliate_referred_users', (table) => { | ||
table.string('refereeAddress').primary().notNullable(); | ||
table.string('affiliateAddress').notNullable(); | ||
table.integer('referredAtBlock').notNullable(); | ||
|
||
// Index on affiliateAddress for faster queries | ||
table.index(['affiliateAddress']); | ||
}); | ||
} | ||
|
||
export async function down(knex: Knex): Promise<void> { | ||
return knex.schema.dropTable('affiliate_referred_users'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
indexer/packages/postgres/src/models/affiliate-referred-users-model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import BaseModel from './base-model'; | ||
|
||
export default class AffiliateReferredUsersModel extends BaseModel { | ||
static get tableName() { | ||
return 'affiliate_referred_users'; | ||
} | ||
|
||
static get idColumn() { | ||
return 'refereeAddress'; | ||
} | ||
|
||
static get jsonSchema() { | ||
return { | ||
type: 'object', | ||
required: [ | ||
'affiliateAddress', | ||
'refereeAddress', | ||
'referredAtBlock', | ||
], | ||
properties: { | ||
affiliateAddress: { type: 'string' }, | ||
refereeAddress: { type: 'string' }, | ||
referredAtBlock: { type: 'integer' }, | ||
}, | ||
}; | ||
} | ||
|
||
/** | ||
* 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 { | ||
affiliateAddress: 'string', | ||
refereeAddress: 'string', | ||
referredAtBlock: 'integer', | ||
}; | ||
} | ||
|
||
affiliateAddress!: string; | ||
|
||
refereeAddress!: string; | ||
|
||
referredAtBlock!: number; | ||
} |
110 changes: 110 additions & 0 deletions
110
indexer/packages/postgres/src/stores/affiliate-referred-users-table.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { QueryBuilder } from 'objection'; | ||
|
||
import { DEFAULT_POSTGRES_OPTIONS } from '../constants'; | ||
import { setupBaseQuery, verifyAllRequiredFields } from '../helpers/stores-helpers'; | ||
import Transaction from '../helpers/transaction'; | ||
import AffiliateReferredUsersModel from '../models/affiliate-referred-users-model'; | ||
import { | ||
Options, | ||
Ordering, | ||
QueryableField, | ||
QueryConfig, | ||
AffiliateReferredUsersColumns, | ||
AffiliateReferredUsersCreateObject, | ||
AffiliateReferredUserFromDatabase, | ||
AffiliateReferredUsersQueryConfig, | ||
} from '../types'; | ||
|
||
export async function findAll( | ||
{ | ||
affiliateAddress, | ||
refereeAddress, | ||
limit, | ||
}: AffiliateReferredUsersQueryConfig, | ||
requiredFields: QueryableField[], | ||
options: Options = DEFAULT_POSTGRES_OPTIONS, | ||
): Promise<AffiliateReferredUserFromDatabase[]> { | ||
verifyAllRequiredFields( | ||
{ | ||
affiliateAddress, | ||
refereeAddress, | ||
limit, | ||
} as QueryConfig, | ||
requiredFields, | ||
); | ||
|
||
// splitting the line after = does not work because it is reformatted to one line by eslint | ||
// eslint-disable-next-line max-len | ||
let baseQuery: QueryBuilder<AffiliateReferredUsersModel> = setupBaseQuery<AffiliateReferredUsersModel>( | ||
AffiliateReferredUsersModel, | ||
options, | ||
); | ||
|
||
if (affiliateAddress) { | ||
baseQuery = baseQuery.where(AffiliateReferredUsersColumns.affiliateAddress, affiliateAddress); | ||
} | ||
|
||
if (refereeAddress) { | ||
baseQuery = baseQuery.where(AffiliateReferredUsersColumns.refereeAddress, refereeAddress); | ||
} | ||
|
||
if (options.orderBy !== undefined) { | ||
for (const [column, order] of options.orderBy) { | ||
baseQuery = baseQuery.orderBy( | ||
column, | ||
order, | ||
); | ||
} | ||
} else { | ||
baseQuery = baseQuery.orderBy( | ||
AffiliateReferredUsersColumns.referredAtBlock, | ||
Ordering.ASC, | ||
); | ||
} | ||
|
||
if (limit) { | ||
baseQuery = baseQuery.limit(limit); | ||
} | ||
|
||
return baseQuery.returning('*'); | ||
} | ||
|
||
export async function create( | ||
entryToCreate: AffiliateReferredUsersCreateObject, | ||
options: Options = { txId: undefined }, | ||
): Promise<AffiliateReferredUserFromDatabase> { | ||
return AffiliateReferredUsersModel.query( | ||
Transaction.get(options.txId), | ||
).insert(entryToCreate).returning('*'); | ||
} | ||
|
||
export async function findByAffiliateAddress( | ||
address: string, | ||
options: Options = DEFAULT_POSTGRES_OPTIONS, | ||
): Promise<AffiliateReferredUserFromDatabase[] | undefined> { | ||
// splitting the line after = does not work because it is reformatted to one line by eslint | ||
// eslint-disable-next-line max-len | ||
const baseQuery: QueryBuilder<AffiliateReferredUsersModel> = setupBaseQuery<AffiliateReferredUsersModel>( | ||
AffiliateReferredUsersModel, | ||
options, | ||
); | ||
return baseQuery | ||
.where('affiliateAddress', address) | ||
.returning('*'); | ||
} | ||
|
||
export async function findByRefereeAddress( | ||
address: string, | ||
options: Options = DEFAULT_POSTGRES_OPTIONS, | ||
): Promise<AffiliateReferredUserFromDatabase | undefined> { | ||
// splitting the line after = does not work because it is reformatted to one line by eslint | ||
// eslint-disable-next-line max-len | ||
const baseQuery: QueryBuilder<AffiliateReferredUsersModel> = setupBaseQuery<AffiliateReferredUsersModel>( | ||
AffiliateReferredUsersModel, | ||
options, | ||
); | ||
return baseQuery | ||
.where('refereeAddress', address) | ||
.returning('*') | ||
.first(); // should only be one since refereeAddress is primary key | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
indexer/packages/postgres/src/types/affiliate-referred-users-types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export interface AffiliateReferredUsersCreateObject { | ||
affiliateAddress: string, | ||
refereeAddress: string, | ||
referredAtBlock: number, | ||
} | ||
|
||
export enum AffiliateReferredUsersColumns { | ||
affiliateAddress = 'affiliateAddress', | ||
refereeAddress = 'refereeAddress', | ||
referredAtBlock = 'referredAtBlock', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters