story-research-zapwall/lib/reviewTipQueries.ts
2026-01-06 14:24:35 +01:00

180 lines
4.8 KiB
TypeScript

import type { Event } from 'nostr-tools'
import { nostrService } from './nostr'
import type { ReviewTip } from '@/types/nostr'
import { parseReviewTipFromEvent } from './nostrEventParsing'
import { objectCache } from './objectCache'
import { getPrimaryRelaySync } from './config'
import { MIN_EVENT_DATE } from './platformConfig'
import { parseObjectId } from './urlGenerator'
function buildReviewTipFilters(articleId?: string, reviewId?: string, authorPubkey?: string, reviewerPubkey?: string) {
const filters: Array<{
kinds: number[]
since?: number
'#p'?: string[]
'#e'?: string[]
'#review_id'?: string[]
'#reviewer'?: string[]
'#kind_type'?: string[]
}> = []
const baseFilter: {
kinds: number[]
since: number
'#kind_type': string[]
} = {
kinds: [9735], // Zap receipt
since: MIN_EVENT_DATE,
'#kind_type': ['review_tip'],
}
if (authorPubkey) {
baseFilter['#p'] = [authorPubkey]
}
if (articleId) {
baseFilter['#e'] = [articleId]
}
if (reviewId) {
baseFilter['#review_id'] = [reviewId]
}
if (reviewerPubkey) {
baseFilter['#reviewer'] = [reviewerPubkey]
}
filters.push(baseFilter)
return filters
}
export async function getReviewTipById(reviewTipId: string, timeoutMs: number = 5000): Promise<ReviewTip | null> {
const parsed = parseObjectId(reviewTipId)
const hash = parsed.hash ?? reviewTipId
// Check cache first
const cached = await objectCache.get('review_tip', hash)
if (cached) {
return cached as ReviewTip
}
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Pool not initialized')
}
const filters = buildReviewTipFilters()
const { createSubscription } = require('@/types/nostr-tools-extended')
return new Promise<ReviewTip | null>((resolve) => {
const relayUrl = getPrimaryRelaySync()
const sub = createSubscription(pool, [relayUrl], filters)
let finished = false
const done = async (value: ReviewTip | null): Promise<void> => {
if (finished) {
return
}
finished = true
sub.unsub()
resolve(value)
}
sub.on('event', async (event: Event) => {
const parsed = await parseReviewTipFromEvent(event)
if (parsed?.id === reviewTipId) {
// Cache the parsed review tip
if (parsed.hash) {
await objectCache.set('review_tip', parsed.hash, event, parsed, 0, false, parsed.index)
}
done(parsed)
}
})
sub.on('eose', (): void => {
void done(null)
})
setTimeout(() => done(null), timeoutMs).unref?.()
})
}
export function getReviewTipsForArticle(articleId: string, timeoutMs: number = 5000): Promise<ReviewTip[]> {
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Pool not initialized')
}
const filters = buildReviewTipFilters(articleId)
const { createSubscription } = require('@/types/nostr-tools-extended')
return new Promise<ReviewTip[]>((resolve) => {
const results: ReviewTip[] = []
const relayUrl = getPrimaryRelaySync()
const sub = createSubscription(pool, [relayUrl], filters)
let finished = false
const done = async () => {
if (finished) {
return
}
finished = true
sub.unsub()
resolve(results)
}
sub.on('event', async (event: Event) => {
const parsed = await parseReviewTipFromEvent(event)
if (parsed?.articleId === articleId) {
// Cache the parsed review tip
if (parsed.hash) {
await objectCache.set('review_tip', parsed.hash, event, parsed, 0, false, parsed.index)
}
results.push(parsed)
}
})
sub.on('eose', () => done())
setTimeout(() => done(), timeoutMs).unref?.()
})
}
export function getReviewTipsForReview(reviewId: string, timeoutMs: number = 5000): Promise<ReviewTip[]> {
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Pool not initialized')
}
const filters = buildReviewTipFilters(undefined, reviewId)
const { createSubscription } = require('@/types/nostr-tools-extended')
return new Promise<ReviewTip[]>((resolve) => {
const results: ReviewTip[] = []
const relayUrl = getPrimaryRelaySync()
const sub = createSubscription(pool, [relayUrl], filters)
let finished = false
const done = async () => {
if (finished) {
return
}
finished = true
sub.unsub()
resolve(results)
}
sub.on('event', async (event: Event) => {
const parsed = await parseReviewTipFromEvent(event)
if (parsed?.reviewId === reviewId) {
// Cache the parsed review tip
if (parsed.hash) {
await objectCache.set('review_tip', parsed.hash, event, parsed, 0, false, parsed.index)
}
results.push(parsed)
}
})
sub.on('eose', () => done())
setTimeout(() => done(), timeoutMs).unref?.()
})
}