From fed98d51bea411598e9f17078e2dcc69d919c716 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Mon, 20 Oct 2025 18:37:54 -0400 Subject: [PATCH 1/5] feat(userdata): Store user data on the database --- src/adapters/localdb/models/offer.js | 1 + src/use-cases/offer/index.js | 16 +++++++++++----- test/unit/use-cases/offer.use-case.unit.js | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/adapters/localdb/models/offer.js b/src/adapters/localdb/models/offer.js index 823aae2..f36c7a7 100644 --- a/src/adapters/localdb/models/offer.js +++ b/src/adapters/localdb/models/offer.js @@ -47,6 +47,7 @@ const Offer = new mongoose.Schema({ tokenIconUrl: { type: String, default: null }, tokenCategories: { type: Array, default: [] }, tokenTags: { type: Array, default: [] }, + userDataStr: { type: String }, // Token user data lastUpdatedTokenData: { type: String, default: null } // ISO timestamp of the last time token data was updated. }) diff --git a/src/use-cases/offer/index.js b/src/use-cases/offer/index.js index c8a5e29..0999583 100644 --- a/src/use-cases/offer/index.js +++ b/src/use-cases/offer/index.js @@ -141,12 +141,18 @@ class OfferUseCases { // Get the mutable data from the cid if it exists. if (mutableDataCid && typeof mutableDataCid === 'string') { const mutableData = await this.retryQueue.addToQueue(this.adapters.wallet.cid2json, mutableDataCid) - // console.log('mutableData: ', mutableData) + console.log('mutableData: ', mutableData) if (mutableData) { - offerEntity.tokenIconUrl = mutableData.tokenIcon - offerEntity.tokenCategories = mutableData.category - offerEntity.tokenTags = mutableData.tags - offerEntity.lastUpdatedTokenData = new Date().getTime() + try { + offerEntity.tokenIconUrl = mutableData.tokenIcon + offerEntity.tokenCategories = mutableData.category + offerEntity.tokenTags = mutableData.tags + offerEntity.lastUpdatedTokenData = new Date().getTime() + + offerEntity.userDataStr = JSON.stringify(mutableData.userData) + } catch (error) { + // skip error + } } } diff --git a/test/unit/use-cases/offer.use-case.unit.js b/test/unit/use-cases/offer.use-case.unit.js index f9ad063..878a771 100644 --- a/test/unit/use-cases/offer.use-case.unit.js +++ b/test/unit/use-cases/offer.use-case.unit.js @@ -112,7 +112,22 @@ describe('#offer-use-case', () => { const tokenDataMock = mockData.nftTokenData01 const offerObj = mockData.offerMockData const mutableDataMock = mockData.mutableDataMock - console.log('mutableDataMock: ', mutableDataMock) + // Mock dependencies + // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false) + sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found')) + sandbox.stub(uut.retryQueue, 'addToQueue') + .onCall(0).resolves({}) // Utxo Status call + .onCall(1).resolves(tokenDataMock) // Token Data call + .onCall(2).resolves(mutableDataMock) + + const result = await uut.createOffer(offerObj) + assert.isTrue(result) + }) + it('should skip userData stringify error', async () => { + const tokenDataMock = mockData.nftTokenData01 + const offerObj = mockData.offerMockData + const mutableDataMock = mockData.mutableDataMock + mutableDataMock.userData = { n: 10n } // Mock dependencies // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false) sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found')) From fddb5499f0e3cf84ec22cd8f85a92bc378eac7a8 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 21 Oct 2025 12:29:23 -0700 Subject: [PATCH 2/5] Making offer use-case unit tests less flaky --- src/use-cases/offer/index.js | 2 +- test/unit/use-cases/offer.use-case.unit.js | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/use-cases/offer/index.js b/src/use-cases/offer/index.js index 0999583..d0229fe 100644 --- a/src/use-cases/offer/index.js +++ b/src/use-cases/offer/index.js @@ -174,7 +174,7 @@ class OfferUseCases { return true } catch (err) { - console.error('Error in createOffer()', err.message) + console.error('\n\nError in createOffer()', err.message, '\n\n') throw err } } diff --git a/test/unit/use-cases/offer.use-case.unit.js b/test/unit/use-cases/offer.use-case.unit.js index 878a771..436bb97 100644 --- a/test/unit/use-cases/offer.use-case.unit.js +++ b/test/unit/use-cases/offer.use-case.unit.js @@ -112,6 +112,7 @@ describe('#offer-use-case', () => { const tokenDataMock = mockData.nftTokenData01 const offerObj = mockData.offerMockData const mutableDataMock = mockData.mutableDataMock + // Mock dependencies // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false) sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found')) @@ -123,6 +124,7 @@ describe('#offer-use-case', () => { const result = await uut.createOffer(offerObj) assert.isTrue(result) }) + it('should skip userData stringify error', async () => { const tokenDataMock = mockData.nftTokenData01 const offerObj = mockData.offerMockData @@ -139,6 +141,7 @@ describe('#offer-use-case', () => { const result = await uut.createOffer(offerObj) assert.isTrue(result) }) + it('should create offer', async () => { const tokenDataMock = mockData.simpleNftTokenData01 const offerObj = mockData.offerMockData @@ -816,27 +819,17 @@ describe('#offer-use-case', () => { assert.equal(result, 'N/A') }) - it('should return if utxo cant be validated', async () => { + it('should return if utxo can not be validated', 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')) + // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').throws(new Error('test error')) + sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('test error')) const result = await uut.acceptCounterOffer({ data: { /** .... */ } }) assert.equal(result, 'N/A') }) - it('should handle axios error', async () => { - // Mock dependencies - const mock = Object.assign({}, mockData.offerMockData.data) - const stubErr = new Error() - stubErr.isAxiosError = true - sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock) - sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').throws(stubErr) - - const result = await uut.acceptCounterOffer({ data: { /** .... */ } }) - assert.equal(result, 'N/A') - }) it('should return if utxo is invalid', async () => { // Mock dependencies const mock = Object.assign({}, mockData.offerMockData.data) From 2f74b741101637ae480d496398c210276f238ba5 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 21 Oct 2025 12:44:48 -0700 Subject: [PATCH 3/5] fix(createOffer()): Handling API errors when token data can not be retrieved --- src/use-cases/offer/index.js | 85 +++++++++++++--------- test/unit/use-cases/offer.use-case.unit.js | 33 +++++++++ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/src/use-cases/offer/index.js b/src/use-cases/offer/index.js index d0229fe..606a1bc 100644 --- a/src/use-cases/offer/index.js +++ b/src/use-cases/offer/index.js @@ -129,44 +129,57 @@ class OfferUseCases { // Get data about the token. const tokenId = offerEntity.tokenId - // const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId) - const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId) - // console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`) - - // Store the mutable and immutable data cids. - const mutableDataCid = tokenData.mutableData - const immutableDataCid = tokenData.immutableData - offerEntity.mutableDataCid = mutableDataCid - offerEntity.immutableDataCid = immutableDataCid - // Get the mutable data from the cid if it exists. - if (mutableDataCid && typeof mutableDataCid === 'string') { - const mutableData = await this.retryQueue.addToQueue(this.adapters.wallet.cid2json, mutableDataCid) - console.log('mutableData: ', mutableData) - if (mutableData) { - try { - offerEntity.tokenIconUrl = mutableData.tokenIcon - offerEntity.tokenCategories = mutableData.category - offerEntity.tokenTags = mutableData.tags - offerEntity.lastUpdatedTokenData = new Date().getTime() - - offerEntity.userDataStr = JSON.stringify(mutableData.userData) - } catch (error) { - // skip error - } - } + let tokenData = null + try { + tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId) + } catch (err) { + console.error('Error in OfferUseCases/createOffer() getting token data: ', err.message) } - console.log('offerEntity: ', offerEntity) - // Generate a 'display category' for the token. This will allow the - // front end UI to figure out how to display the token. - const displayCategory = this.categorizeToken(offerEntity, tokenData) - console.log('displayCategory: ', displayCategory) - offerEntity.displayCategory = displayCategory + // 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 + offerEntity.mutableDataCid = mutableDataCid + offerEntity.immutableDataCid = immutableDataCid - // Detect if user set the NSFW flag. - // const nsfw = false - // nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData) - // offerEntity.nsfw = nsfw + // 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 { + offerEntity.tokenIconUrl = mutableData.tokenIcon + offerEntity.tokenCategories = mutableData.category + offerEntity.tokenTags = mutableData.tags + offerEntity.lastUpdatedTokenData = new Date().getTime() + + offerEntity.userDataStr = JSON.stringify(mutableData.userData) + } catch (error) { + // skip error + } + } + } + console.log('offerEntity: ', offerEntity) + + // Generate a 'display category' for the token. This will allow the + // front end UI to figure out how to display the token. + const displayCategory = this.categorizeToken(offerEntity, tokenData) + console.log('displayCategory: ', displayCategory) + offerEntity.displayCategory = displayCategory + + // Detect if user set the NSFW flag. + // const nsfw = false + // nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData) + // offerEntity.nsfw = nsfw + } // Add offer to the local database. const offerModel = new this.OfferModel(offerEntity) @@ -174,7 +187,7 @@ class OfferUseCases { return true } catch (err) { - console.error('\n\nError in createOffer()', err.message, '\n\n') + console.error('\n\nError in createOffer()', err.message, '\n\n', err) throw err } } diff --git a/test/unit/use-cases/offer.use-case.unit.js b/test/unit/use-cases/offer.use-case.unit.js index 436bb97..716f132 100644 --- a/test/unit/use-cases/offer.use-case.unit.js +++ b/test/unit/use-cases/offer.use-case.unit.js @@ -142,6 +142,39 @@ describe('#offer-use-case', () => { assert.isTrue(result) }) + it('should handle error getting token data', async () => { + // const tokenDataMock = mockData.nftTokenData01 + const offerObj = mockData.offerMockData + const mutableDataMock = mockData.mutableDataMock + mutableDataMock.userData = { n: 10n } + // Mock dependencies + // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false) + sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found')) + sandbox.stub(uut.retryQueue, 'addToQueue') + .onCall(0).resolves({}) // Utxo Status call + .onCall(1).throws(new Error('test error')) + + const result = await uut.createOffer(offerObj) + assert.isTrue(result) + }) + + it('should handle error getting mutable data', async () => { + const tokenDataMock = mockData.nftTokenData01 + const offerObj = mockData.offerMockData + const mutableDataMock = mockData.mutableDataMock + mutableDataMock.userData = { n: 10n } + // Mock dependencies + // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false) + sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found')) + sandbox.stub(uut.retryQueue, 'addToQueue') + .onCall(0).resolves({}) // Utxo Status call + .onCall(1).resolves(tokenDataMock) // Token Data call + .onCall(2).throws(new Error('test error')) // cid2json call + + const result = await uut.createOffer(offerObj) + assert.isTrue(result) + }) + it('should create offer', async () => { const tokenDataMock = mockData.simpleNftTokenData01 const offerObj = mockData.offerMockData From e570519ec8d89f684f358db5643e5eecab496642 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 21 Oct 2025 12:56:07 -0700 Subject: [PATCH 4/5] Switching default back end to free-bch --- config/env/common.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/env/common.js b/config/env/common.js index 58f058c..9fcdea4 100644 --- a/config/env/common.js +++ b/config/env/common.js @@ -47,8 +47,8 @@ export default { useFullStackCash: process.env.USE_FULLSTACKCASH ? true : false, consumerUrl: process.env.CONSUMER_URL ? process.env.CONSUMER_URL - // : 'https://free-bch.fullstack.cash', - : 'https://dev-consumer.psfoundation.info', + : 'https://free-bch.fullstack.cash', + // : 'https://dev-consumer.psfoundation.info', // : 'https://wa-usa-bch-consumer.fullstackcash.nl', // P2WDB URL that will accept API calls from the p2wdb npm library. From 8cd2896115879e1885863cc59d98627c70e13eaf Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 21 Oct 2025 12:56:23 -0700 Subject: [PATCH 5/5] linting --- config/env/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/env/common.js b/config/env/common.js index 9fcdea4..448f1bf 100644 --- a/config/env/common.js +++ b/config/env/common.js @@ -48,7 +48,7 @@ export default { consumerUrl: process.env.CONSUMER_URL ? process.env.CONSUMER_URL : 'https://free-bch.fullstack.cash', - // : 'https://dev-consumer.psfoundation.info', + // : 'https://dev-consumer.psfoundation.info', // : 'https://wa-usa-bch-consumer.fullstackcash.nl', // P2WDB URL that will accept API calls from the p2wdb npm library.