From 2be83ae59b24dff5b4d9c08d18cde9a29dc3d6f2 Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Fri, 6 May 2022 10:54:52 +0200 Subject: [PATCH 1/5] feat: Add pagination and filters to the /collections endpoint --- src/Collection/Collection.model.ts | 119 +++++++++++++++++- src/Collection/Collection.router.spec.ts | 147 ++++++++++++++++++++--- src/Collection/Collection.router.ts | 46 ++++++- src/Curation/Curation.types.ts | 15 +++ 4 files changed, 299 insertions(+), 28 deletions(-) diff --git a/src/Collection/Collection.model.ts b/src/Collection/Collection.model.ts index 429d101f..415ad7d6 100644 --- a/src/Collection/Collection.model.ts +++ b/src/Collection/Collection.model.ts @@ -1,4 +1,7 @@ import { Model, raw, SQL } from 'decentraland-server' +import { DEFAULT_LIMIT } from '../Pagination/utils' +import { CurationStatusFilter, CurationStatusSort } from '../Curation' +import { CollectionCuration } from '../Curation/CollectionCuration' import { database } from '../database/database' import { Item } from '../Item/Item.model' import { CollectionAttributes } from './Collection.types' @@ -7,16 +10,122 @@ type CollectionWithItemCount = CollectionAttributes & { item_count: number } +type CollectionWithCounts = CollectionWithItemCount & { + collection_count: number +} + +type FindCollectionParams = { + limit?: number + offset?: number + q?: string + assignee?: string + status?: CurationStatusFilter + sort?: CurationStatusSort + isPublished: boolean +} + export class Collection extends Model { static tableName = 'collections' - static findAll() { - return this.query(SQL` - SELECT *, (SELECT COUNT(*) FROM ${raw( + static getOrderByStatement(sort?: CurationStatusSort) { + switch (sort) { + case CurationStatusSort.MOST_RELEVANT: + return SQL` + ORDER BY + CASE WHEN( + collection_curations.assignee is NULL) + THEN 0 + WHEN(collection_curations.assignee is NOT NULL AND collection_curations.status = ${CurationStatusFilter.PENDING}) + THEN 1 + WHEN(collection_curations.status = ${CurationStatusFilter.APPROVED}) + THEN 2 + WHEN(collection_curations.status = ${CurationStatusFilter.REJECTED}) + THEN 3 + ELSE 4 + END + ` + case CurationStatusSort.NAME_ASC: + return SQL`ORDER BY collections.name ASC` + case CurationStatusSort.NAME_DESC: + return SQL`ORDER BY collections.name DESC` + case CurationStatusSort.NEWEST: + return SQL`ORDER BY collections.created_at DESC` + default: + return SQL`` + } + } + + static getFindAllWhereStatement({ + q, + assignee, + status, + }: Pick) { + if (!q && !assignee && !status) { + return SQL`` + } + const conditions = [ + q ? SQL`collections.name LIKE '%' || ${q} || '%'` : undefined, + assignee ? SQL`collection_curations.assignee = ${assignee}` : undefined, + status + ? [ + CurationStatusFilter.PENDING, + CurationStatusFilter.APPROVED, + CurationStatusFilter.REJECTED, + ].includes(status) + ? SQL`collection_curations.status = ${status}` + : status === CurationStatusFilter.TO_REVIEW + ? SQL`collection_curations.assignee is NULL` + : status === CurationStatusFilter.UNDER_REVIEW + ? SQL`collection_curations.assignee is NOT NULL AND collection_curations.status = ${CurationStatusFilter.PENDING}` + : SQL`` + : undefined, + ].filter(Boolean) + + const result = SQL`WHERE ` + conditions.forEach((condition, index) => { + if (condition) { + result.append(condition) + if (conditions[index + 1]) { + result.append(SQL` AND `) + } + } + }) + + return result + } + + static getPublishedJoinStatement(isPublished: boolean) { + return isPublished + ? SQL`JOIN ${raw( + Item.tableName + )} items on items.collection_id = collections.id AND items.blockchain_item_id is NOT NULL` + : SQL`` + } + + static findAll({ + limit = DEFAULT_LIMIT, + offset = 0, + q, + assignee, + status, + sort, + isPublished, + }: FindCollectionParams) { + const query = SQL` + SELECT collections.*, COUNT(*) OVER() as collection_count, (SELECT COUNT(*) FROM ${raw( Item.tableName )} WHERE items.collection_id = collections.id) as item_count - FROM ${raw(this.tableName)} - `) + FROM ${raw(this.tableName)} collections + ${SQL`LEFT JOIN ${raw( + CollectionCuration.tableName + )} collection_curations ON collection_curations.collection_id = collections.id`} + ${SQL`${this.getPublishedJoinStatement(isPublished)}`} + ${SQL`${this.getFindAllWhereStatement({ q, assignee, status })}`} + ${SQL`${this.getOrderByStatement(sort)}`} + LIMIT ${limit} + OFFSET ${offset} + ` + return this.query(query) } static findByAllByAddress(address: string) { diff --git a/src/Collection/Collection.router.spec.ts b/src/Collection/Collection.router.spec.ts index 9a37ab49..c9cd2ab8 100644 --- a/src/Collection/Collection.router.spec.ts +++ b/src/Collection/Collection.router.spec.ts @@ -800,33 +800,144 @@ describe('Collection router', () => { describe('when retrieving all the collections', () => { beforeEach(() => { ;(isCommitteeMember as jest.Mock).mockResolvedValueOnce(true) - ;(Collection.findAll as jest.Mock) - .mockResolvedValueOnce([dbCollection]) - .mockResolvedValueOnce([]) ;(Collection.findByContractAddresses as jest.Mock).mockResolvedValueOnce( [] ) ;(collectionAPI.fetchCollections as jest.Mock).mockResolvedValueOnce([]) thirdPartyAPIMock.fetchThirdParties.mockResolvedValueOnce([]) - url = `/collections` }) - it('should respond with all the collections with the URN', () => { - return server - .get(buildURL(url)) - .set(createAuthHeaders('get', url)) - .expect(200) - .then((response: any) => { - expect(response.body).toEqual({ - data: [ - { - ...resultingCollectionAttributes, - urn: `urn:decentraland:mumbai:collections-v2:${dbCollection.contract_address}`, + describe('and sending pagination params', () => { + let page: number, limit: number + let baseUrl: string + let totalCollectionsFromDb: number + beforeEach(() => { + ;(page = 1), (limit = 3) + totalCollectionsFromDb = 1 + baseUrl = '/collections' + url = `${baseUrl}?limit=${limit}&page=${page}` + ;(Collection.findAll as jest.Mock).mockResolvedValueOnce([ + { ...dbCollection, collection_count: totalCollectionsFromDb }, + ]) + }) + it('should respond with pagination data and should have call the findAll method with the params', () => { + return server + .get(buildURL(url)) + .set(createAuthHeaders('get', baseUrl)) + .expect(200) + .then((response: any) => { + expect(response.body).toEqual({ + data: { + total: totalCollectionsFromDb, + pages: totalCollectionsFromDb, + page, + limit, + results: [ + { + ...resultingCollectionAttributes, + urn: `urn:decentraland:mumbai:collections-v2:${dbCollection.contract_address}`, + }, + ], }, - ], - ok: true, + + ok: true, + }) + expect(Collection.findAll).toHaveBeenCalledWith({ + assignee: undefined, + isPublished: false, + q: undefined, + sort: undefined, + status: undefined, + limit, + offset: page - 1, // it's the offset + }) }) - }) + }) + }) + + describe('and sending pagination params plus filtering options', () => { + let page: number, + limit: number, + baseUrl: string, + totalCollectionsFromDb: number, + q: string, + assignee: string, + status: string, + sort: string, + isPublished: string + beforeEach(() => { + ;(page = 1), (limit = 3) + assignee = '0x1234567890123456789012345678901234567890' + status = 'published' + sort = 'NAME_DESC' + isPublished = 'true' + q = 'collection name 1' + totalCollectionsFromDb = 1 + baseUrl = '/collections' + url = `${baseUrl}?limit=${limit}&page=${page}&assignee=${assignee}&status=${status}&sort=${sort}&is_published=${isPublished}&q=${q}` + ;(Collection.findAll as jest.Mock).mockResolvedValueOnce([ + { ...dbCollection, collection_count: totalCollectionsFromDb }, + ]) + }) + it('should respond with pagination data and should have call the findAll method with the right params', () => { + return server + .get(buildURL(url)) + .set(createAuthHeaders('get', baseUrl)) + .expect(200) + .then((response: any) => { + expect(response.body).toEqual({ + data: { + total: totalCollectionsFromDb, + pages: totalCollectionsFromDb, + page, + limit, + results: [ + { + ...resultingCollectionAttributes, + urn: `urn:decentraland:mumbai:collections-v2:${dbCollection.contract_address}`, + }, + ], + }, + + ok: true, + }) + expect(Collection.findAll).toHaveBeenCalledWith({ + q, + assignee, + status, + sort, + isPublished: true, + offset: page - 1, // it's the offset + limit, + }) + }) + }) + }) + + describe('and not sending any pagination params ', () => { + beforeEach(() => { + url = `/collections` + ;(Collection.findAll as jest.Mock) + .mockResolvedValueOnce([dbCollection]) + .mockResolvedValueOnce([]) + }) + it('should respond with all the collections with the URN and the legacy response', () => { + return server + .get(buildURL(url)) + .set(createAuthHeaders('get', url)) + .expect(200) + .then((response: any) => { + expect(response.body).toEqual({ + data: [ + { + ...resultingCollectionAttributes, + urn: `urn:decentraland:mumbai:collections-v2:${dbCollection.contract_address}`, + }, + ], + ok: true, + }) + }) + }) }) }) diff --git a/src/Collection/Collection.router.ts b/src/Collection/Collection.router.ts index 48ee346f..d1e9dea5 100644 --- a/src/Collection/Collection.router.ts +++ b/src/Collection/Collection.router.ts @@ -1,4 +1,5 @@ import { server } from 'decentraland-server' +import { omit } from 'decentraland-commons/dist/utils' import { Router } from '../common/Router' import { HTTPError, STATUS_CODES } from '../common/HTTPError' import { getValidator } from '../utils/validator' @@ -30,6 +31,13 @@ import { } from '../Forum' import { sendDataToWarehouse } from '../warehouse' import { Cheque } from '../SlotUsageCheque' +import { PaginatedResponse } from '../Pagination' +import { + generatePaginatedResponse, + getOffset, + getPaginationParams, +} from '../Pagination/utils' +import { CurationStatusFilter, CurationStatusSort } from '../Curation' import { hasTPCollectionURN, isTPCollection } from '../utils/urn' import { Collection } from './Collection.model' import { CollectionService } from './Collection.service' @@ -209,7 +217,11 @@ export class CollectionRouter extends Router { } } - getCollections = async (req: AuthRequest): Promise => { + getCollections = async ( + req: AuthRequest + ): Promise | FullCollection[]> => { + const { page, limit } = getPaginationParams(req) + const { assignee, status, sort, q, is_published } = req.query const eth_address = req.auth.ethAddress const canRequestCollections = await isCommitteeMember(eth_address) @@ -222,27 +234,51 @@ export class CollectionRouter extends Router { } const [ - dbCollections, + allCollectionsWithCount, remoteCollections, dbTPCollections, ] = await Promise.all([ - Collection.findAll(), + Collection.findAll({ + q: q as string, + assignee: assignee as string, + status: status as CurationStatusFilter, + sort: sort as CurationStatusSort, + isPublished: is_published === 'true', + offset: page && limit ? getOffset(page, limit) : undefined, + limit, + }), collectionAPI.fetchCollections(), this.service.getDbTPCollections(), ]) + const totalCollections = + Number(allCollectionsWithCount[0]?.collection_count) || 0 + + const dbCollections = allCollectionsWithCount.map((collectionWithCount) => + omit(collectionWithCount, ['collection_count']) + ) + + const dbBlockchainContractAddresses = dbCollections.map( + (collection) => collection.contract_address + ) const consolidatedCollections = await Bridge.consolidateCollections( dbCollections, - remoteCollections + remoteCollections.filter((remoteCollection) => + dbBlockchainContractAddresses.includes(remoteCollection.id) + ) ) const consolidatedTPCollections = await Bridge.consolidateTPCollections( dbTPCollections ) // Build the full collection - return consolidatedCollections + const concatenated = consolidatedCollections .concat(consolidatedTPCollections) .map(toFullCollection) + + return page && limit + ? generatePaginatedResponse(concatenated, totalCollections, limit, page) + : concatenated } getAddressCollections = async ( diff --git a/src/Curation/Curation.types.ts b/src/Curation/Curation.types.ts index 3f4e0023..64ed12aa 100644 --- a/src/Curation/Curation.types.ts +++ b/src/Curation/Curation.types.ts @@ -9,6 +9,21 @@ export enum CurationStatus { REJECTED = 'rejected', } +export enum CurationStatusFilter { + PENDING = 'pending', + APPROVED = 'approved', + REJECTED = 'rejected', + TO_REVIEW = 'to_review', + UNDER_REVIEW = 'under_review', +} + +export enum CurationStatusSort { + MOST_RELEVANT = 'MOST_RELEVANT', + NEWEST = 'NEWEST', + NAME_DESC = 'NAME_DESC', + NAME_ASC = 'NAME_ASC', +} + export const patchCurationSchema = Object.freeze({ type: 'object', properties: { From a7c95e24cdd6db3f11ae6830fe5f4856c48775dc Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Fri, 6 May 2022 19:26:00 +0200 Subject: [PATCH 2/5] feat: create a consolidation method for all collections --- src/Collection/Collection.model.ts | 34 ++++++--- src/Collection/Collection.router.spec.ts | 7 +- src/Collection/Collection.router.ts | 75 ++++++++------------ src/Collection/Collection.service.ts | 17 ++++- src/ethereum/api/Bridge.ts | 87 +++++++++++++++++++----- 5 files changed, 144 insertions(+), 76 deletions(-) diff --git a/src/Collection/Collection.model.ts b/src/Collection/Collection.model.ts index 415ad7d6..97ea7f72 100644 --- a/src/Collection/Collection.model.ts +++ b/src/Collection/Collection.model.ts @@ -10,18 +10,20 @@ type CollectionWithItemCount = CollectionAttributes & { item_count: number } -type CollectionWithCounts = CollectionWithItemCount & { +export type CollectionWithCounts = CollectionWithItemCount & { collection_count: number } -type FindCollectionParams = { +export type FindCollectionParams = { limit?: number offset?: number q?: string + address?: string + thirdPartyIds?: string[] assignee?: string status?: CurationStatusFilter sort?: CurationStatusSort - isPublished: boolean + isPublished?: boolean } export class Collection extends Model { @@ -59,13 +61,23 @@ export class Collection extends Model { q, assignee, status, - }: Pick) { - if (!q && !assignee && !status) { + address, + thirdPartyIds, + }: Pick< + FindCollectionParams, + 'q' | 'assignee' | 'status' | 'address' | 'thirdPartyIds' + >) { + if (!q && !assignee && !status && !address && !thirdPartyIds?.length) { return SQL`` } const conditions = [ q ? SQL`collections.name LIKE '%' || ${q} || '%'` : undefined, assignee ? SQL`collection_curations.assignee = ${assignee}` : undefined, + address + ? thirdPartyIds?.length + ? SQL`(collections.eth_address = ${address} OR third_party_id = ANY(${thirdPartyIds}))` + : SQL`collections.eth_address = ${address}` + : undefined, status ? [ CurationStatusFilter.PENDING, @@ -81,6 +93,10 @@ export class Collection extends Model { : undefined, ].filter(Boolean) + if (!conditions.length) { + return SQL`` + } + const result = SQL`WHERE ` conditions.forEach((condition, index) => { if (condition) { @@ -94,7 +110,7 @@ export class Collection extends Model { return result } - static getPublishedJoinStatement(isPublished: boolean) { + static getPublishedJoinStatement(isPublished = false) { return isPublished ? SQL`JOIN ${raw( Item.tableName @@ -105,11 +121,9 @@ export class Collection extends Model { static findAll({ limit = DEFAULT_LIMIT, offset = 0, - q, - assignee, - status, sort, isPublished, + ...whereFilters }: FindCollectionParams) { const query = SQL` SELECT collections.*, COUNT(*) OVER() as collection_count, (SELECT COUNT(*) FROM ${raw( @@ -120,7 +134,7 @@ export class Collection extends Model { CollectionCuration.tableName )} collection_curations ON collection_curations.collection_id = collections.id`} ${SQL`${this.getPublishedJoinStatement(isPublished)}`} - ${SQL`${this.getFindAllWhereStatement({ q, assignee, status })}`} + ${SQL`${this.getFindAllWhereStatement(whereFilters)}`} ${SQL`${this.getOrderByStatement(sort)}`} LIMIT ${limit} OFFSET ${offset} diff --git a/src/Collection/Collection.router.spec.ts b/src/Collection/Collection.router.spec.ts index c9cd2ab8..cbc67e36 100644 --- a/src/Collection/Collection.router.spec.ts +++ b/src/Collection/Collection.router.spec.ts @@ -849,7 +849,8 @@ describe('Collection router', () => { sort: undefined, status: undefined, limit, - offset: page - 1, // it's the offset + offset: page - 1, // it's the offset, + thirdPartyIds: [], }) }) }) @@ -909,6 +910,7 @@ describe('Collection router', () => { isPublished: true, offset: page - 1, // it's the offset limit, + thirdPartyIds: [], }) }) }) @@ -943,8 +945,9 @@ describe('Collection router', () => { describe('when retrieving the collections of an address', () => { beforeEach(() => { - ;(Collection.findByAllByAddress as jest.Mock).mockReturnValueOnce([ + ;(Collection.findAll as jest.Mock).mockReturnValueOnce([ dbCollection, + dbTPCollection, ]) ;(Collection.findByContractAddresses as jest.Mock).mockReturnValueOnce([]) ;(Collection.findByThirdPartyIds as jest.Mock).mockReturnValueOnce([ diff --git a/src/Collection/Collection.router.ts b/src/Collection/Collection.router.ts index d1e9dea5..44f748f3 100644 --- a/src/Collection/Collection.router.ts +++ b/src/Collection/Collection.router.ts @@ -233,12 +233,8 @@ export class CollectionRouter extends Router { ) } - const [ - allCollectionsWithCount, - remoteCollections, - dbTPCollections, - ] = await Promise.all([ - Collection.findAll({ + const [allCollectionsWithCount, remoteCollections] = await Promise.all([ + this.service.getCollections({ q: q as string, assignee: assignee as string, status: status as CurationStatusFilter, @@ -248,7 +244,6 @@ export class CollectionRouter extends Router { limit, }), collectionAPI.fetchCollections(), - this.service.getDbTPCollections(), ]) const totalCollections = @@ -258,32 +253,19 @@ export class CollectionRouter extends Router { omit(collectionWithCount, ['collection_count']) ) - const dbBlockchainContractAddresses = dbCollections.map( - (collection) => collection.contract_address - ) - const consolidatedCollections = await Bridge.consolidateCollections( - dbCollections, - remoteCollections.filter((remoteCollection) => - dbBlockchainContractAddresses.includes(remoteCollection.id) - ) - ) - const consolidatedTPCollections = await Bridge.consolidateTPCollections( - dbTPCollections - ) - - // Build the full collection - const concatenated = consolidatedCollections - .concat(consolidatedTPCollections) - .map(toFullCollection) + const consolidated = ( + await Bridge.consolidateAllCollections(dbCollections, remoteCollections) + ).map(toFullCollection) return page && limit - ? generatePaginatedResponse(concatenated, totalCollections, limit, page) - : concatenated + ? generatePaginatedResponse(consolidated, totalCollections, limit, page) + : consolidated } getAddressCollections = async ( req: AuthRequest - ): Promise => { + ): Promise | FullCollection[]> => { + const { page, limit } = getPaginationParams(req) const eth_address = server.extractFromReq(req, 'address') const auth_address = req.auth.ethAddress @@ -295,28 +277,31 @@ export class CollectionRouter extends Router { ) } - const [ - dbCollections, - remoteCollections, - dbTPCollections, - ] = await Promise.all([ - Collection.findByAllByAddress(eth_address), + const [allCollectionsWithCount, remoteCollections] = await Promise.all([ + this.service.getCollections( + { + offset: page && limit ? getOffset(page, limit) : undefined, + limit, + address: eth_address, + }, + eth_address + ), collectionAPI.fetchCollectionsByAuthorizedUser(eth_address), - this.service.getDbTPCollectionsByManager(eth_address), ]) - const [ - consolidatedCollections, - consolidatedTPCollections, - ] = await Promise.all([ - Bridge.consolidateCollections(dbCollections, remoteCollections), - Bridge.consolidateTPCollections(dbTPCollections), - ]) + const totalCollections = + Number(allCollectionsWithCount[0]?.collection_count) || 0 - // Build the full collection - return consolidatedCollections - .concat(consolidatedTPCollections) - .map(toFullCollection) + const consolidated = ( + await Bridge.consolidateAllCollections( + allCollectionsWithCount, + remoteCollections + ) + ).map(toFullCollection) + + return page && limit + ? generatePaginatedResponse(consolidated, totalCollections, limit, page) + : consolidated } getCollection = async (req: AuthRequest): Promise => { diff --git a/src/Collection/Collection.service.ts b/src/Collection/Collection.service.ts index d9bff4fa..45f203dc 100644 --- a/src/Collection/Collection.service.ts +++ b/src/Collection/Collection.service.ts @@ -41,7 +41,11 @@ import { PublishCollectionResponse, ThirdPartyCollectionAttributes, } from './Collection.types' -import { Collection } from './Collection.model' +import { + Collection, + CollectionWithCounts, + FindCollectionParams, +} from './Collection.model' import { CollectionAction, AlreadyPublishedCollectionError, @@ -531,6 +535,17 @@ export class CollectionService { return false } + public async getCollections( + params: FindCollectionParams, + manager?: string + ): Promise { + const thirdParties = manager + ? await thirdPartyAPI.fetchThirdPartiesByManager(manager) + : await thirdPartyAPI.fetchThirdParties() + const thirdPartyIds = thirdParties.map((thirdParty) => thirdParty.id) + return Collection.findAll({ ...params, thirdPartyIds }) + } + public async getDbTPCollections(): Promise { const thirdParties = await thirdPartyAPI.fetchThirdParties() return this.getDbTPCollectionsByThirdParties(thirdParties) diff --git a/src/ethereum/api/Bridge.ts b/src/ethereum/api/Bridge.ts index cb507dde..f5f556d2 100644 --- a/src/ethereum/api/Bridge.ts +++ b/src/ethereum/api/Bridge.ts @@ -20,37 +20,88 @@ import { CatalystItem, peerAPI } from './peer' export class Bridge { /** - * Takes TP collections found in the database and combines each one with the data from the last published item it has. - * To get the published information, it'll check the last curation made to an item each collection has, as each curation is updated *after* being uploaded to the Catalyst. - * If no published item is found or a non-TP collection is supplied, it'll be returned as-is. - * For more info on what data is merged, see `Bridge.mergeTPCollection` - * @param dbCollections - TP collections from the database + * Takes collections found in the database and combines each one with the data from the remote published (blockchain) collection counterpart + * If no published collection is found, it'll be returned as-is. + * For more info on what data is updated from the published item, see `Bridge.mergeCollection` + * @param dbCollections - DB standard collections + * @param remoteCollections - Blockchain standard collections */ - static async consolidateTPCollections( - dbCollections: CollectionAttributes[] + static async consolidateAllCollections( + dbCollections: CollectionAttributes[], + remoteCollections: CollectionFragment[] ): Promise { const collections: CollectionAttributes[] = [] - for (const dbCollection of dbCollections) { - let fullCollection: CollectionAttributes = { ...dbCollection } + // const dbCollectionsByRemotes = await Collection.findByContractAddresses( + // remoteCollections.map((collection) => collection.id) + // ) + // const contractsAddresses = remoteCollections.map( + // (collection) => collection.id + // ) + // const dbCollectionsByRemotes = dbCollections.filter( + // (collection) => + // collection.contract_address && + // contractsAddresses.includes(collection.contract_address) + // ) + + // // Filter collections already found on the database to avoid duplicates + // const allDbCollections = this.distinctById([ + // ...dbCollections, + // ...dbCollectionsByRemotes, + // ]) + for (const dbCollection of dbCollections) { if (isTPCollection(dbCollection)) { - const lastItemCuration = await ItemCuration.findLastByCollectionId( - dbCollection.id - ) - if (lastItemCuration) { - fullCollection = Bridge.mergeTPCollection( - dbCollection, - lastItemCuration + collections.push(await this.consolidateTPCollection(dbCollection)) + } else { + let remoteCollection: CollectionFragment | undefined + if (dbCollection.contract_address !== null) { + const contractAddress = dbCollection.contract_address.toLowerCase() + + remoteCollection = remoteCollections.find( + (remoteCollection) => + remoteCollection.id.toLowerCase() === contractAddress ) } - } - collections.push(fullCollection) + const collection = remoteCollection + ? Bridge.mergeCollection(dbCollection, remoteCollection) + : dbCollection + + collections.push(collection) + } } + return collections } + /** + * Takes a TP collection found in the database and combines it with the data from the last published item it has. + * To get the published information, it'll check the last curation made to an item each collection has, as each curation is updated *after* being uploaded to the Catalyst. + * If no published item is found or a non-TP collection is supplied, it'll be returned as-is. + * For more info on what data is merged, see `Bridge.mergeTPCollection` + * @param dbCollection - TP collection from the database + */ + static async consolidateTPCollection( + dbTPCollection: CollectionAttributes + ): Promise { + let fullCollection: CollectionAttributes = { ...dbTPCollection } + + if (isTPCollection(dbTPCollection)) { + const lastItemCuration = await ItemCuration.findLastByCollectionId( + dbTPCollection.id + ) + if (lastItemCuration) { + fullCollection = Bridge.mergeTPCollection( + dbTPCollection, + lastItemCuration + ) + } + } + + return fullCollection + } + /** * Takes TP items found in the database and it'll fetch the catalyst item for each one to combine their data. * If neither catalyst item or item curation are found the item will just be converted to FullItem and returned as-is. From e7bdd08bd7b3f291e7c03af1e3b6956e49bce58e Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Mon, 9 May 2022 10:15:43 +0200 Subject: [PATCH 3/5] chore: remove commented out code --- src/ethereum/api/Bridge.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/ethereum/api/Bridge.ts b/src/ethereum/api/Bridge.ts index f5f556d2..95a2f4bd 100644 --- a/src/ethereum/api/Bridge.ts +++ b/src/ethereum/api/Bridge.ts @@ -32,24 +32,6 @@ export class Bridge { ): Promise { const collections: CollectionAttributes[] = [] - // const dbCollectionsByRemotes = await Collection.findByContractAddresses( - // remoteCollections.map((collection) => collection.id) - // ) - // const contractsAddresses = remoteCollections.map( - // (collection) => collection.id - // ) - // const dbCollectionsByRemotes = dbCollections.filter( - // (collection) => - // collection.contract_address && - // contractsAddresses.includes(collection.contract_address) - // ) - - // // Filter collections already found on the database to avoid duplicates - // const allDbCollections = this.distinctById([ - // ...dbCollections, - // ...dbCollectionsByRemotes, - // ]) - for (const dbCollection of dbCollections) { if (isTPCollection(dbCollection)) { collections.push(await this.consolidateTPCollection(dbCollection)) From 005c8ba1b432b4b6be569f754417a6c00eea13bc Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Tue, 10 May 2022 11:02:09 +0200 Subject: [PATCH 4/5] fix: fixes Items findItemsByAddress query --- src/Item/Item.model.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Item/Item.model.ts b/src/Item/Item.model.ts index cd4fb25b..eadcc9a8 100644 --- a/src/Item/Item.model.ts +++ b/src/Item/Item.model.ts @@ -119,21 +119,23 @@ export class Item extends Model { return this.query(SQL` SELECT items.*, count(*) OVER() AS total_count FROM ${raw(this.tableName)} items - LEFT JOIN ${raw( - Collection.tableName - )} collections ON collections.id = items.collection_id - WHERE - ( - collections.third_party_id = ANY(${thirdPartyIds}) - OR - (items.eth_address = ${address} AND items.urn_suffix IS NULL) - ) ${ - collectionId - ? SQL`AND items.collection_id ${ - collectionId === 'null' ? SQL`is NULL` : SQL`= ${collectionId}` - }` - : SQL`` + collectionId !== 'null' + ? SQL`LEFT JOIN ${raw( + Collection.tableName + )} collections ON collections.id = items.collection_id + WHERE + ( + collections.third_party_id = ANY(${thirdPartyIds}) + OR + (items.eth_address = ${address} AND items.urn_suffix IS NULL) + ) + ${ + collectionId + ? SQL`AND items.collection_id = ${collectionId}` + : SQL`` + }` + : SQL`WHERE items.collection_id is NULL` } LIMIT ${limit} OFFSET ${offset} From d13dd3642040a3161ff9598cc7ad4b6f37bd2f61 Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Thu, 12 May 2022 10:38:16 +0200 Subject: [PATCH 5/5] fix: fix /items endpoint query --- src/Item/Item.model.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Item/Item.model.ts b/src/Item/Item.model.ts index eadcc9a8..54d28bed 100644 --- a/src/Item/Item.model.ts +++ b/src/Item/Item.model.ts @@ -119,11 +119,9 @@ export class Item extends Model { return this.query(SQL` SELECT items.*, count(*) OVER() AS total_count FROM ${raw(this.tableName)} items - ${ - collectionId !== 'null' - ? SQL`LEFT JOIN ${raw( - Collection.tableName - )} collections ON collections.id = items.collection_id + ${SQL`LEFT JOIN ${raw( + Collection.tableName + )} collections ON collections.id = items.collection_id WHERE ( collections.third_party_id = ANY(${thirdPartyIds}) @@ -132,11 +130,11 @@ export class Item extends Model { ) ${ collectionId - ? SQL`AND items.collection_id = ${collectionId}` + ? collectionId !== 'null' + ? SQL`AND items.collection_id = ${collectionId}` + : SQL`AND items.collection_id is NULL` : SQL`` - }` - : SQL`WHERE items.collection_id is NULL` - } + }`} LIMIT ${limit} OFFSET ${offset} `)