From 5da896951dee5668ceb6060d6617ef9293e2ffdb Mon Sep 17 00:00:00 2001 From: Mango Habanero Date: Mon, 27 Nov 2023 09:18:14 +0300 Subject: [PATCH] fix(user): Implements fixes to correctly rebuild user in cache. - Separates retrieval of graph and transaction data from cache. - Improves typing of interfaces to handle data changes in graph. - Appends personal information to graph data (previously unhandled). - Handle formatting of dates appropriately for reconstructed statements. Closes #105 --- src/lib/graph/user.ts | 54 ++++++++++++++++++++++++++++++---------- src/services/transfer.ts | 12 ++++----- src/services/user.ts | 12 ++++++++- 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/lib/graph/user.ts b/src/lib/graph/user.ts index 4a04dca..5620651 100644 --- a/src/lib/graph/user.ts +++ b/src/lib/graph/user.ts @@ -1,6 +1,7 @@ import { GraphQLClient } from 'graphql-request'; import { Redis as RedisClient } from 'ioredis'; import { UserService } from '@services/user'; +import {handleResults} from "@lib/ussd"; export enum Gender { MALE = 'MALE', FEMALE = 'FEMALE' } @@ -46,7 +47,7 @@ export interface GraphMarketplace { export interface GraphTransaction { sender_address: string; - date_block: number; + date_block: string; recipient_address: string; tx_hash: string; tx_value: number; @@ -207,14 +208,9 @@ export async function getGraphPersonalInformation(address: string, graphql: Grap return data.personal_information[0] } -export async function getFullGraphUserData( - address: string, - graphql: GraphQLClient, - interfaceIdentifier: string, - activated = true, - transactionsLimit = 9, - transactionSuccess = true){ - const query = `query retrieveFullUserGraphData($activated: Boolean!, $address: String!, $interfaceIdentifier: String!, $transactionSuccess: Boolean!, $transactionsLimit: Int!) { + +async function retrieveUserGraphData(graphql: GraphQLClient, interfaceIdentifier: string, activated = true) { + const query = `query retrieveUserGraphData($activated: Boolean!, $interfaceIdentifier: String!) { users(where: {interface_identifier: {_eq: $interfaceIdentifier}, activated: {_eq: $activated}, interface_type: {_eq: USSD}}) { id accounts(limit: 1){ @@ -225,6 +221,19 @@ export async function getFullGraphUserData( } personal_information{ ${personalInformationFields} } } + }` + + const variables = { + activated, + interfaceIdentifier + } + + return await graphql.request<{ users: GraphUser[] }>(query, variables) +} + + +async function retrieveUserGraphTransactions(graphql: GraphQLClient, address: string, transactionSuccess: boolean, transactionsLimit: number) { + const query = `query retrieveUserGraphTransactions($address: String!, $transactionSuccess: Boolean!, $transactionsLimit: Int!) { transactions(where: {_or: [{recipient_address: {_eq: $address}}, {sender_address: {_eq: $address}}], success: {_eq: $transactionSuccess}}, limit: $transactionsLimit, order_by: {date_block: desc}) { recipient_address sender_address @@ -234,16 +243,35 @@ export async function getFullGraphUserData( date_block tx_hash } - }`; + }` const variables = { - activated, address, - interfaceIdentifier, transactionSuccess, transactionsLimit } - return await graphql.request<{ users: GraphUser[], transactions: GraphTransaction[] }>(query, variables) + + return await graphql.request<{ transactions: GraphTransaction[] }>(query, variables) +} + + +export async function getFullGraphUserData( + address: string, + graphql: GraphQLClient, + interfaceIdentifier: string, + activated = true, + transactionsLimit = 9, + transactionSuccess = true){ + + const promises: Promise[] = [] + promises.push(retrieveUserGraphData(graphql, interfaceIdentifier, activated)) + promises.push(retrieveUserGraphTransactions(graphql, address, transactionSuccess, transactionsLimit)) + + const results = await handleResults(await Promise.allSettled(promises)) + return { + users: results[0].users, + transactions: results[1].transactions + } } export async function updateGraphUser(graphql: GraphQLClient, id: number, user: Partial): Promise> { diff --git a/src/services/transfer.ts b/src/services/transfer.ts index 6e51ccc..df85ced 100644 --- a/src/services/transfer.ts +++ b/src/services/transfer.ts @@ -1,9 +1,9 @@ -import { CachedVoucher, cashRounding, formatDate, getVoucherSymbol, handleResults } from '@lib/ussd'; +import { CachedVoucher, cashRounding, getVoucherSymbol, handleResults } from '@lib/ussd'; import { PostgresDb } from '@fastify/postgres'; import { Redis as RedisClient } from 'ioredis'; import { ethers } from 'ethers'; import { getPhoneNumberFromAddress } from '@services/account'; -import { getUserTag, User } from '@services/user'; +import {generateUserTag, User} from '@services/user'; import { GraphQLClient } from 'graphql-request'; import { GraphTransaction } from '@lib/graph/user'; import { tHelpers } from '@i18n/translators'; @@ -25,12 +25,12 @@ export interface Transaction { value: number; } -async function getTransferUserTag(address: string, db: PostgresDb, redis: RedisClient) { +async function getTransferUserTag(address: string, db: PostgresDb, graphql: GraphQLClient, redis: RedisClient) { const phoneNumber = await getPhoneNumberFromAddress(address, db, redis); if (!phoneNumber) { return null; } - return await getUserTag(phoneNumber, redis); + return await generateUserTag(address, graphql, phoneNumber); } export async function generateStatement( @@ -55,7 +55,7 @@ export async function generateStatement( const userTags = await Promise.allSettled( Array.from(addressSet).map(async (addr) => { - const userTag = await getTransferUserTag(addr, db, redis); + const userTag = await getTransferUserTag(addr, db, graphql, redis); return [addr, userTag]; }), ); @@ -84,7 +84,7 @@ export async function generateStatement( recipient, value: cashRounding(ethers.formatUnits(transaction.tx_value, 6)), symbol, - timestamp: await formatDate(transaction.date_block), + timestamp: transaction.date_block, transactionHash: transaction.tx_hash, type: transactionType, }; diff --git a/src/services/user.ts b/src/services/user.ts index 1e05619..7f06ccd 100644 --- a/src/services/user.ts +++ b/src/services/user.ts @@ -87,6 +87,10 @@ async function parseFullGraphUser(users: Partial[]) { graph.account.vpas = account.vpas } + if(user.personal_information) { + graph.personalInformation = user.personal_information + } + return graph } @@ -138,11 +142,17 @@ class ReconstructionService { let activeVoucher; if (heldVouchers.length > 0) { activeVoucher = heldVouchers.find((voucher) => voucher.address === this.account.active_voucher_address); + if (!activeVoucher) { + console.log(`Could not find active voucher ${this.account.active_voucher_address} in held vouchers, reconstructing active voucher.`) + activeVoucher = await this.reconstructActiveVoucher(); + heldVouchers = [activeVoucher, ...heldVouchers]; + } } else { activeVoucher = await this.reconstructActiveVoucher(); heldVouchers = [activeVoucher]; } + if (!activeVoucher) { throw new SystemError(`Could not reconstruct active voucher for ${this.account.address}`); } @@ -209,7 +219,7 @@ export class UserService { const symbolMap = await generateSymbolMap(graphql, this.redis, transactions); const [activeVoucher, heldVouchers] = await reconstructionService.reconstructVouchers(symbolMap); const statement = await reconstructionService.reconstructStatement(db, transactions); - const tag = await getUserTag(account.phone_number, this.redis); + const tag = await generateUserTag(account.address, graphql, account.phone_number); let user: DeepPartial = { account, graph, tag, vouchers: { active: activeVoucher, held: heldVouchers } }