From 2f1d43132f98e41439fa7fae019f727250110357 Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Mon, 5 Aug 2024 20:50:48 +0200 Subject: [PATCH 1/2] feat: make graphql queries compatible with new Squid --- src/adapters/handlers/nfts.ts | 4 ++ src/adapters/handlers/prices.ts | 14 +++--- src/logic/http/params.ts | 5 +++ src/logic/nfts/collections.ts | 2 +- src/logic/prices/collections.ts | 76 +++++++++++++++++++++++---------- src/logic/prices/marketplace.ts | 44 +++++++++++++------ src/ports/nfts/types.ts | 4 +- src/ports/nfts/utils.ts | 61 +++++++++++++++----------- src/ports/prices/component.ts | 2 +- src/ports/sales/utils.ts | 8 +++- src/ports/stats/utils.ts | 6 ++- 11 files changed, 151 insertions(+), 75 deletions(-) diff --git a/src/adapters/handlers/nfts.ts b/src/adapters/handlers/nfts.ts index b67ae649..82778537 100644 --- a/src/adapters/handlers/nfts.ts +++ b/src/adapters/handlers/nfts.ts @@ -33,7 +33,11 @@ export function createNFTsHandler( const isLand = params.getBoolean('isLand') const isOnRent = params.getBoolean('isOnRent') const isWearableHead = params.getBoolean('isWearableHead') + ? params.getString('isWearableHead') === 'true' + : undefined const isWearableAccessory = params.getBoolean('isWearableAccessory') + ? params.getString('isWearableAccessory') === 'true' + : undefined const isWearableSmart = params.getBoolean('isWearableSmart') const wearableCategory = params.getValue( 'wearableCategory', diff --git a/src/adapters/handlers/prices.ts b/src/adapters/handlers/prices.ts index 821d3c1a..dc7af3a5 100644 --- a/src/adapters/handlers/prices.ts +++ b/src/adapters/handlers/prices.ts @@ -24,9 +24,9 @@ export function createPricesHandler( const params = new Params(context.url.searchParams) const category = params.getString('category') as PriceFilterCategory const assetType = params.getString('assetType') as AssetType - const isWearableHead = params.getBoolean('isWearableHead') - const isWearableAccessory = params.getBoolean('isWearableAccessory') - const isWearableSmart = params.getBoolean('isWearableSmart') + const isWearableHead = params.getBooleanValue('isWearableHead') + const isWearableAccessory = params.getBooleanValue('isWearableAccessory') + const isWearableSmart = params.getBooleanValue('isWearableSmart') const wearableCategory = params.getValue( 'wearableCategory', WearableCategory @@ -51,13 +51,13 @@ export function createPricesHandler( const itemRarities = params.getList('itemRarity', Rarity) const network = params.getValue('network', Network) - const adjacentToRoad = params.getBoolean('adjacentToRoad') + const adjacentToRoad = params.getBooleanValue('adjacentToRoad') const minDistanceToPlaza = params.getNumber('minDistanceToPlaza') const maxDistanceToPlaza = params.getNumber('maxDistanceToPlaza') const maxEstateSize = params.getNumber('maxEstateSize') const minEstateSize = params.getNumber('minEstateSize') - const emoteHasSound = params.getBoolean('emoteHasSound') - const emoteHasGeometry = params.getBoolean('emoteHasGeometry') + const emoteHasSound = params.getBooleanValue('emoteHasSound') + const emoteHasGeometry = params.getBooleanValue('emoteHasGeometry') return asJSON( async () => ({ @@ -81,7 +81,7 @@ export function createPricesHandler( maxEstateSize, minEstateSize, emoteHasGeometry, - emoteHasSound + emoteHasSound, }), }), { diff --git a/src/logic/http/params.ts b/src/logic/http/params.ts index abcf85ae..9867088f 100644 --- a/src/logic/http/params.ts +++ b/src/logic/http/params.ts @@ -37,6 +37,11 @@ export class Params { return value !== null } + getBooleanValue(key: string) { + const value = this.params.get(key) + return value !== null ? value === 'true' : undefined + } + getValue( key: string, values: Values = {}, diff --git a/src/logic/nfts/collections.ts b/src/logic/nfts/collections.ts index 8e48b50b..c814bdab 100644 --- a/src/logic/nfts/collections.ts +++ b/src/logic/nfts/collections.ts @@ -188,7 +188,7 @@ export function fromCollectionsFragment( break } default: { - throw new Error(`Uknown itemType=${fragment.itemType}`) + throw new Error(`Unknown itemType=${fragment.itemType}`) } } diff --git a/src/logic/prices/collections.ts b/src/logic/prices/collections.ts index 47e8d500..ff22a4b8 100644 --- a/src/logic/prices/collections.ts +++ b/src/logic/prices/collections.ts @@ -7,6 +7,18 @@ import { AssetType, PriceFilters } from '../../ports/prices/types' const MAX_RESULTS = 1000 +const PRICES_ITEMS_FILTERS_DICT: Record = { + wearableCategory: '$wearableCategory: String', + emoteCategory: '$emoteCategory: String', + isWearableHead: '$isWearableHead: Boolean', + isWearableAccessory: '$isWearableAccessory: Boolean', +} + +const PRICES_FILTERS_DICT: Record = { + expiresAt: '$expiresAt: BigInt', + ...PRICES_ITEMS_FILTERS_DICT, +} + export function collectionsShouldFetch(filters: PriceFilters) { const isCorrectNetworkFilter = !filters.network || !!(filters.network && filters.network === Network.MATIC) @@ -72,12 +84,17 @@ export function collectionsNFTsPricesQuery(filters: PriceFilters) { }, AssetType.NFT ) - return `query NFTPrices( - $expiresAt: String, - $wearableCategory: String - $emoteCategory: String - $isWearableHead: Boolean - $isWearableAccessory: Boolean) { + + const variablesBasedOnFilters = Object.entries(filters) + .filter(([key, value]) => value !== undefined && key in PRICES_FILTERS_DICT) + .map(([key]) => (PRICES_FILTERS_DICT[key] ? PRICES_FILTERS_DICT[key] : '')) + + const variablesDefinition = variablesBasedOnFilters.length + ? `query NFTPrices(${variablesBasedOnFilters.join('\n')})` + : `query NFTPrices` + + return ` + ${variablesDefinition} { prices: nfts ( first: ${MAX_RESULTS}, orderBy: id, @@ -103,12 +120,15 @@ export function collectionsNFTsPricesQueryById(filters: PriceFilters) { AssetType.NFT ) return `query NFTPrices( - $lastId: ID, - $expiresAt: String - $wearableCategory: String - $emoteCategory: String - $isWearableHead: Boolean - $isWearableAccessory: Boolean + $lastId: String, + ${Object.entries(filters) + .filter( + ([key, value]) => value !== undefined && key in PRICES_FILTERS_DICT + ) + .map(([key]) => + PRICES_FILTERS_DICT[key] ? PRICES_FILTERS_DICT[key] : '' + ) + .join('\n')} ) { prices: nfts( first: ${MAX_RESULTS}, @@ -141,12 +161,18 @@ export function collectionsItemsPricesQuery(filters: PriceFilters) { ? '[wearable_v1, wearable_v2, smart_wearable_v1]' : '[emote_v1]' - return `query ItemPrices( - $wearableCategory: String - $emoteCategory: String - $isWearableHead: Boolean - $isWearableAccessory: Boolean - ) { + const variablesBasedOnFilters = Object.entries(filters) + .filter( + ([key, value]) => value !== undefined && key in PRICES_ITEMS_FILTERS_DICT + ) + .map(([key]) => (PRICES_FILTERS_DICT[key] ? PRICES_FILTERS_DICT[key] : '')) + + const variablesDefinition = variablesBasedOnFilters.length + ? `query ItemPrices(${variablesBasedOnFilters.join('\n')})` + : `query ItemPrices` + + return ` + ${variablesDefinition} { prices: items ( first: ${MAX_RESULTS}, orderBy: id, @@ -179,11 +205,15 @@ export function collectionsItemsPricesQueryById(filters: PriceFilters) { : '[emote_v1]' return `query ItemPrices( - $lastId: ID, - $wearableCategory: String - $emoteCategory: String - $isWearableHead: Boolean - $isWearableAccessory: Boolean + $lastId: String, + ${Object.entries(filters) + .filter( + ([key, value]) => value !== undefined && key in PRICES_FILTERS_DICT + ) + .map(([key]) => + PRICES_FILTERS_DICT[key] ? PRICES_FILTERS_DICT[key] : '' + ) + .join('\n')} ) { prices: items ( first: ${MAX_RESULTS}, diff --git a/src/logic/prices/marketplace.ts b/src/logic/prices/marketplace.ts index e6205e10..e1a452d4 100644 --- a/src/logic/prices/marketplace.ts +++ b/src/logic/prices/marketplace.ts @@ -16,6 +16,14 @@ import { const MAX_RESULTS = 1000 +const PRICES_FILTERS_DICT: Record = { + expiresAt: '$expiresAt: BigInt', + wearableCategory: '$wearableCategory: String', + emoteCategory: '$emoteCategory: String', + isWearableHead: '$isWearableHead: Boolean', + isWearableAccessory: '$isWearableAccessory: Boolean', +} + export function marketplaceShouldFetch(filters: PriceFilters) { const isCorrectNetworkFilter = !filters.network || @@ -96,13 +104,18 @@ export function marketplacePricesQuery(filters: PriceFilters) { searchEstateSize_gt: 0, ${additionalWheres.join('\n')} ` - return `query NFTPrices( - $expiresAt: String - $expiresAtSec: String - $wearableCategory: String - $isWearableHead: Boolean - $isWearableAccessory: Boolean - ) { + + const variablesBasedOnFilters = Object.entries(filters) + .filter(([key, value]) => value !== undefined && key in PRICES_FILTERS_DICT) + .map(([key]) => (PRICES_FILTERS_DICT[key] ? PRICES_FILTERS_DICT[key] : '')) + + const variablesDefinition = variablesBasedOnFilters.length + ? `query NFTPrices($expiresAtSec: BigInt, ${variablesBasedOnFilters.join( + '\n' + )})` + : `query NFTPrices($expiresAtSec: BigInt, $expiresAt: BigInt)` + + return `${variablesDefinition} { prices: nfts( first: ${MAX_RESULTS}, orderBy: tokenId, @@ -130,13 +143,16 @@ export function marketplacePricesQueryById(filters: PriceFilters) { const { category, ...rest } = filters const additionalWheres: string[] = getExtraWheres(rest) const categories = getNFTCategoryFromPriceCategory(category) - return `query NFTPrices( - $lastId: ID, - $expiresAt: String - $wearableCategory: String - $isWearableHead: Boolean - $isWearableAccessory: Boolean - ) { + const variablesBasedOnFilters = Object.entries(filters) + .filter(([key, value]) => value !== undefined && key in PRICES_FILTERS_DICT) + .map(([key]) => (PRICES_FILTERS_DICT[key] ? PRICES_FILTERS_DICT[key] : '')) + + const variablesDefinition = variablesBasedOnFilters.length + ? `query NFTPrices($lastId: BigInt, ${variablesBasedOnFilters.join('\n')})` + : `query NFTPrices($lastId: BigInt)` + + return ` + ${variablesDefinition} { prices: nfts( orderBy: tokenId, orderDirection: asc, diff --git a/src/ports/nfts/types.ts b/src/ports/nfts/types.ts index 391418fc..aec37a2e 100644 --- a/src/ports/nfts/types.ts +++ b/src/ports/nfts/types.ts @@ -9,8 +9,8 @@ export type NFTResult = { export type QueryVariables = Omit & { orderBy: string orderDirection: 'asc' | 'desc' - expiresAt: string - expiresAtSec: string + expiresAt?: string + expiresAtSec?: string } export interface INFTsComponent { diff --git a/src/ports/nfts/utils.ts b/src/ports/nfts/utils.ts index 25ecabd2..50b067b4 100644 --- a/src/ports/nfts/utils.ts +++ b/src/ports/nfts/utils.ts @@ -5,18 +5,16 @@ import { QueryVariables } from './types' export const NFT_DEFAULT_SORT_BY = NFTSortBy.NEWEST -const NFTS_FILTERS = ` - $first: Int - $skip: Int - $orderBy: String - $orderDirection: String - $expiresAt: String - $expiresAtSec: String - $owner: String - $wearableCategory: String - $isWearableHead: Boolean - $isWearableAccessory: Boolean -` +const NFT_FILTERS_DICT: Record = { + orderBy: '$orderBy: NFT_orderBy', + orderDirection: '$orderDirection: OrderDirection', + expiresAt: '$expiresAt: BigInt', + expiresAtSec: '$expiresAtSec: BigInt', + owner: '$owner: String', + wearableCategory: '$wearableCategory: String', + isWearableHead: '$isWearableHead: Boolean', + isWearableAccessory: '$isWearableAccessory: Boolean', +} const getArguments = (total: number) => ` first: ${total} @@ -46,14 +44,21 @@ export function getQueryVariables( const { sortBy, ...variables } = options const orderBy = getOrderBy(sortBy) as string const orderDirection = getOrderDirection(sortBy) - const expiresAt = Date.now() - const expiresAtSec = Math.trunc(expiresAt / 1000) + if (options.isOnSale) { + const expiresAt = Date.now() + const expiresAtSec = Math.trunc(expiresAt / 1000) + return { + ...variables, + orderBy, + orderDirection, + expiresAt: expiresAt.toString(), + expiresAtSec: expiresAtSec.toString(), + } + } return { ...variables, orderBy, orderDirection, - expiresAt: expiresAt.toString(), - expiresAtSec: expiresAtSec.toString(), } } @@ -208,7 +213,7 @@ export function getFetchQuery( } if (filters.owner) { - where.push('owner: $owner') + where.push('owner_: {address: $owner}') } if ( @@ -302,7 +307,10 @@ export function getFetchQuery( } const query = `query NFTs( - ${NFTS_FILTERS} + ${Object.entries(filters) + .filter(([key, value]) => value !== undefined && key in NFT_FILTERS_DICT) + .map(([key]) => (NFT_FILTERS_DICT[key] ? NFT_FILTERS_DICT[key] : '')) + .join('\n')} ${getExtraVariables ? getExtraVariables(filters).join('\n') : ''} ) { nfts( @@ -312,10 +320,11 @@ export function getFetchQuery( } }` - return ` + const result = ` ${query} ${isCount ? '' : getNFTFragment()} ` + return result } export function getFetchOneQuery( @@ -323,7 +332,7 @@ export function getFetchOneQuery( getFragment: () => string ) { return ` - query NFTByTokenId($contractAddress: String, $tokenId: String) { + query NFTByTokenId($contractAddress: String, $tokenId: BigInt) { nfts( where: { contractAddress: $contractAddress, tokenId: $tokenId } first: 1 @@ -340,7 +349,7 @@ export function getByTokenIdQuery( getFragment: () => string ) { return ` - query NFTByTokenId($tokenIds: [String!]) { + query NFTByTokenId($tokenIds: [BigInt!]) { nfts( where: { id_in: $tokenIds } first: 1000 @@ -357,7 +366,11 @@ export function getId(contractAddress: string, tokenId: string) { } export function getFuzzySearchQueryForENS(schema: string, searchTerm: string) { - return SQL`SELECT id from ` - .append(schema) - .append(SQL`.ens_active WHERE subdomain % ${searchTerm}`) + const query = + SQL`SELECT subdomain, id, similarity(subdomain, ${searchTerm}) AS match_similarity from ` + .append(schema) + .append( + SQL`.ens_active WHERE subdomain % ${searchTerm} ORDER BY match_similarity DESC;` + ) + return query } diff --git a/src/ports/prices/component.ts b/src/ports/prices/component.ts index b44273ba..4d70c2db 100644 --- a/src/ports/prices/component.ts +++ b/src/ports/prices/component.ts @@ -41,7 +41,6 @@ export function createPricesComponent(options: { let lastId = '' let priceFragments: PriceFragment[] = [] while (true) { - const query = getPricesQuery(queryGetter, filters, lastId) const expiresAt = Date.now() const expiresAtSec = Math.trunc(expiresAt / 1000) const queryVariables = { @@ -50,6 +49,7 @@ export function createPricesComponent(options: { expiresAt: expiresAt.toString(), expiresAtSec: expiresAtSec.toString(), } + const query = getPricesQuery(queryGetter, queryVariables, lastId) const { prices: fragments } = await subgraph.query<{ prices: PriceFragment[] }>(query, queryVariables) diff --git a/src/ports/sales/utils.ts b/src/ports/sales/utils.ts index eb8e1974..e5bc0c1b 100644 --- a/src/ports/sales/utils.ts +++ b/src/ports/sales/utils.ts @@ -1,4 +1,4 @@ -import { ChainId, Sale, SaleFilters, SaleSortBy } from '@dcl/schemas' +import { ChainId, Network, Sale, SaleFilters, SaleSortBy } from '@dcl/schemas' import { AssetsNetworks } from '../../types' import { SaleFragment } from './types' @@ -10,7 +10,11 @@ export function fromSaleFragment( chainId: ChainId ): Sale { const sale: Sale = { - id: 'sale-' + network.toLowerCase() + '-' + fragment.id, + id: + // squid fragments will already have the network as part of the id + fragment.id.includes(Network.ETHEREUM) || fragment.id.includes('POLYGON') + ? `sale-${fragment.id.toLowerCase()}` + : 'sale-' + network.toLowerCase() + '-' + fragment.id, type: fragment.type, buyer: fragment.buyer, seller: fragment.seller, diff --git a/src/ports/stats/utils.ts b/src/ports/stats/utils.ts index b3691fc7..bfcf8619 100644 --- a/src/ports/stats/utils.ts +++ b/src/ports/stats/utils.ts @@ -43,8 +43,12 @@ export function getEstatesSizesQuery(filters: StatsEstateFilters) { ]` } + const variables = wrapWhere + ? `$lastId: String, $expiresAt: BigInt, $expiresAtSec: BigInt` + : `$lastId: String` + return ` - query EstateSizesQuery($lastId: ID, $expiresAt: String, $expiresAtSec: String) { + query EstateSizesQuery(${variables}) { nfts ( first: ${MAX_RESULTS}, where: { From 28788392ed686b5dc1d1b5ad29999fa5000f3103 Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Mon, 5 Aug 2024 20:57:20 +0200 Subject: [PATCH 2/2] feat: revert the getBooleanValue change --- src/adapters/handlers/prices.ts | 16 ++++++++++------ src/logic/http/params.ts | 5 ----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/adapters/handlers/prices.ts b/src/adapters/handlers/prices.ts index dc7af3a5..8d1a9498 100644 --- a/src/adapters/handlers/prices.ts +++ b/src/adapters/handlers/prices.ts @@ -24,9 +24,13 @@ export function createPricesHandler( const params = new Params(context.url.searchParams) const category = params.getString('category') as PriceFilterCategory const assetType = params.getString('assetType') as AssetType - const isWearableHead = params.getBooleanValue('isWearableHead') - const isWearableAccessory = params.getBooleanValue('isWearableAccessory') - const isWearableSmart = params.getBooleanValue('isWearableSmart') + const isWearableHead = params.getBoolean('isWearableHead') + ? params.getString('isWearableAccessory') === 'true' + : undefined + const isWearableAccessory = params.getBoolean('isWearableAccessory') + ? params.getString('isWearableAccessory') === 'true' + : undefined + const isWearableSmart = params.getBoolean('isWearableSmart') const wearableCategory = params.getValue( 'wearableCategory', WearableCategory @@ -51,13 +55,13 @@ export function createPricesHandler( const itemRarities = params.getList('itemRarity', Rarity) const network = params.getValue('network', Network) - const adjacentToRoad = params.getBooleanValue('adjacentToRoad') + const adjacentToRoad = params.getBoolean('adjacentToRoad') const minDistanceToPlaza = params.getNumber('minDistanceToPlaza') const maxDistanceToPlaza = params.getNumber('maxDistanceToPlaza') const maxEstateSize = params.getNumber('maxEstateSize') const minEstateSize = params.getNumber('minEstateSize') - const emoteHasSound = params.getBooleanValue('emoteHasSound') - const emoteHasGeometry = params.getBooleanValue('emoteHasGeometry') + const emoteHasSound = params.getBoolean('emoteHasSound') + const emoteHasGeometry = params.getBoolean('emoteHasGeometry') return asJSON( async () => ({ diff --git a/src/logic/http/params.ts b/src/logic/http/params.ts index 9867088f..abcf85ae 100644 --- a/src/logic/http/params.ts +++ b/src/logic/http/params.ts @@ -37,11 +37,6 @@ export class Params { return value !== null } - getBooleanValue(key: string) { - const value = this.params.get(key) - return value !== null ? value === 'true' : undefined - } - getValue( key: string, values: Values = {},