story-research-zapwall/lib/purchaseQueries.ts
2026-01-06 14:33:32 +01:00

184 lines
4.8 KiB
TypeScript

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