Compare commits

..
52 Commits
Author SHA1 Message Date
Chris Troutner ac83827fca Merge branch 'master' into fullstack 2025-12-04 15:29:13 +00:00
Chris Troutner 0811024030 Merge pull request #123 from Permissionless-Software-Foundation/dh-token-data
feat(offer): Added tokenData to offer model
2025-12-04 08:08:45 -07:00
Daniel Gonzalez 737d35463d feat(offer): Added tokenData to offer model 2025-12-03 19:21:40 -04:00
Chris Troutner 2ed1eaba9c Merge branch 'master' into fullstack 2025-11-26 14:16:56 +00:00
Chris Troutner 6dddfbdcb6 Merge pull request #122 from Permissionless-Software-Foundation/dh-ctr-offr-page
feat(counterOffer): Added new properties to counter offer model
2025-11-25 18:59:41 -08:00
Daniel Gonzalez fc2d7b4b9b feat(counterOffer): Added new properties to counter offer model 2025-11-25 20:57:44 -04:00
Chris Troutner c877898ae7 Merge pull request #121 from Permissionless-Software-Foundation/dh-track-counter-offrs
feat(offers): Track Counter Offers
2025-11-21 15:54:45 -08:00
Daniel Gonzalez 88e27f47f5 feat(offers): Track Counter Offers 2025-11-21 17:34:05 -04:00
Chris Troutner 9681def3e0 Merge branch 'master' into fullstack 2025-11-14 19:24:38 +00:00
Chris Troutner 0057007b4c Merge pull request #120 from Permissionless-Software-Foundation/dh-npub-offer
feat(nostr): Added npub to Offer model
2025-11-13 06:28:42 -08:00
Daniel Gonzalez 8aad93e572 feat(nostr): Added npub to Offer model 2025-11-11 14:48:09 -04:00
Chris Troutner fa0ec3ece2 Merge pull request #119 from Permissionless-Software-Foundation/dh-npub-order
feat(nostr): Added npub to order
2025-11-11 06:56:18 -08:00
Daniel Gonzalez 4f9f2f8370 feat(nostr): Added npub to order 2025-11-10 18:45:37 -04:00
Chris Troutner 0fb93be2cd Merge pull request #118 from Permissionless-Software-Foundation/dh-timer-deleted
feat(chats): Added timer to delete old deleted chat and post database models
2025-11-06 10:58:25 -08:00
Daniel Gonzalez 26dcd6072e feat(chats): Added timer to delete old deleted chat and post database models 2025-11-06 11:42:46 -04:00
Chris Troutner 42f228af0e Merge branch 'master' into fullstack 2025-11-05 18:50:34 +00:00
Chris Troutner 8be70baff2 Merge pull request #117 from Permissionless-Software-Foundation/dh-nostr-route
feat(nostr): Added new route for deleteChat and deletePost models
2025-10-29 09:02:51 -07:00
Daniel Gonzalez 7e5c314000 feat(nostr): Added new route for deleteChat and deletePost models 2025-10-28 19:18:18 -04:00
Chris Troutner 9669112d69 Merge pull request #116 from Permissionless-Software-Foundation/ct-unstable
fix(offer): Adding makerNpub property to db model
2025-10-26 11:14:07 -07:00
Chris Troutner 9a71763f79 fix(offer): Adding makerNpub property to db model 2025-10-26 11:12:49 -07:00
Chris Troutner 1a96eac8c9 Merge pull request #115 from Permissionless-Software-Foundation/ct-unstable
Adding deletedChat and deletedPost DB models
2025-10-25 16:03:55 -07:00
Chris Troutner a2acdef539 fix(database): Adding deletedChat and deletedPost DB models 2025-10-25 16:02:35 -07:00
Chris Troutner 58ddbbcc4e fix(updateUser): Adding util scripts for dev & prod 2025-10-25 14:35:22 -07:00
Chris Troutner 5ff33ff19f Merge remote-tracking branch 'upstream/master' into ct-unstable 2025-10-25 14:34:05 -07:00
Chris Troutner 682f599261 fixing updateUser script 2025-10-25 14:33:51 -07:00
Chris Troutner 7c26e33a8c Merge pull request #181 from Permissionless-Software-Foundation/ct-unstable
fix(util): Fixing problem with updateUser.js script
2025-10-25 14:33:12 -07:00
Chris Troutner 2595ee8295 fix(util): Fixing problem with updateUser.js script 2025-10-25 14:31:33 -07:00
Chris Troutner 531685c0e4 Fixing merge conflicts with upstream 2025-10-25 14:26:16 -07:00
Chris Troutner 476fdcd0aa Merge pull request #180 from Permissionless-Software-Foundation/ct-unstable
fix(utils): Adding script to update users password or convert to admin
2025-10-25 14:24:43 -07:00
Chris Troutner b6762b215e fix(utils): Adding script to update users password or convert to admin 2025-10-25 14:23:28 -07:00
Chris Troutner 4ac7f59378 Merge branch 'master' into fullstack 2025-10-25 18:30:01 +00:00
Chris Troutner 3cdf49bb56 Merge pull request #114 from Permissionless-Software-Foundation/dh-update-endpoint
feat(update): Added Update button REST endpoint
2025-10-25 11:25:02 -07:00
Chris Troutner 28615403b8 Formatting and adding dev comments 2025-10-25 11:22:13 -07:00
Daniel Gonzalez bb10bd819a feat(update): Added Update button REST endpoint 2025-10-24 19:58:56 -04:00
Chris Troutner 725c1c39a5 Merge branch 'master' into fullstack 2025-10-09 15:09:25 +00:00
Chris Troutner 7290c8db30 Merge branch 'master' into fullstack 2025-08-10 18:11:09 +00:00
Chris Troutner 36e1ec1f8e Merge branch 'master' into fullstack 2025-07-28 18:19:52 +00:00
Chris Troutner baa574a93e Merge branch 'master' into fullstack 2025-07-08 23:56:41 +00:00
Chris Troutner 8333563d98 Merge branch 'master' into fullstack 2025-07-06 21:44:33 +00:00
Chris Troutner 99fd116b6d Merge branch 'master' into fullstack 2025-06-27 16:28:28 +00:00
Chris Troutner ccae89ad01 Merge branch 'master' into fullstack 2025-06-06 18:53:44 +00:00
Chris Troutner 0ecae58be2 Merge branch 'master' into fullstack 2025-06-02 01:52:27 +00:00
Chris Troutner 5f442835e5 Customizing scrips 2025-05-20 19:38:16 +00:00
Chris Troutner aa8fd89227 Merge branch 'master' into fullstack 2025-05-19 22:45:42 +00:00
Chris Troutner 3da7432273 Building UI from psfoundation branch 2025-05-16 17:10:43 +00:00
Chris Troutner 61c1dd5878 Merge branch 'master' into fullstack 2025-05-16 14:56:09 +00:00
Chris Troutner c095e7af39 noop 2025-05-16 14:55:57 +00:00
Chris Troutner fb9612bb65 Using ct-unstable branch 2025-05-16 02:22:19 +00:00
Chris Troutner e4a37b1c10 Merge branch 'ct-unstable' into fullstack 2025-05-16 00:58:49 +00:00
Chris Troutner ba4f847e57 Merge branch 'master' into fullstack 2025-05-16 00:39:57 +00:00
Chris Troutner 81e623e35f Merge branch 'ct-unstable' into fullstack 2025-03-09 01:10:05 +00:00
Chris Troutner db1da9172c Using fullstack branch of UI 2024-12-29 09:37:28 -08:00
42 changed files with 2392 additions and 23 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v
WORKDIR /home/safeuser/bch-dex-ui-v3
# For development: switch to unstable branch
RUN git checkout ct-unstable
RUN git checkout psfoundation
# Install dependencies
#RUN mkdir .ipfsdata
@@ -31,4 +31,7 @@ export APP_ID=bch-dex-001
#export P2WDB_URL=http://172.17.0.1:5667
export DISABLE_NEW_ACCOUNTS=true
export ADMIN_PASSWORD=decatur
npm start
+11
View File
@@ -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
}
+6
View File
@@ -9,6 +9,9 @@ import Order from './models/order.js'
import Offer from './models/offer.js'
import Usage from './models/usage.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 {
constructor () {
@@ -19,6 +22,9 @@ class LocalDB {
this.Offer = Offer
this.Usage = Usage
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)
+2
View File
@@ -18,6 +18,7 @@ const Offer = new mongoose.Schema({
p2wdbHash: { type: String },
offerStatus: { type: String },
makerAddr: { type: String },
makerNpub: { type: String, default: null },
// Authentication data
signature: { type: String },
@@ -45,6 +46,7 @@ const Offer = new mongoose.Schema({
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
+1
View File
@@ -24,6 +24,7 @@ const Order = new mongoose.Schema({
p2wdbTxid: { type: String },
p2wdbHash: { type: String },
makerAddr: { type: String, required: true },
makerNpub: { type: String, required: true },
// Authentication data
signature: { type: String },
+28
View File
@@ -7,6 +7,8 @@ import BchNostr from 'bch-nostr'
import { RelayPool } from 'nostr'
import RetryQueue from '@chris.troutner/retry-queue'
import * as nip19 from 'nostr-tools/nip19'
import { base58_to_binary as base58Tobinary } from 'base58-js'
import { getPublicKey } from 'nostr-tools/pure'
class NostrAdapter {
constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) {
@@ -44,6 +46,7 @@ class NostrAdapter {
this.npub2pubkey = this.npub2pubkey.bind(this)
this.readGlobalFeed = this.readGlobalFeed.bind(this)
this.getFollowers = this.getFollowers.bind(this)
this.privKeyToNpub = this.privKeyToNpub.bind(this)
}
// Create nostr keys.
@@ -247,6 +250,31 @@ class NostrAdapter {
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
+5
View File
@@ -19,6 +19,7 @@ import OrderRouter from './order/index.js'
import P2WDBRouter from './p2wdb/index.js'
import UsageRESTController from './usage/index.js'
import SmAccountRouter from './smAccount/index.js'
import NostrRouter from './nostr/index.js'
class RESTControllers {
constructor (localConfig = {}) {
@@ -90,6 +91,10 @@ class RESTControllers {
const smAccountRouter = new SmAccountRouter(dependencies)
smAccountRouter.attach(app)
// Attach the REST API Controllers associated with the /nostr route
const nostrRouter = new NostrRouter(dependencies)
nostrRouter.attach(app)
}
}
@@ -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
+81
View File
@@ -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.takeOffer = this.takeOffer.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)
}
@@ -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
handleError (ctx, err) {
console.log('err', err.message)
+2
View File
@@ -55,10 +55,12 @@ class OfferRouter {
// Define the routes and attach the controller.
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
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/nft/:page', this.offerRESTController.listNftOffers)
this.router.get('/list/fungible/:page', this.offerRESTController.listFungibleOffers)
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.
app.use(this.router.routes())
+21 -1
View File
@@ -28,6 +28,7 @@ class TimerControllers {
this.backupUsageInterval = 60000 * 10 // 10 minutes
this.newSmAccountsInterval = 60000 * 60 // 60 minutes
this.updateSmAccountsInterval = 60000 * 10 // 10 minutes
this.removeOlderDeletedChatsAndPostsInterval = 60000 * 60 * 1 // 1 hour
// Encapsulate dependencies
this.config = config
@@ -42,7 +43,7 @@ class TimerControllers {
this.backupUsage = this.backupUsage.bind(this)
this.newSmAccounts = this.newSmAccounts.bind(this)
this.updateSmAccounts = this.updateSmAccounts.bind(this)
this.removeOlderDeletedChatsAndPosts = this.removeOlderDeletedChatsAndPosts.bind(this)
// State
this.gcOrdersInt = null
this.gcOffersInt = null
@@ -50,6 +51,7 @@ class TimerControllers {
this.loadOffersInt = null
this.newSmAccountsInt = null
this.updateSmAccountsInt = null
this.removeOlderDeletedChatsAndPostsInt = null
}
// Start all the time-based controllers.
@@ -62,6 +64,7 @@ class TimerControllers {
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
this.newSmAccountsInt = setInterval(this.newSmAccounts, this.newSmAccountsInterval)
this.updateSmAccountsInt = setInterval(this.updateSmAccounts, this.updateSmAccountsInterval)
this.removeOlderDeletedChatsAndPostsInt = setInterval(this.removeOlderDeletedChatsAndPosts, this.removeOlderDeletedChatsAndPostsInterval)
return true
}
@@ -74,6 +77,7 @@ class TimerControllers {
clearInterval(this.backupUsageHandle)
clearInterval(this.newSmAccountsInt)
clearInterval(this.updateSmAccountsInt)
clearInterval(this.removeOlderDeletedChatsAndPostsInt)
}
// Garbage Collect the Orders.
@@ -194,6 +198,22 @@ class TimerControllers {
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
+68
View File
@@ -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
+17
View File
@@ -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
+17
View File
@@ -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
+7 -1
View File
@@ -31,6 +31,7 @@ class OfferEntity {
utxoVout,
offerStatus,
makerAddr,
makerNpub,
ticker,
tokenType,
nostrEventId,
@@ -89,6 +90,10 @@ class OfferEntity {
if (!operatorPercentage || typeof operatorPercentage !== '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.
let timestamp = new Date(offerData.timestamp)
@@ -113,7 +118,8 @@ class OfferEntity {
makerAddr,
ticker,
tokenType,
nostrEventId
nostrEventId,
makerNpub
}
// console.log('offer entity validatedOfferData: ', validatedOfferData)
+2
View File
@@ -11,6 +11,7 @@ import OfferUseCases from './offer/index.js'
import OrderUseCases from './order.js'
import { UsageUseCases } from './usage-use-cases.js'
import SmAccountUseCases from './smAccount-use-cases.js'
import NostrUseCases from './nostr-use-cases.js'
class UseCases {
constructor (localConfig = {}) {
@@ -29,6 +30,7 @@ class UseCases {
this.offer = new OfferUseCases(localConfig)
this.usage = new UsageUseCases(localConfig)
this.smAccount = new SmAccountUseCases(localConfig)
this.nostr = new NostrUseCases(localConfig)
}
// Run any startup Use Cases at the start of the app.
+130
View File
@@ -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
+118
View File
@@ -19,6 +19,7 @@ import RetryQueue from '@chris.troutner/retry-queue'
// Local libraries
import OfferEntity from '../../entities/offer.js'
import CounterOfferEntity from '../../entities/counterOffer.js'
import config from '../../../config/index.js'
const DEFAULT_ENTRIES_PER_PAGE = 20
@@ -45,7 +46,9 @@ class OfferUseCases {
this.config = config
this.axios = axios
this.offerEntity = new OfferEntity()
this.counterOfferEntity = new CounterOfferEntity()
this.OfferModel = this.adapters.localdb.Offer
this.CounterOfferModel = this.adapters.localdb.CounterOffer
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
// Bind 'this' object to functions
@@ -65,6 +68,8 @@ class OfferUseCases {
this.flagOffer = this.flagOffer.bind(this)
this.loadOffers = this.loadOffers.bind(this)
this.listOffersByAddress = this.listOffersByAddress.bind(this)
this.syncOfferMutableData = this.syncOfferMutableData.bind(this)
this.listCounterOffersByAddress = this.listCounterOffersByAddress.bind(this)
// State
this.seenOffers = []
@@ -143,6 +148,7 @@ class OfferUseCases {
const immutableDataCid = tokenData.immutableData
offerEntity.mutableDataCid = mutableDataCid
offerEntity.immutableDataCid = immutableDataCid
offerEntity.tokenData = tokenData
// Get the mutable data from the cid if it exists.
if (mutableDataCid && typeof mutableDataCid === 'string') {
@@ -594,10 +600,23 @@ class OfferUseCases {
// and broadcasts the transaction to accept the Counter Offer.
async acceptCounterOffer (offerData) {
try {
console.log('offerData', offerData)
// console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
// Quickly skip over offers that have already been processed.
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)) {
// console.log(`Offer with event ID ${eventId} already processed. Skipping.`)
return false
@@ -935,6 +954,105 @@ class OfferUseCases {
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
+3
View File
@@ -116,6 +116,9 @@ class OrderLib {
orderEntity.operatorAddress = this.config.operatorAddress
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
// config file.
const postObj = {
+188
View File
@@ -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)
}
})
})
})
}
+29
View File
@@ -224,4 +224,33 @@ describe('#nostr.js', () => {
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',
dataType: 'dataType',
makerAddr: 'makerAddr',
makerNpub: 'makerNpub',
rateInBaseUnit: 'rateInBaseUnit',
minUnitsToExchange: 'minUnitsToExchange',
ticker: 'ticker',
@@ -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', () => {
it('should still throw error if there is no message', () => {
@@ -187,4 +187,20 @@ describe('#Timer-Controllers', () => {
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)
})
})
})
+34 -1
View File
@@ -491,6 +491,36 @@ describe('#Offer-Entity', () => {
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', () => {
const offerObj = {
@@ -513,7 +543,8 @@ describe('#Offer-Entity', () => {
tokenType: 1,
nostrEventId: 'test',
operatorAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
operatorPercentage: 10
operatorPercentage: 10,
makerNpub: 'npub1dtkf954h5k2yuv04xp44g5hnmk5k0ppv8mekxafm2gc0vveaxh6s0297qg'
},
timestamp: '2021-09-20T17:54:26.395Z',
localTimeStamp: '9/20/2021, 10:54:26 AM',
@@ -539,6 +570,8 @@ describe('#Offer-Entity', () => {
assert.property(result, 'txid')
assert.property(result, 'p2wdbHash')
assert.property(result, 'tokenType')
assert.property(result, 'makerAddr')
assert.property(result, 'makerNpub')
})
})
})
+38 -2
View File
@@ -154,6 +154,17 @@ const localdb = {
return {}
}
},
CounterOffer: class CounterOffer {
constructor (obj) {}
static findById () {}
static find () {}
static findOne () {}
static countDocuments(){}
async save () {
return {}
}
},
SmAccount: class SmAccount {
constructor (obj) {}
@@ -165,7 +176,31 @@ const localdb = {
async save () {
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 = {
@@ -216,7 +251,8 @@ const nostr = {
read: async () => { return true },
eventId2note: () => { return 'testNoteId' },
readGlobalFeed: async () => { return [] },
getFollowers: async () => { return [] }
getFollowers: async () => { return [] },
privKeyToNpub: async () => { return 'fakeNpub' }
}
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs, nostr}
+28
View File
@@ -71,6 +71,12 @@ class Offer {
async acceptCounterOffer() {
return {}
}
async syncOfferMutableData(){
return {}
}
async listCounterOffersByAddress() {
return []
}
}
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 {
constuctor(localConfig = {}) {
// this.user = new UserUseCaseMock(localConfig)
@@ -143,6 +170,7 @@ class UseCasesMock {
order = new Order()
usage = new UsageUseCaseMock()
smAccount = new SmAccountUseCaseMock()
nostr = new NostrUseCasesMock()
}
export default UseCasesMock;
+287
View File
@@ -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')
}
})
})
})
+213 -13
View File
@@ -107,14 +107,14 @@ describe('#offer-use-case', () => {
const result = await uut.createOffer(offerObj)
assert.isFalse(result)
})
it('should create offer with mutable data', async () => {
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.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
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
@@ -123,6 +123,31 @@ describe('#offer-use-case', () => {
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 () => {
@@ -835,6 +860,25 @@ describe('#offer-use-case', () => {
})
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 () => {
const offerObj = mockData.offerMockData
uut.seenOffers.push(offerObj.data.nostrEventId)
@@ -846,35 +890,57 @@ describe('#offer-use-case', () => {
it('should return if order is not found!', async () => {
// Mock dependencies
const offerObj = mockData.offerMockData
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')
})
it('should return N/A on axios error', async () => {
// Mock dependencies
const offerObj = mockData.offerMockData
const mock = Object.assign({}, mockData.offerMockData.data)
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').throws(new Error('test error'))
const axiosErr = new Error('axios err')
axiosErr.isAxiosError = true
sandbox.stub(uut.retryQueue, 'addToQueue').throws(axiosErr)
const result = await uut.acceptCounterOffer(offerObj)
assert.equal(result, 'N/A')
})
it('should return if utxo can not be validated', async () => {
// Mock dependencies
const offerObj = mockData.offerMockData
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({ data: { /** .... */ } })
const result = await uut.acceptCounterOffer(offerObj)
assert.equal(result, 'N/A')
})
it('should return if utxo is invalid', async () => {
// Mock dependencies
const offerObj = mockData.offerMockData
const mock = Object.assign({}, mockData.offerMockData.data)
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
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')
})
it('should handle error if counter offer cant be calculated', async () => {
try {
const offerObj = mockData.offerMockData
// Mock Data
const mock = Object.assign({}, mockData.offerMockData.data)
mock.rateInBaseUnit = null
@@ -883,8 +949,7 @@ describe('#offer-use-case', () => {
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
const result = await uut.acceptCounterOffer({ data: {} })
console.log('result: ', result)
await uut.acceptCounterOffer(offerObj)
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'Could not calculate the amount of BCH offered in the Counter Offer')
@@ -894,6 +959,7 @@ describe('#offer-use-case', () => {
it('should handle error for wrong transaction output', async () => {
try {
// Mock Data
const mock = Object.assign({}, mockData.offerMockData.data)
// Mock dependencies
@@ -911,6 +977,7 @@ describe('#offer-use-case', () => {
it('should skip transactions that do not have an output for the operator', async () => {
// Mock data
const mock = Object.assign({}, mockData.offerMockData.data)
mock.makerAddr = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477' // Unknow Adress
mock.rateInBaseUnit = 0
@@ -922,7 +989,7 @@ describe('#offer-use-case', () => {
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')
})
it('should skip transactions if operator address does not match', async () => {
@@ -940,7 +1007,7 @@ describe('#offer-use-case', () => {
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
await uut.acceptCounterOffer({ data: {} })
await uut.acceptCounterOffer({ data: mock })
assert.fail('unexpected code path')
} catch (error) {
assert.include(error.message, 'The Counter Offer has an output address of')
@@ -962,7 +1029,7 @@ describe('#offer-use-case', () => {
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
await uut.acceptCounterOffer({ data: {} })
await uut.acceptCounterOffer({ data: mock })
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'The Counter Offer has an output address of')
@@ -985,7 +1052,7 @@ describe('#offer-use-case', () => {
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')
})
@@ -1005,9 +1072,142 @@ describe('#offer-use-case', () => {
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.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)
})
})
})
+31
View File
@@ -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()
+4 -4
View File
@@ -11,12 +11,12 @@ import axios from 'axios'
// import User from '../../../src/adapters/localdb/models/users.js'
// Customize these variables
const EMAIL = 'someone@somewhere.com'
const NAME = 'Someone'
const PASSWORD = 'test'
const EMAIL = 'chris.troutner@gmail.com'
const NAME = 'Chris'
const PASSWORD = '0mega16'
const ADMIN_EMAIL = 'system@system.com'
const ADMIN_PASSWORD = 'admin'
const ADMIN_PASSWORD = 'decatur'
async function createUser () {
try {
+32
View File
@@ -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()