676 lines
21 KiB
TypeScript
676 lines
21 KiB
TypeScript
import { nostrService } from './nostr'
|
|
import { createArticleInvoice, createPreviewEvent } from './articleInvoice'
|
|
import { storePrivateContent, getStoredPrivateContent } from './articleStorage'
|
|
import { buildTags } from './nostrTagSystem'
|
|
import { PLATFORM_SERVICE } from './platformConfig'
|
|
import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator'
|
|
import { buildObjectId } from './urlGenerator'
|
|
import type { ArticleDraft, PublishedArticle } from './articlePublisher'
|
|
import type { AlbyInvoice } from '@/types/alby'
|
|
import type { Article, Review, Series } from '@/types/nostr'
|
|
import { writeOrchestrator } from './writeOrchestrator'
|
|
import { finalizeEvent, type EventTemplate } from 'nostr-tools'
|
|
import { hexToBytes } from 'nostr-tools/utils'
|
|
import { buildParsedArticleFromDraft as buildParsedArticleFromDraftCore } from './articleDraftToParsedArticle'
|
|
import { getPublishRelays } from './relaySelection'
|
|
|
|
export interface ArticleUpdateResult extends PublishedArticle {
|
|
originalArticleId: string
|
|
}
|
|
|
|
function ensureKeys(authorPubkey: string, authorPrivateKey?: string): void {
|
|
nostrService.setPublicKey(authorPubkey)
|
|
if (authorPrivateKey) {
|
|
nostrService.setPrivateKey(authorPrivateKey)
|
|
} else if (!nostrService.getPrivateKey()) {
|
|
throw new Error('Private key required for signing. Connect a wallet that can sign.')
|
|
}
|
|
}
|
|
|
|
function requireCategory(category?: ArticleDraft['category']): asserts category is NonNullable<ArticleDraft['category']> {
|
|
if (category !== 'science-fiction' && category !== 'scientific-research') {
|
|
throw new Error('Vous devez sélectionner une catégorie (science-fiction ou recherche scientifique).')
|
|
}
|
|
}
|
|
|
|
async function ensurePresentation(authorPubkey: string): Promise<string> {
|
|
const { articlePublisher } = await import('./articlePublisher')
|
|
const presentation = await articlePublisher.getAuthorPresentation(authorPubkey)
|
|
if (!presentation) {
|
|
throw new Error('Vous devez créer un article de présentation avant de publier des articles.')
|
|
}
|
|
return presentation.id
|
|
}
|
|
|
|
async function buildParsedArticleFromDraft(
|
|
draft: ArticleDraft,
|
|
invoice: AlbyInvoice,
|
|
authorPubkey: string
|
|
): Promise<{ article: Article; hash: string; version: number; index: number }> {
|
|
return buildParsedArticleFromDraftCore({ draft, invoice, authorPubkey })
|
|
}
|
|
|
|
interface PublishPreviewWithInvoiceParams {
|
|
draft: ArticleDraft
|
|
invoice: AlbyInvoice
|
|
authorPubkey: string
|
|
presentationId: string
|
|
extraTags?: string[][]
|
|
customArticle?: Article
|
|
}
|
|
|
|
async function publishPreviewWithInvoice(
|
|
params: PublishPreviewWithInvoiceParams
|
|
): Promise<import('nostr-tools').Event | null> {
|
|
const payload = await resolvePublicationPayload(params)
|
|
|
|
// Build event template
|
|
const previewEventTemplate = await createPreviewEvent({
|
|
draft: params.draft,
|
|
invoice: params.invoice,
|
|
authorPubkey: params.authorPubkey,
|
|
authorPresentationId: params.presentationId,
|
|
...(params.extraTags ? { extraTags: params.extraTags } : {}),
|
|
})
|
|
|
|
// Set private key in orchestrator
|
|
const privateKey = nostrService.getPrivateKey()
|
|
if (!privateKey) {
|
|
throw new Error('Private key required for signing')
|
|
}
|
|
writeOrchestrator.setPrivateKey(privateKey)
|
|
|
|
// Finalize event
|
|
const secretKey = hexToBytes(privateKey)
|
|
const event = finalizeEvent(previewEventTemplate, secretKey)
|
|
|
|
return publishPublicationToRelays({ event, payload })
|
|
}
|
|
|
|
async function resolvePublicationPayload(params: PublishPreviewWithInvoiceParams): Promise<{
|
|
article: Article
|
|
hash: string
|
|
version: number
|
|
index: number
|
|
}> {
|
|
if (params.customArticle) {
|
|
return {
|
|
article: params.customArticle,
|
|
hash: params.customArticle.hash,
|
|
version: params.customArticle.version,
|
|
index: params.customArticle.index ?? 0,
|
|
}
|
|
}
|
|
return buildParsedArticleFromDraft(params.draft, params.invoice, params.authorPubkey)
|
|
}
|
|
|
|
async function publishPublicationToRelays(params: {
|
|
event: import('nostr-tools').Event
|
|
payload: { article: Article; hash: string; version: number; index: number }
|
|
}): Promise<import('nostr-tools').Event | null> {
|
|
const relays = await getPublishRelays()
|
|
const result = await writeOrchestrator.writeAndPublish(
|
|
{
|
|
objectType: 'publication',
|
|
hash: params.payload.hash,
|
|
event: params.event,
|
|
parsed: params.payload.article,
|
|
version: params.payload.version,
|
|
hidden: false,
|
|
index: params.payload.index,
|
|
},
|
|
relays
|
|
)
|
|
return result.success ? params.event : null
|
|
}
|
|
|
|
export async function publishSeries(params: {
|
|
title: string
|
|
description: string
|
|
preview?: string
|
|
coverUrl?: string
|
|
category: ArticleDraft['category']
|
|
authorPubkey: string
|
|
authorPrivateKey?: string
|
|
}): Promise<Series> {
|
|
ensureKeys(params.authorPubkey, params.authorPrivateKey)
|
|
const category = requireSeriesCategory(params.category)
|
|
const newCategory = mapSeriesCategoryToTag(category)
|
|
const preview = buildSeriesPreview(params.preview, params.description)
|
|
|
|
const hashId = await generateSeriesHashId({
|
|
pubkey: params.authorPubkey,
|
|
title: params.title,
|
|
description: params.description,
|
|
category: newCategory,
|
|
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
|
|
})
|
|
|
|
const parsedSeries = buildParsedSeries({
|
|
authorPubkey: params.authorPubkey,
|
|
title: params.title,
|
|
description: params.description,
|
|
preview,
|
|
coverUrl: params.coverUrl,
|
|
category,
|
|
hashId,
|
|
})
|
|
|
|
const eventTemplate = buildSeriesEventTemplate({
|
|
authorPubkey: params.authorPubkey,
|
|
title: params.title,
|
|
description: params.description,
|
|
preview,
|
|
coverUrl: params.coverUrl,
|
|
category: newCategory,
|
|
hashId,
|
|
})
|
|
|
|
const event = finalizeEvent(eventTemplate, hexToBytes(getPrivateKeyForSigning(params.authorPrivateKey)))
|
|
const relays = await getPublishRelays()
|
|
const result = await writeOrchestrator.writeAndPublish(
|
|
{
|
|
objectType: 'series',
|
|
hash: parsedSeries.hash,
|
|
event,
|
|
parsed: parsedSeries,
|
|
version: parsedSeries.version,
|
|
hidden: false,
|
|
index: parsedSeries.index,
|
|
},
|
|
relays
|
|
)
|
|
|
|
if (!result.success) {
|
|
throw new Error('Failed to publish series')
|
|
}
|
|
|
|
return parsedSeries
|
|
}
|
|
|
|
function requireSeriesCategory(category: ArticleDraft['category']): NonNullable<ArticleDraft['category']> {
|
|
requireCategory(category)
|
|
return category
|
|
}
|
|
|
|
function mapSeriesCategoryToTag(category: NonNullable<ArticleDraft['category']>): 'sciencefiction' | 'research' {
|
|
return category === 'science-fiction' ? 'sciencefiction' : 'research'
|
|
}
|
|
|
|
function buildSeriesPreview(preview: string | undefined, description: string): string {
|
|
return preview ?? description.substring(0, 200)
|
|
}
|
|
|
|
function buildParsedSeries(params: {
|
|
authorPubkey: string
|
|
title: string
|
|
description: string
|
|
preview: string
|
|
coverUrl: string | undefined
|
|
category: NonNullable<ArticleDraft['category']>
|
|
hashId: string
|
|
}): Series {
|
|
const hash = params.hashId
|
|
const version = 0
|
|
const index = 0
|
|
const id = buildObjectId(hash, index, version)
|
|
return {
|
|
id,
|
|
hash,
|
|
version,
|
|
index,
|
|
pubkey: params.authorPubkey,
|
|
title: params.title,
|
|
description: params.description,
|
|
preview: params.preview,
|
|
thumbnailUrl: params.coverUrl ?? '',
|
|
category: params.category,
|
|
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
|
|
kindType: 'series',
|
|
}
|
|
}
|
|
|
|
function buildSeriesEventTemplate(params: {
|
|
authorPubkey: string
|
|
title: string
|
|
description: string
|
|
preview: string
|
|
coverUrl: string | undefined
|
|
category: 'sciencefiction' | 'research'
|
|
hashId: string
|
|
}): EventTemplate {
|
|
const tags = buildTags({
|
|
type: 'series',
|
|
category: params.category,
|
|
id: params.hashId,
|
|
service: PLATFORM_SERVICE,
|
|
version: 0,
|
|
hidden: false,
|
|
paywall: false,
|
|
title: params.title,
|
|
description: params.description,
|
|
preview: params.preview,
|
|
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
|
|
})
|
|
tags.push(['json', buildSeriesJson(params)])
|
|
return { kind: 1, created_at: Math.floor(Date.now() / 1000), content: params.preview, tags }
|
|
}
|
|
|
|
function buildSeriesJson(params: {
|
|
authorPubkey: string
|
|
title: string
|
|
description: string
|
|
preview: string
|
|
coverUrl: string | undefined
|
|
category: 'sciencefiction' | 'research'
|
|
hashId: string
|
|
}): string {
|
|
return JSON.stringify({
|
|
type: 'series',
|
|
pubkey: params.authorPubkey,
|
|
title: params.title,
|
|
description: params.description,
|
|
preview: params.preview,
|
|
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
|
|
category: params.category,
|
|
id: params.hashId,
|
|
version: 0,
|
|
index: 0,
|
|
})
|
|
}
|
|
|
|
function getPrivateKeyForSigning(authorPrivateKey: string | undefined): string {
|
|
const privateKey = authorPrivateKey ?? nostrService.getPrivateKey()
|
|
if (!privateKey) {
|
|
throw new Error('Private key required for signing')
|
|
}
|
|
writeOrchestrator.setPrivateKey(privateKey)
|
|
return privateKey
|
|
}
|
|
|
|
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)
|
|
const {category} = params
|
|
requireCategory(category)
|
|
|
|
// Generate hash ID from review data
|
|
const { generateReviewHashId } = await import('./hashIdGenerator')
|
|
const hashId = await generateReviewHashId({
|
|
pubkey: params.reviewerPubkey,
|
|
articleId: params.articleId,
|
|
reviewerPubkey: params.reviewerPubkey,
|
|
content: params.content,
|
|
...(params.title ? { title: params.title } : {}),
|
|
})
|
|
|
|
const hash = hashId
|
|
const version = 0
|
|
const index = 0
|
|
const id = buildObjectId(hash, index, version)
|
|
|
|
// Build parsed Review object
|
|
const parsedReview: Review = {
|
|
id,
|
|
hash,
|
|
version,
|
|
index,
|
|
articleId: params.articleId,
|
|
authorPubkey: params.authorPubkey,
|
|
reviewerPubkey: params.reviewerPubkey,
|
|
content: params.content,
|
|
description: params.content.substring(0, 200),
|
|
createdAt: Math.floor(Date.now() / 1000),
|
|
...(params.title ? { title: params.title } : {}),
|
|
...(params.text ? { text: params.text } : {}),
|
|
kindType: 'review',
|
|
}
|
|
|
|
// Build event template
|
|
const eventTemplate = await buildReviewEvent(params, category)
|
|
|
|
// Set private key in orchestrator
|
|
const privateKey = params.authorPrivateKey ?? nostrService.getPrivateKey()
|
|
if (!privateKey) {
|
|
throw new Error('Private key required for signing')
|
|
}
|
|
writeOrchestrator.setPrivateKey(privateKey)
|
|
|
|
// Finalize event
|
|
const secretKey = hexToBytes(privateKey)
|
|
const event = finalizeEvent(eventTemplate, secretKey)
|
|
|
|
// Get active relays
|
|
const { relaySessionManager } = await import('./relaySessionManager')
|
|
const activeRelays = await relaySessionManager.getActiveRelays()
|
|
const { getPrimaryRelay } = await import('./config')
|
|
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
|
|
|
|
// Publish via writeOrchestrator (parallel network + local write)
|
|
const result = await writeOrchestrator.writeAndPublish(
|
|
{
|
|
objectType: 'review',
|
|
hash,
|
|
event,
|
|
parsed: parsedReview,
|
|
version,
|
|
hidden: false,
|
|
index,
|
|
},
|
|
relays
|
|
)
|
|
|
|
if (!result.success) {
|
|
throw new Error('Failed to publish review')
|
|
}
|
|
|
|
return parsedReview
|
|
}
|
|
|
|
async function buildReviewEvent(
|
|
params: {
|
|
articleId: string
|
|
seriesId: string
|
|
authorPubkey: string
|
|
reviewerPubkey: string
|
|
content: string
|
|
title?: string
|
|
text?: string
|
|
},
|
|
category: NonNullable<ArticleDraft['category']>
|
|
): Promise<{
|
|
kind: number
|
|
created_at: number
|
|
content: string
|
|
tags: string[][]
|
|
}> {
|
|
// Map category to new system
|
|
const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research'
|
|
|
|
// Generate hash ID from review data
|
|
const { generateReviewHashId } = await import('./hashIdGenerator')
|
|
const hashId = await generateReviewHashId({
|
|
pubkey: params.reviewerPubkey,
|
|
articleId: params.articleId,
|
|
reviewerPubkey: params.reviewerPubkey,
|
|
content: params.content,
|
|
...(params.title ? { title: params.title } : {}),
|
|
})
|
|
|
|
// Build JSON metadata
|
|
const reviewJson = JSON.stringify({
|
|
type: 'review',
|
|
pubkey: params.reviewerPubkey,
|
|
articleId: params.articleId,
|
|
reviewerPubkey: params.reviewerPubkey,
|
|
content: params.content,
|
|
title: params.title,
|
|
category: newCategory,
|
|
id: hashId,
|
|
version: 0,
|
|
index: 0,
|
|
})
|
|
|
|
const tags = buildTags({
|
|
type: 'quote',
|
|
category: newCategory,
|
|
id: hashId,
|
|
service: PLATFORM_SERVICE,
|
|
version: 0, // New object
|
|
hidden: false,
|
|
paywall: false,
|
|
articleId: params.articleId,
|
|
reviewerPubkey: params.reviewerPubkey,
|
|
...(params.title ? { title: params.title } : {}),
|
|
})
|
|
|
|
// Add text tag if provided
|
|
if (params.text) {
|
|
tags.push(['text', params.text])
|
|
}
|
|
|
|
// Add JSON metadata as a tag
|
|
tags.push(['json', reviewJson])
|
|
|
|
return {
|
|
kind: 1,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
content: params.content,
|
|
tags,
|
|
}
|
|
}
|
|
|
|
async function buildUpdateTags(params: {
|
|
draft: ArticleDraft
|
|
originalArticleId: string
|
|
newCategory: 'sciencefiction' | 'research'
|
|
authorPubkey: string
|
|
currentVersion?: number
|
|
}): Promise<string[][]> {
|
|
// Generate hash ID from publication data
|
|
const hashId = await generatePublicationHashId({
|
|
pubkey: params.authorPubkey,
|
|
title: params.draft.title,
|
|
preview: params.draft.preview,
|
|
category: params.newCategory,
|
|
seriesId: params.draft.seriesId ?? undefined,
|
|
bannerUrl: params.draft.bannerUrl ?? undefined,
|
|
zapAmount: params.draft.zapAmount,
|
|
})
|
|
|
|
// Increment version for update
|
|
const currentVersion = params.currentVersion ?? 0
|
|
const nextVersion = currentVersion + 1
|
|
|
|
const updateTags = buildTags({
|
|
type: 'publication',
|
|
category: params.newCategory,
|
|
id: hashId,
|
|
service: PLATFORM_SERVICE,
|
|
version: nextVersion,
|
|
hidden: false,
|
|
paywall: true,
|
|
title: params.draft.title,
|
|
preview: params.draft.preview,
|
|
zapAmount: params.draft.zapAmount,
|
|
...(params.draft.seriesId ? { seriesId: params.draft.seriesId } : {}),
|
|
...(params.draft.bannerUrl ? { bannerUrl: params.draft.bannerUrl } : {}),
|
|
})
|
|
updateTags.push(['e', params.originalArticleId], ['replace', 'article-update'])
|
|
return updateTags
|
|
}
|
|
|
|
async function publishUpdate(
|
|
draft: ArticleDraft,
|
|
authorPubkey: string,
|
|
originalArticleId: string
|
|
): Promise<ArticleUpdateResult> {
|
|
const category = requireUpdateCategory(draft)
|
|
const originalArticle = await loadOriginalArticleForUpdate(originalArticleId)
|
|
if (!originalArticle) {
|
|
return updateFailure(originalArticleId, 'Original article not found in cache')
|
|
}
|
|
if (originalArticle.pubkey !== authorPubkey) {
|
|
return updateFailure(originalArticleId, 'Only the author can update this article')
|
|
}
|
|
|
|
const presentationId = await ensurePresentation(authorPubkey)
|
|
const invoice = await createArticleInvoice(draft)
|
|
const newCategory = mapPublicationCategoryToTag(category)
|
|
const currentVersion = originalArticle.version ?? 0
|
|
|
|
const updateTags = await buildUpdateTags({ draft, originalArticleId, newCategory, authorPubkey, currentVersion })
|
|
const updatedArticle = await buildUpdatedArticleForUpdate({ draft, invoice, authorPubkey, currentVersion })
|
|
const publishedEvent = await publishPreviewWithInvoice({ draft, invoice, authorPubkey, presentationId, extraTags: updateTags, customArticle: updatedArticle })
|
|
if (!publishedEvent) {
|
|
return updateFailure(originalArticleId, 'Failed to publish article update')
|
|
}
|
|
|
|
await storePrivateContent({ articleId: publishedEvent.id, content: draft.content, authorPubkey, invoice })
|
|
return { articleId: publishedEvent.id, previewEventId: publishedEvent.id, invoice, success: true, originalArticleId }
|
|
}
|
|
|
|
function requireUpdateCategory(draft: ArticleDraft): NonNullable<ArticleDraft['category']> {
|
|
requireCategory(draft.category)
|
|
return draft.category
|
|
}
|
|
|
|
async function loadOriginalArticleForUpdate(originalArticleId: string): Promise<Article | null> {
|
|
const { objectCache } = await import('./objectCache')
|
|
return (await objectCache.getById('publication', originalArticleId)) as Article | null
|
|
}
|
|
|
|
function mapPublicationCategoryToTag(category: NonNullable<ArticleDraft['category']>): 'sciencefiction' | 'research' {
|
|
return category === 'science-fiction' ? 'sciencefiction' : 'research'
|
|
}
|
|
|
|
async function buildUpdatedArticleForUpdate(params: {
|
|
draft: ArticleDraft
|
|
invoice: AlbyInvoice
|
|
authorPubkey: string
|
|
currentVersion: number
|
|
}): Promise<Article> {
|
|
const { article } = await buildParsedArticleFromDraft(params.draft, params.invoice, params.authorPubkey)
|
|
return { ...article, version: params.currentVersion + 1 }
|
|
}
|
|
|
|
export async function publishArticleUpdate(
|
|
originalArticleId: string,
|
|
draft: ArticleDraft,
|
|
authorPubkey: string,
|
|
authorPrivateKey?: string
|
|
): Promise<ArticleUpdateResult> {
|
|
try {
|
|
ensureKeys(authorPubkey, authorPrivateKey)
|
|
return publishUpdate(draft, authorPubkey, originalArticleId)
|
|
} catch (error) {
|
|
return updateFailure(originalArticleId, error instanceof Error ? error.message : 'Unknown error')
|
|
}
|
|
}
|
|
|
|
function updateFailure(originalArticleId: string, error?: string): ArticleUpdateResult {
|
|
return {
|
|
articleId: '',
|
|
previewEventId: '',
|
|
success: false,
|
|
originalArticleId,
|
|
...(error ? { error } : {}),
|
|
}
|
|
}
|
|
|
|
export async function deleteArticleEvent(articleId: string, authorPubkey: string, authorPrivateKey?: string): Promise<void> {
|
|
ensureKeys(authorPubkey, authorPrivateKey)
|
|
|
|
const originalEvent = await getOriginalPublicationEventOrThrow(articleId)
|
|
assertAuthorOwnsEvent({ eventPubkey: originalEvent.pubkey, authorPubkey })
|
|
|
|
const deleteEventTemplate = await buildDeleteEventTemplateOrThrow({ originalEvent, authorPubkey })
|
|
const originalParsed = await parseOriginalArticleOrThrow(originalEvent)
|
|
const deletePayload = await buildDeletedArticlePayload({ originalParsed, deleteEventTemplate })
|
|
|
|
const event = await finalizeEventTemplate({ template: deleteEventTemplate, authorPrivateKey })
|
|
const relays = await getPublishRelays()
|
|
await publishDeletion({ event, relays, payload: deletePayload })
|
|
}
|
|
|
|
async function getOriginalPublicationEventOrThrow(articleId: string): Promise<import('nostr-tools').Event> {
|
|
const { objectCache } = await import('./objectCache')
|
|
const originalEvent = await objectCache.getEventById('publication', articleId)
|
|
if (!originalEvent) {
|
|
throw new Error('Article not found in cache')
|
|
}
|
|
return originalEvent
|
|
}
|
|
|
|
function assertAuthorOwnsEvent(params: { eventPubkey: string; authorPubkey: string }): void {
|
|
if (params.eventPubkey !== params.authorPubkey) {
|
|
throw new Error('Only the author can delete this article')
|
|
}
|
|
}
|
|
|
|
async function buildDeleteEventTemplateOrThrow(params: {
|
|
originalEvent: import('nostr-tools').Event
|
|
authorPubkey: string
|
|
}): Promise<import('nostr-tools').EventTemplate> {
|
|
const { buildDeleteEvent } = await import('./objectModification')
|
|
const template = await buildDeleteEvent(params.originalEvent, params.authorPubkey)
|
|
if (!template) {
|
|
throw new Error('Failed to build delete event')
|
|
}
|
|
return template
|
|
}
|
|
|
|
async function parseOriginalArticleOrThrow(originalEvent: import('nostr-tools').Event): Promise<Article> {
|
|
const { parseArticleFromEvent } = await import('./nostrEventParsing')
|
|
const parsed = await parseArticleFromEvent(originalEvent)
|
|
if (!parsed) {
|
|
throw new Error('Failed to parse original article')
|
|
}
|
|
return parsed
|
|
}
|
|
|
|
async function buildDeletedArticlePayload(params: {
|
|
originalParsed: Article
|
|
deleteEventTemplate: import('nostr-tools').EventTemplate
|
|
}): Promise<{ hash: string; index: number; version: number; parsed: Article }> {
|
|
const { extractTagsFromEvent } = await import('./nostrTagSystem')
|
|
const tags = extractTagsFromEvent(params.deleteEventTemplate)
|
|
const version = tags.version ?? params.originalParsed.version + 1
|
|
const index = params.originalParsed.index ?? 0
|
|
const parsed: Article = { ...params.originalParsed, version }
|
|
return { hash: params.originalParsed.hash, index, version, parsed }
|
|
}
|
|
|
|
async function finalizeEventTemplate(params: {
|
|
template: import('nostr-tools').EventTemplate
|
|
authorPrivateKey: string | undefined
|
|
}): Promise<import('nostr-tools').Event> {
|
|
const privateKey = params.authorPrivateKey ?? nostrService.getPrivateKey()
|
|
if (!privateKey) {
|
|
throw new Error('Private key required for signing')
|
|
}
|
|
const { writeOrchestrator: writeOrchestratorInstance } = await import('./writeOrchestrator')
|
|
writeOrchestratorInstance.setPrivateKey(privateKey)
|
|
|
|
const { finalizeEvent: finalizeNostrEvent } = await import('nostr-tools')
|
|
const { hexToBytes: hexToBytesUtil } = await import('nostr-tools/utils')
|
|
const secretKey = hexToBytesUtil(privateKey)
|
|
return finalizeNostrEvent(params.template, secretKey)
|
|
}
|
|
|
|
async function publishDeletion(params: {
|
|
event: import('nostr-tools').Event
|
|
relays: string[]
|
|
payload: { hash: string; index: number; version: number; parsed: Article }
|
|
}): Promise<void> {
|
|
const { writeOrchestrator: writeOrchestratorInstance } = await import('./writeOrchestrator')
|
|
const result = await writeOrchestratorInstance.writeAndPublish(
|
|
{
|
|
objectType: 'publication',
|
|
hash: params.payload.hash,
|
|
event: params.event,
|
|
parsed: params.payload.parsed,
|
|
version: params.payload.version,
|
|
hidden: true,
|
|
index: params.payload.index,
|
|
},
|
|
params.relays
|
|
)
|
|
if (!result.success) {
|
|
throw new Error('Failed to publish delete event')
|
|
}
|
|
}
|
|
|
|
// Re-export for convenience to avoid circular imports in hooks
|
|
export { articlePublisher } from './articlePublisher'
|
|
export const getStoredContent = getStoredPrivateContent
|