feat: add Memo post thread endpoint

This commit is contained in:
9z25
2026-07-16 14:51:14 -07:00
parent 85142094d0
commit 1005481504
5 changed files with 163 additions and 4 deletions
+24
View File
@@ -90,6 +90,30 @@ class PostQuery {
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
@@ -17,6 +17,7 @@ class PostsRESTControllerLib {
this.getRecentPosts = this.getRecentPosts.bind(this)
this.getPostsByAddr = this.getPostsByAddr.bind(this)
this.getPostThread = this.getPostThread.bind(this)
this.handleError = this.handleError.bind(this)
}
@@ -98,6 +99,18 @@ class PostsRESTControllerLib {
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
+1
View File
@@ -26,6 +26,7 @@ class PostsRouter {
attach (app) {
this.router.get('/recent', this.postsRESTController.getRecentPosts)
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.allowedMethods())
}
+101
View File
@@ -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
View File
@@ -5,26 +5,46 @@
import ListRecentProfiles from './list-recent-profiles.js'
import ListRecentPosts from './list-recent-posts.js'
import ListPostsByAddr from './list-posts-by-addr.js'
import GetPostThread from './get-post-thread.js'
class UseCases {
constructor (localConfig = {}) {
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error('Adapters required when instantiating UseCases.')
throw new Error(
'Adapters required when instantiating UseCases.'
)
}
this.listRecentProfiles = null
this.listRecentPosts = null
this.listPostsByAddr = null
this.getPostThread = null
}
async start () {
this.listRecentProfiles = new ListRecentProfiles({ adapters: this.adapters })
this.listRecentPosts = new ListRecentPosts({ adapters: this.adapters })
this.listPostsByAddr = new ListPostsByAddr({ adapters: this.adapters })
this.listRecentProfiles = new ListRecentProfiles({
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.')
return true
}
}
export default UseCases