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 | |
|---|---|---|---|
|
|
5f91395766 | ||
|
|
dda6cb2aa4 | ||
|
|
578aade39b | ||
|
|
b09890a527 | ||
|
|
a32bd840b9 | ||
|
|
0bdc232938 |
@@ -14,8 +14,8 @@ const SmAccount = new mongoose.Schema({
|
||||
pfp: { type: String, default: '' },
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
updatedAt: { type: Date, default: Date.now },
|
||||
followers: { type: Number, default: 0 },
|
||||
following: { type: Number, default: 0 },
|
||||
followerCnt: { type: Number, default: 0 },
|
||||
followingCnt: { type: Number, default: 0 },
|
||||
posts: { type: Number, default: 0 },
|
||||
likes: { type: Number, default: 0 },
|
||||
comments: { type: Number, default: 0 },
|
||||
|
||||
@@ -41,7 +41,9 @@ class NostrAdapter {
|
||||
this.read = this.read.bind(this)
|
||||
this.eventId2note = this.eventId2note.bind(this)
|
||||
this.pubkey2npub = this.pubkey2npub.bind(this)
|
||||
this.npub2pubkey = this.npub2pubkey.bind(this)
|
||||
this.readGlobalFeed = this.readGlobalFeed.bind(this)
|
||||
this.getFollowers = this.getFollowers.bind(this)
|
||||
}
|
||||
|
||||
// Create nostr keys.
|
||||
@@ -140,6 +142,10 @@ class NostrAdapter {
|
||||
return nip19.npubEncode(pubkey)
|
||||
}
|
||||
|
||||
npub2pubkey (npub) {
|
||||
return nip19.decode(npub)
|
||||
}
|
||||
|
||||
// Read the global feed.
|
||||
async readGlobalFeed (inObj = {}) {
|
||||
try {
|
||||
@@ -190,6 +196,57 @@ class NostrAdapter {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async getFollowers (inObj = {}) {
|
||||
try {
|
||||
const { pubkey } = inObj
|
||||
|
||||
if (!pubkey || typeof pubkey !== 'string') {
|
||||
throw new Error('pubkey must be a string!')
|
||||
}
|
||||
|
||||
const relays = [this.relayWs]
|
||||
const pool = this.RelayPool(relays)
|
||||
|
||||
const nostrData = new Promise((resolve, reject) => {
|
||||
const followers = []
|
||||
|
||||
pool.on('open', (relay) => {
|
||||
// Query for kind 3 events where the target pubkey appears in tags
|
||||
// This finds all contact lists that include the target pubkey
|
||||
relay.subscribe('subid', {
|
||||
limit: 100,
|
||||
kinds: [3],
|
||||
'#p': [pubkey] // Filter by tag 'p' containing the target pubkey
|
||||
})
|
||||
})
|
||||
|
||||
pool.on('eose', relay => {
|
||||
relay.close()
|
||||
resolve(followers)
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
// Each event represents a contact list from someone who follows the target
|
||||
// The author of this event is a follower
|
||||
const follower = {
|
||||
pubkey: ev.pubkey,
|
||||
npub: this.pubkey2npub(ev.pubkey),
|
||||
contactList: ev.tags,
|
||||
createdAt: ev.created_at
|
||||
}
|
||||
|
||||
followers.push(follower)
|
||||
})
|
||||
})
|
||||
|
||||
const followers = await nostrData
|
||||
return followers
|
||||
} catch (error) {
|
||||
console.log(`Error in nostr.js/getFollowers() ${error.message} `)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NostrAdapter
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/*
|
||||
REST API Controller library for the /offer route
|
||||
|
||||
TODO:
|
||||
- Add api-doc documentation for all endpoints in this file.
|
||||
*/
|
||||
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
@@ -30,6 +33,7 @@ class OfferRESTControllerLib {
|
||||
this.listNftOffers = this.listNftOffers.bind(this)
|
||||
this.listFungibleOffers = this.listFungibleOffers.bind(this)
|
||||
this.takeOffer = this.takeOffer.bind(this)
|
||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
@@ -115,6 +119,21 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// List all offers being made by a given address.
|
||||
// curl -X GET http://localhost:5700/offer/list/addr/bitcoincash:qrpxtnrlhfz9wsuuse7z5k2mxmvw0r3pu5qepyhmq2
|
||||
async listOffersByAddress (ctx) {
|
||||
try {
|
||||
const addr = ctx.params.addr
|
||||
|
||||
const offers = await this.useCases.offer.listOffersByAddress(addr)
|
||||
|
||||
ctx.body = offers
|
||||
} catch (err) {
|
||||
console.log('Error in listOffersByAddress REST API handler: ', err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err.message)
|
||||
|
||||
@@ -8,8 +8,6 @@ import Router from 'koa-router'
|
||||
// Local libraries.
|
||||
import OfferRESTControllerLib from './controller.js'
|
||||
|
||||
let _this
|
||||
|
||||
class OfferRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
@@ -38,7 +36,8 @@ class OfferRouter {
|
||||
const baseUrl = '/offer'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
_this = this
|
||||
// Bind 'this' object to all subfunctions.
|
||||
this.attach = this.attach.bind(this)
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
@@ -50,19 +49,20 @@ class OfferRouter {
|
||||
|
||||
// 12/3/24 CT:
|
||||
// Note: The createOffer() path was used by a P2WDB webhook to generate an
|
||||
// Offer from and Order. This has been deprecated and Offers are now created
|
||||
// Offer from an Order. This has been deprecated and Offers are now created
|
||||
// by a Timer Controller monitoring a Nostr topic.
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
|
||||
this.router.post('/take', _this.offerRESTController.takeOffer)
|
||||
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.post('/take', this.offerRESTController.takeOffer)
|
||||
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)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
app.use(_this.router.allowedMethods())
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,16 @@ class SmAccountRESTControllerLib {
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /sm/list/all/{page} List all Social Media Accounts
|
||||
* @apiPermission public
|
||||
* @apiName ListAccounts
|
||||
* @apiGroup REST Social Media Accounts
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/sm/list/all/0
|
||||
*
|
||||
*/
|
||||
// curl -X GET http://localhost:5700/sm/list/all/0
|
||||
async listAccounts (ctx) {
|
||||
try {
|
||||
@@ -44,6 +54,68 @@ class SmAccountRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /sm/npub/{npub} Get Social Media Account by Npub
|
||||
* @apiPermission public
|
||||
* @apiName GetAccountByNpub
|
||||
* @apiGroup REST Social Media Accounts
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/sm/npub/npub1y3xq402pu9aqms3khetnt5gzm54t63pzcla56wwfmwshde0ws77qkff5gc
|
||||
*
|
||||
*/
|
||||
// curl -X GET http://localhost:5700/sm/npub/npub1y3xq402pu9aqms3khetnt5gzm54t63pzcla56wwfmwshde0ws77qkff5gc
|
||||
async getAccountByNpub (ctx) {
|
||||
try {
|
||||
const npub = ctx.params.npub
|
||||
if (!npub) {
|
||||
ctx.throw(400, 'Npub is required')
|
||||
}
|
||||
|
||||
const account = await _this.useCases.smAccount.getAccountByNpub(npub)
|
||||
|
||||
if (!account) {
|
||||
ctx.throw(404, 'Account not found')
|
||||
}
|
||||
|
||||
ctx.body = account
|
||||
} catch (err) {
|
||||
// console.log('Error in getAccountByNpub REST API handler: ', err)
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /sm/bchAddr/{bchAddr} Get Social Media Account by BCH address
|
||||
* @apiPermission public
|
||||
* @apiName GetAccountByBchAddr
|
||||
* @apiGroup REST Social Media Accounts
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5700/sm/bchAddr/bitcoincash:qz6lf6gpmn3secx73g7ucfcgy8mrh3sz2y2ylk643x
|
||||
*
|
||||
*/
|
||||
// curl -X GET http://localhost:5700/sm/bchAddr/bitcoincash:qz6lf6gpmn3secx73g7ucfcgy8mrh3sz2y2ylk643x
|
||||
async getAccountByBchAddr (ctx) {
|
||||
try {
|
||||
const bchAddr = ctx.params.bchAddr
|
||||
if (!bchAddr) {
|
||||
ctx.throw(400, 'BCH address is required')
|
||||
}
|
||||
|
||||
const account = await _this.useCases.smAccount.getAccountByBchAddr(bchAddr)
|
||||
|
||||
if (!account) {
|
||||
ctx.throw(404, 'Account not found')
|
||||
}
|
||||
|
||||
ctx.body = account
|
||||
} catch (err) {
|
||||
// console.log('Error in getAccountByBchAddr REST API handler: ', err)
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err)
|
||||
|
||||
@@ -53,6 +53,8 @@ class SmAccountRouter {
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/list/all/:page', _this.smAccountRESTController.listAccounts)
|
||||
this.router.get('/npub/:npub', _this.smAccountRESTController.getAccountByNpub)
|
||||
this.router.get('/bchAddr/:bchAddr', _this.smAccountRESTController.getAccountByBchAddr)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
|
||||
@@ -27,6 +27,7 @@ class TimerControllers {
|
||||
this.cleanUsageInterval = 60000 * 60 // 1 hour
|
||||
this.backupUsageInterval = 60000 * 10 // 10 minutes
|
||||
this.newSmAccountsInterval = 60000 * 60 // 60 minutes
|
||||
this.updateSmAccountsInterval = 60000 * 10 // 10 minutes
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
@@ -40,6 +41,7 @@ class TimerControllers {
|
||||
this.cleanUsage = this.cleanUsage.bind(this)
|
||||
this.backupUsage = this.backupUsage.bind(this)
|
||||
this.newSmAccounts = this.newSmAccounts.bind(this)
|
||||
this.updateSmAccounts = this.updateSmAccounts.bind(this)
|
||||
|
||||
// State
|
||||
this.gcOrdersInt = null
|
||||
@@ -47,6 +49,7 @@ class TimerControllers {
|
||||
this.checkDupOffersInt = null
|
||||
this.loadOffersInt = null
|
||||
this.newSmAccountsInt = null
|
||||
this.updateSmAccountsInt = null
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
@@ -58,7 +61,7 @@ class TimerControllers {
|
||||
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
|
||||
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
|
||||
this.newSmAccountsInt = setInterval(this.newSmAccounts, this.newSmAccountsInterval)
|
||||
|
||||
this.updateSmAccountsInt = setInterval(this.updateSmAccounts, this.updateSmAccountsInterval)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -70,6 +73,7 @@ class TimerControllers {
|
||||
clearInterval(this.cleanUsageHandle)
|
||||
clearInterval(this.backupUsageHandle)
|
||||
clearInterval(this.newSmAccountsInt)
|
||||
clearInterval(this.updateSmAccountsInt)
|
||||
}
|
||||
|
||||
// Garbage Collect the Orders.
|
||||
@@ -178,6 +182,17 @@ class TimerControllers {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Update the Social Media Accounts.
|
||||
async updateSmAccounts () {
|
||||
try {
|
||||
await this.useCases.smAccount.updateSmAccounts()
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in time-controller.js/updateSmAccounts(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TimerControllers
|
||||
|
||||
@@ -64,6 +64,7 @@ class OfferUseCases {
|
||||
this.removeStaleOffers = this.removeStaleOffers.bind(this)
|
||||
this.flagOffer = this.flagOffer.bind(this)
|
||||
this.loadOffers = this.loadOffers.bind(this)
|
||||
this.listOffersByAddress = this.listOffersByAddress.bind(this)
|
||||
|
||||
// State
|
||||
this.seenOffers = []
|
||||
@@ -808,6 +809,17 @@ class OfferUseCases {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// List all offers being made by a given address.
|
||||
async listOffersByAddress (addr) {
|
||||
try {
|
||||
const offers = await this.OfferModel.find({ makerAddr: addr })
|
||||
return offers
|
||||
} catch (err) {
|
||||
console.error('Error in listOffersByAddress(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default OfferUseCases
|
||||
|
||||
@@ -17,6 +17,9 @@ class smAccountUseCases {
|
||||
// Bind 'this' object to all methods
|
||||
this.checkForNewSmAccounts = this.checkForNewSmAccounts.bind(this)
|
||||
this.listAccounts = this.listAccounts.bind(this)
|
||||
this.getAccountByNpub = this.getAccountByNpub.bind(this)
|
||||
this.getAccountByBchAddr = this.getAccountByBchAddr.bind(this)
|
||||
this.updateSmAccounts = this.updateSmAccounts.bind(this)
|
||||
}
|
||||
|
||||
// Check for new Social Media Accounts.
|
||||
@@ -73,6 +76,57 @@ class smAccountUseCases {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get a Social Media Account by Npub.
|
||||
async getAccountByNpub (npub) {
|
||||
try {
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
const account = await SmAccount.findOne({ npub })
|
||||
return account
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/getAccountByNpub(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get a Social Media Account by BCH address.
|
||||
async getAccountByBchAddr (bchAddr) {
|
||||
try {
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
const account = await SmAccount.findOne({ bchAddr })
|
||||
console.log('Account found: ', account)
|
||||
|
||||
return account
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/getAccountByBchAddr(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Update metadata about social media accounts being tracked.
|
||||
async updateSmAccounts () {
|
||||
try {
|
||||
console.log('Updating Social Media Accounts...')
|
||||
const SmAccount = await this.adapters.localdb.SmAccount
|
||||
const accounts = await SmAccount.find({})
|
||||
for (let i = 0; i < accounts.length; i++) {
|
||||
const account = accounts[i]
|
||||
// console.log(`Account ${i}: `, account)
|
||||
|
||||
// Get the number of accounts following this account
|
||||
const pubkey = account.pubkey
|
||||
const followList = await this.adapters.nostr.getFollowers({ pubkey })
|
||||
// console.log(`Follow list ${i}: `, JSON.stringify(followList, null, 2))
|
||||
|
||||
// Update the follower count for this account.
|
||||
const followerCnt = followList.length
|
||||
await SmAccount.updateOne({ _id: account._id }, { $set: { followerCnt } })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in smAccount-use-cases.js/updateSmAccounts(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default smAccountUseCases
|
||||
|
||||
Reference in New Issue
Block a user