diff --git a/src/Collection/Collection.errors.ts b/src/Collection/Collection.errors.ts index db12b35d..2c82f1de 100644 --- a/src/Collection/Collection.errors.ts +++ b/src/Collection/Collection.errors.ts @@ -29,6 +29,14 @@ export class AlreadyPublishedCollectionError extends Error { } } +export class URNAlreadyInUseError extends Error { + constructor(public id: string, public urn: string, action: CollectionAction) { + super( + `The URN provided already belongs to a collection. The collection can't be ${action}.` + ) + } +} + export class WrongCollectionError extends Error { constructor(m: string, public data: Record) { super(m) diff --git a/src/Collection/Collection.router.spec.ts b/src/Collection/Collection.router.spec.ts index 0981caca..d8de26ba 100644 --- a/src/Collection/Collection.router.spec.ts +++ b/src/Collection/Collection.router.spec.ts @@ -275,9 +275,10 @@ describe('Collection router', () => { ok: false, data: { id: dbTPCollection.id, + urn: collectionToUpsert.urn, }, error: - "The third party collection already has published items. It can't be updated or inserted.", + "The URN provided already belongs to a collection. The collection can't be updated.", }) }) }) @@ -426,7 +427,7 @@ describe('Collection router', () => { ) }) - it('should respond with a 409 and a message saying that the collection has already been published', () => { + it('should respond with a 409 and a message saying that the there is a collection with that urn already published', () => { return server .put(buildURL(url)) .set(createAuthHeaders('put', url)) @@ -437,9 +438,10 @@ describe('Collection router', () => { ok: false, data: { id: dbTPCollection.id, + urn: collectionToUpsert.urn, }, error: - "The third party collection already has published items. It can't be updated or inserted.", + "The URN provided already belongs to a collection. The collection can't be updated or inserted.", }) }) }) diff --git a/src/Collection/Collection.router.ts b/src/Collection/Collection.router.ts index 327c2dad..48ee346f 100644 --- a/src/Collection/Collection.router.ts +++ b/src/Collection/Collection.router.ts @@ -48,6 +48,7 @@ import { NonExistentCollectionError, UnauthorizedCollectionEditError, UnpublishedCollectionError, + URNAlreadyInUseError, WrongCollectionError, } from './Collection.errors' @@ -488,6 +489,12 @@ export class CollectionRouter extends Router { { id: error.id }, STATUS_CODES.locked ) + } else if (error instanceof URNAlreadyInUseError) { + throw new HTTPError( + error.message, + { id: error.id, urn: error.urn }, + STATUS_CODES.conflict + ) } else if (error instanceof AlreadyPublishedCollectionError) { throw new HTTPError( error.message, diff --git a/src/Collection/Collection.service.ts b/src/Collection/Collection.service.ts index 7d3d3b53..fd66a7e9 100644 --- a/src/Collection/Collection.service.ts +++ b/src/Collection/Collection.service.ts @@ -52,6 +52,7 @@ import { WrongCollectionError, UnpublishedCollectionError, InsufficientSlotsError, + URNAlreadyInUseError, } from './Collection.errors' export class CollectionService { @@ -409,8 +410,10 @@ export class CollectionService { // Check if the new URN for the collection already exists await this.checkIfThirdPartyCollectionURNExists( id, + collectionJSON.urn, collection.third_party_id!, - urn_suffix + urn_suffix, + CollectionAction.UPDATE ) } if (this.isLockActive(collection.lock)) { @@ -425,8 +428,10 @@ export class CollectionService { // Check if the URN for the new collection already exists await this.checkIfThirdPartyCollectionURNExists( id, + collectionJSON.urn, third_party_id, - urn_suffix + urn_suffix, + CollectionAction.UPSERT ) } @@ -607,16 +612,13 @@ export class CollectionService { private async checkIfThirdPartyCollectionURNExists( id: string, + urn: string, third_party_id: string, urn_suffix: string, action = CollectionAction.UPSERT ): Promise { if (await Collection.isURNRepeated(id, third_party_id, urn_suffix)) { - throw new AlreadyPublishedCollectionError( - id, - CollectionType.THIRD_PARTY, - action - ) + throw new URNAlreadyInUseError(id, urn, action) } } } diff --git a/src/Item/Item.errors.ts b/src/Item/Item.errors.ts index 3556227f..102bf457 100644 --- a/src/Item/Item.errors.ts +++ b/src/Item/Item.errors.ts @@ -28,6 +28,14 @@ export class ThirdPartyItemAlreadyPublishedError extends Error { } } +export class URNAlreadyInUseError extends Error { + constructor(public id: string, public urn: string, action: ItemAction) { + super( + `The URN provided already belong to another item. The item can't be ${action}.` + ) + } +} + export class DCLItemAlreadyPublishedError extends Error { constructor( public id: string, diff --git a/src/Item/Item.model.ts b/src/Item/Item.model.ts index 9a069c3b..f78f1306 100644 --- a/src/Item/Item.model.ts +++ b/src/Item/Item.model.ts @@ -68,6 +68,24 @@ export class Item extends Model { WHERE ${where}`) } + static async isURNRepeated( + id: string, + thirdPartyId: string, + urnSuffix: string + ): Promise { + const counts = await this.query<{ count: string }>(SQL` + SELECT COUNT(*) as count + FROM ${raw(this.tableName)} items + JOIN ${raw( + Collection.tableName + )} collections ON items.collection_id = collections.id + WHERE items.id != ${id} + AND collections.third_party_id = ${thirdPartyId} + AND items.urn_suffix = ${urnSuffix}`) + + return Number(counts[0].count) > 0 + } + // PAGINATED QUERIES static findItemsByAddress( diff --git a/src/Item/Item.router.spec.ts b/src/Item/Item.router.spec.ts index 7a75e7a0..0a70fa3b 100644 --- a/src/Item/Item.router.spec.ts +++ b/src/Item/Item.router.spec.ts @@ -994,28 +994,53 @@ describe('Item router', () => { }) describe('and the item is not published but the new URN is already in use', () => { - beforeEach(() => { - mockThirdPartyItemCurationExists(dbItem.id, false) - mockThirdPartyURNExists(itemToUpsert.urn!, true) + describe('if the URN is in the catalyst', () => { + beforeEach(() => { + mockThirdPartyItemCurationExists(dbItem.id, false) + mockThirdPartyURNExists(itemToUpsert.urn!, true) + }) + it('should fail with 409 and a message saying that the URN is already assigned to another item', () => { + return server + .put(buildURL(url)) + .send({ item: itemToUpsert }) + .set(createAuthHeaders('put', url)) + .expect(409) + .then((response: any) => { + expect(response.body).toEqual({ + data: { + id: itemToUpsert.id, + urn: itemToUpsert.urn!, + }, + error: + "The URN provided already belong to another item. The item can't be inserted or updated.", + ok: false, + }) + }) + }) }) - it('should fail with 409 and a message saying that the item is already published', () => { - return server - .put(buildURL(url)) - .send({ item: itemToUpsert }) - .set(createAuthHeaders('put', url)) - .expect(409) - .then((response: any) => { - expect(response.body).toEqual({ - data: { - id: itemToUpsert.id, - urn: itemToUpsert.urn!, - }, - error: - "The third party item is already published. It can't be inserted or updated.", - ok: false, + describe('if there is a db item with the same third_party_id & urn_suffix', () => { + beforeEach(() => { + mockItem.isURNRepeated.mockResolvedValueOnce(true) + }) + it('should fail with 409 and a message saying that the URN is already assigned to another item', () => { + return server + .put(buildURL(url)) + .send({ item: itemToUpsert }) + .set(createAuthHeaders('put', url)) + .expect(409) + .then((response: any) => { + expect(response.body).toEqual({ + data: { + id: itemToUpsert.id, + urn: itemToUpsert.urn!, + }, + error: + "The URN provided already belong to another item. The item can't be inserted or updated.", + ok: false, + }) }) - }) + }) }) }) }) @@ -1128,7 +1153,7 @@ describe('Item router', () => { urn: itemToUpsert.urn!, }, error: - "The third party item is already published. It can't be inserted or updated.", + "The URN provided already belong to another item. The item can't be inserted.", ok: false, }) }) diff --git a/src/Item/Item.router.ts b/src/Item/Item.router.ts index 491b2009..fc2a5773 100644 --- a/src/Item/Item.router.ts +++ b/src/Item/Item.router.ts @@ -44,6 +44,7 @@ import { ThirdPartyItemAlreadyPublishedError, UnauthorizedToChangeToCollectionError, UnauthorizedToUpsertError, + URNAlreadyInUseError, } from './Item.errors' export class ItemRouter extends Router { @@ -399,6 +400,12 @@ export class ItemRouter extends Router { { id, urn: error.urn }, STATUS_CODES.conflict ) + } else if (error instanceof URNAlreadyInUseError) { + throw new HTTPError( + error.message, + { id, urn: error.urn }, + STATUS_CODES.conflict + ) } else if (error instanceof InvalidItemURNError) { throw new HTTPError(error.message, null, STATUS_CODES.badRequest) } diff --git a/src/Item/Item.service.ts b/src/Item/Item.service.ts index 31aa7402..7018e723 100644 --- a/src/Item/Item.service.ts +++ b/src/Item/Item.service.ts @@ -32,6 +32,7 @@ import { UnauthorizedToUpsertError, UnauthorizedToChangeToCollectionError, InvalidItemURNError, + URNAlreadyInUseError, } from './Item.errors' import { Item } from './Item.model' import { @@ -585,18 +586,8 @@ export class ItemService { dbCollection.urn_suffix!, decodedURN.item_urn_suffix ) - // Check if the new URN is not already in use - const [wearable] = await peerAPI.fetchWearables([ - itemURN, - ]) - if (wearable) { - throw new ThirdPartyItemAlreadyPublishedError( - item.id, - item.urn!, - ItemAction.UPSERT - ) - } + await this.checkIfThirdPartyItemURNExists(item.id, itemURN) } } } @@ -613,17 +604,11 @@ export class ItemService { decodedItemURN.item_urn_suffix ) - // Check if the chosen URN is already in use - const [wearable] = await peerAPI.fetchWearables([ + await this.checkIfThirdPartyItemURNExists( + item.id, itemURN, - ]) - if (wearable) { - throw new ThirdPartyItemAlreadyPublishedError( - item.id, - itemURN, - ItemAction.UPSERT - ) - } + ItemAction.INSERT + ) } const attributes = toDBItem({ @@ -645,4 +630,25 @@ export class ItemService { } return Bridge.toFullItem(upsertedItem, dbCollection) } + + private async checkIfThirdPartyItemURNExists( + id: string, + urn: string, + action = ItemAction.UPSERT + ): Promise { + const decodedItemURN = decodeThirdPartyItemURN(urn) + if ( + await Item.isURNRepeated( + id, + decodedItemURN.third_party_id, + decodedItemURN.item_urn_suffix + ) + ) { + throw new URNAlreadyInUseError(id, urn, action) + } + const [wearable] = await peerAPI.fetchWearables([urn]) + if (wearable) { + throw new URNAlreadyInUseError(id, urn, action) + } + } }