mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
Merge pull request #114 from Permissionless-Software-Foundation/dh-update-endpoint
feat(update): Added Update button REST endpoint
This commit is contained in:
@@ -34,6 +34,7 @@ class OfferRESTControllerLib {
|
|||||||
this.listFungibleOffers = this.listFungibleOffers.bind(this)
|
this.listFungibleOffers = this.listFungibleOffers.bind(this)
|
||||||
this.takeOffer = this.takeOffer.bind(this)
|
this.takeOffer = this.takeOffer.bind(this)
|
||||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||||
|
this.syncOfferMutableData = this.syncOfferMutableData.bind(this)
|
||||||
this.handleError = this.handleError.bind(this)
|
this.handleError = this.handleError.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,6 +396,18 @@ class OfferRESTControllerLib {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncOfferMutableData (ctx) {
|
||||||
|
try {
|
||||||
|
const tokenId = ctx.request.body.tokenId
|
||||||
|
const offer = await this.useCases.offer.syncOfferMutableData(tokenId)
|
||||||
|
|
||||||
|
ctx.body = offer
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error in syncOfferMutableData REST API handler: ', err)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DRY error handler
|
// DRY error handler
|
||||||
handleError (ctx, err) {
|
handleError (ctx, err) {
|
||||||
console.log('err', err.message)
|
console.log('err', err.message)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class OfferRouter {
|
|||||||
// Define the routes and attach the controller.
|
// Define the routes and attach the controller.
|
||||||
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
|
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
|
||||||
this.router.post('/take', this.offerRESTController.takeOffer)
|
this.router.post('/take', this.offerRESTController.takeOffer)
|
||||||
|
this.router.post('/mutable/sync/', this.offerRESTController.syncOfferMutableData)
|
||||||
this.router.get('/list/all/:page', this.offerRESTController.listOffers)
|
this.router.get('/list/all/:page', this.offerRESTController.listOffers)
|
||||||
this.router.get('/list/nft/:page', this.offerRESTController.listNftOffers)
|
this.router.get('/list/nft/:page', this.offerRESTController.listNftOffers)
|
||||||
this.router.get('/list/fungible/:page', this.offerRESTController.listFungibleOffers)
|
this.router.get('/list/fungible/:page', this.offerRESTController.listFungibleOffers)
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class OfferUseCases {
|
|||||||
this.flagOffer = this.flagOffer.bind(this)
|
this.flagOffer = this.flagOffer.bind(this)
|
||||||
this.loadOffers = this.loadOffers.bind(this)
|
this.loadOffers = this.loadOffers.bind(this)
|
||||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||||
|
this.syncOfferMutableData = this.syncOfferMutableData.bind(this)
|
||||||
|
|
||||||
// State
|
// State
|
||||||
this.seenOffers = []
|
this.seenOffers = []
|
||||||
@@ -935,6 +936,90 @@ class OfferUseCases {
|
|||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update offer model mutable data
|
||||||
|
async syncOfferMutableData (tokenId) {
|
||||||
|
try {
|
||||||
|
// validate input
|
||||||
|
if (!tokenId || typeof tokenId !== 'string') {
|
||||||
|
throw new Error('tokenId must be a string!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate existing offer with the associated tokenId
|
||||||
|
const offer = await this.OfferModel.findOne({ tokenId })
|
||||||
|
if (!offer) throw new Error('Associated offer not found!')
|
||||||
|
|
||||||
|
// Verify last update timestamp. This prevents users from spamming the API with requests.
|
||||||
|
// It only updates the mutable data if it has been more than 5 minutes since the last update.
|
||||||
|
const lastUpdateTs = Number(offer.lastUpdatedTokenData)
|
||||||
|
const now = new Date().getTime()
|
||||||
|
const period = 5
|
||||||
|
if (lastUpdateTs) {
|
||||||
|
// add 5 minutes to the last update
|
||||||
|
const lastUpdate = new Date(lastUpdateTs)
|
||||||
|
console.log('lastUpdate', lastUpdate)
|
||||||
|
lastUpdate.setMinutes(lastUpdate.getMinutes() + period)
|
||||||
|
|
||||||
|
console.log(new Date().getMinutes() + ' ' + lastUpdate.getMinutes())
|
||||||
|
// if now is less than the lastUpdate + 5 minutos , them skip.
|
||||||
|
if (now < lastUpdate.getTime()) {
|
||||||
|
console.log('Skipping , token lastUpdate is less than 5 minutes.')
|
||||||
|
return offer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenData = null
|
||||||
|
try {
|
||||||
|
tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
|
||||||
|
} catch (err) {
|
||||||
|
// Dev Note: If getTokenData() fails, the code below will
|
||||||
|
console.error('Error in OfferUseCases/createOffer() getting token data: ', err.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do additional data analysis if the token data was successfully retrieved.
|
||||||
|
if (tokenData) {
|
||||||
|
// Store the mutable and immutable data cids.
|
||||||
|
const mutableDataCid = tokenData.mutableData
|
||||||
|
const immutableDataCid = tokenData.immutableData
|
||||||
|
offer.mutableDataCid = mutableDataCid
|
||||||
|
offer.immutableDataCid = immutableDataCid
|
||||||
|
|
||||||
|
// Get the mutable data from the cid if it exists.
|
||||||
|
if (mutableDataCid && typeof mutableDataCid === 'string') {
|
||||||
|
let mutableData = null
|
||||||
|
try {
|
||||||
|
mutableData = await this.retryQueue.addToQueue(this.adapters.wallet.cid2json, mutableDataCid)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in OfferUseCases/createOffer() getting mutable data: ', err.message)
|
||||||
|
}
|
||||||
|
console.log('mutableData: ', mutableData)
|
||||||
|
|
||||||
|
if (mutableData) {
|
||||||
|
try {
|
||||||
|
offer.tokenIconUrl = mutableData.tokenIcon
|
||||||
|
offer.tokenCategories = mutableData.category
|
||||||
|
offer.tokenTags = mutableData.tags
|
||||||
|
|
||||||
|
offer.userDataStr = JSON.stringify(mutableData.userData)
|
||||||
|
} catch (error) {
|
||||||
|
// skip error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save update time stamp
|
||||||
|
offer.lastUpdatedTokenData = new Date().getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the updated offer data to the database.
|
||||||
|
await offer.save()
|
||||||
|
|
||||||
|
// Return the updated offer data.
|
||||||
|
return offer
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in syncOfferMutableData(): ', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OfferUseCases
|
export default OfferUseCases
|
||||||
|
|||||||
@@ -229,6 +229,27 @@ describe('#Offer-REST-Router', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('#syncOfferMutableData', () => {
|
||||||
|
it('should sync mutable data', async () => {
|
||||||
|
ctx.request.body = { tokenId: 'tokenId' }
|
||||||
|
sandbox.stub(uut.useCases.offer, 'syncOfferMutableData').resolves({})
|
||||||
|
await uut.syncOfferMutableData(ctx)
|
||||||
|
assert.isObject(ctx.body)
|
||||||
|
})
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
ctx.request.body = { tokenId: 'tokenId' }
|
||||||
|
sandbox
|
||||||
|
.stub(uut.useCases.offer, 'syncOfferMutableData')
|
||||||
|
.throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.syncOfferMutableData(ctx)
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('#handleError', () => {
|
describe('#handleError', () => {
|
||||||
it('should still throw error if there is no message', () => {
|
it('should still throw error if there is no message', () => {
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ class Offer {
|
|||||||
async acceptCounterOffer() {
|
async acceptCounterOffer() {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
async syncOfferMutableData(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Order {
|
class Order {
|
||||||
|
|||||||
@@ -852,6 +852,18 @@ describe('#offer-use-case', () => {
|
|||||||
assert.equal(result, 'N/A')
|
assert.equal(result, 'N/A')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return N/A on axios error', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||||
|
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').throws(new Error('test error'))
|
||||||
|
const axiosErr = new Error('axios err')
|
||||||
|
axiosErr.isAxiosError = true
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue').throws(axiosErr)
|
||||||
|
|
||||||
|
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||||
|
assert.equal(result, 'N/A')
|
||||||
|
})
|
||||||
it('should return if utxo can not be validated', async () => {
|
it('should return if utxo can not be validated', async () => {
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
@@ -1010,4 +1022,112 @@ describe('#offer-use-case', () => {
|
|||||||
assert.notEqual('N/A')
|
assert.notEqual('N/A')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#syncOfferMutableData', () => {
|
||||||
|
it('should throw error if tokenId is not provided!', async () => {
|
||||||
|
try {
|
||||||
|
await uut.syncOfferMutableData()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'tokenId must be a string!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error if tokenId is not found!', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').resolves(null)
|
||||||
|
|
||||||
|
await uut.syncOfferMutableData('tokenId')
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Associated offer not found!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return current data if lastUpdatedTokenData is less than 5 minutes', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
const lastUpdatedTokenData = new Date().getTime()
|
||||||
|
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||||
|
offerMock.lastUpdatedTokenData = lastUpdatedTokenData
|
||||||
|
// stub
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||||
|
const spy = sandbox.stub(uut.retryQueue, 'addToQueue').resolves(true)
|
||||||
|
|
||||||
|
const offer = await uut.syncOfferMutableData('tokenId')
|
||||||
|
|
||||||
|
assert.isObject(offer)
|
||||||
|
assert.isTrue(spy.notCalled, 'it should not call retryQueue functions.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sync offer', async () => {
|
||||||
|
// create a timestamp 6 minutes in the past
|
||||||
|
|
||||||
|
// Create offer mock
|
||||||
|
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||||
|
offerMock.save = () => { }
|
||||||
|
|
||||||
|
// Stub functions
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||||
|
.onCall(0).resolves(mockData.simpleNftTokenData01) // Token Data call
|
||||||
|
.onCall(1).resolves(mockData.mutableDataMock)
|
||||||
|
|
||||||
|
const offer = await uut.syncOfferMutableData('tokenId')
|
||||||
|
assert.isObject(offer)
|
||||||
|
assert.isNumber(offer.lastUpdatedTokenData)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should skip userData stringify error', async () => {
|
||||||
|
// create mutable data mock
|
||||||
|
const mutableDataMock = mockData.mutableDataMock
|
||||||
|
mutableDataMock.userData = { n: 10n }
|
||||||
|
|
||||||
|
// create offer mock
|
||||||
|
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||||
|
offerMock.save = () => { }
|
||||||
|
|
||||||
|
// Stub functions
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||||
|
.onCall(0).resolves(mockData.simpleNftTokenData01) // Token Data call
|
||||||
|
.onCall(1).resolves(mutableDataMock)
|
||||||
|
|
||||||
|
const offer = await uut.syncOfferMutableData('tokenId')
|
||||||
|
|
||||||
|
assert.isObject(offer)
|
||||||
|
assert.isNumber(offer.lastUpdatedTokenData)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle error getting token data', async () => {
|
||||||
|
// create offer mock
|
||||||
|
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||||
|
offerMock.save = () => { }
|
||||||
|
|
||||||
|
// Stub functions
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||||
|
.onCall(0).throws(new Error('tokendata error')) // Token Data call
|
||||||
|
.onCall(1).resolves(mockData.mutableDataMock)
|
||||||
|
|
||||||
|
const offer = await uut.syncOfferMutableData('tokenId')
|
||||||
|
assert.isObject(offer)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle error getting mutable data', async () => {
|
||||||
|
// create offer mock
|
||||||
|
const offerMock = Object.assign({}, mockData.offerMockData)
|
||||||
|
offerMock.save = () => { }
|
||||||
|
|
||||||
|
// Stub functions
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').returns(offerMock)
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||||
|
.onCall(0).resolves(mockData.simpleNftTokenData01) // Token Data call
|
||||||
|
.onCall(1).throws(new Error('mutable data error'))
|
||||||
|
|
||||||
|
const offer = await uut.syncOfferMutableData('tokenId')
|
||||||
|
assert.isObject(offer)
|
||||||
|
assert.isNumber(offer.lastUpdatedTokenData)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user