mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 01:02:00 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac83827fca | ||
|
|
0811024030 | ||
|
|
737d35463d | ||
|
|
2ed1eaba9c | ||
|
|
6dddfbdcb6 | ||
|
|
fc2d7b4b9b | ||
|
|
c877898ae7 | ||
|
|
88e27f47f5 | ||
|
|
9681def3e0 | ||
|
|
0057007b4c | ||
|
|
8aad93e572 | ||
|
|
fa0ec3ece2 | ||
|
|
4f9f2f8370 | ||
|
|
0fb93be2cd | ||
|
|
26dcd6072e | ||
|
|
42f228af0e | ||
|
|
8be70baff2 | ||
|
|
7e5c314000 | ||
|
|
9669112d69 | ||
|
|
9a71763f79 | ||
|
|
1a96eac8c9 | ||
|
|
a2acdef539 | ||
|
|
58ddbbcc4e | ||
|
|
5ff33ff19f | ||
|
|
682f599261 | ||
|
|
7c26e33a8c | ||
|
|
2595ee8295 | ||
|
|
531685c0e4 | ||
|
|
476fdcd0aa | ||
|
|
b6762b215e | ||
|
|
4ac7f59378 | ||
|
|
3cdf49bb56 | ||
|
|
28615403b8 | ||
|
|
bb10bd819a | ||
|
|
3882217e6a | ||
|
|
8cd2896115 | ||
|
|
e570519ec8 | ||
|
|
2f74b74110 | ||
|
|
fddb5499f0 | ||
|
|
fed98d51be | ||
|
|
8e3ee3a0fb | ||
|
|
e57faf8f26 | ||
|
|
725c1c39a5 | ||
|
|
61fa6b0ed6 | ||
|
|
99843061ab | ||
|
|
3a23763800 | ||
|
|
3f4d49a228 | ||
|
|
88cfadbc17 | ||
|
|
6c1da41cb3 | ||
|
|
0e6a411012 | ||
|
|
55e9e35c1f | ||
|
|
84603356ef | ||
|
|
2f6b4dfc09 | ||
|
|
7290c8db30 | ||
|
|
36e1ec1f8e | ||
|
|
baa574a93e | ||
|
|
8333563d98 | ||
|
|
99fd116b6d | ||
|
|
ccae89ad01 | ||
|
|
0ecae58be2 | ||
|
|
5f442835e5 | ||
|
|
aa8fd89227 | ||
|
|
3da7432273 | ||
|
|
61c1dd5878 | ||
|
|
c095e7af39 | ||
|
|
fb9612bb65 | ||
|
|
e4a37b1c10 | ||
|
|
ba4f847e57 | ||
|
|
81e623e35f | ||
|
|
db1da9172c |
Vendored
+2
-2
@@ -47,8 +47,8 @@ export default {
|
|||||||
useFullStackCash: process.env.USE_FULLSTACKCASH ? true : false,
|
useFullStackCash: process.env.USE_FULLSTACKCASH ? true : false,
|
||||||
consumerUrl: process.env.CONSUMER_URL
|
consumerUrl: process.env.CONSUMER_URL
|
||||||
? process.env.CONSUMER_URL
|
? process.env.CONSUMER_URL
|
||||||
// : 'https://free-bch.fullstack.cash',
|
: 'https://free-bch.fullstack.cash',
|
||||||
: 'https://dev-consumer.psfoundation.info',
|
// : 'https://dev-consumer.psfoundation.info',
|
||||||
// : 'https://wa-usa-bch-consumer.fullstackcash.nl',
|
// : 'https://wa-usa-bch-consumer.fullstackcash.nl',
|
||||||
|
|
||||||
// P2WDB URL that will accept API calls from the p2wdb npm library.
|
// P2WDB URL that will accept API calls from the p2wdb npm library.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v
|
|||||||
WORKDIR /home/safeuser/bch-dex-ui-v3
|
WORKDIR /home/safeuser/bch-dex-ui-v3
|
||||||
|
|
||||||
# For development: switch to unstable branch
|
# For development: switch to unstable branch
|
||||||
RUN git checkout ct-unstable
|
RUN git checkout psfoundation
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
#RUN mkdir .ipfsdata
|
#RUN mkdir .ipfsdata
|
||||||
|
|||||||
@@ -31,4 +31,7 @@ export APP_ID=bch-dex-001
|
|||||||
|
|
||||||
#export P2WDB_URL=http://172.17.0.1:5667
|
#export P2WDB_URL=http://172.17.0.1:5667
|
||||||
|
|
||||||
|
export DISABLE_NEW_ACCOUNTS=true
|
||||||
|
export ADMIN_PASSWORD=decatur
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"privateKey": "L4VyBDiWYYeeZhkM3xE3nsZigXJYmvRM29YBxoC7da4FzvubjxFo",
|
||||||
|
"publicKey": "028d6c77d865e6140db4b879a9bdf69ee4846593c0b6c48a82c448d5792806aab1",
|
||||||
|
"mnemonic": "split title trigger soda execute frost brush labor vague protect bicycle fiscal",
|
||||||
|
"cashAddress": "bitcoincash:qprrdk7en7m774gsafkycxcymc9ry209jus5v8wx5z",
|
||||||
|
"address": "bitcoincash:qprrdk7en7m774gsafkycxcymc9ry209jus5v8wx5z",
|
||||||
|
"legacyAddress": "17QG25rNUfnfj2NtdaFiyiqKSJatvEnMYd",
|
||||||
|
"hdPath": "m/44'/245'/0'/0/0",
|
||||||
|
"slpAddress": "simpleledger:qprrdk7en7m774gsafkycxcymc9ry209juu08umx2u",
|
||||||
|
"nextAddress": 1
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ import Order from './models/order.js'
|
|||||||
import Offer from './models/offer.js'
|
import Offer from './models/offer.js'
|
||||||
import Usage from './models/usage.js'
|
import Usage from './models/usage.js'
|
||||||
import SmAccount from './models/smAccount.js'
|
import SmAccount from './models/smAccount.js'
|
||||||
|
import DeletedChat from './models/deletedChat.js'
|
||||||
|
import DeletedPost from './models/deletedPost.js'
|
||||||
|
import CounterOffer from './models/counter-offer.js'
|
||||||
|
|
||||||
class LocalDB {
|
class LocalDB {
|
||||||
constructor () {
|
constructor () {
|
||||||
@@ -19,6 +22,9 @@ class LocalDB {
|
|||||||
this.Offer = Offer
|
this.Offer = Offer
|
||||||
this.Usage = Usage
|
this.Usage = Usage
|
||||||
this.SmAccount = SmAccount
|
this.SmAccount = SmAccount
|
||||||
|
this.DeletedChat = DeletedChat
|
||||||
|
this.DeletedPost = DeletedPost
|
||||||
|
this.CounterOffer = CounterOffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import mongoose from 'mongoose'
|
||||||
|
|
||||||
|
const CounterOffer = new mongoose.Schema({
|
||||||
|
nostrEventId: { type: String, required: true }, // Nostr Event Id.
|
||||||
|
takerAddr: { type: String, default: null },
|
||||||
|
takerNpub: { type: String, default: null },
|
||||||
|
makerNpub: { type: String, default: null },
|
||||||
|
counterOfferAddr: { type: String, default: null },
|
||||||
|
counterOfferUtxo: { type: String, default: null },
|
||||||
|
takerOfferUtxo: { type: String, default: null },
|
||||||
|
tokenId: { type: String, default: null }
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mongoose.model('counterOffer', CounterOffer)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Database model for tracking deleted Nostr chat messages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import mongoose from 'mongoose'
|
||||||
|
|
||||||
|
const DeletedChat = new mongoose.Schema({
|
||||||
|
eventId: { type: String, required: true, default: null }, // Event ID of the deleted message.
|
||||||
|
npub: { type: String, default: null }, // Nostr public key of the user who created the message.
|
||||||
|
bchAddr: { type: String, default: null }, // bch address of the user who created the message.
|
||||||
|
pubkey: { type: String, default: null }, // Nostr public key of the user who created the message.
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mongoose.model('deletedChat', DeletedChat)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Database model for tracking deleted Nostr posts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import mongoose from 'mongoose'
|
||||||
|
|
||||||
|
const DeletedPost = new mongoose.Schema({
|
||||||
|
eventId: { type: String, required: true, default: null }, // Event ID of the deleted post.
|
||||||
|
npub: { type: String, default: null }, // Nostr public key of the user who created the post.
|
||||||
|
bchAddr: { type: String, default: null }, // bch address of the user who created the post.
|
||||||
|
pubkey: { type: String, default: null }, // Nostr public key of the user who created the post.
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mongoose.model('deletedPost', DeletedPost)
|
||||||
@@ -18,6 +18,7 @@ const Offer = new mongoose.Schema({
|
|||||||
p2wdbHash: { type: String },
|
p2wdbHash: { type: String },
|
||||||
offerStatus: { type: String },
|
offerStatus: { type: String },
|
||||||
makerAddr: { type: String },
|
makerAddr: { type: String },
|
||||||
|
makerNpub: { type: String, default: null },
|
||||||
|
|
||||||
// Authentication data
|
// Authentication data
|
||||||
signature: { type: String },
|
signature: { type: String },
|
||||||
@@ -39,7 +40,17 @@ const Offer = new mongoose.Schema({
|
|||||||
|
|
||||||
// Operator fee and address
|
// Operator fee and address
|
||||||
operatorAddress: { type: String },
|
operatorAddress: { type: String },
|
||||||
operatorPercentage: { type: Number }
|
operatorPercentage: { type: Number },
|
||||||
|
|
||||||
|
// Token Data
|
||||||
|
immutableDataCid: { type: String, default: null },
|
||||||
|
mutableDataCid: { type: String, default: null },
|
||||||
|
tokenIconUrl: { type: String, default: null },
|
||||||
|
tokenData: { type: Object },
|
||||||
|
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.
|
||||||
})
|
})
|
||||||
|
|
||||||
export default mongoose.model('offer', Offer)
|
export default mongoose.model('offer', Offer)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const Order = new mongoose.Schema({
|
|||||||
p2wdbTxid: { type: String },
|
p2wdbTxid: { type: String },
|
||||||
p2wdbHash: { type: String },
|
p2wdbHash: { type: String },
|
||||||
makerAddr: { type: String, required: true },
|
makerAddr: { type: String, required: true },
|
||||||
|
makerNpub: { type: String, required: true },
|
||||||
|
|
||||||
// Authentication data
|
// Authentication data
|
||||||
signature: { type: String },
|
signature: { type: String },
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import BchNostr from 'bch-nostr'
|
|||||||
import { RelayPool } from 'nostr'
|
import { RelayPool } from 'nostr'
|
||||||
import RetryQueue from '@chris.troutner/retry-queue'
|
import RetryQueue from '@chris.troutner/retry-queue'
|
||||||
import * as nip19 from 'nostr-tools/nip19'
|
import * as nip19 from 'nostr-tools/nip19'
|
||||||
|
import { base58_to_binary as base58Tobinary } from 'base58-js'
|
||||||
|
import { getPublicKey } from 'nostr-tools/pure'
|
||||||
|
|
||||||
class NostrAdapter {
|
class NostrAdapter {
|
||||||
constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) {
|
constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) {
|
||||||
@@ -44,6 +46,7 @@ class NostrAdapter {
|
|||||||
this.npub2pubkey = this.npub2pubkey.bind(this)
|
this.npub2pubkey = this.npub2pubkey.bind(this)
|
||||||
this.readGlobalFeed = this.readGlobalFeed.bind(this)
|
this.readGlobalFeed = this.readGlobalFeed.bind(this)
|
||||||
this.getFollowers = this.getFollowers.bind(this)
|
this.getFollowers = this.getFollowers.bind(this)
|
||||||
|
this.privKeyToNpub = this.privKeyToNpub.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create nostr keys.
|
// Create nostr keys.
|
||||||
@@ -247,6 +250,31 @@ class NostrAdapter {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert a BCH private key (WIF) to a Nostr npub.
|
||||||
|
privKeyToNpub (privKey) {
|
||||||
|
try {
|
||||||
|
if (!privKey || typeof privKey !== 'string') {
|
||||||
|
throw new Error('privKey must be a string!')
|
||||||
|
}
|
||||||
|
// Extract the privaty key from the WIF, using this guide:
|
||||||
|
// https://learnmeabitcoin.com/technical/keys/private-key/wif/
|
||||||
|
const wifBuf = base58Tobinary(privKey)
|
||||||
|
const privBuf = wifBuf.slice(1, 33)
|
||||||
|
// console.log('privBuf: ', privBuf)
|
||||||
|
|
||||||
|
const nostrPubKey = getPublicKey(privBuf)
|
||||||
|
// console.log('nostrPubKey: ', nostrPubKey)
|
||||||
|
// Convert pubkey to npub
|
||||||
|
const nostrNpub = nip19.npubEncode(nostrPubKey)
|
||||||
|
// console.log('nostrNpub: ', nostrNpub)
|
||||||
|
|
||||||
|
return nostrNpub
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error in nostr.js/privKeyToNpub() ${error.message} `)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NostrAdapter
|
export default NostrAdapter
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class WalletAdapter {
|
|||||||
this.completeTx = this.completeTx.bind(this)
|
this.completeTx = this.completeTx.bind(this)
|
||||||
this.reclaimTokens = this.reclaimTokens.bind(this)
|
this.reclaimTokens = this.reclaimTokens.bind(this)
|
||||||
this.moveTokensFromCustomWallet = this.moveTokensFromCustomWallet.bind(this)
|
this.moveTokensFromCustomWallet = this.moveTokensFromCustomWallet.bind(this)
|
||||||
|
this.cid2json = this.cid2json.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the wallet file, or create one if the file doesn't exist.
|
// Open the wallet file, or create one if the file doesn't exist.
|
||||||
@@ -677,6 +678,25 @@ class WalletAdapter {
|
|||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cid2json (urlOrCid) {
|
||||||
|
try {
|
||||||
|
// Input validation
|
||||||
|
if (!urlOrCid || typeof urlOrCid !== 'string') {
|
||||||
|
throw new Error('urlOrCid must be a string!')
|
||||||
|
}
|
||||||
|
// Extract the cid from the url or cid.
|
||||||
|
const cid = urlOrCid.split('/').pop()
|
||||||
|
console.log('cid to json: ', cid)
|
||||||
|
const jsonRes = await this.bchWallet.cid2json({ cid })
|
||||||
|
const json = jsonRes.json
|
||||||
|
// console.log('json: ', json)
|
||||||
|
return json
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in wallet.js/cid2json()', err.message)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WalletAdapter
|
export default WalletAdapter
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import OrderRouter from './order/index.js'
|
|||||||
import P2WDBRouter from './p2wdb/index.js'
|
import P2WDBRouter from './p2wdb/index.js'
|
||||||
import UsageRESTController from './usage/index.js'
|
import UsageRESTController from './usage/index.js'
|
||||||
import SmAccountRouter from './smAccount/index.js'
|
import SmAccountRouter from './smAccount/index.js'
|
||||||
|
import NostrRouter from './nostr/index.js'
|
||||||
|
|
||||||
class RESTControllers {
|
class RESTControllers {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -90,6 +91,10 @@ class RESTControllers {
|
|||||||
|
|
||||||
const smAccountRouter = new SmAccountRouter(dependencies)
|
const smAccountRouter = new SmAccountRouter(dependencies)
|
||||||
smAccountRouter.attach(app)
|
smAccountRouter.attach(app)
|
||||||
|
|
||||||
|
// Attach the REST API Controllers associated with the /nostr route
|
||||||
|
const nostrRouter = new NostrRouter(dependencies)
|
||||||
|
nostrRouter.attach(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,16 +71,25 @@ class IpfsRESTControllerLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {POST} /ipfs/peers Get IPFS peers
|
* @api {post} /ipfs/peers Get information on IPFS peers this node is connected to
|
||||||
* @apiPermission public
|
* @apiPermission public
|
||||||
* @apiName GetIpfsPeers
|
* @apiName GetIpfsPeers
|
||||||
* @apiGroup REST IPFS
|
* @apiGroup REST IPFS
|
||||||
*
|
*
|
||||||
* @apiExample Example usage:
|
* @apiParam {Boolean} [showAll=false] Whether to include detailed peer data
|
||||||
* curl -H "Content-Type: application/json" -X POST -d '{ "showAll": true }' localhost:5001/ipfs/peers
|
|
||||||
*
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/peers \
|
||||||
|
* -d '{"showAll": false}'
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object[]} peers Array of peer objects
|
||||||
|
* @apiSuccess {String} peers[].peer Peer ID
|
||||||
|
* @apiSuccess {String} peers[].name Peer name
|
||||||
|
* @apiSuccess {String} peers[].protocol Protocol used by the peer
|
||||||
|
* @apiSuccess {String} peers[].version Peer version
|
||||||
|
* @apiSuccess {String} peers[].connectionAddr Connection address
|
||||||
|
* @apiSuccess {Object} [peers[].peerData] Detailed peer data (when showAll=true)
|
||||||
*/
|
*/
|
||||||
// Return information on IPFS peers this node is connected to.
|
|
||||||
async getPeers (ctx) {
|
async getPeers (ctx) {
|
||||||
try {
|
try {
|
||||||
const showAll = ctx.request.body.showAll
|
const showAll = ctx.request.body.showAll
|
||||||
@@ -96,16 +105,22 @@ class IpfsRESTControllerLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {POST} /ipfs/relays Get IPFS relays
|
* @api {post} /ipfs/relays Get data about the known Circuit Relays
|
||||||
* @apiPermission public
|
* @apiPermission public
|
||||||
* @apiName GetIpfsRelays
|
* @apiName GetIpfsRelays
|
||||||
* @apiGroup REST IPFS
|
* @apiGroup REST IPFS
|
||||||
*
|
*
|
||||||
|
* @apiDescription Returns information about Circuit Relays, both v1 and v2, that this node knows about. V2 relays are hydrated with peer data from the connected peers list.
|
||||||
|
*
|
||||||
* @apiExample Example usage:
|
* @apiExample Example usage:
|
||||||
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/relays
|
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/relays
|
||||||
*
|
*
|
||||||
* @apiSuccessExample {json} Success-Response:
|
* @apiSuccess {Object} relays Object containing relay information
|
||||||
* HTTP/1.1 200 OK
|
* @apiSuccess {Object[]} relays.v2Relays Array of v2 Circuit Relay objects
|
||||||
|
* @apiSuccess {String} relays.v2Relays[].ipfsId IPFS ID of the relay
|
||||||
|
* @apiSuccess {String} relays.v2Relays[].name Name of the relay (hydrated from peer data)
|
||||||
|
* @apiSuccess {String} relays.v2Relays[].description Description of the relay (hydrated from peer data)
|
||||||
|
* @apiSuccess {Object[]} relays.v1Relays Array of v1 Circuit Relay configurations
|
||||||
*/
|
*/
|
||||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||||
async getRelays (ctx) {
|
async getRelays (ctx) {
|
||||||
@@ -121,15 +136,22 @@ class IpfsRESTControllerLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @api {post} /ipfs/connect Connect to a specific IPFS peer
|
||||||
* @api {POST} /ipfs/connect Connect to an IPFS peer
|
|
||||||
* @apiPermission public
|
* @apiPermission public
|
||||||
* @apiName ConnectToIpfsPeer
|
* @apiName ConnectToIpfsPeer
|
||||||
* @apiGroup REST IPFS
|
* @apiGroup REST IPFS
|
||||||
*
|
*
|
||||||
* @apiExample Example usage:
|
* @apiDescription Attempts to establish a connection to a specific IPFS peer using the provided multiaddr. Optionally returns detailed information about the connection.
|
||||||
* curl -H "Content-Type: application/json" -X POST -d '{ "multiaddr": "/ip4/123.45.67.89/tcp/4001/p2p/1234567890", "getDetails": true }' localhost:5001/ipfs/connect
|
|
||||||
*
|
*
|
||||||
|
* @apiParam {String} multiaddr Multiaddress of the peer to connect to (required)
|
||||||
|
* @apiParam {Boolean} [getDetails=false] Whether to return detailed connection information
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/connect \
|
||||||
|
* -d '{"multiaddr": "/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu", "getDetails": false}'
|
||||||
|
*
|
||||||
|
* @apiSuccess {Boolean} success Indicates whether the connection attempt was successful
|
||||||
|
* @apiSuccess {Object} [details] Additional connection details (when getDetails=true)
|
||||||
*/
|
*/
|
||||||
async connect (ctx) {
|
async connect (ctx) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
REST API Controller library for the /nostr route
|
||||||
|
*/
|
||||||
|
import wlogger from '../../../adapters/wlogger.js'
|
||||||
|
|
||||||
|
class NostrRESTControllerLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating /nostr REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating /nostr REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createDeletedChat = this.createDeletedChat.bind(this)
|
||||||
|
this.getDeletedChats = this.getDeletedChats.bind(this)
|
||||||
|
this.createDeletedPost = this.createDeletedPost.bind(this)
|
||||||
|
this.getDeletedPosts = this.getDeletedPosts.bind(this)
|
||||||
|
this.handleError = this.handleError.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /nostr/deletedChat Create Deleted Chat.
|
||||||
|
* @apiPermission admin
|
||||||
|
* @apiName CreateDeletedChat
|
||||||
|
* @apiGroup REST Nostr
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" -X POST -d '{"eventId": "note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q" , "npub" : "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8" , "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", "pubkey":"6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1"}' localhost:5700/nostr/deletedChat
|
||||||
|
*
|
||||||
|
* @apiSuccess {String} _id DB Model id
|
||||||
|
* @apiSuccess {String} eventId Event id
|
||||||
|
* @apiSuccess {String} bchAddr BCH Address
|
||||||
|
* @apiSuccess {String} npub Nostr Npub
|
||||||
|
* @apiSuccess {String} pubkey Nostr pubkey
|
||||||
|
* @apiSuccess {Date} createdAt Created at date.
|
||||||
|
* @apiSuccess {Date} updatedAt Updated at date.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiSuccessExample {json} Success-Response:
|
||||||
|
* HTTP/1.1 200 OK
|
||||||
|
* {
|
||||||
|
* "_id": "6900f87f9cafee38028ea27e",
|
||||||
|
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||||
|
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||||
|
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||||
|
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||||
|
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @apiUse TokenError
|
||||||
|
*/
|
||||||
|
async createDeletedChat (ctx) {
|
||||||
|
try {
|
||||||
|
const chat = ctx.request.body
|
||||||
|
|
||||||
|
const result = await this.useCases.nostr.createDeletedChat(chat)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
deletedChat: result
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in createDeletedChat() REST API handler.')
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /nostr/deletedChat Get Deleted Chats.
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetDeletedChats
|
||||||
|
* @apiGroup REST Nostr
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5700/nostr/deletedChat
|
||||||
|
*
|
||||||
|
* @apiSuccess {String} _id DB Model id
|
||||||
|
* @apiSuccess {String} eventId Event id
|
||||||
|
* @apiSuccess {String} bchAddr BCH Address
|
||||||
|
* @apiSuccess {String} npub Nostr Npub
|
||||||
|
* @apiSuccess {String} pubkey Nostr pubkey
|
||||||
|
* @apiSuccess {Date} createdAt Created at date.
|
||||||
|
* @apiSuccess {Date} updatedAt Updated at date.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiSuccessExample {json} Success-Response:
|
||||||
|
* HTTP/1.1 200 OK
|
||||||
|
* [{
|
||||||
|
* "_id": "6900f87f9cafee38028ea27e",
|
||||||
|
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||||
|
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||||
|
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||||
|
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||||
|
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||||
|
* }]
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
async getDeletedChats (ctx) {
|
||||||
|
try {
|
||||||
|
const deletedChats = await this.useCases.nostr.getDeletedChats()
|
||||||
|
|
||||||
|
ctx.body = { deletedChats }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in getDeletedChats() REST API handler.')
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /nostr/deletedPost Create Deleted Post.
|
||||||
|
* @apiPermission admin
|
||||||
|
* @apiName CreateDeletedPost
|
||||||
|
* @apiGroup REST Nostr
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" -X POST -d '{"eventId": "note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q" , "npub" : "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8" , "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", "pubkey":"6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1"}' localhost:5700/nostr/deletedPost
|
||||||
|
*
|
||||||
|
* @apiSuccess {String} _id DB Model id
|
||||||
|
* @apiSuccess {String} eventId Event id
|
||||||
|
* @apiSuccess {String} bchAddr BCH Address
|
||||||
|
* @apiSuccess {String} npub Nostr Npub
|
||||||
|
* @apiSuccess {String} pubkey Nostr pubkey
|
||||||
|
* @apiSuccess {Date} createdAt Created at date.
|
||||||
|
* @apiSuccess {Date} updatedAt Updated at date.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiSuccessExample {json} Success-Response:
|
||||||
|
* HTTP/1.1 200 OK
|
||||||
|
* {
|
||||||
|
* "_id": "6900f87f9cafee38028ea27e",
|
||||||
|
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||||
|
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||||
|
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||||
|
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||||
|
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @apiUse TokenError
|
||||||
|
*/
|
||||||
|
async createDeletedPost (ctx) {
|
||||||
|
try {
|
||||||
|
const post = ctx.request.body
|
||||||
|
|
||||||
|
const result = await this.useCases.nostr.createDeletedPost(post)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
deletedPost: result
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in createDeletedPost() REST API handler.')
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /nostr/deletedPost Get Deleted Posts.
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetDeletedPosts
|
||||||
|
* @apiGroup REST Nostr
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5700/nostr/deletedPost
|
||||||
|
*
|
||||||
|
* @apiSuccess {String} _id DB Model id
|
||||||
|
* @apiSuccess {String} eventId Event id
|
||||||
|
* @apiSuccess {String} bchAddr BCH Address
|
||||||
|
* @apiSuccess {String} npub Nostr Npub
|
||||||
|
* @apiSuccess {String} pubkey Nostr pubkey
|
||||||
|
* @apiSuccess {Date} createdAt Created at date.
|
||||||
|
* @apiSuccess {Date} updatedAt Updated at date.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiSuccessExample {json} Success-Response:
|
||||||
|
* HTTP/1.1 200 OK
|
||||||
|
* [{
|
||||||
|
* "_id": "6900f87f9cafee38028ea27e",
|
||||||
|
* "npub": "npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8",
|
||||||
|
* "bchAddr" : "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a",
|
||||||
|
* "pubKey": "6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1",
|
||||||
|
* "createdAt":"2025-10-28T17:08:15.409Z",
|
||||||
|
* "updatedAt": "2025-10-28T17:08:15.409Z"
|
||||||
|
* }]
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
async getDeletedPosts (ctx) {
|
||||||
|
try {
|
||||||
|
const deletedPosts = await this.useCases.nostr.getDeletedPosts()
|
||||||
|
|
||||||
|
ctx.body = { deletedPosts }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in getDeletedPosts() REST API handler.')
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DRY error handler
|
||||||
|
handleError (ctx, err) {
|
||||||
|
// If an HTTP status is specified by the buisiness logic, use that.
|
||||||
|
if (err.status) {
|
||||||
|
if (err.message) {
|
||||||
|
ctx.throw(err.status, err.message)
|
||||||
|
} else {
|
||||||
|
ctx.throw(err.status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default use a 422 error if the HTTP status is not specified.
|
||||||
|
ctx.throw(422, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NostrRESTControllerLib
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
REST API library for /nostr route.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries.
|
||||||
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
// Local libraries.
|
||||||
|
import NostrRESTControllerLib from './controller.js'
|
||||||
|
|
||||||
|
import Validators from '../middleware/validators.js'
|
||||||
|
|
||||||
|
import config from '../../../../config/index.js'
|
||||||
|
|
||||||
|
class NostrRouter {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating NostrRouter REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating NostrRouter REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = {
|
||||||
|
adapters: this.adapters,
|
||||||
|
useCases: this.useCases
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies.
|
||||||
|
this.config = config
|
||||||
|
this.nostrRESTController = new NostrRESTControllerLib(dependencies)
|
||||||
|
this.validators = new Validators()
|
||||||
|
|
||||||
|
// Instantiate the router and set the base route.
|
||||||
|
const baseUrl = '/nostr'
|
||||||
|
this.router = new Router({ prefix: baseUrl })
|
||||||
|
|
||||||
|
this.attach = this.attach.bind(this)
|
||||||
|
this.createDeletedChat = this.createDeletedChat.bind(this)
|
||||||
|
this.createDeletedPost = this.createDeletedPost.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
attach (app) {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error(
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the routes and attach the controller.
|
||||||
|
this.router.post('/deletedChat', this.createDeletedChat)
|
||||||
|
this.router.get('/deletedChat', this.nostrRESTController.getDeletedChats)
|
||||||
|
this.router.post('/deletedPost', this.createDeletedPost)
|
||||||
|
this.router.get('/deletedPost', this.nostrRESTController.getDeletedPosts)
|
||||||
|
|
||||||
|
// Attach the Controller routes to the Koa app.
|
||||||
|
app.use(this.router.routes())
|
||||||
|
app.use(this.router.allowedMethods())
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDeletedChat (ctx, next) {
|
||||||
|
await this.validators.ensureAdmin(ctx, next)
|
||||||
|
await this.nostrRESTController.createDeletedChat(ctx, next)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDeletedPost (ctx, next) {
|
||||||
|
await this.validators.ensureAdmin(ctx, next)
|
||||||
|
await this.nostrRESTController.createDeletedPost(ctx, next)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NostrRouter
|
||||||
@@ -34,6 +34,8 @@ 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.listCounterOffersByAddress = this.listCounterOffersByAddress.bind(this)
|
||||||
this.handleError = this.handleError.bind(this)
|
this.handleError = this.handleError.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,6 +397,64 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /offer/list/counter-offer/:addr List all counter offers being made by a given address
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName listCounterOffersByAddress
|
||||||
|
* @apiGroup REST Offer
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5000/offer/list/counter-offer/bitcoincash:qq54a3xyyptty63ztlcavzzh64gy4d247qs4ueaupe
|
||||||
|
*
|
||||||
|
*
|
||||||
|
|
||||||
|
* @apiSuccess {String} offers.nostrEventId Nostr event id
|
||||||
|
* @apiSuccess {String} offers.takerNpub Nostr Npub.
|
||||||
|
* @apiSuccess {String} offers.takerAddr BCH Address.
|
||||||
|
* @apiSuccess {String} offers.counterOfferAddr BCH Address
|
||||||
|
* @apiSuccess {String} offers.counterOfferUtxo Utxo txid.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiSuccessExample {json} Success-Response:
|
||||||
|
* HTTP/1.1 200 OK
|
||||||
|
* {
|
||||||
|
* "counterOffers": [{
|
||||||
|
* "_id": "56bd1da600a526986cf65c80",
|
||||||
|
* "nostrEventId": "1234567890",
|
||||||
|
* "takerAddr": "bitcoincash:qq54a3xyyptty63ztlcavzzh64gy4d247qs4ueaupe",
|
||||||
|
* "takerNpub": "npub1nhc47z2tf42a4ps7plzcyax2j76gdausve763tqw3xuw7jjd7ceqppquly",
|
||||||
|
* "counterOfferAddr": "bitcoincash:qzltx40ldxr53edchprppj5lpg4q7vl20usx25ga44",
|
||||||
|
* "counterOfferUtxo": "71dab5b81cf06f3495aeebcc70fb351109e5e88bc85da67d0d144c0e5ca0bd6a",
|
||||||
|
* }]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @apiUse TokenError
|
||||||
|
*/
|
||||||
|
async listCounterOffersByAddress (ctx) {
|
||||||
|
try {
|
||||||
|
const addr = ctx.params.addr
|
||||||
|
|
||||||
|
const counterOffers = await this.useCases.offer.listCounterOffersByAddress(addr)
|
||||||
|
|
||||||
|
ctx.body = { counterOffers }
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error in listCounterOffersByAddress 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,10 +55,12 @@ 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)
|
||||||
this.router.get('/list/addr/:addr', this.offerRESTController.listOffersByAddress)
|
this.router.get('/list/addr/:addr', this.offerRESTController.listOffersByAddress)
|
||||||
|
this.router.get('/list/counter-offer/:addr', this.offerRESTController.listCounterOffersByAddress)
|
||||||
|
|
||||||
// Attach the Controller routes to the Koa app.
|
// Attach the Controller routes to the Koa app.
|
||||||
app.use(this.router.routes())
|
app.use(this.router.routes())
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class OrderRouter {
|
|||||||
// Define the routes and attach the controller.
|
// Define the routes and attach the controller.
|
||||||
this.router.post('/', this.createOrder)
|
this.router.post('/', this.createOrder)
|
||||||
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
|
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
|
||||||
this.router.post('/delete', _this.orderRESTController.deleteOrder)
|
this.router.post('/delete', this.deleteOrder)
|
||||||
|
|
||||||
// Attach the Controller routes to the Koa app.
|
// Attach the Controller routes to the Koa app.
|
||||||
app.use(_this.router.routes())
|
app.use(_this.router.routes())
|
||||||
@@ -65,6 +65,11 @@ class OrderRouter {
|
|||||||
await _this.validators.ensureUser(ctx, next)
|
await _this.validators.ensureUser(ctx, next)
|
||||||
await _this.orderRESTController.createOrder(ctx, next)
|
await _this.orderRESTController.createOrder(ctx, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteOrder (ctx, next) {
|
||||||
|
await _this.validators.ensureUser(ctx, next)
|
||||||
|
await _this.orderRESTController.deleteOrder(ctx, next)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OrderRouter
|
export default OrderRouter
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class TimerControllers {
|
|||||||
this.backupUsageInterval = 60000 * 10 // 10 minutes
|
this.backupUsageInterval = 60000 * 10 // 10 minutes
|
||||||
this.newSmAccountsInterval = 60000 * 60 // 60 minutes
|
this.newSmAccountsInterval = 60000 * 60 // 60 minutes
|
||||||
this.updateSmAccountsInterval = 60000 * 10 // 10 minutes
|
this.updateSmAccountsInterval = 60000 * 10 // 10 minutes
|
||||||
|
this.removeOlderDeletedChatsAndPostsInterval = 60000 * 60 * 1 // 1 hour
|
||||||
|
|
||||||
// Encapsulate dependencies
|
// Encapsulate dependencies
|
||||||
this.config = config
|
this.config = config
|
||||||
@@ -42,7 +43,7 @@ class TimerControllers {
|
|||||||
this.backupUsage = this.backupUsage.bind(this)
|
this.backupUsage = this.backupUsage.bind(this)
|
||||||
this.newSmAccounts = this.newSmAccounts.bind(this)
|
this.newSmAccounts = this.newSmAccounts.bind(this)
|
||||||
this.updateSmAccounts = this.updateSmAccounts.bind(this)
|
this.updateSmAccounts = this.updateSmAccounts.bind(this)
|
||||||
|
this.removeOlderDeletedChatsAndPosts = this.removeOlderDeletedChatsAndPosts.bind(this)
|
||||||
// State
|
// State
|
||||||
this.gcOrdersInt = null
|
this.gcOrdersInt = null
|
||||||
this.gcOffersInt = null
|
this.gcOffersInt = null
|
||||||
@@ -50,6 +51,7 @@ class TimerControllers {
|
|||||||
this.loadOffersInt = null
|
this.loadOffersInt = null
|
||||||
this.newSmAccountsInt = null
|
this.newSmAccountsInt = null
|
||||||
this.updateSmAccountsInt = null
|
this.updateSmAccountsInt = null
|
||||||
|
this.removeOlderDeletedChatsAndPostsInt = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start all the time-based controllers.
|
// Start all the time-based controllers.
|
||||||
@@ -62,6 +64,7 @@ class TimerControllers {
|
|||||||
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
|
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
|
||||||
this.newSmAccountsInt = setInterval(this.newSmAccounts, this.newSmAccountsInterval)
|
this.newSmAccountsInt = setInterval(this.newSmAccounts, this.newSmAccountsInterval)
|
||||||
this.updateSmAccountsInt = setInterval(this.updateSmAccounts, this.updateSmAccountsInterval)
|
this.updateSmAccountsInt = setInterval(this.updateSmAccounts, this.updateSmAccountsInterval)
|
||||||
|
this.removeOlderDeletedChatsAndPostsInt = setInterval(this.removeOlderDeletedChatsAndPosts, this.removeOlderDeletedChatsAndPostsInterval)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +77,7 @@ class TimerControllers {
|
|||||||
clearInterval(this.backupUsageHandle)
|
clearInterval(this.backupUsageHandle)
|
||||||
clearInterval(this.newSmAccountsInt)
|
clearInterval(this.newSmAccountsInt)
|
||||||
clearInterval(this.updateSmAccountsInt)
|
clearInterval(this.updateSmAccountsInt)
|
||||||
|
clearInterval(this.removeOlderDeletedChatsAndPostsInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Garbage Collect the Orders.
|
// Garbage Collect the Orders.
|
||||||
@@ -194,6 +198,22 @@ class TimerControllers {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove older deleted chats and posts.
|
||||||
|
async removeOlderDeletedChatsAndPosts () {
|
||||||
|
try {
|
||||||
|
// console.log('removeOlderDeletedChatsAndPosts() Timer Controller executing at ', new Date().toLocaleString())
|
||||||
|
clearInterval(this.removeOlderDeletedChatsAndPostsInt)
|
||||||
|
await this.useCases.nostr.removeOlderDeletedChats()
|
||||||
|
await this.useCases.nostr.removeOlderDeletedPosts()
|
||||||
|
this.removeOlderDeletedChatsAndPostsInt = setInterval(this.removeOlderDeletedChatsAndPosts, this.removeOlderDeletedChatsAndPostsInterval)
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in time-controller.js/removeOlderDeletedChatsAndPosts(): ', err)
|
||||||
|
this.removeOlderDeletedChatsAndPostsInt = setInterval(this.removeOlderDeletedChatsAndPosts, this.removeOlderDeletedChatsAndPostsInterval)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimerControllers
|
export default TimerControllers
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
Counter Offer Entity
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CounterOfferEntity {
|
||||||
|
validate (offerData = {}) {
|
||||||
|
console.log('counter offer data input validation ', offerData)
|
||||||
|
// Throw an error if input object does not have a data property
|
||||||
|
if (!offerData.data) {
|
||||||
|
throw new Error(
|
||||||
|
'Input to counterOffer.validate() must be an object with a data property.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
nostrEventId,
|
||||||
|
takerAddr,
|
||||||
|
takerNpub,
|
||||||
|
makerNpub,
|
||||||
|
counterOfferAddr,
|
||||||
|
counterOfferUtxo,
|
||||||
|
takerOfferUtxo,
|
||||||
|
tokenId
|
||||||
|
} = offerData.data
|
||||||
|
|
||||||
|
// Input Validation
|
||||||
|
if (!nostrEventId || typeof nostrEventId !== 'string') {
|
||||||
|
throw new Error("Property 'nostrEventId' must be a string.")
|
||||||
|
}
|
||||||
|
// Next validations are optional, to allow old counter offers without the data.
|
||||||
|
if (takerAddr && typeof takerAddr !== 'string') {
|
||||||
|
throw new Error("Property 'takerAddr' must be a string.")
|
||||||
|
}
|
||||||
|
if (takerNpub && typeof takerNpub !== 'string') {
|
||||||
|
throw new Error("Property 'takerNpub' must be a string.")
|
||||||
|
}
|
||||||
|
if (counterOfferAddr && typeof counterOfferAddr !== 'string') {
|
||||||
|
throw new Error("Property 'counterOfferAddr' must be a string.")
|
||||||
|
}
|
||||||
|
if (counterOfferUtxo && typeof counterOfferUtxo !== 'string') {
|
||||||
|
throw new Error("Property 'counterOfferUtxo' must be a string.")
|
||||||
|
}
|
||||||
|
if (takerOfferUtxo && typeof takerOfferUtxo !== 'string') {
|
||||||
|
throw new Error("Property 'takerOfferUtxo' must be a string.")
|
||||||
|
}
|
||||||
|
if (tokenId && typeof tokenId !== 'string') {
|
||||||
|
throw new Error("Property 'tokenId' must be a string.")
|
||||||
|
}
|
||||||
|
if (makerNpub && typeof makerNpub !== 'string') {
|
||||||
|
throw new Error("Property 'makerNpub' must be a string.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedCounterOfferData = {
|
||||||
|
nostrEventId,
|
||||||
|
takerAddr,
|
||||||
|
takerNpub,
|
||||||
|
counterOfferAddr,
|
||||||
|
counterOfferUtxo,
|
||||||
|
takerOfferUtxo,
|
||||||
|
tokenId,
|
||||||
|
makerNpub
|
||||||
|
}
|
||||||
|
|
||||||
|
return validatedCounterOfferData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CounterOfferEntity
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
DeletedChat Entity
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DeletedChat {
|
||||||
|
validate (data = {}) {
|
||||||
|
const { eventId } = data
|
||||||
|
// Input Validation
|
||||||
|
if (!eventId || typeof eventId !== 'string') {
|
||||||
|
throw new Error("Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeletedChat
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
DeletedPost Entity
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DeletedPost {
|
||||||
|
validate (data = {}) {
|
||||||
|
const { eventId } = data
|
||||||
|
// Input Validation
|
||||||
|
if (!eventId || typeof eventId !== 'string') {
|
||||||
|
throw new Error("Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeletedPost
|
||||||
@@ -31,6 +31,7 @@ class OfferEntity {
|
|||||||
utxoVout,
|
utxoVout,
|
||||||
offerStatus,
|
offerStatus,
|
||||||
makerAddr,
|
makerAddr,
|
||||||
|
makerNpub,
|
||||||
ticker,
|
ticker,
|
||||||
tokenType,
|
tokenType,
|
||||||
nostrEventId,
|
nostrEventId,
|
||||||
@@ -89,6 +90,10 @@ class OfferEntity {
|
|||||||
if (!operatorPercentage || typeof operatorPercentage !== 'number') {
|
if (!operatorPercentage || typeof operatorPercentage !== 'number') {
|
||||||
throw new Error("Property 'operatorPercentage' must be a number.")
|
throw new Error("Property 'operatorPercentage' must be a number.")
|
||||||
}
|
}
|
||||||
|
// Validate npub type if it exist
|
||||||
|
if (makerNpub && typeof makerNpub !== 'string') {
|
||||||
|
throw new Error("Property 'makerNpub' must be a string.")
|
||||||
|
}
|
||||||
|
|
||||||
// Convert the timestamp to a number.
|
// Convert the timestamp to a number.
|
||||||
let timestamp = new Date(offerData.timestamp)
|
let timestamp = new Date(offerData.timestamp)
|
||||||
@@ -113,7 +118,8 @@ class OfferEntity {
|
|||||||
makerAddr,
|
makerAddr,
|
||||||
ticker,
|
ticker,
|
||||||
tokenType,
|
tokenType,
|
||||||
nostrEventId
|
nostrEventId,
|
||||||
|
makerNpub
|
||||||
}
|
}
|
||||||
// console.log('offer entity validatedOfferData: ', validatedOfferData)
|
// console.log('offer entity validatedOfferData: ', validatedOfferData)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import OfferUseCases from './offer/index.js'
|
|||||||
import OrderUseCases from './order.js'
|
import OrderUseCases from './order.js'
|
||||||
import { UsageUseCases } from './usage-use-cases.js'
|
import { UsageUseCases } from './usage-use-cases.js'
|
||||||
import SmAccountUseCases from './smAccount-use-cases.js'
|
import SmAccountUseCases from './smAccount-use-cases.js'
|
||||||
|
import NostrUseCases from './nostr-use-cases.js'
|
||||||
|
|
||||||
class UseCases {
|
class UseCases {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -29,6 +30,7 @@ class UseCases {
|
|||||||
this.offer = new OfferUseCases(localConfig)
|
this.offer = new OfferUseCases(localConfig)
|
||||||
this.usage = new UsageUseCases(localConfig)
|
this.usage = new UsageUseCases(localConfig)
|
||||||
this.smAccount = new SmAccountUseCases(localConfig)
|
this.smAccount = new SmAccountUseCases(localConfig)
|
||||||
|
this.nostr = new NostrUseCases(localConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run any startup Use Cases at the start of the app.
|
// Run any startup Use Cases at the start of the app.
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
This library contains business-logic for dealing with deleted chats. Most of these
|
||||||
|
functions are called by the /nostr REST API endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import wlogger from '../adapters/wlogger.js'
|
||||||
|
import DeletedChatEntity from '../entities/deletedChat.js'
|
||||||
|
import DeletedPostEntity from '../entities/deletedPost.js'
|
||||||
|
|
||||||
|
class NostrUseCases {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of adapters must be passed in when instantiating Nostr Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Encapsulate dependencies
|
||||||
|
this.DeletedChatEntity = new DeletedChatEntity()
|
||||||
|
this.DeletedPostEntity = new DeletedPostEntity()
|
||||||
|
this.DeletedChatModel = this.adapters.localdb.DeletedChat
|
||||||
|
this.DeletedPostModel = this.adapters.localdb.DeletedPost
|
||||||
|
|
||||||
|
this.createDeletedChat = this.createDeletedChat.bind(this)
|
||||||
|
this.getDeletedChats = this.getDeletedChats.bind(this)
|
||||||
|
this.createDeletedPost = this.createDeletedPost.bind(this)
|
||||||
|
this.getDeletedPosts = this.getDeletedPosts.bind(this)
|
||||||
|
this.removeOlderDeletedChats = this.removeOlderDeletedChats.bind(this)
|
||||||
|
this.removeOlderDeletedPosts = this.removeOlderDeletedPosts.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new deleted chat model and add it to the Mongo database.
|
||||||
|
async createDeletedChat (deletedChatObj) {
|
||||||
|
try {
|
||||||
|
// Input Validation
|
||||||
|
|
||||||
|
const deletedChatEntity = this.DeletedChatEntity.validate(deletedChatObj)
|
||||||
|
const deletedChat = new this.DeletedChatModel(deletedChatEntity)
|
||||||
|
|
||||||
|
// Save the new model to the database.
|
||||||
|
await deletedChat.save()
|
||||||
|
return deletedChat
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('createDeletedChat() error: ', err)
|
||||||
|
wlogger.error('Error in lib/nostr.js/createDeletedChat()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an array of all deleted chats models in the Mongo database.
|
||||||
|
async getDeletedChats () {
|
||||||
|
try {
|
||||||
|
// Get deleted chats models.
|
||||||
|
const deletedChats = await this.DeletedChatModel.find({})
|
||||||
|
|
||||||
|
return deletedChats
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in lib/users.js/getAllDeletedChat()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new deleted post model and add it to the Mongo database.
|
||||||
|
async createDeletedPost (deletedPostObj) {
|
||||||
|
try {
|
||||||
|
// Input Validation
|
||||||
|
|
||||||
|
const deletedPostEntity = this.DeletedPostEntity.validate(deletedPostObj)
|
||||||
|
const deletedPost = new this.DeletedPostModel(deletedPostEntity)
|
||||||
|
|
||||||
|
// Save the new model to the database.
|
||||||
|
await deletedPost.save()
|
||||||
|
return deletedPost
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in lib/users.js/createDeletedPost()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an array of all deleted posts models in the Mongo database.
|
||||||
|
async getDeletedPosts () {
|
||||||
|
try {
|
||||||
|
// Get deleted posts models.
|
||||||
|
const deletedPosts = await this.DeletedPostModel.find({})
|
||||||
|
|
||||||
|
return deletedPosts
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in lib/users.js/getDeletedPosts()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deleted chats that are older than 3 months.
|
||||||
|
async removeOlderDeletedChats () {
|
||||||
|
try {
|
||||||
|
const deletedChats = await this.DeletedChatModel.find({})
|
||||||
|
const threeMonthsAgo = new Date()
|
||||||
|
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3)
|
||||||
|
for (let i = 0; i < deletedChats.length; i++) {
|
||||||
|
const deletedChat = deletedChats[i]
|
||||||
|
if (deletedChat.createdAt.getTime() < threeMonthsAgo.getTime()) {
|
||||||
|
await deletedChat.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in lib/users.js/removeOlderDeletedChats()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deleted posts that are older than 3 months.
|
||||||
|
async removeOlderDeletedPosts () {
|
||||||
|
try {
|
||||||
|
const deletedPosts = await this.DeletedPostModel.find({})
|
||||||
|
const threeMonthsAgo = new Date()
|
||||||
|
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3)
|
||||||
|
for (let i = 0; i < deletedPosts.length; i++) {
|
||||||
|
const deletedPost = deletedPosts[i]
|
||||||
|
if (deletedPost.createdAt.getTime() < threeMonthsAgo.getTime()) {
|
||||||
|
await deletedPost.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in lib/users.js/removeOlderDeletedPosts()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NostrUseCases
|
||||||
+205
-14
@@ -19,6 +19,7 @@ import RetryQueue from '@chris.troutner/retry-queue'
|
|||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import OfferEntity from '../../entities/offer.js'
|
import OfferEntity from '../../entities/offer.js'
|
||||||
|
import CounterOfferEntity from '../../entities/counterOffer.js'
|
||||||
import config from '../../../config/index.js'
|
import config from '../../../config/index.js'
|
||||||
|
|
||||||
const DEFAULT_ENTRIES_PER_PAGE = 20
|
const DEFAULT_ENTRIES_PER_PAGE = 20
|
||||||
@@ -45,7 +46,9 @@ class OfferUseCases {
|
|||||||
this.config = config
|
this.config = config
|
||||||
this.axios = axios
|
this.axios = axios
|
||||||
this.offerEntity = new OfferEntity()
|
this.offerEntity = new OfferEntity()
|
||||||
|
this.counterOfferEntity = new CounterOfferEntity()
|
||||||
this.OfferModel = this.adapters.localdb.Offer
|
this.OfferModel = this.adapters.localdb.Offer
|
||||||
|
this.CounterOfferModel = this.adapters.localdb.CounterOffer
|
||||||
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
|
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
|
||||||
|
|
||||||
// Bind 'this' object to functions
|
// Bind 'this' object to functions
|
||||||
@@ -65,6 +68,8 @@ 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)
|
||||||
|
this.listCounterOffersByAddress = this.listCounterOffersByAddress.bind(this)
|
||||||
|
|
||||||
// State
|
// State
|
||||||
this.seenOffers = []
|
this.seenOffers = []
|
||||||
@@ -129,20 +134,58 @@ class OfferUseCases {
|
|||||||
|
|
||||||
// Get data about the token.
|
// Get data about the token.
|
||||||
const tokenId = offerEntity.tokenId
|
const tokenId = offerEntity.tokenId
|
||||||
// const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId)
|
let tokenData = null
|
||||||
const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
|
try {
|
||||||
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in OfferUseCases/createOffer() getting token data: ', err.message)
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a 'display category' for the token. This will allow the
|
// Do additional data analysis if the token data was successfully retrieved.
|
||||||
// front end UI to figure out how to display the token.
|
if (tokenData) {
|
||||||
const displayCategory = this.categorizeToken(offerEntity, tokenData)
|
// Store the mutable and immutable data cids.
|
||||||
console.log('displayCategory: ', displayCategory)
|
const mutableDataCid = tokenData.mutableData
|
||||||
offerEntity.displayCategory = displayCategory
|
const immutableDataCid = tokenData.immutableData
|
||||||
|
offerEntity.mutableDataCid = mutableDataCid
|
||||||
|
offerEntity.immutableDataCid = immutableDataCid
|
||||||
|
offerEntity.tokenData = tokenData
|
||||||
|
|
||||||
// Detect if user set the NSFW flag.
|
// Get the mutable data from the cid if it exists.
|
||||||
// const nsfw = false
|
if (mutableDataCid && typeof mutableDataCid === 'string') {
|
||||||
// nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData)
|
let mutableData = null
|
||||||
// offerEntity.nsfw = nsfw
|
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.
|
// Add offer to the local database.
|
||||||
const offerModel = new this.OfferModel(offerEntity)
|
const offerModel = new this.OfferModel(offerEntity)
|
||||||
@@ -150,7 +193,7 @@ class OfferUseCases {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in createOffer()', err.message)
|
console.error('\n\nError in createOffer()', err.message, '\n\n', err)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +310,7 @@ class OfferUseCases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async listNftOffers (page = 0, nsfw = false) {
|
/* async listNftOffers (page = 0, nsfw = false) {
|
||||||
try {
|
try {
|
||||||
const data = await this.OfferModel.find({
|
const data = await this.OfferModel.find({
|
||||||
displayCategory: { $ne: 'fungible' },
|
displayCategory: { $ne: 'fungible' },
|
||||||
@@ -287,6 +330,42 @@ class OfferUseCases {
|
|||||||
console.error('Error in use-cases/offer/listNftOffers()')
|
console.error('Error in use-cases/offer/listNftOffers()')
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
async listNftOffers (page = 0, nsfw = false) {
|
||||||
|
try {
|
||||||
|
const query = {
|
||||||
|
displayCategory: { $ne: 'fungible' },
|
||||||
|
nsfw
|
||||||
|
}
|
||||||
|
// Total query offers
|
||||||
|
const totalOffers = await this.OfferModel.countDocuments(query)
|
||||||
|
// Total pages
|
||||||
|
const totalPages = Math.ceil(totalOffers / NFT_ENTRIES_PER_PAGE)
|
||||||
|
|
||||||
|
const data = await this.OfferModel.find(query)
|
||||||
|
// Sort entries so newest entries show first.
|
||||||
|
.sort('-timestamp')
|
||||||
|
// Skip to the start of the selected page.
|
||||||
|
.skip(page * NFT_ENTRIES_PER_PAGE)
|
||||||
|
// Only return 20 results.
|
||||||
|
.limit(NFT_ENTRIES_PER_PAGE)
|
||||||
|
|
||||||
|
// console.log('listNftOffers() returning this data: ', data)
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
pagination: {
|
||||||
|
currentPage: page,
|
||||||
|
totalPages,
|
||||||
|
totalOffers,
|
||||||
|
pageSize: NFT_ENTRIES_PER_PAGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in use-cases/offer/listNftOffers()')
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async listFungibleOffers (page = 0) {
|
async listFungibleOffers (page = 0) {
|
||||||
@@ -521,10 +600,23 @@ class OfferUseCases {
|
|||||||
// and broadcasts the transaction to accept the Counter Offer.
|
// and broadcasts the transaction to accept the Counter Offer.
|
||||||
async acceptCounterOffer (offerData) {
|
async acceptCounterOffer (offerData) {
|
||||||
try {
|
try {
|
||||||
|
console.log('offerData', offerData)
|
||||||
// console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
|
// console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
|
||||||
|
|
||||||
// Quickly skip over offers that have already been processed.
|
// Quickly skip over offers that have already been processed.
|
||||||
const eventId = offerData.data.nostrEventId
|
const eventId = offerData.data.nostrEventId
|
||||||
|
|
||||||
|
// Create counter offer entity for nostr event id if it not exist
|
||||||
|
const existingCounterOffer = await this.CounterOfferModel.findOne(({ nostrEventId: eventId }))
|
||||||
|
if (!existingCounterOffer) {
|
||||||
|
// Add taker offer utxo to counter offer data.
|
||||||
|
offerData.data.takerOfferUtxo = offerData.data.utxoTxid
|
||||||
|
const counterOEntity = this.counterOfferEntity.validate(offerData)
|
||||||
|
// Create new counter offfer model and save it.
|
||||||
|
const counterOffer = new this.CounterOfferModel(counterOEntity)
|
||||||
|
await counterOffer.save()
|
||||||
|
}
|
||||||
|
|
||||||
if (this.seenOffers.includes(eventId)) {
|
if (this.seenOffers.includes(eventId)) {
|
||||||
// console.log(`Offer with event ID ${eventId} already processed. Skipping.`)
|
// console.log(`Offer with event ID ${eventId} already processed. Skipping.`)
|
||||||
return false
|
return false
|
||||||
@@ -862,6 +954,105 @@ 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
|
||||||
|
offer.tokenData = tokenData
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listCounterOffersByAddress (takerAddr) {
|
||||||
|
try {
|
||||||
|
if (!takerAddr || typeof takerAddr !== 'string') {
|
||||||
|
throw new Error('takerAddr must be a string')
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterOffers = await this.CounterOfferModel.find({ takerAddr })
|
||||||
|
return counterOffers
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in listCounterOffersByAddress(): ', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OfferUseCases
|
export default OfferUseCases
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ class OrderLib {
|
|||||||
orderEntity.operatorAddress = this.config.operatorAddress
|
orderEntity.operatorAddress = this.config.operatorAddress
|
||||||
orderEntity.operatorPercentage = this.config.operatorPercentage
|
orderEntity.operatorPercentage = this.config.operatorPercentage
|
||||||
|
|
||||||
|
// Convert the Seller's BCH private key to a Nostr npub. This is used to identify the seller in Nostr.
|
||||||
|
orderEntity.makerNpub = this.adapters.nostr.privKeyToNpub(userWallet.walletInfo.privateKey)
|
||||||
|
|
||||||
// Post the new Order information to Nostr under the topic set in the
|
// Post the new Order information to Nostr under the topic set in the
|
||||||
// config file.
|
// config file.
|
||||||
const postObj = {
|
const postObj = {
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import testUtils from '../../utils/test-utils.js'
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import config from '../../../config/index.js'
|
||||||
|
import axios from 'axios'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import util from 'util'
|
||||||
|
|
||||||
|
util.inspect.defaultOptions = { depth: 1 }
|
||||||
|
|
||||||
|
const LOCALHOST = `http://localhost:${config.port}`
|
||||||
|
|
||||||
|
const context = {}
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
// const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||||
|
|
||||||
|
if (!config.noMongo) {
|
||||||
|
describe('Nostr', () => {
|
||||||
|
before(async () => {
|
||||||
|
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||||
|
|
||||||
|
// Create a second test user.
|
||||||
|
const userObj = {
|
||||||
|
email: 'test2@test.com',
|
||||||
|
password: 'pass2',
|
||||||
|
name: 'test2'
|
||||||
|
}
|
||||||
|
const testUser = await testUtils.createUser(userObj)
|
||||||
|
// console.log(`testUser2: ${JSON.stringify(testUser, null, 2)}`)
|
||||||
|
|
||||||
|
context.user2 = testUser.user
|
||||||
|
context.token2 = testUser.token
|
||||||
|
context.id2 = testUser.user._id
|
||||||
|
|
||||||
|
// Get the JWT used to log in as the admin 'system' user.
|
||||||
|
const adminJWT = await testUtils.getAdminJWT()
|
||||||
|
console.log(`adminJWT: ${adminJWT}`)
|
||||||
|
context.adminJWT = adminJWT
|
||||||
|
|
||||||
|
// const admin = await testUtils.loginAdminUser()
|
||||||
|
// context.adminJWT = admin.token
|
||||||
|
|
||||||
|
// const admin = await adminLib.loginAdmin()
|
||||||
|
// console.log(`admin: ${JSON.stringify(admin, null, 2)}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// const useCases = new UseCases({ adapters })
|
||||||
|
// uut = new UserController({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('POST /nostr/deletedChat - Create Deleted Chat', () => {
|
||||||
|
it('should not create deleted chat if the authorization header is missing', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedChat`
|
||||||
|
}
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should not create deleted chat if the authorization header is invalid scheme', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedChat`,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer invalid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should not create deleted chat if the authorization header is invalid token', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedChat`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${context.token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should not create deleted chat if token is not admin', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedChat`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${context.token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /nostr/deletedPost - Create Deleted Post', () => {
|
||||||
|
it('should not create deleted post if the authorization header is missing', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedPost`
|
||||||
|
}
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create deleted post if the authorization header is invalid scheme', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedPost`,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer invalid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should not create deleted post if the authorization header is invalid token', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedPost`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${context.token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should not create deleted post if token is not admin', async () => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${LOCALHOST}/nostr/deletedPost`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${context.token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await axios(options)
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected behavior')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.response.status, 401)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -224,4 +224,33 @@ describe('#nostr.js', () => {
|
|||||||
assert.isArray(result)
|
assert.isArray(result)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('#privKeyToNpub', () => {
|
||||||
|
it('should handle error if providedprivKey is not a string', async () => {
|
||||||
|
try {
|
||||||
|
uut.privKeyToNpub()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'privKey must be a string!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return npub if privKey is valid', async () => {
|
||||||
|
const result = uut.privKeyToNpub('L2kh34VinQNRq1ad8nnXnGoFrb9pT7j8GJXiT6Scsg18KMxsUVDr')
|
||||||
|
// console.log('result: ', result)
|
||||||
|
assert.isString(result)
|
||||||
|
assert.include(result, 'npub')
|
||||||
|
})
|
||||||
|
it('should handle unexpected error', async () => {
|
||||||
|
try {
|
||||||
|
uut.privKeyToNpub('invalid format')
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'Invalid base58 character')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ describe('#OrderPagination', () => {
|
|||||||
nostrEventId: 'nostrEventId',
|
nostrEventId: 'nostrEventId',
|
||||||
dataType: 'dataType',
|
dataType: 'dataType',
|
||||||
makerAddr: 'makerAddr',
|
makerAddr: 'makerAddr',
|
||||||
|
makerNpub: 'makerNpub',
|
||||||
rateInBaseUnit: 'rateInBaseUnit',
|
rateInBaseUnit: 'rateInBaseUnit',
|
||||||
minUnitsToExchange: 'minUnitsToExchange',
|
minUnitsToExchange: 'minUnitsToExchange',
|
||||||
ticker: 'ticker',
|
ticker: 'ticker',
|
||||||
|
|||||||
@@ -493,4 +493,35 @@ describe('#wallet', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('#cid2json', () => {
|
||||||
|
it('should throw error if cid is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.cid2json()
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'urlOrCid must be a string!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should convert cid to json', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.bchWallet, 'cid2json').resolves({ json: offerMockData.mutableDataMock })
|
||||||
|
const cid = 'bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm'
|
||||||
|
const result = await uut.cid2json(cid)
|
||||||
|
assert.isObject(result)
|
||||||
|
} catch (error) {
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should convert ipfs url to json', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.bchWallet, 'cid2json').resolves({ json: offerMockData.mutableDataMock })
|
||||||
|
const url = 'https://ipfs.io/ipfs/bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm'
|
||||||
|
const result = await uut.cid2json(url)
|
||||||
|
assert.isObject(result)
|
||||||
|
} catch (error) {
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /nostr endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
import NostrController from '../../../../../src/controllers/rest-api/nostr/controller.js'
|
||||||
|
|
||||||
|
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
describe('#Nostr-REST-Controller', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new NostrController({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new NostrController()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating /nostr REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new NostrController({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantiating /nostr REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#POST /nostr/deletedChat', () => {
|
||||||
|
it('should return 422 status on biz logic error', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.useCases.nostr, 'createDeletedChat').rejects(new Error('test error'))
|
||||||
|
await uut.createDeletedChat(ctx)
|
||||||
|
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(err)
|
||||||
|
assert.equal(err.status, 422)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 200 status on success', async () => {
|
||||||
|
ctx.request.body = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||||
|
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||||
|
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||||
|
}
|
||||||
|
|
||||||
|
await uut.createDeletedChat(ctx)
|
||||||
|
|
||||||
|
// Assert the expected HTTP response
|
||||||
|
assert.equal(ctx.status, 200)
|
||||||
|
|
||||||
|
assert.isObject(ctx.response.body)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#GET /nostr/deletedChat', () => {
|
||||||
|
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox
|
||||||
|
.stub(uut.useCases.nostr, 'getDeletedChats')
|
||||||
|
.rejects(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.getDeletedChats(ctx)
|
||||||
|
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.status, 422)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 200 status on success', async () => {
|
||||||
|
await uut.getDeletedChats(ctx)
|
||||||
|
|
||||||
|
// Assert the expected HTTP response
|
||||||
|
assert.equal(ctx.status, 200)
|
||||||
|
|
||||||
|
// Assert that expected properties exist in the returned data.
|
||||||
|
assert.isObject(ctx.response.body)
|
||||||
|
assert.property(ctx.response.body, 'deletedChats')
|
||||||
|
assert.isArray(ctx.response.body.deletedChats)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#POST /nostr/deletedPost', () => {
|
||||||
|
it('should return 422 status on biz logic error', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.useCases.nostr, 'createDeletedPost').rejects(new Error('test error'))
|
||||||
|
await uut.createDeletedPost(ctx)
|
||||||
|
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.status, 422)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should return 200 status on success', async () => {
|
||||||
|
ctx.request.body = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||||
|
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||||
|
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||||
|
}
|
||||||
|
|
||||||
|
await uut.createDeletedPost(ctx)
|
||||||
|
|
||||||
|
assert.equal(ctx.status, 200)
|
||||||
|
|
||||||
|
assert.isObject(ctx.response.body)
|
||||||
|
assert.property(ctx.response.body, 'deletedPost')
|
||||||
|
assert.isObject(ctx.response.body.deletedPost)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#GET /nostr/deletedPost', () => {
|
||||||
|
it('should return 422 status on biz logic error', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.useCases.nostr, 'getDeletedPosts').rejects(new Error('test error'))
|
||||||
|
await uut.getDeletedPosts(ctx)
|
||||||
|
|
||||||
|
assert.fail('Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.status, 422)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should return 200 status on success', async () => {
|
||||||
|
await uut.getDeletedPosts(ctx)
|
||||||
|
|
||||||
|
// Assert the expected HTTP response
|
||||||
|
assert.equal(ctx.status, 200)
|
||||||
|
|
||||||
|
assert.isObject(ctx.response.body)
|
||||||
|
assert.property(ctx.response.body, 'deletedPosts')
|
||||||
|
assert.isArray(ctx.response.body.deletedPosts)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#handleError', () => {
|
||||||
|
it('should still throw error if there is no message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 404
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Not Found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error with message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 422,
|
||||||
|
message: 'test error'
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /nostr endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
|
||||||
|
// const app = require('../../../mocks/app-mock')
|
||||||
|
|
||||||
|
import NostrRouter from '../../../../../src/controllers/rest-api/nostr/index.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
// let ctx
|
||||||
|
|
||||||
|
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||||
|
|
||||||
|
describe('#Nostr-REST-Router', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new NostrRouter({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
// ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new NostrRouter()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating NostrRouter REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new NostrRouter({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantiating NostrRouter REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#attach', () => {
|
||||||
|
it('should throw an error if app is not passed in.', () => {
|
||||||
|
try {
|
||||||
|
uut.attach()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createDeletedChat', () => {
|
||||||
|
it('should route to the createDeletedChat function', async () => {
|
||||||
|
// Stub functions
|
||||||
|
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
|
||||||
|
sandbox.stub(uut.nostrRESTController, 'createDeletedChat').resolves(true)
|
||||||
|
|
||||||
|
// Call function
|
||||||
|
const res = await uut.createDeletedChat()
|
||||||
|
assert.isTrue(validationSpy.calledOnce, 'Admin validator should be called')
|
||||||
|
assert.isTrue(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createDeletedPost', () => {
|
||||||
|
it('should route to the createDeletedPost function', async () => {
|
||||||
|
// Stub functions
|
||||||
|
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
|
||||||
|
sandbox.stub(uut.nostrRESTController, 'createDeletedPost').resolves(true)
|
||||||
|
|
||||||
|
// Call function
|
||||||
|
const res = await uut.createDeletedPost()
|
||||||
|
assert.isTrue(validationSpy.calledOnce, 'Admin validator should be called')
|
||||||
|
assert.isTrue(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -229,6 +229,49 @@ 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('#listCounterOffersByAddress', () => {
|
||||||
|
it('should list counter offers by address', async () => {
|
||||||
|
ctx.params = { addr: 'testAddress' }
|
||||||
|
sandbox.stub(uut.useCases.offer, 'listCounterOffersByAddress').resolves([])
|
||||||
|
await uut.listCounterOffersByAddress(ctx)
|
||||||
|
assert.isArray(ctx.body.counterOffers)
|
||||||
|
})
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
ctx.params = { addr: 'testAddress' }
|
||||||
|
sandbox
|
||||||
|
.stub(uut.useCases.offer, 'listCounterOffersByAddress')
|
||||||
|
.throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.listCounterOffersByAddress(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', () => {
|
||||||
|
|||||||
@@ -83,4 +83,13 @@ describe('#Order-REST-Router', () => {
|
|||||||
assert.isTrue(spy.calledOnce)
|
assert.isTrue(spy.calledOnce)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('#deleteOrder', () => {
|
||||||
|
it('should route to controller', async () => {
|
||||||
|
sandbox.stub(uut.validators, 'ensureUser').resolves(true)
|
||||||
|
const spy = sandbox.stub(uut.orderRESTController, 'deleteOrder').resolves(true)
|
||||||
|
|
||||||
|
await uut.deleteOrder()
|
||||||
|
assert.isTrue(spy.calledOnce)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -187,4 +187,20 @@ describe('#Timer-Controllers', () => {
|
|||||||
assert.equal(result, false)
|
assert.equal(result, false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('#removeOlderDeletedChatsAndPosts', () => {
|
||||||
|
it('should kick off the Use Cases and return true', async () => {
|
||||||
|
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedChats').resolves()
|
||||||
|
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedPosts').resolves()
|
||||||
|
const result = await uut.removeOlderDeletedChatsAndPosts()
|
||||||
|
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should kick off the Use Cases and return false on error', async () => {
|
||||||
|
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedChats').throws(new Error('test error'))
|
||||||
|
sandbox.stub(uut.useCases.nostr, 'removeOlderDeletedPosts').throws(new Error('test error'))
|
||||||
|
const result = await uut.removeOlderDeletedChatsAndPosts()
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the CounterOffer entity library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import CounterOfferEntity from '../../../src/entities/counterOffer.js'
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
let uut
|
||||||
|
|
||||||
|
describe('#CounterOffer-Entity', () => {
|
||||||
|
before(async () => { })
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uut = new CounterOfferEntity()
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#validate', () => {
|
||||||
|
it('should throw an error if data is not provided', () => {
|
||||||
|
try {
|
||||||
|
uut.validate({})
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Input to counterOffer.validate() must be an object with a data property.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if nostrEventId is not provided', () => {
|
||||||
|
try {
|
||||||
|
uut.validate({ data: {} })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'nostrEventId' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if provided takerAddr is not string', () => {
|
||||||
|
try {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 1234
|
||||||
|
}
|
||||||
|
uut.validate({ data: inObj })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'takerAddr' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if provided takerNpub is not string', () => {
|
||||||
|
try {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 'taker address',
|
||||||
|
takerNpub: 1234
|
||||||
|
}
|
||||||
|
uut.validate({ data: inObj })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'takerNpub' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if provided makerNpub is not string', () => {
|
||||||
|
try {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 'taker address',
|
||||||
|
takerNpub: 'npub',
|
||||||
|
makerNpub: 1234
|
||||||
|
}
|
||||||
|
uut.validate({ data: inObj })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'makerNpub' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if provided counterOfferAddr is not string', () => {
|
||||||
|
try {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 'taker address',
|
||||||
|
takerNpub: 'npub',
|
||||||
|
makerNpub: 'npub',
|
||||||
|
counterOfferAddr: 1234
|
||||||
|
}
|
||||||
|
uut.validate({ data: inObj })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'counterOfferAddr' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if provided counterOfferUtxo is not string', () => {
|
||||||
|
try {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 'taker address',
|
||||||
|
takerNpub: 'npub',
|
||||||
|
makerNpub: 'npub',
|
||||||
|
counterOfferAddr: 'counter offer address',
|
||||||
|
counterOfferUtxo: 1234
|
||||||
|
}
|
||||||
|
uut.validate({ data: inObj })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'counterOfferUtxo' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if provided takerOfferUtxo is not string', () => {
|
||||||
|
try {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 'taker address',
|
||||||
|
takerNpub: 'npub',
|
||||||
|
makerNpub: 'npub',
|
||||||
|
counterOfferAddr: 'counter offer address',
|
||||||
|
counterOfferUtxo: 'utxo txid',
|
||||||
|
takerOfferUtxo: 1234
|
||||||
|
}
|
||||||
|
uut.validate({ data: inObj })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'takerOfferUtxo' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if provided tokenId is not string', () => {
|
||||||
|
try {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 'taker address',
|
||||||
|
takerNpub: 'npub',
|
||||||
|
makerNpub: 'npub',
|
||||||
|
counterOfferAddr: 'counter offer address',
|
||||||
|
counterOfferUtxo: 'utxo txid',
|
||||||
|
takerOfferUtxo: 'utxo txid',
|
||||||
|
tokenId: 1234
|
||||||
|
}
|
||||||
|
uut.validate({ data: inObj })
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'tokenId' must be a string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a counter offer object', () => {
|
||||||
|
const inObj = {
|
||||||
|
nostrEventId: 'nostr event id',
|
||||||
|
takerAddr: 'taker address',
|
||||||
|
takerNpub: 'npub',
|
||||||
|
makerNpub: 'npub',
|
||||||
|
counterOfferAddr: 'counter offer address',
|
||||||
|
counterOfferUtxo: 'utxo txid',
|
||||||
|
takerOfferUtxo: 'utxo txid',
|
||||||
|
tokenId: 'token id'
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterOfferEntity = uut.validate({ data: inObj })
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'nostrEventId')
|
||||||
|
assert.equal(counterOfferEntity.nostrEventId, inObj.nostrEventId)
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'takerAddr')
|
||||||
|
assert.equal(counterOfferEntity.takerAddr, inObj.takerAddr)
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'takerNpub')
|
||||||
|
assert.equal(counterOfferEntity.takerNpub, inObj.takerNpub)
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'makerNpub')
|
||||||
|
assert.equal(counterOfferEntity.makerNpub, inObj.makerNpub)
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'counterOfferAddr')
|
||||||
|
assert.equal(counterOfferEntity.counterOfferAddr, inObj.counterOfferAddr)
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'counterOfferUtxo')
|
||||||
|
assert.equal(counterOfferEntity.counterOfferUtxo, inObj.counterOfferUtxo)
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'takerOfferUtxo')
|
||||||
|
assert.equal(counterOfferEntity.takerOfferUtxo, inObj.takerOfferUtxo)
|
||||||
|
|
||||||
|
assert.property(counterOfferEntity, 'tokenId')
|
||||||
|
assert.equal(counterOfferEntity.tokenId, inObj.tokenId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the DeletedChat entity library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import DeletedChat from '../../../src/entities/deletedChat.js'
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
let uut
|
||||||
|
|
||||||
|
describe('#DeletedChat-Entity', () => {
|
||||||
|
before(async () => {})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uut = new DeletedChat()
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#validate', () => {
|
||||||
|
it('should throw an error if eventId is not provided', () => {
|
||||||
|
try {
|
||||||
|
uut.validate()
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a DeletedChat object', () => {
|
||||||
|
const inputData = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||||
|
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||||
|
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedChat = uut.validate(inputData)
|
||||||
|
// console.log('entry: ', entry)
|
||||||
|
|
||||||
|
assert.property(deletedChat, 'eventId')
|
||||||
|
assert.equal(deletedChat.eventId, inputData.eventId)
|
||||||
|
|
||||||
|
assert.property(deletedChat, 'npub')
|
||||||
|
assert.equal(deletedChat.npub, inputData.npub)
|
||||||
|
|
||||||
|
assert.property(deletedChat, 'bchAddr')
|
||||||
|
assert.equal(deletedChat.bchAddr, inputData.bchAddr)
|
||||||
|
|
||||||
|
assert.property(deletedChat, 'pubkey')
|
||||||
|
assert.equal(deletedChat.pubkey, inputData.pubkey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the DeletedPost entity library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import DeletedPost from '../../../src/entities/deletedPost.js'
|
||||||
|
|
||||||
|
let sandbox
|
||||||
|
let uut
|
||||||
|
|
||||||
|
describe('#DeletedPost-Entity', () => {
|
||||||
|
before(async () => { })
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uut = new DeletedPost()
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#validate', () => {
|
||||||
|
it('should throw an error if eventId is not provided', () => {
|
||||||
|
try {
|
||||||
|
uut.validate()
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a DeletedPost object', () => {
|
||||||
|
const inputData = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||||
|
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||||
|
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedPost = uut.validate(inputData)
|
||||||
|
// console.log('entry: ', entry)
|
||||||
|
|
||||||
|
assert.property(deletedPost, 'eventId')
|
||||||
|
assert.equal(deletedPost.eventId, inputData.eventId)
|
||||||
|
|
||||||
|
assert.property(deletedPost, 'npub')
|
||||||
|
assert.equal(deletedPost.npub, inputData.npub)
|
||||||
|
|
||||||
|
assert.property(deletedPost, 'bchAddr')
|
||||||
|
assert.equal(deletedPost.bchAddr, inputData.bchAddr)
|
||||||
|
|
||||||
|
assert.property(deletedPost, 'pubkey')
|
||||||
|
assert.equal(deletedPost.pubkey, inputData.pubkey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -491,6 +491,36 @@ describe('#Offer-Entity', () => {
|
|||||||
assert.include(err.message, "Property 'operatorPercentage' must be a number.")
|
assert.include(err.message, "Property 'operatorPercentage' must be a number.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
it('should throw an error if makerNpub is not a string', () => {
|
||||||
|
try {
|
||||||
|
const offerData = {
|
||||||
|
data: {
|
||||||
|
messageType: 1,
|
||||||
|
messageClass: 1,
|
||||||
|
tokenId: 'fakeId',
|
||||||
|
buyOrSell: 'buy',
|
||||||
|
rateInBaseUnit: 1000,
|
||||||
|
minUnitsToExchange: 350,
|
||||||
|
numTokens: 1,
|
||||||
|
utxoTxid: 'fakeTxid',
|
||||||
|
utxoVout: 0,
|
||||||
|
offerStatus: 'posted',
|
||||||
|
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||||
|
tokenType: 1,
|
||||||
|
nostrEventId: 'test',
|
||||||
|
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||||
|
operatorPercentage: 10,
|
||||||
|
makerNpub: 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uut.validate(offerData)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(err)
|
||||||
|
assert.include(err.message, "Property 'makerNpub' must be a string.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('should validate a new offer', () => {
|
it('should validate a new offer', () => {
|
||||||
const offerObj = {
|
const offerObj = {
|
||||||
@@ -513,7 +543,8 @@ describe('#Offer-Entity', () => {
|
|||||||
tokenType: 1,
|
tokenType: 1,
|
||||||
nostrEventId: 'test',
|
nostrEventId: 'test',
|
||||||
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||||
operatorPercentage: 10
|
operatorPercentage: 10,
|
||||||
|
makerNpub: 'npub1dtkf954h5k2yuv04xp44g5hnmk5k0ppv8mekxafm2gc0vveaxh6s0297qg'
|
||||||
},
|
},
|
||||||
timestamp: '2021-09-20T17:54:26.395Z',
|
timestamp: '2021-09-20T17:54:26.395Z',
|
||||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||||
@@ -539,6 +570,8 @@ describe('#Offer-Entity', () => {
|
|||||||
assert.property(result, 'txid')
|
assert.property(result, 'txid')
|
||||||
assert.property(result, 'p2wdbHash')
|
assert.property(result, 'p2wdbHash')
|
||||||
assert.property(result, 'tokenType')
|
assert.property(result, 'tokenType')
|
||||||
|
assert.property(result, 'makerAddr')
|
||||||
|
assert.property(result, 'makerNpub')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -149,7 +149,18 @@ const localdb = {
|
|||||||
static findById () {}
|
static findById () {}
|
||||||
static find () {}
|
static find () {}
|
||||||
static findOne () {}
|
static findOne () {}
|
||||||
|
static countDocuments(){}
|
||||||
|
async save () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CounterOffer: class CounterOffer {
|
||||||
|
constructor (obj) {}
|
||||||
|
|
||||||
|
static findById () {}
|
||||||
|
static find () {}
|
||||||
|
static findOne () {}
|
||||||
|
static countDocuments(){}
|
||||||
async save () {
|
async save () {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
@@ -165,7 +176,31 @@ const localdb = {
|
|||||||
async save () {
|
async save () {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
DeletedChat: class DeletedChat {
|
||||||
|
constructor (obj) {}
|
||||||
|
|
||||||
|
static findById () {}
|
||||||
|
static find () {}
|
||||||
|
static findOne () {}
|
||||||
|
static updateOne () {}
|
||||||
|
|
||||||
|
async save () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DeletedPost: class DeletedPost {
|
||||||
|
constructor (obj) {}
|
||||||
|
|
||||||
|
static findById () {}
|
||||||
|
static find () {}
|
||||||
|
static findOne () {}
|
||||||
|
static updateOne () {}
|
||||||
|
|
||||||
|
async save () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bch = {
|
const bch = {
|
||||||
@@ -216,7 +251,8 @@ const nostr = {
|
|||||||
read: async () => { return true },
|
read: async () => { return true },
|
||||||
eventId2note: () => { return 'testNoteId' },
|
eventId2note: () => { return 'testNoteId' },
|
||||||
readGlobalFeed: async () => { return [] },
|
readGlobalFeed: async () => { return [] },
|
||||||
getFollowers: async () => { return [] }
|
getFollowers: async () => { return [] },
|
||||||
|
privKeyToNpub: async () => { return 'fakeNpub' }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs, nostr}
|
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs, nostr}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class MockBchWallet {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.optimize = async () => { };
|
this.optimize = async () => { };
|
||||||
|
this.cid2json = async () => {};
|
||||||
this.ar = new AdapterRoute()
|
this.ar = new AdapterRoute()
|
||||||
// Environment variable is used by wallet-balance.unit.js to force an error.
|
// Environment variable is used by wallet-balance.unit.js to force an error.
|
||||||
if (process.env.NO_UTXO) {
|
if (process.env.NO_UTXO) {
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ class Offer {
|
|||||||
async acceptCounterOffer() {
|
async acceptCounterOffer() {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
async syncOfferMutableData(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
async listCounterOffersByAddress() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Order {
|
class Order {
|
||||||
@@ -132,6 +138,27 @@ class SmAccountUseCaseMock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NostrUseCasesMock {
|
||||||
|
async createDeletedChat() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
async getDeletedChats() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
async createDeletedPost() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
async getDeletedPosts() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
async removeOlderDeletedChats() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
async removeOlderDeletedPosts() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class UseCasesMock {
|
class UseCasesMock {
|
||||||
constuctor(localConfig = {}) {
|
constuctor(localConfig = {}) {
|
||||||
// this.user = new UserUseCaseMock(localConfig)
|
// this.user = new UserUseCaseMock(localConfig)
|
||||||
@@ -143,6 +170,7 @@ class UseCasesMock {
|
|||||||
order = new Order()
|
order = new Order()
|
||||||
usage = new UsageUseCaseMock()
|
usage = new UsageUseCaseMock()
|
||||||
smAccount = new SmAccountUseCaseMock()
|
smAccount = new SmAccountUseCaseMock()
|
||||||
|
nostr = new NostrUseCasesMock()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UseCasesMock;
|
export default UseCasesMock;
|
||||||
|
|||||||
@@ -126,6 +126,20 @@ const fungibleTokenData01 = {
|
|||||||
"immutableData": "troutsblog.com",
|
"immutableData": "troutsblog.com",
|
||||||
"mutableData": ""
|
"mutableData": ""
|
||||||
}
|
}
|
||||||
|
const mutableDataMock = {
|
||||||
|
schema: 'ps007-v1.0.3',
|
||||||
|
schemaDocs: 'https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps007-token-data-schema.md',
|
||||||
|
tokenIcon: 'https://files.tokentiger.com/ipfs/view/bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm',
|
||||||
|
fullSizedUrl: '',
|
||||||
|
nsfw: false,
|
||||||
|
userData: { currentUrl: 'https://tokentiger.com' },
|
||||||
|
jsonLd: {},
|
||||||
|
about: 'This is AI generated art.',
|
||||||
|
category: '',
|
||||||
|
tags: [],
|
||||||
|
mediaType: 'image',
|
||||||
|
currentOwner: {},
|
||||||
|
}
|
||||||
|
|
||||||
const offerMockData = {
|
const offerMockData = {
|
||||||
data: {
|
data: {
|
||||||
@@ -214,5 +228,6 @@ export default {
|
|||||||
fungibleTokenData01,
|
fungibleTokenData01,
|
||||||
offerMockData,
|
offerMockData,
|
||||||
deserealizeTxMockNoOperatorOut,
|
deserealizeTxMockNoOperatorOut,
|
||||||
deserealizeTxMock
|
deserealizeTxMock,
|
||||||
|
mutableDataMock
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the Nostr Use Case library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Unit under test (uut)
|
||||||
|
import NostrUseCases from '../../../src/use-cases/nostr-use-cases.js'
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
describe('#nostr-use-cases', () => {
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
// Delete all previous users in the database.
|
||||||
|
// await testUtils.deleteAllUsers()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
uut = new NostrUseCases({ adapters })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new NostrUseCases()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
console.log(uut) // linter
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of adapters must be passed in when instantiating Nostr Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createDeletedChat', () => {
|
||||||
|
it('should throw an error if no input is given', async () => {
|
||||||
|
try {
|
||||||
|
await uut.createDeletedChat()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(err)
|
||||||
|
// assert.equal(err.status, 422)
|
||||||
|
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if eventId is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.createDeletedChat({})
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw DB errors', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error with the database.
|
||||||
|
sandbox.stub(uut, 'DeletedChatModel').throws(new Error('test error'))
|
||||||
|
|
||||||
|
const inObj = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||||
|
}
|
||||||
|
|
||||||
|
await uut.createDeletedChat(inObj)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a new deleted chat model with minimum inputs', async () => {
|
||||||
|
const inObj = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await uut.createDeletedChat(inObj)
|
||||||
|
|
||||||
|
assert.isObject(res)
|
||||||
|
})
|
||||||
|
it('should create a new deleted chat model with maximum inputs', async () => {
|
||||||
|
const inObj = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||||
|
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||||
|
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await uut.createDeletedChat(inObj)
|
||||||
|
|
||||||
|
assert.isObject(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getDeletedChats', () => {
|
||||||
|
it('should return all deleted chats from the database', async () => {
|
||||||
|
sandbox.stub(uut.DeletedChatModel, 'find').resolves([])
|
||||||
|
const res = await uut.getDeletedChats()
|
||||||
|
|
||||||
|
assert.isArray(res)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error.
|
||||||
|
sandbox.stub(uut.DeletedChatModel, 'find').rejects(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.getDeletedChats()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#createDeletedPost', () => {
|
||||||
|
it('should throw an error if no input is given', async () => {
|
||||||
|
try {
|
||||||
|
await uut.createDeletedPost()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if eventId is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.createDeletedPost({})
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'eventId' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw DB errors', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error with the database.
|
||||||
|
sandbox.stub(uut, 'DeletedPostModel').throws(new Error('test error'))
|
||||||
|
|
||||||
|
const inObj = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||||
|
}
|
||||||
|
|
||||||
|
await uut.createDeletedPost(inObj)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should create a new deleted post model with minimum inputs', async () => {
|
||||||
|
const inObj = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await uut.createDeletedPost(inObj)
|
||||||
|
|
||||||
|
assert.isObject(res)
|
||||||
|
})
|
||||||
|
it('should create a new deleted post model with maximum inputs', async () => {
|
||||||
|
const inObj = {
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
npub: 'npub1d4ed5x49d7p24xn63flj4985dc4gpfngdhtqcxpth0ywhm6czxcscfpcq8',
|
||||||
|
bchAddr: 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
|
||||||
|
pubkey: '6d72da1aa56f82aa9a7a8a7f2a94f46e2a80a6686dd60c182bbbc8ebef5811b1'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await uut.createDeletedPost(inObj)
|
||||||
|
|
||||||
|
assert.isObject(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getDeletedPosts', () => {
|
||||||
|
it('should return all deleted posts from the database', async () => {
|
||||||
|
sandbox.stub(uut.DeletedPostModel, 'find').resolves([])
|
||||||
|
const res = await uut.getDeletedPosts()
|
||||||
|
|
||||||
|
assert.isArray(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw an error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error.
|
||||||
|
sandbox.stub(uut.DeletedPostModel, 'find').rejects(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.getDeletedPosts()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
describe('#removeOlderDeletedChats', () => {
|
||||||
|
it('should remove older deleted chats from the database', async () => {
|
||||||
|
const threeMonthsAgo = new Date()
|
||||||
|
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) // 3 months ago
|
||||||
|
threeMonthsAgo.setDate(threeMonthsAgo.getDate() - 1) // 1 day ago
|
||||||
|
const spy = sinon.spy()
|
||||||
|
const deletedChatsMock = [
|
||||||
|
// Newer deleted chat
|
||||||
|
{
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
createdAt: new Date(),
|
||||||
|
remove: spy
|
||||||
|
},
|
||||||
|
// Older deleted chat
|
||||||
|
{
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
createdAt: threeMonthsAgo,
|
||||||
|
remove: spy
|
||||||
|
}
|
||||||
|
]
|
||||||
|
sandbox.stub(uut.DeletedChatModel, 'find').resolves(deletedChatsMock)
|
||||||
|
await uut.removeOlderDeletedChats()
|
||||||
|
assert.equal(spy.callCount, 1, 'Expected to be called once.')
|
||||||
|
})
|
||||||
|
it('should handle error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error.
|
||||||
|
sandbox.stub(uut.DeletedChatModel, 'find').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.removeOlderDeletedChats()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#removeOlderDeletedPosts', () => {
|
||||||
|
it('should remove older deleted posts from the database', async () => {
|
||||||
|
const threeMonthsAgo = new Date()
|
||||||
|
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) // 3 months ago
|
||||||
|
threeMonthsAgo.setDate(threeMonthsAgo.getDate() - 1) // 1 day ago
|
||||||
|
const spy = sinon.spy()
|
||||||
|
const deletedPostsMock = [
|
||||||
|
// Newer deleted post
|
||||||
|
{
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
createdAt: new Date(),
|
||||||
|
remove: spy
|
||||||
|
},
|
||||||
|
// Older deleted post
|
||||||
|
{
|
||||||
|
eventId: 'note19e93faw4ffqepsqsrwrnstd3ee00nmzakwwuyfjm43dankgummfqms4p6q',
|
||||||
|
createdAt: threeMonthsAgo,
|
||||||
|
remove: spy
|
||||||
|
}
|
||||||
|
]
|
||||||
|
sandbox.stub(uut.DeletedPostModel, 'find').resolves(deletedPostsMock)
|
||||||
|
await uut.removeOlderDeletedPosts()
|
||||||
|
assert.equal(spy.callCount, 1, 'Expected to be called once.')
|
||||||
|
})
|
||||||
|
it('should handle error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error.
|
||||||
|
sandbox.stub(uut.DeletedPostModel, 'find').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.removeOlderDeletedPosts()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -107,6 +107,98 @@ describe('#offer-use-case', () => {
|
|||||||
const result = await uut.createOffer(offerObj)
|
const result = await uut.createOffer(offerObj)
|
||||||
assert.isFalse(result)
|
assert.isFalse(result)
|
||||||
})
|
})
|
||||||
|
it('should create offer with token data', async () => {
|
||||||
|
const tokenDataMock = mockData.nftTokenData01
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
const mutableDataMock = mockData.mutableDataMock
|
||||||
|
const offerEntityMock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.offerEntity, 'validate').returns(offerEntityMock)
|
||||||
|
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)
|
||||||
|
console.log('offerEntityMock', offerEntityMock)
|
||||||
|
assert.property(offerEntityMock, 'tokenData')
|
||||||
|
})
|
||||||
|
it('should create offer with mutable data', async () => {
|
||||||
|
const tokenDataMock = mockData.nftTokenData01
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
const mutableDataMock = mockData.mutableDataMock
|
||||||
|
const offerEntityMock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.offerEntity, 'validate').returns(offerEntityMock)
|
||||||
|
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)
|
||||||
|
console.log('offerEntityMock', offerEntityMock)
|
||||||
|
|
||||||
|
assert.property(offerEntityMock, 'tokenIconUrl')
|
||||||
|
assert.property(offerEntityMock, 'tokenCategories')
|
||||||
|
assert.property(offerEntityMock, 'tokenTags')
|
||||||
|
assert.property(offerEntityMock, 'lastUpdatedTokenData')
|
||||||
|
})
|
||||||
|
|
||||||
|
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'))
|
||||||
|
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 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 () => {
|
it('should create offer', async () => {
|
||||||
const tokenDataMock = mockData.simpleNftTokenData01
|
const tokenDataMock = mockData.simpleNftTokenData01
|
||||||
@@ -400,7 +492,7 @@ describe('#offer-use-case', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should list orders', async () => {
|
it('should list offers', async () => {
|
||||||
const queryMock = {
|
const queryMock = {
|
||||||
sort () {
|
sort () {
|
||||||
return this
|
return this
|
||||||
@@ -414,7 +506,11 @@ describe('#offer-use-case', () => {
|
|||||||
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||||
|
|
||||||
const result = await uut.listNftOffers(1, true)
|
const result = await uut.listNftOffers(1, true)
|
||||||
assert.isArray(result)
|
assert.isObject(result)
|
||||||
|
assert.property(result, 'data')
|
||||||
|
assert.property(result, 'pagination')
|
||||||
|
assert.isArray(result.data)
|
||||||
|
assert.isObject(result.pagination)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -764,6 +860,25 @@ describe('#offer-use-case', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('#acceptCounterOffer', () => {
|
describe('#acceptCounterOffer', () => {
|
||||||
|
it('should capture counter offer if does not exist', async () => {
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
sandbox.stub(uut.CounterOfferModel, 'findOne').resolves(null)
|
||||||
|
const spy = sandbox.stub(uut.CounterOfferModel.prototype, 'save')
|
||||||
|
// Mock dependencies
|
||||||
|
|
||||||
|
await uut.acceptCounterOffer(offerObj)
|
||||||
|
assert.isTrue(spy.calledOnce)
|
||||||
|
})
|
||||||
|
it('should not save counter offer if already exist', async () => {
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
sandbox.stub(uut.CounterOfferModel, 'findOne').resolves(offerObj.data)
|
||||||
|
const spy = sandbox.stub(uut.CounterOfferModel.prototype, 'save')
|
||||||
|
// Mock dependencies
|
||||||
|
|
||||||
|
await uut.acceptCounterOffer(offerObj)
|
||||||
|
assert.isFalse(spy.calledOnce)
|
||||||
|
})
|
||||||
|
|
||||||
it('should return false if order already processed', async () => {
|
it('should return false if order already processed', async () => {
|
||||||
const offerObj = mockData.offerMockData
|
const offerObj = mockData.offerMockData
|
||||||
uut.seenOffers.push(offerObj.data.nostrEventId)
|
uut.seenOffers.push(offerObj.data.nostrEventId)
|
||||||
@@ -775,45 +890,57 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
it('should return if order is not found!', async () => {
|
it('should return if order is not found!', async () => {
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
|
||||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').throws(new Error('test error'))
|
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').throws(new Error('test error'))
|
||||||
|
|
||||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
const result = await uut.acceptCounterOffer(offerObj)
|
||||||
assert.equal(result, 'N/A')
|
assert.equal(result, 'N/A')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return if utxo cant be validated', async () => {
|
it('should return N/A on axios error', async () => {
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
|
||||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
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'))
|
||||||
|
const axiosErr = new Error('axios err')
|
||||||
|
axiosErr.isAxiosError = true
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue').throws(axiosErr)
|
||||||
|
|
||||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
const result = await uut.acceptCounterOffer(offerObj)
|
||||||
assert.equal(result, 'N/A')
|
assert.equal(result, 'N/A')
|
||||||
})
|
})
|
||||||
|
it('should return if utxo can not be validated', async () => {
|
||||||
it('should handle axios error', async () => {
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
const offerObj = mockData.offerMockData
|
||||||
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: { /** .... */ } })
|
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.retryQueue, 'addToQueue').throws(new Error('test error'))
|
||||||
|
|
||||||
|
const result = await uut.acceptCounterOffer(offerObj)
|
||||||
assert.equal(result, 'N/A')
|
assert.equal(result, 'N/A')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return if utxo is invalid', async () => {
|
it('should return if utxo is invalid', async () => {
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
|
||||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||||
|
|
||||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
const result = await uut.acceptCounterOffer(offerObj)
|
||||||
assert.equal(result, 'N/A')
|
assert.equal(result, 'N/A')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle error if counter offer cant be calculated', async () => {
|
it('should handle error if counter offer cant be calculated', async () => {
|
||||||
try {
|
try {
|
||||||
|
const offerObj = mockData.offerMockData
|
||||||
|
|
||||||
// Mock Data
|
// Mock Data
|
||||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
mock.rateInBaseUnit = null
|
mock.rateInBaseUnit = null
|
||||||
@@ -822,8 +949,7 @@ describe('#offer-use-case', () => {
|
|||||||
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||||
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
|
||||||
|
|
||||||
const result = await uut.acceptCounterOffer({ data: {} })
|
await uut.acceptCounterOffer(offerObj)
|
||||||
console.log('result: ', result)
|
|
||||||
assert.fail('unexpected code path')
|
assert.fail('unexpected code path')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
assert.include(err.message, 'Could not calculate the amount of BCH offered in the Counter Offer')
|
assert.include(err.message, 'Could not calculate the amount of BCH offered in the Counter Offer')
|
||||||
@@ -833,6 +959,7 @@ describe('#offer-use-case', () => {
|
|||||||
it('should handle error for wrong transaction output', async () => {
|
it('should handle error for wrong transaction output', async () => {
|
||||||
try {
|
try {
|
||||||
// Mock Data
|
// Mock Data
|
||||||
|
|
||||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
@@ -850,6 +977,7 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
it('should skip transactions that do not have an output for the operator', async () => {
|
it('should skip transactions that do not have an output for the operator', async () => {
|
||||||
// Mock data
|
// Mock data
|
||||||
|
|
||||||
const mock = Object.assign({}, mockData.offerMockData.data)
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
mock.makerAddr = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477' // Unknow Adress
|
mock.makerAddr = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477' // Unknow Adress
|
||||||
mock.rateInBaseUnit = 0
|
mock.rateInBaseUnit = 0
|
||||||
@@ -861,7 +989,7 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMockNoOperatorOut)
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMockNoOperatorOut)
|
||||||
|
|
||||||
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
const result = await uut.acceptCounterOffer({ data: mock })
|
||||||
assert.equal(result, 'N/A')
|
assert.equal(result, 'N/A')
|
||||||
})
|
})
|
||||||
it('should skip transactions if operator address does not match', async () => {
|
it('should skip transactions if operator address does not match', async () => {
|
||||||
@@ -879,7 +1007,7 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||||
|
|
||||||
await uut.acceptCounterOffer({ data: {} })
|
await uut.acceptCounterOffer({ data: mock })
|
||||||
assert.fail('unexpected code path')
|
assert.fail('unexpected code path')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert.include(error.message, 'The Counter Offer has an output address of')
|
assert.include(error.message, 'The Counter Offer has an output address of')
|
||||||
@@ -901,7 +1029,7 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||||
|
|
||||||
await uut.acceptCounterOffer({ data: {} })
|
await uut.acceptCounterOffer({ data: mock })
|
||||||
assert.fail('unexpected code path')
|
assert.fail('unexpected code path')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
assert.include(err.message, 'The Counter Offer has an output address of')
|
assert.include(err.message, 'The Counter Offer has an output address of')
|
||||||
@@ -924,7 +1052,7 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||||
|
|
||||||
const result = await uut.acceptCounterOffer({ data: {} })
|
const result = await uut.acceptCounterOffer({ data: mock })
|
||||||
assert.equal(result, 'N/A')
|
assert.equal(result, 'N/A')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -944,9 +1072,142 @@ describe('#offer-use-case', () => {
|
|||||||
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||||
|
|
||||||
//
|
//
|
||||||
const result = await uut.acceptCounterOffer({ data: {} })
|
const result = await uut.acceptCounterOffer({ data: mock })
|
||||||
assert.isString(result)
|
assert.isString(result)
|
||||||
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)
|
||||||
|
|
||||||
|
assert.property(offer, 'tokenData')
|
||||||
|
assert.property(offer, 'tokenIconUrl')
|
||||||
|
assert.property(offer, 'tokenCategories')
|
||||||
|
assert.property(offer, 'tokenTags')
|
||||||
|
assert.property(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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#listCounterOffersByAddress', () => {
|
||||||
|
it('should throw an error if takerAddr is not provided!', async () => {
|
||||||
|
try {
|
||||||
|
await uut.listCounterOffersByAddress()
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'takerAddr must be a string')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return counter offers by address', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.CounterOfferModel, 'find').resolves([])
|
||||||
|
|
||||||
|
const result = await uut.listCounterOffersByAddress('bitcoincash:qzltx40ldxr53edchprppj5lpg4q7vl20usx25ga44')
|
||||||
|
assert.isArray(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import mongoose from 'mongoose'
|
||||||
|
import config from '../../../config/index.js'
|
||||||
|
import User from '../../../src/adapters/localdb/models/users.js'
|
||||||
|
|
||||||
|
async function getUsers () {
|
||||||
|
// Connect to the Mongo Database.
|
||||||
|
mongoose.Promise = global.Promise
|
||||||
|
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||||
|
await mongoose.connect(
|
||||||
|
config.database,
|
||||||
|
{ useNewUrlParser: true, useUnifiedTopology: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the user by email.
|
||||||
|
const user = await User.findOne({
|
||||||
|
email: 'test@test.com'
|
||||||
|
}, '-password')
|
||||||
|
// console.log('user: ', user)
|
||||||
|
|
||||||
|
// Update the users password
|
||||||
|
user.password = 'test'
|
||||||
|
|
||||||
|
// Change the user to an admin
|
||||||
|
// user.type = 'admin'
|
||||||
|
|
||||||
|
// Save the changes to the database.
|
||||||
|
await user.save()
|
||||||
|
|
||||||
|
mongoose.connection.close()
|
||||||
|
}
|
||||||
|
getUsers()
|
||||||
@@ -11,12 +11,12 @@ import axios from 'axios'
|
|||||||
// import User from '../../../src/adapters/localdb/models/users.js'
|
// import User from '../../../src/adapters/localdb/models/users.js'
|
||||||
|
|
||||||
// Customize these variables
|
// Customize these variables
|
||||||
const EMAIL = 'someone@somewhere.com'
|
const EMAIL = 'chris.troutner@gmail.com'
|
||||||
const NAME = 'Someone'
|
const NAME = 'Chris'
|
||||||
const PASSWORD = 'test'
|
const PASSWORD = '0mega16'
|
||||||
|
|
||||||
const ADMIN_EMAIL = 'system@system.com'
|
const ADMIN_EMAIL = 'system@system.com'
|
||||||
const ADMIN_PASSWORD = 'admin'
|
const ADMIN_PASSWORD = 'decatur'
|
||||||
|
|
||||||
async function createUser () {
|
async function createUser () {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import mongoose from 'mongoose'
|
||||||
|
// import config from '../../config/index.js'
|
||||||
|
import User from '../../src/adapters/localdb/models/users.js'
|
||||||
|
|
||||||
|
const mongooseConnectStr = 'mongodb://172.17.0.1:5666/bch-swap-service-prod'
|
||||||
|
|
||||||
|
async function getUsers () {
|
||||||
|
// Connect to the Mongo Database.
|
||||||
|
mongoose.Promise = global.Promise
|
||||||
|
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||||
|
await mongoose.connect(
|
||||||
|
mongooseConnectStr,
|
||||||
|
{ useNewUrlParser: true, useUnifiedTopology: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the user by email.
|
||||||
|
const user = await User.findOne({
|
||||||
|
email: 'test@test.com'
|
||||||
|
}, '-password')
|
||||||
|
|
||||||
|
// Update the users password
|
||||||
|
// user.password = 'newpassword'
|
||||||
|
|
||||||
|
// Change the user to an admin
|
||||||
|
// user.type = 'admin'
|
||||||
|
|
||||||
|
// Save the changes to the database.
|
||||||
|
await user.save()
|
||||||
|
|
||||||
|
mongoose.connection.close()
|
||||||
|
}
|
||||||
|
getUsers()
|
||||||
Reference in New Issue
Block a user