Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface ResponseMessage {
message: string
}
export const TX_PAGE_SIZE_LIMIT = 2000
export const ADDRESS_TX_PAGE_SIZE_LIMIT = 500
export const RESPONSE_MESSAGES = {
SUCCESSFULLY_SYNCED_200: { statusCode: 200, message: 'Successfully synced.' },
STARTED_SYNC_200: { statusCode: 200, message: 'Sync started.' },
Expand Down Expand Up @@ -89,6 +90,7 @@ export const RESPONSE_MESSAGES = {
COULD_NOT_DOWNLOAD_FILE_500: { statusCode: 500, message: 'Failed to download file.' },
INVALID_DATA_JSON_WITH_VARIABLES_400: (variables: string[]) => { return { statusCode: 400, message: `Data is not valid. Make sure that ${variables.join(', ')} are not inside quotes.` } },
PAGE_SIZE_LIMIT_EXCEEDED_400: { statusCode: 400, message: `Page size limit should be at most ${TX_PAGE_SIZE_LIMIT}.` },
ADDRESS_PAGE_SIZE_LIMIT_EXCEEDED_400: { statusCode: 400, message: `Page size limit should be at most ${ADDRESS_TX_PAGE_SIZE_LIMIT}.` },
PAGE_SIZE_AND_PAGE_SHOULD_BE_NUMBERS_400: { statusCode: 400, message: 'pageSize and page parameters should be valid integers.' },
INVALID_OUTPUT_SCRIPT_LENGTH_500: (l: number) => { return { statusCode: 500, message: `Invalid outputScript length ${l}` } },
FAILED_TO_PARSE_TX_OP_RETURN_500: { statusCode: 500, message: 'Failed to parse OP_RETURN data in Tx.' },
Expand Down
10 changes: 7 additions & 3 deletions pages/api/address/transactions/[address].ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextApiResponse, NextApiRequest } from 'next'
import { parseAddress } from 'utils/validators'
import { DEFAULT_TX_PAGE_SIZE, RESPONSE_MESSAGES, TX_PAGE_SIZE_LIMIT } from 'constants/index'
import { ADDRESS_TX_PAGE_SIZE_LIMIT, DEFAULT_TX_PAGE_SIZE, RESPONSE_MESSAGES } from 'constants/index'
import { fetchPaginatedAddressTransactions, getSimplifiedTransactions } from 'services/transactionService'
import { upsertAddress } from 'services/addressService'
import Cors from 'cors'
Expand All @@ -27,8 +27,8 @@ export default async (req: NextApiRequest, res: NextApiResponse): Promise<void>
if (isNaN(page) || isNaN(pageSize)) {
throw new Error(RESPONSE_MESSAGES.PAGE_SIZE_AND_PAGE_SHOULD_BE_NUMBERS_400.message)
}
if (pageSize > TX_PAGE_SIZE_LIMIT) {
throw new Error(RESPONSE_MESSAGES.PAGE_SIZE_LIMIT_EXCEEDED_400.message)
if (pageSize > ADDRESS_TX_PAGE_SIZE_LIMIT) {
throw new Error(RESPONSE_MESSAGES.ADDRESS_PAGE_SIZE_LIMIT_EXCEEDED_400.message)
}

const address = parseAddress(req.query.address as string)
Expand Down Expand Up @@ -62,6 +62,10 @@ export default async (req: NextApiRequest, res: NextApiResponse): Promise<void>
res.status(INVALID_ADDRESS_400.statusCode).json(INVALID_ADDRESS_400)
break
}
case RESPONSE_MESSAGES.ADDRESS_PAGE_SIZE_LIMIT_EXCEEDED_400.message:
res.status(RESPONSE_MESSAGES.ADDRESS_PAGE_SIZE_LIMIT_EXCEEDED_400.statusCode)
.json(RESPONSE_MESSAGES.ADDRESS_PAGE_SIZE_LIMIT_EXCEEDED_400)
break
case NO_ADDRESS_FOUND_404.message: {
res.status(NO_ADDRESS_FOUND_404.statusCode).json(NO_ADDRESS_FOUND_404)
break
Expand Down
18 changes: 13 additions & 5 deletions services/transactionService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import prisma from 'prisma-local/clientInstance'
import { Prisma, Transaction } from '@prisma/client'
import { RESPONSE_MESSAGES, USD_QUOTE_ID, CAD_QUOTE_ID, N_OF_QUOTES, UPSERT_TRANSACTION_PRICES_ON_DB_TIMEOUT, SupportedQuotesType, NETWORK_IDS, PRICES_CONNECTION_BATCH_SIZE, PRICES_CONNECTION_TIMEOUT, HUMAN_READABLE_DATE_FORMAT } from 'constants/index'
import { fetchAddressBySubstring, fetchAddressById, fetchAddressesByPaybuttonId, addressExists } from 'services/addressService'
import { fetchAddressBySubstring, fetchAddressById, fetchAddressesByPaybuttonId } from 'services/addressService'
import { AllPrices, QuoteValues, fetchPricesForNetworkAndTimestamp, flattenTimestamp } from 'services/priceService'
import _ from 'lodash'
import { CacheSet } from 'redis/index'
Expand Down Expand Up @@ -319,12 +319,20 @@ export async function * generateTransactionsWithPaybuttonsAndPricesForAddress (a
export async function fetchPaginatedAddressTransactions (addressString: string, page: number, pageSize: number, orderBy?: string, orderDesc = true): Promise<TransactionWithAddressAndPrices[]> {
const orderByQuery = transactionOrderBy(orderBy, orderDesc)
const parsedAddress = parseAddress(addressString)
await addressExists(parsedAddress, true)
const address = await prisma.address.findUnique({
where: {
address: parsedAddress
},
select: {
id: true
}
})
if (address === null) {
throw new Error(RESPONSE_MESSAGES.NO_ADDRESS_FOUND_404.message)
}
const txs = await prisma.transaction.findMany({
where: {
address: {
address: parsedAddress
}
addressId: address.id
},
include: includeAddressAndPrices,
orderBy: orderByQuery,
Expand Down
13 changes: 11 additions & 2 deletions tests/unittests/transactionService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,15 +498,24 @@ describe('address transaction orderBy blacklist', () => {
const address = `ecash:${exampleAddresses.ecash}`

beforeEach(() => {
prismaMock.address.findUniqueOrThrow.mockResolvedValue(mockedBCHAddress)
prisma.address.findUniqueOrThrow = prismaMock.address.findUniqueOrThrow
prismaMock.address.findUnique.mockResolvedValue({ id: mockedBCHAddress.id } as any)
prisma.address.findUnique = prismaMock.address.findUnique
prismaMock.transaction.findMany.mockResolvedValue([])
prisma.transaction.findMany = prismaMock.transaction.findMany
})

it('queries by addressId after resolving the address', async () => {
await transactionService.fetchPaginatedAddressTransactions(address, 0, 10, 'timestamp', true)
expect(prismaMock.transaction.findMany.mock.calls[0][0]).toEqual(expect.objectContaining({
where: { addressId: mockedBCHAddress.id },
orderBy: { timestamp: 'desc' }
}))
})

it('sorts by an allowed nested address field', async () => {
await transactionService.fetchPaginatedAddressTransactions(address, 0, 10, 'address.networkId', false)
expect(prismaMock.transaction.findMany.mock.calls[0][0]).toEqual(expect.objectContaining({
where: { addressId: mockedBCHAddress.id },
orderBy: { address: { networkId: 'asc' } }
}))
})
Expand Down
Loading