diff --git a/src/Item/Item.errors.ts b/src/Item/Item.errors.ts index 69f5d4da..df9131b9 100644 --- a/src/Item/Item.errors.ts +++ b/src/Item/Item.errors.ts @@ -1,3 +1,5 @@ +import { MAX_TAGS_LENGTH } from './Item.model' + export enum ItemAction { DELETE = 'deleted', INSERT = 'inserted', @@ -42,6 +44,14 @@ export class URNAlreadyInUseError extends Error { } } +export class MaximunAmountOfTagsReachedError extends Error { + constructor(public id: string) { + super( + `You hace exceeded the maximun amount of tags allowed (${MAX_TAGS_LENGTH}).` + ) + } +} + export class DCLItemAlreadyPublishedError extends Error { constructor( public id: string, diff --git a/src/Item/Item.model.ts b/src/Item/Item.model.ts index 83fea789..77334d71 100644 --- a/src/Item/Item.model.ts +++ b/src/Item/Item.model.ts @@ -5,6 +5,8 @@ import { ItemCuration } from '../Curation/ItemCuration' import { DEFAULT_LIMIT } from '../Pagination/utils' import { DBItemApprovalData, ItemAttributes } from './Item.types' +export const MAX_TAGS_LENGTH = 20 + type ItemWithTotalCount = ItemAttributes & { total_count: number } type ItemQueryOptions = { diff --git a/src/Item/Item.service.spec.ts b/src/Item/Item.service.spec.ts index afd660c0..3a7fa507 100644 --- a/src/Item/Item.service.spec.ts +++ b/src/Item/Item.service.spec.ts @@ -8,15 +8,18 @@ import { itemFragmentMock, } from '../../spec/mocks/items' import { wearableMock } from '../../spec/mocks/peer' +import { mockOwnableCanUpsert } from '../../spec/utils' import { Collection } from '../Collection/Collection.model' +import { CollectionService } from '../Collection/Collection.service' import { Bridge } from '../ethereum/api/Bridge' import { collectionAPI } from '../ethereum/api/collection' import { peerAPI } from '../ethereum/api/peer' import { ItemCantBeMovedFromCollectionError, + MaximunAmountOfTagsReachedError, ThirdPartyItemInsertByURNError, } from './Item.errors' -import { Item } from './Item.model' +import { Item, MAX_TAGS_LENGTH } from './Item.model' import { ItemService } from './Item.service' import { ItemAttributes } from './Item.types' import { VIDEO_PATH } from './utils' @@ -80,7 +83,7 @@ describe('Item Service', () => { }) it('should throw the ItemCantBeMovedFromCollectionError error', () => { - return expect(() => + return expect( service.upsertItem( Bridge.toFullItem(dbTPItem, dbTPCollectionMock), '0xnewCreator' @@ -102,7 +105,7 @@ describe('Item Service', () => { }) it('should throw the ItemCantBeMovedFromCollectionError error', () => { - return expect(() => + return expect( service.upsertItem(Bridge.toFullItem(dbItem), '0xnewCreator') ).rejects.toThrowError(ItemCantBeMovedFromCollectionError) }) @@ -115,7 +118,7 @@ describe('Item Service', () => { }) it('should throw the ThirdPartyItemInsertByURNError error', () => { - return expect(() => + return expect( service.upsertItem( Bridge.toFullItem(dbTPItem, dbTPCollectionMock), '0xnewCreator' @@ -123,6 +126,123 @@ describe('Item Service', () => { ).rejects.toThrowError(ThirdPartyItemInsertByURNError) }) }) + + describe('and the item being upserted contains tags', () => { + beforeEach(() => { + CollectionService.prototype.getDBCollection = jest.fn() + }) + describe('and it is an insert operation', () => { + beforeEach(() => { + ;(Item.findByURNSuffix as jest.Mock).mockResolvedValueOnce(undefined) + }) + describe('and it is inserting less than the maximun amount of tags', () => { + beforeEach(() => { + dbItem = { + ...dbItemMock, + eth_address: '0xAddress', + data: { + ...dbItemMock.data, + tags: Array(MAX_TAGS_LENGTH - 1).fill('tag'), + }, + } + dbCollectionMock.eth_address = dbItem.eth_address + mockOwnableCanUpsert(Item, dbItem.id, dbItem.eth_address, true) + mockOwnableCanUpsert( + Collection, + dbCollectionMock.id, + dbItem.eth_address, + true + ) + ;(CollectionService.prototype + .getDBCollection as jest.Mock).mockResolvedValueOnce( + dbCollectionMock + ) + ;(Item.upsert as jest.Mock).mockResolvedValueOnce(dbItem) + CollectionService.prototype.isDCLPublished = jest.fn() + }) + it('should not throw any errors and return the inserted item', () => { + const result = service.upsertItem( + Bridge.toFullItem(dbItem, dbTPCollectionMock), + dbItem.eth_address + ) + return expect(result).resolves.toEqual( + Bridge.toFullItem(dbItem, dbCollectionMock) + ) + }) + }) + + describe('and it is inserting more than the maximun amount of tags', () => { + beforeEach(() => { + dbItem = { + ...dbItemMock, + data: { + ...dbItemMock.data, + tags: Array(MAX_TAGS_LENGTH + 1).fill('tag'), + }, + } + }) + it('should throw the MaximunAmountOfTagsReachedError error', () => { + return expect( + service.upsertItem( + Bridge.toFullItem(dbItem, dbTPCollectionMock), + dbItem.eth_address + ) + ).rejects.toThrowError(MaximunAmountOfTagsReachedError) + }) + }) + }) + + describe('and it is an update operation', () => { + beforeEach(() => { + ;(Item.findByURNSuffix as jest.Mock).mockResolvedValueOnce(dbItem) + }) + describe('and the item already has the maximun amount of tags', () => { + it('should throw the MaximunAmountOfTagsReachedError error', () => { + return expect( + service.upsertItem( + Bridge.toFullItem(dbItem, dbTPCollectionMock), + dbItem.eth_address + ) + ).rejects.toThrowError(MaximunAmountOfTagsReachedError) + }) + }) + + describe('and the item has less than the maximun amount of tags', () => { + beforeEach(() => { + dbItem = { + ...dbItemMock, + eth_address: '0xAddress', + data: { + ...dbItemMock.data, + tags: Array(MAX_TAGS_LENGTH - 1).fill('tag'), + }, + } + dbCollectionMock.eth_address = dbItem.eth_address + mockOwnableCanUpsert(Item, dbItem.id, dbItem.eth_address, true) + mockOwnableCanUpsert( + Collection, + dbCollectionMock.id, + dbItem.eth_address, + true + ) + ;(CollectionService.prototype + .getDBCollection as jest.Mock).mockResolvedValueOnce( + dbCollectionMock + ) + ;(Item.upsert as jest.Mock).mockResolvedValueOnce(dbItem) + }) + it('should not throw any error and return the inserted item', () => { + const result = service.upsertItem( + Bridge.toFullItem(dbItem, dbTPCollectionMock), + dbItem.eth_address + ) + return expect(result).resolves.toEqual( + Bridge.toFullItem(dbItem, dbCollectionMock) + ) + }) + }) + }) + }) }) describe('getItemByContractAddressAndTokenId', () => { diff --git a/src/Item/Item.service.ts b/src/Item/Item.service.ts index 91f8ba76..20f467c8 100644 --- a/src/Item/Item.service.ts +++ b/src/Item/Item.service.ts @@ -34,8 +34,9 @@ import { InvalidItemURNError, URNAlreadyInUseError, ThirdPartyItemInsertByURNError, + MaximunAmountOfTagsReachedError, } from './Item.errors' -import { Item } from './Item.model' +import { Item, MAX_TAGS_LENGTH } from './Item.model' import { FullItem, ItemAttributes, @@ -62,6 +63,7 @@ export class ItemService { item: FullItem, eth_address: string ): Promise { + console.log('here1') const decodedItemURN = !item.id && item.urn ? decodeThirdPartyItemURN(item.urn) : null @@ -72,12 +74,25 @@ export class ItemService { decodedItemURN.item_urn_suffix ) : await Item.findOne(item.id) + console.log('here2') + if (item.data.tags.length > MAX_TAGS_LENGTH) { + const isAlreadyExceeded = + !!dbItem && dbItem.data.tags.length > MAX_TAGS_LENGTH + const isAddingMoreTags = + !!dbItem && item.data.tags.length > dbItem.data.tags.length + if (!dbItem || (isAlreadyExceeded && isAddingMoreTags)) { + throw new MaximunAmountOfTagsReachedError(item.id) + } + } + console.log('here3') // Inserting by URN is not allowed if (!item.id && item.urn && !dbItem) { throw new ThirdPartyItemInsertByURNError(item.urn) } + console.log('here4') + const isMovingItemFromACollectionToAnother = dbItem && this.isMovingItemFromACollectionToAnother(item, dbItem) const isMovingOrphanItemIntoACollection = @@ -115,6 +130,7 @@ export class ItemService { // Set the item dates item = { ...item, ...buildModelDates(dbItem?.created_at) } + console.log('here5') // An item is a third party item if it's current collection or the collection // that is going to be inserted into is a third party collection. if (dbItemCollection && isTPCollection(dbItemCollection)) { @@ -494,6 +510,7 @@ export class ItemService { const isMovingItemBetweenCollections = dbItem && this.isMovingItemFromACollectionToAnother(item, dbItem) + console.log('here6') const [ isDbItemCollectionPublished, isItemCollectionPublished, @@ -506,6 +523,7 @@ export class ItemService { itemCollection.contract_address && this.collectionService.isDCLPublished(itemCollection.contract_address), ]) + console.log('here7') const isDbItemCollectionOwner = dbCollection && this.isCollectionOwner(eth_address, dbCollection) @@ -515,7 +533,6 @@ export class ItemService { // Check if we have permissions to move or edit an orphaned item if (!dbItem?.collection_id) { const canUpsert = await new Ownable(Item).canUpsert(item.id, eth_address) - if (!canUpsert) { throw new UnauthorizedToUpsertError(item.id, eth_address) }