Skip to content

Commit

Permalink
feat: Add specific error for URN duplication cases (#496)
Browse files Browse the repository at this point in the history
* feat: Add specific error for URN duplication cases

* feat: add validation for items URN as well

* chore: changes error message
  • Loading branch information
juanmahidalgo committed Apr 20, 2022
1 parent 337b5a2 commit d5648f8
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 51 deletions.
8 changes: 8 additions & 0 deletions src/Collection/Collection.errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>) {
super(m)
Expand Down
8 changes: 5 additions & 3 deletions src/Collection/Collection.router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
})
})
})
Expand Down Expand Up @@ -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))
Expand All @@ -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.",
})
})
})
Expand Down
7 changes: 7 additions & 0 deletions src/Collection/Collection.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
NonExistentCollectionError,
UnauthorizedCollectionEditError,
UnpublishedCollectionError,
URNAlreadyInUseError,
WrongCollectionError,
} from './Collection.errors'

Expand Down Expand Up @@ -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,
Expand Down
16 changes: 9 additions & 7 deletions src/Collection/Collection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
WrongCollectionError,
UnpublishedCollectionError,
InsufficientSlotsError,
URNAlreadyInUseError,
} from './Collection.errors'

export class CollectionService {
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
)
}

Expand Down Expand Up @@ -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<void> {
if (await Collection.isURNRepeated(id, third_party_id, urn_suffix)) {
throw new AlreadyPublishedCollectionError(
id,
CollectionType.THIRD_PARTY,
action
)
throw new URNAlreadyInUseError(id, urn, action)
}
}
}
8 changes: 8 additions & 0 deletions src/Item/Item.errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions src/Item/Item.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ export class Item extends Model<ItemAttributes> {
WHERE ${where}`)
}

static async isURNRepeated(
id: string,
thirdPartyId: string,
urnSuffix: string
): Promise<boolean> {
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(
Expand Down
65 changes: 45 additions & 20 deletions src/Item/Item.router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
})
})
})
})
})
})
Expand Down Expand Up @@ -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,
})
})
Expand Down
7 changes: 7 additions & 0 deletions src/Item/Item.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
ThirdPartyItemAlreadyPublishedError,
UnauthorizedToChangeToCollectionError,
UnauthorizedToUpsertError,
URNAlreadyInUseError,
} from './Item.errors'

export class ItemRouter extends Router {
Expand Down Expand Up @@ -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)
}
Expand Down
48 changes: 27 additions & 21 deletions src/Item/Item.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
UnauthorizedToUpsertError,
UnauthorizedToChangeToCollectionError,
InvalidItemURNError,
URNAlreadyInUseError,
} from './Item.errors'
import { Item } from './Item.model'
import {
Expand Down Expand Up @@ -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<ThirdPartyWearable>([
itemURN,
])
if (wearable) {
throw new ThirdPartyItemAlreadyPublishedError(
item.id,
item.urn!,
ItemAction.UPSERT
)
}
await this.checkIfThirdPartyItemURNExists(item.id, itemURN)
}
}
}
Expand All @@ -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<ThirdPartyWearable>([
await this.checkIfThirdPartyItemURNExists(
item.id,
itemURN,
])
if (wearable) {
throw new ThirdPartyItemAlreadyPublishedError(
item.id,
itemURN,
ItemAction.UPSERT
)
}
ItemAction.INSERT
)
}

const attributes = toDBItem({
Expand All @@ -645,4 +630,25 @@ export class ItemService {
}
return Bridge.toFullItem(upsertedItem, dbCollection)
}

private async checkIfThirdPartyItemURNExists(
id: string,
urn: string,
action = ItemAction.UPSERT
): Promise<void> {
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<ThirdPartyWearable>([urn])
if (wearable) {
throw new URNAlreadyInUseError(id, urn, action)
}
}
}

0 comments on commit d5648f8

Please sign in to comment.