mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo-db.git
synced 2026-09-21 16:52:02 -07:00
Merge pull request #1 from Permissionless-Software-Foundation/add-post-thread-endpoint
feat: add Memo post thread endpoint
This commit is contained in:
@@ -90,6 +90,30 @@ class PostQuery {
|
|||||||
|
|
||||||
return posts
|
return posts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async buildReplyCountMap () {
|
||||||
|
const counts = new Map()
|
||||||
|
let total = 0
|
||||||
|
|
||||||
|
for await (const [childTxid, child] of this.postChildrenDb.iterator()) {
|
||||||
|
total++
|
||||||
|
|
||||||
|
console.log('Indexed reply:', {
|
||||||
|
childTxid,
|
||||||
|
child,
|
||||||
|
parentTxid: child?.parentTxid
|
||||||
|
})
|
||||||
|
|
||||||
|
const parentTxid = child?.parentTxid
|
||||||
|
if (!parentTxid) continue
|
||||||
|
|
||||||
|
counts.set(parentTxid, (counts.get(parentTxid) || 0) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Total postChildrenDb records: ${total}`)
|
||||||
|
|
||||||
|
return counts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PostQuery
|
export default PostQuery
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class PostsRESTControllerLib {
|
|||||||
|
|
||||||
this.getRecentPosts = this.getRecentPosts.bind(this)
|
this.getRecentPosts = this.getRecentPosts.bind(this)
|
||||||
this.getPostsByAddr = this.getPostsByAddr.bind(this)
|
this.getPostsByAddr = this.getPostsByAddr.bind(this)
|
||||||
|
this.getPostThread = this.getPostThread.bind(this)
|
||||||
this.handleError = this.handleError.bind(this)
|
this.handleError = this.handleError.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +99,18 @@ class PostsRESTControllerLib {
|
|||||||
this.handleError(ctx, err)
|
this.handleError(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPostThread (ctx) {
|
||||||
|
try {
|
||||||
|
const { txid } = ctx.params
|
||||||
|
|
||||||
|
ctx.body = await this.useCases.getPostThread.execute({
|
||||||
|
txid
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PostsRESTControllerLib
|
export default PostsRESTControllerLib
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class PostsRouter {
|
|||||||
attach (app) {
|
attach (app) {
|
||||||
this.router.get('/recent', this.postsRESTController.getRecentPosts)
|
this.router.get('/recent', this.postsRESTController.getRecentPosts)
|
||||||
this.router.get('/by/:addr', this.postsRESTController.getPostsByAddr)
|
this.router.get('/by/:addr', this.postsRESTController.getPostsByAddr)
|
||||||
|
this.router.get('/:txid/thread',this.postsRESTController.getPostThread)
|
||||||
app.use(this.router.routes())
|
app.use(this.router.routes())
|
||||||
app.use(this.router.allowedMethods())
|
app.use(this.router.allowedMethods())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
Retrieve a Memo post and its nested replies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class GetPostThread {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Adapters required when instantiating GetPostThread.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.execute = this.execute.bind(this)
|
||||||
|
this.buildThreadNode = this.buildThreadNode.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute ({ txid } = {}) {
|
||||||
|
if (!txid || typeof txid !== 'string') {
|
||||||
|
const err = new Error('A transaction ID is required.')
|
||||||
|
err.status = 400
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootPost = await this.buildThreadNode(txid)
|
||||||
|
|
||||||
|
if (!rootPost) {
|
||||||
|
const err = new Error('Post not found.')
|
||||||
|
err.status = 404
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
post: rootPost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async buildThreadNode (txid, visited = new Set()) {
|
||||||
|
if (visited.has(txid)) return null
|
||||||
|
|
||||||
|
visited.add(txid)
|
||||||
|
|
||||||
|
let post
|
||||||
|
|
||||||
|
try {
|
||||||
|
post = await this.adapters.postQuery.postsDb.get(txid)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.notFound || err.code === 'LEVEL_NOT_FOUND') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
const childTxids = []
|
||||||
|
|
||||||
|
for await (
|
||||||
|
const [, child]
|
||||||
|
of this.adapters.postQuery.postChildrenDb.iterator()
|
||||||
|
) {
|
||||||
|
if (child?.parentTxid !== txid) continue
|
||||||
|
if (!child?.childTxid) continue
|
||||||
|
|
||||||
|
childTxids.push(child.childTxid)
|
||||||
|
}
|
||||||
|
|
||||||
|
const replies = []
|
||||||
|
|
||||||
|
for (const childTxid of childTxids) {
|
||||||
|
const reply = await this.buildThreadNode(childTxid, visited)
|
||||||
|
|
||||||
|
if (reply) {
|
||||||
|
replies.push(reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replies.sort((a, b) => {
|
||||||
|
const blockDifference =
|
||||||
|
(a.blockHeight ?? 0) - (b.blockHeight ?? 0)
|
||||||
|
|
||||||
|
if (blockDifference !== 0) {
|
||||||
|
return blockDifference
|
||||||
|
}
|
||||||
|
|
||||||
|
return (a.seen ?? 0) - (b.seen ?? 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
txid,
|
||||||
|
addr: post.addr,
|
||||||
|
text: post.text,
|
||||||
|
seen: post.seen,
|
||||||
|
blockHeight: post.blockHeight ?? 0,
|
||||||
|
replyCount: replies.length,
|
||||||
|
replies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GetPostThread
|
||||||
+24
-4
@@ -5,26 +5,46 @@
|
|||||||
import ListRecentProfiles from './list-recent-profiles.js'
|
import ListRecentProfiles from './list-recent-profiles.js'
|
||||||
import ListRecentPosts from './list-recent-posts.js'
|
import ListRecentPosts from './list-recent-posts.js'
|
||||||
import ListPostsByAddr from './list-posts-by-addr.js'
|
import ListPostsByAddr from './list-posts-by-addr.js'
|
||||||
|
import GetPostThread from './get-post-thread.js'
|
||||||
|
|
||||||
class UseCases {
|
class UseCases {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
this.adapters = localConfig.adapters
|
this.adapters = localConfig.adapters
|
||||||
|
|
||||||
if (!this.adapters) {
|
if (!this.adapters) {
|
||||||
throw new Error('Adapters required when instantiating UseCases.')
|
throw new Error(
|
||||||
|
'Adapters required when instantiating UseCases.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listRecentProfiles = null
|
this.listRecentProfiles = null
|
||||||
this.listRecentPosts = null
|
this.listRecentPosts = null
|
||||||
this.listPostsByAddr = null
|
this.listPostsByAddr = null
|
||||||
|
this.getPostThread = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async start () {
|
async start () {
|
||||||
this.listRecentProfiles = new ListRecentProfiles({ adapters: this.adapters })
|
this.listRecentProfiles = new ListRecentProfiles({
|
||||||
this.listRecentPosts = new ListRecentPosts({ adapters: this.adapters })
|
adapters: this.adapters
|
||||||
this.listPostsByAddr = new ListPostsByAddr({ adapters: this.adapters })
|
})
|
||||||
|
|
||||||
|
this.listRecentPosts = new ListRecentPosts({
|
||||||
|
adapters: this.adapters
|
||||||
|
})
|
||||||
|
|
||||||
|
this.listPostsByAddr = new ListPostsByAddr({
|
||||||
|
adapters: this.adapters
|
||||||
|
})
|
||||||
|
|
||||||
|
this.getPostThread = new GetPostThread({
|
||||||
|
adapters: this.adapters
|
||||||
|
})
|
||||||
|
|
||||||
console.log('Use cases initialized.')
|
console.log('Use cases initialized.')
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UseCases
|
export default UseCases
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user