story-research-zapwall/lib/reviewRewardUpdate.ts
2026-01-10 09:41:57 +01:00

169 lines
5.2 KiB
TypeScript

import { nostrService } from './nostr'
import { PLATFORM_COMMISSIONS } from './platformCommissions'
import type { Event as NostrEvent } from 'nostr-tools'
import { finalizeEvent } from 'nostr-tools'
import { hexToBytes } from 'nostr-tools/utils'
import { objectCache } from './objectCache'
import type { Review } from '@/types/nostr'
import { getPublishRelays } from './relaySelection'
export async function fetchOriginalReviewEvent(reviewId: string): Promise<NostrEvent | null> {
// Read only from IndexedDB cache
const parsed = await objectCache.getById('review', reviewId)
if (parsed) {
// Get the event from cache
const event = await objectCache.getEventById('review', reviewId)
if (event) {
return event
}
}
// Not found in cache - return null (no network request)
return null
}
export function buildRewardEvent(originalEvent: NostrEvent, reviewId: string): {
kind: number
created_at: number
tags: string[][]
content: string
} {
return {
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [
...originalEvent.tags.filter((tag) => tag[0] !== 'rewarded' && tag[0] !== 'reward_amount'),
['e', reviewId],
['rewarded', 'true'],
['reward_amount', PLATFORM_COMMISSIONS.review.total.toString()],
],
content: originalEvent.content,
}
}
export function checkIfAlreadyRewarded(originalEvent: NostrEvent, reviewId: string): boolean {
const alreadyRewarded = originalEvent.tags.some((tag) => tag[0] === 'rewarded' && tag[1] === 'true')
if (alreadyRewarded) {
console.warn('Review already marked as rewarded', {
reviewId,
timestamp: new Date().toISOString(),
})
}
return alreadyRewarded
}
export async function publishRewardEvent(
updatedEventTemplate: {
kind: number
created_at: number
tags: string[][]
content: string
},
reviewId: string
): Promise<void> {
try {
const { updatedParsed, hash, index, newVersion } = await buildRewardedReviewUpdate(reviewId)
const privateKey = getPrivateKeyOrThrow()
const { writeOrchestrator } = await import('./writeOrchestrator')
writeOrchestrator.setPrivateKey(privateKey)
const event = finalizeEvent(updatedEventTemplate, hexToBytes(privateKey))
const relays = await getPublishRelays()
const result = await writeOrchestrator.writeAndPublish(
{ objectType: 'review', hash, event, parsed: updatedParsed, version: newVersion, hidden: false, index },
relays
)
logRewardPublishResult({ reviewId, eventId: event.id, success: result.success })
} catch (error) {
console.error('Error publishing reward event', {
reviewId,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
})
}
}
function getPrivateKeyOrThrow(): string {
const privateKey = nostrService.getPrivateKey()
if (!privateKey) {
throw new Error('Private key required for signing')
}
return privateKey
}
async function buildRewardedReviewUpdate(reviewId: string): Promise<{
updatedParsed: Review
hash: string
index: number
newVersion: number
}> {
const originalEvent = await fetchOriginalReviewEvent(reviewId)
if (!originalEvent) {
throw new Error('Original review event not found in cache')
}
const { parseReviewFromEvent } = await import('./nostrEventParsing')
const originalParsed = await parseReviewFromEvent(originalEvent)
if (!originalParsed) {
throw new Error('Failed to parse original review')
}
const newVersion = (originalParsed.version ?? 0) + 1
return {
updatedParsed: buildRewardedParsedReview(originalParsed, newVersion),
hash: originalParsed.hash,
index: originalParsed.index ?? 0,
newVersion,
}
}
function buildRewardedParsedReview(originalParsed: Review, newVersion: number): Review {
return {
...originalParsed,
version: newVersion,
rewarded: true,
rewardAmount: PLATFORM_COMMISSIONS.review.total,
}
}
function logRewardPublishResult(params: { reviewId: string; eventId: string; success: boolean }): void {
if (params.success) {
console.warn('Review updated with reward tag', {
reviewId: params.reviewId,
updatedEventId: params.eventId,
timestamp: new Date().toISOString(),
})
return
}
console.error('Failed to publish updated review event', {
reviewId: params.reviewId,
timestamp: new Date().toISOString(),
})
}
export async function updateReviewWithReward(reviewId: string, authorPrivateKey: string): Promise<void> {
try {
const originalEvent = await fetchOriginalReviewEvent(reviewId)
if (!originalEvent) {
console.error('Original review event not found', {
reviewId,
timestamp: new Date().toISOString(),
})
return
}
if (checkIfAlreadyRewarded(originalEvent, reviewId)) {
return
}
nostrService.setPrivateKey(authorPrivateKey)
nostrService.setPublicKey(originalEvent.pubkey)
const updatedEvent = buildRewardEvent(originalEvent, reviewId)
await publishRewardEvent(updatedEvent, reviewId)
} catch (error) {
console.error('Error updating review with reward', {
reviewId,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
})
}
}