From 136f466358d7f2ab0feda30196fa33de53d68f20 Mon Sep 17 00:00:00 2001 From: LautaroPetaccio Date: Fri, 23 Aug 2024 15:31:23 -0300 Subject: [PATCH] feat: Add is mapping complete property to TP items --- spec/mocks/items.ts | 44 +++++++++++++++++--------- src/Item/Item.router.spec.ts | 60 +++++++++++++++++------------------- src/Item/Item.service.ts | 6 ++-- src/Item/Item.types.ts | 1 + src/ethereum/api/Bridge.ts | 34 ++++++++++---------- 5 files changed, 82 insertions(+), 63 deletions(-) diff --git a/spec/mocks/items.ts b/spec/mocks/items.ts index c543fb6c..cb094c72 100644 --- a/spec/mocks/items.ts +++ b/spec/mocks/items.ts @@ -1,5 +1,11 @@ import { constants } from 'ethers' -import { Rarity, Wearable, BodyShape, WearableCategory } from '@dcl/schemas' +import { + Rarity, + Wearable, + BodyShape, + WearableCategory, + ThirdPartyProps, +} from '@dcl/schemas' import { v4 as uuidv4 } from 'uuid' import { ItemFragment, @@ -16,7 +22,7 @@ import { import { toUnixTimestamp } from '../../src/utils/parse' import { buildTPItemURN } from '../../src/Item/utils' import { CollectionAttributes } from '../../src/Collection' -import { isTPCollection } from '../../src/utils/urn' +import { decodeThirdPartyItemURN, isTPCollection } from '../../src/utils/urn' import { CatalystItem } from '../../src/ethereum/api/peer' import { dbCollectionMock, dbTPCollectionMock } from './collections' @@ -73,11 +79,25 @@ export function asResultItem(item: ItemAttributes): ResultItem { export function toResultTPItem( itemAttributes: ItemAttributes, - dbCollection?: CollectionAttributes, - catalystItem?: Wearable + dbCollection: CollectionAttributes, + catalystItem?: Wearable & Partial ): ResultItem { - const hasURN = - itemAttributes.urn_suffix && dbCollection && isTPCollection(dbCollection) + if ( + !dbCollection.third_party_id || + !dbCollection.urn_suffix || + !itemAttributes.urn_suffix + ) { + throw new Error('Collection is not a third party collection') + } + // const hasURN = + // itemAttributes.urn_suffix && dbCollection && isTPCollection(dbCollection) + const urn: string = catalystItem + ? catalystItem.id + : buildTPItemURN( + dbCollection.third_party_id, + dbCollection.urn_suffix, + itemAttributes.urn_suffix + ) const resultItem = { ...itemAttributes, @@ -86,17 +106,12 @@ export function toResultTPItem( is_approved: !!catalystItem, in_catalyst: !!catalystItem, is_published: !!catalystItem, - urn: hasURN - ? buildTPItemURN( - dbCollection!.third_party_id!, - dbCollection!.urn_suffix!, - itemAttributes!.urn_suffix! - ) - : null, - blockchain_item_id: itemAttributes.urn_suffix, + urn, + blockchain_item_id: decodeThirdPartyItemURN(urn).item_urn_suffix, total_supply: 0, price: '0', beneficiary: constants.AddressZero, + isMappingComplete: !!catalystItem?.mappings, content_hash: null, catalyst_content_hash: catalystItem ? (catalystItem as any)?.merkleProof.entityHash @@ -160,6 +175,7 @@ export const dbItemMock: ItemAttributes = { export const dbTPItemMock: ThirdPartyItemAttributes = { ...dbItemMock, id: uuidv4(), + beneficiary: constants.AddressZero, blockchain_item_id: null, collection_id: dbTPCollectionMock.id, urn_suffix: '1', diff --git a/src/Item/Item.router.spec.ts b/src/Item/Item.router.spec.ts index 7fc39293..097e625e 100644 --- a/src/Item/Item.router.spec.ts +++ b/src/Item/Item.router.spec.ts @@ -1,4 +1,10 @@ -import { MappingType, Rarity, Wearable } from '@dcl/schemas' +import { + ContractNetwork, + MappingType, + Rarity, + ThirdPartyProps, + Wearable, +} from '@dcl/schemas' import supertest from 'supertest' import { v4 as uuidv4 } from 'uuid' import { ethers, Wallet } from 'ethers' @@ -284,6 +290,7 @@ describe('Item router', () => { dbTPCollectionMock.urn_suffix, dbTPItemNotPublishedMock.urn_suffix! ), + isMappingComplete: false, }, resultTPItemPublished, ], @@ -586,16 +593,29 @@ describe('Item router', () => { ;(ItemCuration.findByCollectionId as jest.Mock).mockResolvedValueOnce([ dbItemCuration, ]) - ;(peerAPI.fetchWearables as jest.Mock).mockResolvedValueOnce([ - tpWearable, - ]) ;(collectionAPI.buildItemId as jest.Mock).mockImplementation( (contractAddress, tokenId) => contractAddress + '-' + tokenId ) + const tpWearableWithMappings: Wearable & Partial = { + ...tpWearable, + mappings: { + [ContractNetwork.SEPOLIA]: { + '0x0': [{ type: MappingType.SINGLE, id: '4' }], + }, + }, + } + resultingTPItem = toResultTPItem( + dbTPItem, + dbTPCollectionMock, + tpWearableWithMappings + ) + ;(peerAPI.fetchWearables as jest.Mock).mockResolvedValueOnce([ + tpWearableWithMappings, + ]) url = `/collections/${dbCollectionMock.id}/items` }) - it('should return all the items of a collection that are published with URN and the ones that are not without it', () => { + it('should return all the items of a collection with their URNs and the isMappingComplete property as true for the item with a mapping', () => { return server .get(buildURL(url)) .set(createAuthHeaders('get', url)) @@ -603,7 +623,7 @@ describe('Item router', () => { .then((response: any) => { expect(response.body).toEqual({ data: [ - resultingTPItem, + { ...resultingTPItem, isMappingComplete: true }, resultTPItemPublished, resultTPItemNotPublished, ], @@ -996,15 +1016,7 @@ describe('Item router', () => { 'b3520ef20163848f0fc69fc6aee1f7240c7ef4960944fcd92ce2e67a62828f6f', } resultingItem = { - ...toResultItem( - updatedItem, - undefined, - undefined, - tpCollectionMock - ), - beneficiary: ethers.constants.AddressZero, - blockchain_item_id: updatedItem.urn_suffix, - price: '0', + ...toResultTPItem(updatedItem, tpCollectionMock), updated_at: expect.stringMatching(isoDateStringMatcher), } // Mock get TP item @@ -1072,15 +1084,7 @@ describe('Item router', () => { 'b3520ef20163848f0fc69fc6aee1f7240c7ef4960944fcd92ce2e67a62828f6f', } resultingItem = { - ...toResultItem( - updatedItem, - undefined, - undefined, - tpCollectionMock - ), - beneficiary: ethers.constants.AddressZero, - blockchain_item_id: updatedItem.urn_suffix, - price: '0', + ...toResultTPItem(updatedItem, tpCollectionMock), updated_at: expect.stringMatching(isoDateStringMatcher), } // Mock get TP item @@ -1229,7 +1233,6 @@ describe('Item router', () => { mockItem.upsert.mockImplementation((createdItem) => Promise.resolve({ ...createdItem, - blockchain_item_id: null, }) ) itemToUpsert.mappings = { @@ -1250,12 +1253,7 @@ describe('Item router', () => { } mockThirdPartyURNExists(itemToUpsert.urn!, false) resultingItem = { - ...toResultItem( - updatedItem, - undefined, - undefined, - tpCollectionMock - ), + ...toResultTPItem(updatedItem, tpCollectionMock), updated_at: expect.stringMatching(isoDateStringMatcher), created_at: expect.stringMatching(isoDateStringMatcher), beneficiary: ethers.constants.AddressZero, diff --git a/src/Item/Item.service.ts b/src/Item/Item.service.ts index f1a47086..19b35b3a 100644 --- a/src/Item/Item.service.ts +++ b/src/Item/Item.service.ts @@ -1,4 +1,4 @@ -import { Wearable } from '@dcl/schemas' +import { ThirdPartyProps, Wearable } from '@dcl/schemas' import { omit } from 'decentraland-commons/dist/utils' import { Collection, @@ -376,7 +376,9 @@ export class ItemService { ? Bridge.mergeTPCollection(collection, lastItemCuration) : collection - const catalystItems = await peerAPI.fetchWearables([urn]) + const catalystItems = await peerAPI.fetchWearables< + Wearable & ThirdPartyProps + >([urn]) item = Bridge.mergeTPItem( dbItem, collection as ThirdPartyCollectionAttributes, diff --git a/src/Item/Item.types.ts b/src/Item/Item.types.ts index af925275..00fbe5d6 100644 --- a/src/Item/Item.types.ts +++ b/src/Item/Item.types.ts @@ -60,6 +60,7 @@ export type FullItem = Omit & { total_supply: number content_hash: string | null catalyst_content_hash: string | null + isMappingComplete?: boolean } export type DBItemApprovalData = Pick & diff --git a/src/ethereum/api/Bridge.ts b/src/ethereum/api/Bridge.ts index 104180e6..94a695c3 100644 --- a/src/ethereum/api/Bridge.ts +++ b/src/ethereum/api/Bridge.ts @@ -1,4 +1,4 @@ -import { Wearable } from '@dcl/schemas' +import { ThirdPartyProps, Wearable } from '@dcl/schemas' import { constants } from 'ethers' import { utils } from 'decentraland-commons' import { @@ -127,9 +127,9 @@ export class Bridge { itemsByURN[urn] = item } - const tpCatalystItems = await peerAPI.fetchWearables( - Object.keys(itemsByURN) - ) + const tpCatalystItems = await peerAPI.fetchWearables< + Wearable & ThirdPartyProps + >(Object.keys(itemsByURN)) const fullItems: FullItem[] = [] @@ -169,7 +169,7 @@ export class Bridge { static mergeTPItem( dbItem: ItemAttributes, dbCollection: ThirdPartyCollectionAttributes, - catalystItem?: Wearable + catalystItem?: Wearable & ThirdPartyProps ): FullItem { const data = dbItem.data const category = data.category @@ -180,7 +180,6 @@ export class Bridge { dbCollection.urn_suffix, dbItem.urn_suffix! ) - return { ...Bridge.toFullItem(dbItem), // The total supply for TP items will be 0 as they won't be minted. @@ -196,6 +195,7 @@ export class Bridge { is_published: !!catalystItem, // For now, items are always approved. Rejecting (or disabling) items will be done at the record level, for all collections that apply. is_approved: !!catalystItem, + isMappingComplete: !!catalystItem?.mappings, content_hash: null, catalyst_content_hash: catalystItem ? (catalystItem as any).merkleProof.entityHash @@ -422,26 +422,28 @@ export class Bridge { dbCollection?: CollectionAttributes ): FullItem { const hasURN = !!dbItem.urn_suffix + const isThirdPartyItem = + hasURN && dbCollection && isTPCollection(dbCollection) return utils.omit( { ...dbItem, - urn: - hasURN && dbCollection && isTPCollection(dbCollection) - ? buildTPItemURN( - dbCollection.third_party_id, - dbCollection.urn_suffix, - dbItem.urn_suffix! - ) - : dbCollection && !isTPCollection(dbCollection) - ? getDecentralandItemURN(dbItem, dbCollection.contract_address!) - : null, + urn: isThirdPartyItem + ? buildTPItemURN( + dbCollection.third_party_id, + dbCollection.urn_suffix, + dbItem.urn_suffix! + ) + : dbCollection && !isTPCollection(dbCollection) + ? getDecentralandItemURN(dbItem, dbCollection.contract_address!) + : null, in_catalyst: false, is_approved: false, is_published: false, content_hash: null, catalyst_content_hash: null, total_supply: 0, + ...(isThirdPartyItem ? { isMappingComplete: false } : {}), }, ['urn_suffix'] )