134 lines
5.7 KiB
TypeScript
134 lines
5.7 KiB
TypeScript
import type { ArticleDraft } from '../articlePublisher'
|
|
import type { Review } from '@/types/nostr'
|
|
import { buildTags } from '../nostrTagSystem'
|
|
import { PLATFORM_SERVICE } from '../platformConfig'
|
|
import { buildObjectId } from '../urlGenerator'
|
|
import { getPublishRelays } from '../relaySelection'
|
|
import { writeOrchestrator } from '../writeOrchestrator'
|
|
import { finalizeEvent, type EventTemplate } from 'nostr-tools'
|
|
import { hexToBytes } from 'nostr-tools/utils'
|
|
import { ensureKeys, getPrivateKeyForSigning, mapDraftCategoryToTag, requireCategory } from './shared'
|
|
|
|
export async function publishReview(params: {
|
|
articleId: string
|
|
seriesId: string
|
|
category: ArticleDraft['category']
|
|
authorPubkey: string
|
|
reviewerPubkey: string
|
|
content: string
|
|
title?: string
|
|
text?: string
|
|
authorPrivateKey?: string
|
|
}): Promise<Review> {
|
|
ensureKeys(params.reviewerPubkey, params.authorPrivateKey)
|
|
requireCategory(params.category)
|
|
const hash = await buildReviewHashId({ reviewerPubkey: params.reviewerPubkey, articleId: params.articleId, content: params.content, ...(params.title ? { title: params.title } : {}) })
|
|
const parsedReview = buildParsedReview({ params, hash, version: 0, index: 0 })
|
|
const eventTemplate = await buildReviewEvent(params, params.category)
|
|
const privateKey = getPrivateKeyForSigning(params.authorPrivateKey)
|
|
const event = finalizeEvent(eventTemplate, hexToBytes(privateKey))
|
|
await publishReviewToRelays({ parsedReview, event })
|
|
return parsedReview
|
|
}
|
|
|
|
function buildParsedReview(params: {
|
|
params: { articleId: string; authorPubkey: string; reviewerPubkey: string; content: string; title?: string; text?: string }
|
|
hash: string
|
|
version: number
|
|
index: number
|
|
}): Review {
|
|
return {
|
|
id: buildObjectId(params.hash, params.index, params.version),
|
|
hash: params.hash,
|
|
version: params.version,
|
|
index: params.index,
|
|
articleId: params.params.articleId,
|
|
authorPubkey: params.params.authorPubkey,
|
|
reviewerPubkey: params.params.reviewerPubkey,
|
|
content: params.params.content,
|
|
description: params.params.content.substring(0, 200),
|
|
createdAt: Math.floor(Date.now() / 1000),
|
|
...(params.params.title ? { title: params.params.title } : {}),
|
|
...(params.params.text ? { text: params.params.text } : {}),
|
|
kindType: 'review',
|
|
}
|
|
}
|
|
|
|
async function publishReviewToRelays(params: { parsedReview: Review; event: import('nostr-tools').Event }): Promise<void> {
|
|
const relays = await getPublishRelays()
|
|
const result = await writeOrchestrator.writeAndPublish(
|
|
{ objectType: 'review', hash: params.parsedReview.hash, event: params.event, parsed: params.parsedReview, version: params.parsedReview.version, hidden: false, index: params.parsedReview.index },
|
|
relays
|
|
)
|
|
if (!result.success) {
|
|
throw new Error('Failed to publish review')
|
|
}
|
|
}
|
|
|
|
async function buildReviewEvent(
|
|
params: { articleId: string; seriesId: string; authorPubkey: string; reviewerPubkey: string; content: string; title?: string; text?: string },
|
|
category: NonNullable<ArticleDraft['category']>
|
|
): Promise<Pick<EventTemplate, 'kind' | 'created_at' | 'content' | 'tags'>> {
|
|
const ctx = await buildReviewEventContext(params, category)
|
|
return { kind: 1, created_at: Math.floor(Date.now() / 1000), content: params.content, tags: ctx.tags }
|
|
}
|
|
|
|
async function buildReviewEventContext(
|
|
params: { articleId: string; reviewerPubkey: string; content: string; title?: string; text?: string },
|
|
category: NonNullable<ArticleDraft['category']>
|
|
): Promise<{ tags: string[][] }> {
|
|
const newCategory = mapDraftCategoryToTag(category)
|
|
const hashId = await buildReviewHashId({ reviewerPubkey: params.reviewerPubkey, articleId: params.articleId, content: params.content, ...(params.title ? { title: params.title } : {}) })
|
|
const reviewJson = buildReviewJson({ reviewerPubkey: params.reviewerPubkey, articleId: params.articleId, content: params.content, ...(params.title ? { title: params.title } : {}), newCategory, hashId })
|
|
const tags = buildReviewTags({ newCategory, hashId, articleId: params.articleId, reviewerPubkey: params.reviewerPubkey, ...(params.title ? { title: params.title } : {}), ...(params.text ? { text: params.text } : {}), reviewJson })
|
|
return { tags }
|
|
}
|
|
|
|
async function buildReviewHashId(params: { reviewerPubkey: string; articleId: string; content: string; title?: string }): Promise<string> {
|
|
const { generateReviewHashId } = await import('../hashIdGenerator')
|
|
return generateReviewHashId({ pubkey: params.reviewerPubkey, articleId: params.articleId, reviewerPubkey: params.reviewerPubkey, content: params.content, ...(params.title ? { title: params.title } : {}) })
|
|
}
|
|
|
|
function buildReviewJson(params: { reviewerPubkey: string; articleId: string; content: string; title?: string; newCategory: 'sciencefiction' | 'research'; hashId: string }): string {
|
|
return JSON.stringify({
|
|
type: 'review',
|
|
pubkey: params.reviewerPubkey,
|
|
articleId: params.articleId,
|
|
reviewerPubkey: params.reviewerPubkey,
|
|
content: params.content,
|
|
title: params.title,
|
|
category: params.newCategory,
|
|
id: params.hashId,
|
|
version: 0,
|
|
index: 0,
|
|
})
|
|
}
|
|
|
|
function buildReviewTags(params: {
|
|
newCategory: 'sciencefiction' | 'research'
|
|
hashId: string
|
|
articleId: string
|
|
reviewerPubkey: string
|
|
title?: string
|
|
text?: string
|
|
reviewJson: string
|
|
}): string[][] {
|
|
const tags = buildTags({
|
|
type: 'quote',
|
|
category: params.newCategory,
|
|
id: params.hashId,
|
|
service: PLATFORM_SERVICE,
|
|
version: 0,
|
|
hidden: false,
|
|
paywall: false,
|
|
articleId: params.articleId,
|
|
reviewerPubkey: params.reviewerPubkey,
|
|
...(params.title ? { title: params.title } : {}),
|
|
})
|
|
if (params.text) {
|
|
tags.push(['text', params.text])
|
|
}
|
|
tags.push(['json', params.reviewJson])
|
|
return tags
|
|
}
|