story-research-zapwall/lib/articlePublisherPublish.ts
2026-01-07 01:51:26 +01:00

97 lines
3.3 KiB
TypeScript

import { nostrService } from './nostr'
import type { ArticleDraft, PublishedArticle } from './articlePublisherTypes'
import type { AlbyInvoice } from '@/types/alby'
import { createArticleInvoice, createPreviewEvent } from './articleInvoice'
import { encryptArticleContent, encryptDecryptionKey } from './articleEncryption'
import { storePrivateContent } from './articleStorage'
import type { PublishResult } from './publishResult'
export function buildFailure(error?: string): PublishedArticle {
const base: PublishedArticle = {
articleId: '',
previewEventId: '',
success: false,
}
return error ? { ...base, error } : base
}
export async function publishPreview(
draft: ArticleDraft,
invoice: AlbyInvoice,
authorPubkey: string,
presentationId: string,
extraTags?: string[][],
encryptedContent?: string,
encryptedKey?: string,
returnStatus?: boolean
): Promise<import('nostr-tools').Event | null | PublishResult> {
const previewEvent = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey)
if (returnStatus) {
return await nostrService.publishEvent(previewEvent, true)
}
const publishedEvent = await nostrService.publishEvent(previewEvent, false)
return publishedEvent ?? null
}
export function buildArticleExtraTags(draft: ArticleDraft, _category: NonNullable<ArticleDraft['category']>): string[][] {
// Media tags are still supported in the new system
const extraTags: string[][] = []
if (draft.media && draft.media.length > 0) {
draft.media.forEach((m) => {
extraTags.push(['media', m.url, m.type])
})
}
return extraTags
}
export async function encryptAndPublish(
draft: ArticleDraft,
authorPubkey: string,
authorPrivateKeyForEncryption: string,
category: NonNullable<ArticleDraft['category']>,
presentationId: string
): Promise<PublishedArticle> {
const { encryptedContent, key, iv } = await encryptArticleContent(draft.content)
const encryptedKey = await encryptDecryptionKey(key, iv, authorPrivateKeyForEncryption, authorPubkey)
const invoice = await createArticleInvoice(draft)
const extraTags = buildArticleExtraTags(draft, category)
const publishResult = await publishPreview(draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey, true)
if (!publishResult) {
return buildFailure('Failed to publish article')
}
// Handle both old format (Event | null) and new format (PublishResult)
let event: import('nostr-tools').Event | null = null
let relayStatuses: import('./publishResult').RelayPublishStatus[] | undefined
if (publishResult && 'event' in publishResult && 'relayStatuses' in publishResult) {
// New format with statuses
event = publishResult.event
relayStatuses = publishResult.relayStatuses
} else if (publishResult && 'id' in publishResult) {
// Old format (Event)
event = publishResult
}
if (!event) {
return buildFailure('Failed to publish article')
}
await storePrivateContent(event.id, draft.content, authorPubkey, invoice, key, iv)
console.warn('Article published with encrypted content', {
articleId: event.id,
authorPubkey,
timestamp: new Date().toISOString(),
relayStatuses,
})
return {
articleId: event.id,
previewEventId: event.id,
invoice,
success: true,
relayStatuses,
}
}