lint fix wip
This commit is contained in:
parent
964f4aeb60
commit
9f7a0e1527
@ -4,10 +4,13 @@ import { storePrivateContent, getStoredPrivateContent } from './articleStorage'
|
|||||||
import { buildTags } from './nostrTagSystem'
|
import { buildTags } from './nostrTagSystem'
|
||||||
import { PLATFORM_SERVICE } from './platformConfig'
|
import { PLATFORM_SERVICE } from './platformConfig'
|
||||||
import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator'
|
import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator'
|
||||||
import { parseObjectId } from './urlGenerator'
|
import { buildObjectId } from './urlGenerator'
|
||||||
import type { ArticleDraft, PublishedArticle } from './articlePublisher'
|
import type { ArticleDraft, PublishedArticle } from './articlePublisher'
|
||||||
import type { AlbyInvoice } from '@/types/alby'
|
import type { AlbyInvoice } from '@/types/alby'
|
||||||
import type { Review, Series } from '@/types/nostr'
|
import type { Article, Review, Series } from '@/types/nostr'
|
||||||
|
import { writeOrchestrator } from './writeOrchestrator'
|
||||||
|
import { finalizeEvent } from 'nostr-tools'
|
||||||
|
import { hexToBytes } from 'nostr-tools/utils'
|
||||||
|
|
||||||
export interface ArticleUpdateResult extends PublishedArticle {
|
export interface ArticleUpdateResult extends PublishedArticle {
|
||||||
originalArticleId: string
|
originalArticleId: string
|
||||||
@ -36,6 +39,55 @@ async function ensurePresentation(authorPubkey: string): Promise<string> {
|
|||||||
return presentation.id
|
return presentation.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function buildParsedArticleFromDraft(
|
||||||
|
draft: ArticleDraft,
|
||||||
|
invoice: AlbyInvoice,
|
||||||
|
authorPubkey: string
|
||||||
|
): Promise<{ article: Article; hash: string; version: number; index: number }> {
|
||||||
|
const category = draft.category === 'science-fiction' ? 'sciencefiction' : draft.category === 'scientific-research' ? 'research' : 'sciencefiction'
|
||||||
|
|
||||||
|
const hashId = await generatePublicationHashId({
|
||||||
|
pubkey: authorPubkey,
|
||||||
|
title: draft.title,
|
||||||
|
preview: draft.preview,
|
||||||
|
category,
|
||||||
|
seriesId: draft.seriesId ?? undefined,
|
||||||
|
bannerUrl: draft.bannerUrl ?? undefined,
|
||||||
|
zapAmount: draft.zapAmount,
|
||||||
|
})
|
||||||
|
|
||||||
|
const hash = hashId
|
||||||
|
const version = 0
|
||||||
|
const index = 0
|
||||||
|
const id = buildObjectId(hash, index, version)
|
||||||
|
|
||||||
|
const article: Article = {
|
||||||
|
id,
|
||||||
|
hash,
|
||||||
|
version,
|
||||||
|
index,
|
||||||
|
pubkey: authorPubkey,
|
||||||
|
title: draft.title,
|
||||||
|
preview: draft.preview,
|
||||||
|
content: '',
|
||||||
|
description: draft.preview,
|
||||||
|
contentDescription: draft.preview,
|
||||||
|
createdAt: Math.floor(Date.now() / 1000),
|
||||||
|
zapAmount: draft.zapAmount,
|
||||||
|
paid: false,
|
||||||
|
thumbnailUrl: draft.bannerUrl ?? '',
|
||||||
|
invoice: invoice.invoice,
|
||||||
|
paymentHash: invoice.paymentHash ?? undefined,
|
||||||
|
category: draft.category,
|
||||||
|
...(draft.seriesId ? { seriesId: draft.seriesId } : {}),
|
||||||
|
...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}),
|
||||||
|
...(draft.pages && draft.pages.length > 0 ? { pages: draft.pages } : {}),
|
||||||
|
kindType: 'article',
|
||||||
|
}
|
||||||
|
|
||||||
|
return { article, hash, version, index }
|
||||||
|
}
|
||||||
|
|
||||||
async function publishPreviewWithInvoice(
|
async function publishPreviewWithInvoice(
|
||||||
draft: ArticleDraft,
|
draft: ArticleDraft,
|
||||||
invoice: AlbyInvoice,
|
invoice: AlbyInvoice,
|
||||||
@ -43,9 +95,48 @@ async function publishPreviewWithInvoice(
|
|||||||
presentationId: string,
|
presentationId: string,
|
||||||
extraTags?: string[][]
|
extraTags?: string[][]
|
||||||
): Promise<import('nostr-tools').Event | null> {
|
): Promise<import('nostr-tools').Event | null> {
|
||||||
const previewEvent = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags)
|
// Build parsed article object
|
||||||
const publishedEvent = await nostrService.publishEvent(previewEvent)
|
const { article, hash, version, index } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey)
|
||||||
return publishedEvent ?? null
|
|
||||||
|
// Build event template
|
||||||
|
const previewEventTemplate = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, 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)
|
||||||
|
|
||||||
|
// 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: 'publication',
|
||||||
|
hash,
|
||||||
|
event,
|
||||||
|
parsed: article,
|
||||||
|
version,
|
||||||
|
hidden: false,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
relays
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function publishSeries(params: {
|
export async function publishSeries(params: {
|
||||||
@ -60,18 +151,27 @@ export async function publishSeries(params: {
|
|||||||
ensureKeys(params.authorPubkey, params.authorPrivateKey)
|
ensureKeys(params.authorPubkey, params.authorPrivateKey)
|
||||||
const {category} = params
|
const {category} = params
|
||||||
requireCategory(category)
|
requireCategory(category)
|
||||||
const event = await buildSeriesEvent(params, category)
|
|
||||||
const published = await nostrService.publishEvent(event)
|
// Map category to new system
|
||||||
if (!published) {
|
const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research'
|
||||||
throw new Error('Failed to publish series')
|
|
||||||
}
|
// Generate hash ID from series data
|
||||||
const parsed = parseObjectId(published.id)
|
const hashId = await generateSeriesHashId({
|
||||||
const {hash: parsedHash, version: parsedVersion, index: parsedIndex} = parsed
|
pubkey: params.authorPubkey,
|
||||||
const hash = parsedHash ?? published.id
|
title: params.title,
|
||||||
const version = parsedVersion ?? 0
|
description: params.description,
|
||||||
const index = parsedIndex ?? 0
|
category: newCategory,
|
||||||
return {
|
coverUrl: params.coverUrl ?? undefined,
|
||||||
id: published.id,
|
})
|
||||||
|
|
||||||
|
const hash = hashId
|
||||||
|
const version = 0
|
||||||
|
const index = 0
|
||||||
|
const id = buildObjectId(hash, index, version)
|
||||||
|
|
||||||
|
// Build parsed Series object
|
||||||
|
const parsedSeries: Series = {
|
||||||
|
id,
|
||||||
hash,
|
hash,
|
||||||
version,
|
version,
|
||||||
index,
|
index,
|
||||||
@ -84,6 +184,46 @@ export async function publishSeries(params: {
|
|||||||
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
|
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
|
||||||
kindType: 'series',
|
kindType: 'series',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build event template
|
||||||
|
const eventTemplate = await buildSeriesEvent(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: 'series',
|
||||||
|
hash,
|
||||||
|
event,
|
||||||
|
parsed: parsedSeries,
|
||||||
|
version,
|
||||||
|
hidden: false,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
relays
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error('Failed to publish series')
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedSeries
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildSeriesEvent(
|
async function buildSeriesEvent(
|
||||||
@ -166,18 +306,28 @@ export async function publishReview(params: {
|
|||||||
ensureKeys(params.reviewerPubkey, params.authorPrivateKey)
|
ensureKeys(params.reviewerPubkey, params.authorPrivateKey)
|
||||||
const {category} = params
|
const {category} = params
|
||||||
requireCategory(category)
|
requireCategory(category)
|
||||||
const event = await buildReviewEvent(params, category)
|
|
||||||
const published = await nostrService.publishEvent(event)
|
// Map category to new system
|
||||||
if (!published) {
|
const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research'
|
||||||
throw new Error('Failed to publish review')
|
|
||||||
}
|
// Generate hash ID from review data
|
||||||
const parsed = parseObjectId(published.id)
|
const { generateReviewHashId } = await import('./hashIdGenerator')
|
||||||
const {hash: parsedHash, version: parsedVersion, index: parsedIndex} = parsed
|
const hashId = await generateReviewHashId({
|
||||||
const hash = parsedHash ?? published.id
|
pubkey: params.reviewerPubkey,
|
||||||
const version = parsedVersion ?? 0
|
articleId: params.articleId,
|
||||||
const index = parsedIndex ?? 0
|
reviewerPubkey: params.reviewerPubkey,
|
||||||
return {
|
content: params.content,
|
||||||
id: published.id,
|
...(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,
|
hash,
|
||||||
version,
|
version,
|
||||||
index,
|
index,
|
||||||
@ -186,11 +336,51 @@ export async function publishReview(params: {
|
|||||||
reviewerPubkey: params.reviewerPubkey,
|
reviewerPubkey: params.reviewerPubkey,
|
||||||
content: params.content,
|
content: params.content,
|
||||||
description: params.content.substring(0, 200),
|
description: params.content.substring(0, 200),
|
||||||
createdAt: published.created_at,
|
createdAt: Math.floor(Date.now() / 1000),
|
||||||
...(params.title ? { title: params.title } : {}),
|
...(params.title ? { title: params.title } : {}),
|
||||||
...(params.text ? { text: params.text } : {}),
|
...(params.text ? { text: params.text } : {}),
|
||||||
kindType: 'review',
|
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(
|
async function buildReviewEvent(
|
||||||
|
|||||||
@ -5,6 +5,12 @@ import { createArticleInvoice, createPreviewEvent } from './articleInvoice'
|
|||||||
import { encryptArticleContent, encryptDecryptionKey } from './articleEncryption'
|
import { encryptArticleContent, encryptDecryptionKey } from './articleEncryption'
|
||||||
import { storePrivateContent } from './articleStorage'
|
import { storePrivateContent } from './articleStorage'
|
||||||
import type { PublishResult } from './publishResult'
|
import type { PublishResult } from './publishResult'
|
||||||
|
import { writeOrchestrator } from './writeOrchestrator'
|
||||||
|
import { finalizeEvent } from 'nostr-tools'
|
||||||
|
import { hexToBytes } from 'nostr-tools/utils'
|
||||||
|
import { generatePublicationHashId } from './hashIdGenerator'
|
||||||
|
import { buildObjectId } from './urlGenerator'
|
||||||
|
import type { Article } from '@/types/nostr'
|
||||||
|
|
||||||
export function buildFailure(error?: string): PublishedArticle {
|
export function buildFailure(error?: string): PublishedArticle {
|
||||||
const base: PublishedArticle = {
|
const base: PublishedArticle = {
|
||||||
@ -15,6 +21,55 @@ export function buildFailure(error?: string): PublishedArticle {
|
|||||||
return error ? { ...base, error } : base
|
return error ? { ...base, error } : base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function buildParsedArticleFromDraft(
|
||||||
|
draft: ArticleDraft,
|
||||||
|
invoice: AlbyInvoice,
|
||||||
|
authorPubkey: string
|
||||||
|
): Promise<{ article: Article; hash: string; version: number; index: number }> {
|
||||||
|
const category = draft.category === 'science-fiction' ? 'sciencefiction' : draft.category === 'scientific-research' ? 'research' : 'sciencefiction'
|
||||||
|
|
||||||
|
const hashId = await generatePublicationHashId({
|
||||||
|
pubkey: authorPubkey,
|
||||||
|
title: draft.title,
|
||||||
|
preview: draft.preview,
|
||||||
|
category,
|
||||||
|
seriesId: draft.seriesId ?? undefined,
|
||||||
|
bannerUrl: draft.bannerUrl ?? undefined,
|
||||||
|
zapAmount: draft.zapAmount,
|
||||||
|
})
|
||||||
|
|
||||||
|
const hash = hashId
|
||||||
|
const version = 0
|
||||||
|
const index = 0
|
||||||
|
const id = buildObjectId(hash, index, version)
|
||||||
|
|
||||||
|
const article: Article = {
|
||||||
|
id,
|
||||||
|
hash,
|
||||||
|
version,
|
||||||
|
index,
|
||||||
|
pubkey: authorPubkey,
|
||||||
|
title: draft.title,
|
||||||
|
preview: draft.preview,
|
||||||
|
content: '',
|
||||||
|
description: draft.preview,
|
||||||
|
contentDescription: draft.preview,
|
||||||
|
createdAt: Math.floor(Date.now() / 1000),
|
||||||
|
zapAmount: draft.zapAmount,
|
||||||
|
paid: false,
|
||||||
|
thumbnailUrl: draft.bannerUrl ?? '',
|
||||||
|
invoice: invoice.invoice,
|
||||||
|
paymentHash: invoice.paymentHash ?? undefined,
|
||||||
|
category: draft.category,
|
||||||
|
...(draft.seriesId ? { seriesId: draft.seriesId } : {}),
|
||||||
|
...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}),
|
||||||
|
...(draft.pages && draft.pages.length > 0 ? { pages: draft.pages } : {}),
|
||||||
|
kindType: 'article',
|
||||||
|
}
|
||||||
|
|
||||||
|
return { article, hash, version, index }
|
||||||
|
}
|
||||||
|
|
||||||
export async function publishPreview(
|
export async function publishPreview(
|
||||||
draft: ArticleDraft,
|
draft: ArticleDraft,
|
||||||
invoice: AlbyInvoice,
|
invoice: AlbyInvoice,
|
||||||
@ -25,12 +80,64 @@ export async function publishPreview(
|
|||||||
encryptedKey?: string,
|
encryptedKey?: string,
|
||||||
returnStatus?: boolean
|
returnStatus?: boolean
|
||||||
): Promise<import('nostr-tools').Event | null | PublishResult> {
|
): Promise<import('nostr-tools').Event | null | PublishResult> {
|
||||||
const previewEvent = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey)
|
// Build parsed article object
|
||||||
if (returnStatus) {
|
const { article, hash, version, index } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey)
|
||||||
return await nostrService.publishEvent(previewEvent, true)
|
|
||||||
|
// Build event template
|
||||||
|
const previewEventTemplate = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey)
|
||||||
|
|
||||||
|
// Set private key in orchestrator
|
||||||
|
const privateKey = nostrService.getPrivateKey()
|
||||||
|
if (!privateKey) {
|
||||||
|
throw new Error('Private key required for signing')
|
||||||
}
|
}
|
||||||
const publishedEvent = await nostrService.publishEvent(previewEvent, false)
|
writeOrchestrator.setPrivateKey(privateKey)
|
||||||
return publishedEvent ?? null
|
|
||||||
|
// Finalize event
|
||||||
|
const secretKey = hexToBytes(privateKey)
|
||||||
|
const event = finalizeEvent(previewEventTemplate, 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: 'publication',
|
||||||
|
hash,
|
||||||
|
event,
|
||||||
|
parsed: article,
|
||||||
|
version,
|
||||||
|
hidden: false,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
relays
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnStatus) {
|
||||||
|
// Return PublishResult format
|
||||||
|
const { publishResult } = await import('./publishResult')
|
||||||
|
return {
|
||||||
|
event,
|
||||||
|
relayStatuses: relays.map((relayUrl, idx) => {
|
||||||
|
const isSuccess = typeof result.published === 'object' && result.published.includes(relayUrl)
|
||||||
|
return {
|
||||||
|
relayUrl,
|
||||||
|
success: isSuccess,
|
||||||
|
error: isSuccess ? undefined : 'Failed to publish',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildArticleExtraTags(draft: ArticleDraft, _category: NonNullable<ArticleDraft['category']>): string[][] {
|
export function buildArticleExtraTags(draft: ArticleDraft, _category: NonNullable<ArticleDraft['category']>): string[][] {
|
||||||
|
|||||||
@ -241,7 +241,7 @@ class NotificationDetector {
|
|||||||
/**
|
/**
|
||||||
* Get notification title based on type
|
* Get notification title based on type
|
||||||
*/
|
*/
|
||||||
private getNotificationTitle(type: NotificationType, _obj: CachedObject): string {
|
private _getNotificationTitle(type: NotificationType, _obj: CachedObject): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'purchase':
|
case 'purchase':
|
||||||
return 'Nouvel achat'
|
return 'Nouvel achat'
|
||||||
@ -263,7 +263,7 @@ class NotificationDetector {
|
|||||||
/**
|
/**
|
||||||
* Get notification message based on type
|
* Get notification message based on type
|
||||||
*/
|
*/
|
||||||
private getNotificationMessage(type: NotificationType, _obj: CachedObject): string {
|
private _getNotificationMessage(type: NotificationType, _obj: CachedObject): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'purchase':
|
case 'purchase':
|
||||||
return `Vous avez acheté un article`
|
return `Vous avez acheté un article`
|
||||||
|
|||||||
@ -150,6 +150,24 @@ export async function publishReviewTipNote(params: {
|
|||||||
|
|
||||||
tags.push(['json', paymentJson])
|
tags.push(['json', paymentJson])
|
||||||
|
|
||||||
|
// Build parsed ReviewTip object
|
||||||
|
const parsedReviewTip: ReviewTip = {
|
||||||
|
id,
|
||||||
|
hash: hashId,
|
||||||
|
version: 0,
|
||||||
|
index: 0,
|
||||||
|
payerPubkey: params.payerPubkey,
|
||||||
|
articleId: params.articleId,
|
||||||
|
reviewId: params.reviewId,
|
||||||
|
reviewerPubkey: params.reviewerPubkey,
|
||||||
|
authorPubkey: params.authorPubkey,
|
||||||
|
amount: params.amount,
|
||||||
|
paymentHash: params.paymentHash,
|
||||||
|
createdAt: Math.floor(Date.now() / 1000),
|
||||||
|
...(params.text ? { text: params.text } : {}),
|
||||||
|
kindType: 'review_tip',
|
||||||
|
}
|
||||||
|
|
||||||
const content = params.text
|
const content = params.text
|
||||||
? `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}\n\n${params.text}`
|
? `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}\n\n${params.text}`
|
||||||
: `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}`
|
: `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}`
|
||||||
@ -162,7 +180,37 @@ export async function publishReviewTipNote(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nostrService.setPrivateKey(params.payerPrivateKey)
|
nostrService.setPrivateKey(params.payerPrivateKey)
|
||||||
return nostrService.publishEvent(eventTemplate)
|
writeOrchestrator.setPrivateKey(params.payerPrivateKey)
|
||||||
|
|
||||||
|
// Finalize event
|
||||||
|
const secretKey = hexToBytes(params.payerPrivateKey)
|
||||||
|
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_tip',
|
||||||
|
hash: hashId,
|
||||||
|
event,
|
||||||
|
parsed: parsedReviewTip,
|
||||||
|
version: 0,
|
||||||
|
hidden: false,
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
relays
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -237,6 +285,23 @@ export async function publishSponsoringNote(params: {
|
|||||||
|
|
||||||
tags.push(['json', paymentJson])
|
tags.push(['json', paymentJson])
|
||||||
|
|
||||||
|
// Build parsed Sponsoring object
|
||||||
|
const parsedSponsoring: Sponsoring = {
|
||||||
|
id,
|
||||||
|
hash: hashId,
|
||||||
|
version: 0,
|
||||||
|
index: 0,
|
||||||
|
payerPubkey: params.payerPubkey,
|
||||||
|
authorPubkey: params.authorPubkey,
|
||||||
|
amount: params.amount,
|
||||||
|
paymentHash: params.paymentHash,
|
||||||
|
createdAt: Math.floor(Date.now() / 1000),
|
||||||
|
...(params.seriesId ? { seriesId: params.seriesId } : {}),
|
||||||
|
...(params.articleId ? { articleId: params.articleId } : {}),
|
||||||
|
...(params.text ? { text: params.text } : {}),
|
||||||
|
kindType: 'sponsoring',
|
||||||
|
}
|
||||||
|
|
||||||
const content = params.text
|
const content = params.text
|
||||||
? `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...\n\n${params.text}`
|
? `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...\n\n${params.text}`
|
||||||
: `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...`
|
: `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...`
|
||||||
@ -249,5 +314,35 @@ export async function publishSponsoringNote(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nostrService.setPrivateKey(params.payerPrivateKey)
|
nostrService.setPrivateKey(params.payerPrivateKey)
|
||||||
return nostrService.publishEvent(eventTemplate)
|
writeOrchestrator.setPrivateKey(params.payerPrivateKey)
|
||||||
|
|
||||||
|
// Finalize event
|
||||||
|
const secretKey = hexToBytes(params.payerPrivateKey)
|
||||||
|
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: 'sponsoring',
|
||||||
|
hash: hashId,
|
||||||
|
event,
|
||||||
|
parsed: parsedSponsoring,
|
||||||
|
version: 0,
|
||||||
|
hidden: false,
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
relays
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,8 +141,9 @@ export class PlatformTrackingService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Listen for EOSE via Service Worker messages
|
// Listen for EOSE via Service Worker messages
|
||||||
const handleEOSE = (data: { relays: string[] }): void => {
|
const handleEOSE = (data: unknown): void => {
|
||||||
if (data.relays.includes(relayUrl) && !eoseReceived) {
|
const eoseData = data as { relays: string[] }
|
||||||
|
if (eoseData.relays.includes(relayUrl) && !eoseReceived) {
|
||||||
eoseReceived = true
|
eoseReceived = true
|
||||||
finalize()
|
finalize()
|
||||||
}
|
}
|
||||||
@ -214,8 +215,9 @@ export class PlatformTrackingService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Listen for EOSE via Service Worker messages
|
// Listen for EOSE via Service Worker messages
|
||||||
const handleEOSE = (data: { relays: string[] }): void => {
|
const handleEOSE = (data: unknown): void => {
|
||||||
if (data.relays.includes(relayUrl) && !eoseReceived) {
|
const eoseData = data as { relays: string[] }
|
||||||
|
if (eoseData.relays.includes(relayUrl) && !eoseReceived) {
|
||||||
eoseReceived = true
|
eoseReceived = true
|
||||||
finalize()
|
finalize()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,21 @@
|
|||||||
import { nostrService } from './nostr'
|
import { nostrService } from './nostr'
|
||||||
import { PLATFORM_COMMISSIONS } from './platformCommissions'
|
import { PLATFORM_COMMISSIONS } from './platformCommissions'
|
||||||
import type { Event } from 'nostr-tools'
|
import type { Event } from 'nostr-tools'
|
||||||
|
import { objectCache } from './objectCache'
|
||||||
|
|
||||||
export async function fetchOriginalReviewEvent(reviewId: string): Promise<Event | null> {
|
export async function fetchOriginalReviewEvent(reviewId: string): Promise<Event | null> {
|
||||||
const pool = nostrService.getPool()
|
// Read only from IndexedDB cache
|
||||||
if (!pool) {
|
const parsed = await objectCache.getById('review', reviewId)
|
||||||
throw new Error('Pool not initialized')
|
if (parsed) {
|
||||||
|
// Get the event from cache
|
||||||
|
const event = await objectCache.getEventById('review', reviewId)
|
||||||
|
if (event) {
|
||||||
|
return event
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getPrimaryRelaySync } = await import('./config')
|
// Not found in cache - return null (no network request)
|
||||||
const { createSubscription } = await import('@/types/nostr-tools-extended')
|
return null
|
||||||
const relayUrl = getPrimaryRelaySync()
|
|
||||||
const filters = [
|
|
||||||
{
|
|
||||||
kinds: [1],
|
|
||||||
ids: [reviewId],
|
|
||||||
limit: 1,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return new Promise<Event | null>((resolve) => {
|
|
||||||
let resolved = false
|
|
||||||
const sub = createSubscription(pool, [relayUrl], filters)
|
|
||||||
|
|
||||||
const finalize = (value: Event | null): void => {
|
|
||||||
if (resolved) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resolved = true
|
|
||||||
sub.unsub()
|
|
||||||
resolve(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
sub.on('event', (event: Event) => {
|
|
||||||
finalize(event)
|
|
||||||
})
|
|
||||||
|
|
||||||
sub.on('eose', () => finalize(null))
|
|
||||||
setTimeout(() => finalize(null), 5000)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildRewardEvent(originalEvent: Event, reviewId: string): {
|
export function buildRewardEvent(originalEvent: Event, reviewId: string): {
|
||||||
@ -72,7 +49,7 @@ export function checkIfAlreadyRewarded(originalEvent: Event, reviewId: string):
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function publishRewardEvent(
|
export async function publishRewardEvent(
|
||||||
updatedEvent: {
|
updatedEventTemplate: {
|
||||||
kind: number
|
kind: number
|
||||||
created_at: number
|
created_at: number
|
||||||
tags: string[][]
|
tags: string[][]
|
||||||
@ -80,11 +57,70 @@ export async function publishRewardEvent(
|
|||||||
},
|
},
|
||||||
reviewId: string
|
reviewId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const publishedEvent = await nostrService.publishEvent(updatedEvent)
|
try {
|
||||||
if (publishedEvent) {
|
// Get original review to extract hash and parsed data
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment version for update
|
||||||
|
const newVersion = (originalParsed.version ?? 0) + 1
|
||||||
|
const {hash} = originalParsed
|
||||||
|
const index = originalParsed.index ?? 0
|
||||||
|
|
||||||
|
// Build updated parsed Review object
|
||||||
|
const updatedParsed = {
|
||||||
|
...originalParsed,
|
||||||
|
version: newVersion,
|
||||||
|
rewarded: true,
|
||||||
|
rewardAmount: PLATFORM_COMMISSIONS.review.total,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set private key in orchestrator
|
||||||
|
const privateKey = nostrService.getPrivateKey()
|
||||||
|
if (!privateKey) {
|
||||||
|
throw new Error('Private key required for signing')
|
||||||
|
}
|
||||||
|
const { writeOrchestrator } = await import('./writeOrchestrator')
|
||||||
|
writeOrchestrator.setPrivateKey(privateKey)
|
||||||
|
|
||||||
|
// Finalize event
|
||||||
|
const { finalizeEvent } = await import('nostr-tools')
|
||||||
|
const { hexToBytes } = await import('nostr-tools/utils')
|
||||||
|
const secretKey = hexToBytes(privateKey)
|
||||||
|
const event = finalizeEvent(updatedEventTemplate, 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: updatedParsed,
|
||||||
|
version: newVersion,
|
||||||
|
hidden: false,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
relays
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
console.warn('Review updated with reward tag', {
|
console.warn('Review updated with reward tag', {
|
||||||
reviewId,
|
reviewId,
|
||||||
updatedEventId: publishedEvent.id,
|
updatedEventId: event.id,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -93,6 +129,13 @@ export async function publishRewardEvent(
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error publishing reward event', {
|
||||||
|
reviewId,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateReviewWithReward(reviewId: string, authorPrivateKey: string): Promise<void> {
|
export async function updateReviewWithReward(reviewId: string, authorPrivateKey: string): Promise<void> {
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import { SimplePool } from 'nostr-tools'
|
import { SimplePool } from 'nostr-tools'
|
||||||
import { swClient } from './swClient'
|
import { swClient } from './swClient'
|
||||||
import type { Event } from 'nostr-tools'
|
import type { Event, Filter } from 'nostr-tools'
|
||||||
|
|
||||||
interface ConnectionState {
|
interface ConnectionState {
|
||||||
relayUrl: string
|
relayUrl: string
|
||||||
@ -220,8 +220,9 @@ class WebSocketService {
|
|||||||
this.updateConnectionState(relayUrl, true) // Assume connected when subscribing
|
this.updateConnectionState(relayUrl, true) // Assume connected when subscribing
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create subscription
|
// Create subscription - use first filter or empty filter
|
||||||
const sub = this.pool.subscribe(relays, filters, {
|
const filter: Filter = (filters[0] as Filter) ?? {}
|
||||||
|
const sub = this.pool.subscribe(relays, filter, {
|
||||||
onevent: (event: Event): void => {
|
onevent: (event: Event): void => {
|
||||||
// Notify Service Worker of new event via postMessage
|
// Notify Service Worker of new event via postMessage
|
||||||
void swClient.sendMessage({
|
void swClient.sendMessage({
|
||||||
@ -261,7 +262,7 @@ class WebSocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update all connection states
|
// Update all connection states
|
||||||
this.connectionStates.forEach((state, relayUrl) => {
|
this.connectionStates.forEach((_state, relayUrl) => {
|
||||||
this.updateConnectionState(relayUrl, false)
|
this.updateConnectionState(relayUrl, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@ class WriteOrchestrator {
|
|||||||
params: WriteObjectParams,
|
params: WriteObjectParams,
|
||||||
relays: string[]
|
relays: string[]
|
||||||
): Promise<{ success: boolean; eventId: string; published: false | string[] }> {
|
): Promise<{ success: boolean; eventId: string; published: false | string[] }> {
|
||||||
const { objectType, hash, event, parsed, version, hidden, index, published = false } = params
|
const { objectType, hash, event, parsed, version, hidden, index } = params
|
||||||
|
|
||||||
// Écriture en parallèle : réseau et local indépendamment
|
// Écriture en parallèle : réseau et local indépendamment
|
||||||
const [networkResult, localResult] = await Promise.allSettled([
|
const [networkResult, localResult] = await Promise.allSettled([
|
||||||
@ -117,7 +117,7 @@ class WriteOrchestrator {
|
|||||||
parsed,
|
parsed,
|
||||||
version,
|
version,
|
||||||
hidden,
|
hidden,
|
||||||
index,
|
...(index !== undefined ? { index } : {}),
|
||||||
},
|
},
|
||||||
relays
|
relays
|
||||||
)
|
)
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class WriteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createWorker(): Promise<void> {
|
private createWorker(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
if (typeof window === 'undefined' || !window.Worker) {
|
if (typeof window === 'undefined' || !window.Worker) {
|
||||||
// Fallback: write directly if Worker not available
|
// Fallback: write directly if Worker not available
|
||||||
console.warn('[WriteService] Web Workers not available, using direct writes')
|
console.warn('[WriteService] Web Workers not available, using direct writes')
|
||||||
@ -240,8 +240,8 @@ class WriteService {
|
|||||||
}
|
}
|
||||||
// Fallback: direct write
|
// Fallback: direct write
|
||||||
const { notificationService } = await import('./notificationService')
|
const { notificationService } = await import('./notificationService')
|
||||||
await notificationService.createNotificationDirect(
|
await notificationService.createNotification({
|
||||||
type as Parameters<typeof notificationService.createNotificationDirect>[0],
|
type: type as Parameters<typeof notificationService.createNotification>[0]['type'],
|
||||||
objectType,
|
objectType,
|
||||||
objectId,
|
objectId,
|
||||||
eventId,
|
eventId,
|
||||||
|
|||||||
@ -297,7 +297,7 @@ export default function AuthorPage(): React.ReactElement {
|
|||||||
const { presentation, series, totalSponsoring, loading, error, reload } = useAuthorData(hashIdOrPubkey ?? '')
|
const { presentation, series, totalSponsoring, loading, error, reload } = useAuthorData(hashIdOrPubkey ?? '')
|
||||||
|
|
||||||
if (!hashIdOrPubkey) {
|
if (!hashIdOrPubkey) {
|
||||||
return null
|
return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the actual pubkey from presentation
|
// Get the actual pubkey from presentation
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user