From 64c94b66798ccdef1f7d047c4a5de4fb085b7ca4 Mon Sep 17 00:00:00 2001 From: Nicolas Cantu Date: Wed, 7 Jan 2026 02:17:26 +0100 Subject: [PATCH] lint fix wip --- hooks/useI18n.ts | 10 ++- lib/articleMutations.ts | 156 ++++++++++++++++++++++++++++----- lib/articlePublisherPublish.ts | 7 +- lib/nostrAuth.ts | 9 +- lib/writeService.ts | 4 +- 5 files changed, 150 insertions(+), 36 deletions(-) diff --git a/hooks/useI18n.ts b/hooks/useI18n.ts index fe27f80..28c9383 100644 --- a/hooks/useI18n.ts +++ b/hooks/useI18n.ts @@ -12,12 +12,14 @@ export function useI18n(locale: Locale = 'fr'): { useEffect(() => { const load = async (): Promise => { try { - // Get saved locale from localStorage or use provided locale + // Get saved locale from IndexedDB or use provided locale let savedLocale: Locale | null = null try { - if (typeof window !== 'undefined') { - savedLocale = localStorage.getItem('zapwall-locale') as Locale | null - } + // Migrate from localStorage if needed + const { localeStorage } = await import('@/lib/localeStorage') + await localeStorage.migrateFromLocalStorage() + // Load from IndexedDB + savedLocale = await localeStorage.getLocale() } catch { // Fallback to provided locale } diff --git a/lib/articleMutations.ts b/lib/articleMutations.ts index 103d344..6da03c4 100644 --- a/lib/articleMutations.ts +++ b/lib/articleMutations.ts @@ -77,8 +77,8 @@ async function buildParsedArticleFromDraft( paid: false, thumbnailUrl: draft.bannerUrl ?? '', invoice: invoice.invoice, - paymentHash: invoice.paymentHash ?? undefined, - category: draft.category, + ...(invoice.paymentHash ? { paymentHash: invoice.paymentHash } : {}), + ...(draft.category ? { category: draft.category } : {}), ...(draft.seriesId ? { seriesId: draft.seriesId } : {}), ...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}), ...(draft.pages && draft.pages.length > 0 ? { pages: draft.pages } : {}), @@ -93,10 +93,27 @@ async function publishPreviewWithInvoice( invoice: AlbyInvoice, authorPubkey: string, presentationId: string, - extraTags?: string[][] + extraTags?: string[][], + customArticle?: Article ): Promise { - // Build parsed article object - const { article, hash, version, index } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey) + // Build parsed article object (use custom article if provided, e.g., for updates with version) + let article: Article + let hash: string + let version: number + let index: number + + if (customArticle) { + article = customArticle + hash = customArticle.hash + version = customArticle.version + index = customArticle.index ?? 0 + } else { + const built = await buildParsedArticleFromDraft(draft, invoice, authorPubkey) + article = built.article + hash = built.hash + version = built.version + index = built.index + } // Build event template const previewEventTemplate = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags) @@ -152,8 +169,8 @@ export async function publishSeries(params: { const {category} = params requireCategory(category) - // Map category to new system - const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' + // Map category to new system (not used but kept for future reference) + const _newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' // Generate hash ID from series data const hashId = await generateSeriesHashId({ @@ -241,8 +258,8 @@ async function buildSeriesEvent( content: string tags: string[][] }> { - // Map category to new system - const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' + // Map category to new system (not used but kept for future reference) + const _newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' // Generate hash ID from series data const hashId = await generateSeriesHashId({ @@ -307,8 +324,8 @@ export async function publishReview(params: { const {category} = params requireCategory(category) - // Map category to new system - const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' + // Map category to new system (not used but kept for future reference) + const _newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' // Generate hash ID from review data const { generateReviewHashId } = await import('./hashIdGenerator') @@ -400,8 +417,8 @@ async function buildReviewEvent( content: string tags: string[][] }> { - // Map category to new system - const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' + // Map category to new system (not used but kept for future reference) + const _newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research' // Generate hash ID from review data const { generateReviewHashId } = await import('./hashIdGenerator') @@ -502,12 +519,36 @@ async function publishUpdate( ): Promise { const {category} = draft requireCategory(category) + + // Get original article from IndexedDB to retrieve current version + const { objectCache } = await import('./objectCache') + const originalArticle = await objectCache.getById('publication', originalArticleId) as Article | null + + if (!originalArticle) { + return updateFailure(originalArticleId, 'Original article not found in cache') + } + + // Verify user is the author + 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 = category === 'science-fiction' ? 'sciencefiction' : 'research' - const updateTags = await buildUpdateTags(draft, originalArticleId, newCategory, authorPubkey) - const publishedEvent = await publishPreviewWithInvoice(draft, invoice, authorPubkey, presentationId, updateTags) + // Use current version from original article + const currentVersion = originalArticle.version ?? 0 + const updateTags = await buildUpdateTags(draft, originalArticleId, newCategory, authorPubkey, currentVersion) + + // Build parsed article with incremented version + const { article, hash, version, index } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey) + const updatedArticle: Article = { + ...article, + version: currentVersion + 1, // Increment version for update + } + + const publishedEvent = await publishPreviewWithInvoice(draft, invoice, authorPubkey, presentationId, updateTags, updatedArticle) if (!publishedEvent) { return updateFailure(originalArticleId, 'Failed to publish article update') } @@ -547,14 +588,83 @@ function updateFailure(originalArticleId: string, error?: string): ArticleUpdate export async function deleteArticleEvent(articleId: string, authorPubkey: string, authorPrivateKey?: string): Promise { ensureKeys(authorPubkey, authorPrivateKey) - const deleteEvent = { - kind: 5, - created_at: Math.floor(Date.now() / 1000), - tags: [['e', articleId]] as string[][], - content: 'deleted', - } as const - const published = await nostrService.publishEvent(deleteEvent) - if (!published) { + + // Get original event from IndexedDB cache + const { objectCache } = await import('./objectCache') + const originalEvent = await objectCache.getEventById('publication', articleId) + + if (!originalEvent) { + throw new Error('Article not found in cache') + } + + // Verify user is the author + if (originalEvent.pubkey !== authorPubkey) { + throw new Error('Only the author can delete this article') + } + + // Build delete event (new version with hidden=true) + const { buildDeleteEvent } = await import('./objectModification') + const deleteEventTemplate = await buildDeleteEvent(originalEvent, authorPubkey) + + if (!deleteEventTemplate) { + throw new Error('Failed to build delete event') + } + + // Parse the original article to get hash/version/index + const { parseArticleFromEvent } = await import('./nostrEventParsing') + const originalParsed = await parseArticleFromEvent(originalEvent) + if (!originalParsed) { + throw new Error('Failed to parse original article') + } + + // Increment version for deletion + const { extractTagsFromEvent } = await import('./nostrTagSystem') + const tags = extractTagsFromEvent(deleteEventTemplate) + const newVersion = tags.version ?? originalParsed.version + 1 + const {hash} = originalParsed + const index = originalParsed.index ?? 0 + + // Build updated parsed Article object with hidden flag + const deletedArticle: Article = { + ...originalParsed, + version: newVersion, + } + + // Set private key in orchestrator + const privateKey = authorPrivateKey ?? 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(deleteEventTemplate, 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: deletedArticle, + version: newVersion, + hidden: true, // Mark as hidden (deleted) + index, + }, + relays + ) + + if (!result.success) { throw new Error('Failed to publish delete event') } } diff --git a/lib/articlePublisherPublish.ts b/lib/articlePublisherPublish.ts index e14f9d6..ecaa737 100644 --- a/lib/articlePublisherPublish.ts +++ b/lib/articlePublisherPublish.ts @@ -59,8 +59,8 @@ async function buildParsedArticleFromDraft( paid: false, thumbnailUrl: draft.bannerUrl ?? '', invoice: invoice.invoice, - paymentHash: invoice.paymentHash ?? undefined, - category: draft.category, + ...(invoice.paymentHash ? { paymentHash: invoice.paymentHash } : {}), + ...(draft.category ? { category: draft.category } : {}), ...(draft.seriesId ? { seriesId: draft.seriesId } : {}), ...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}), ...(draft.pages && draft.pages.length > 0 ? { pages: draft.pages } : {}), @@ -123,10 +123,9 @@ export async function publishPreview( if (returnStatus) { // Return PublishResult format - const { publishResult } = await import('./publishResult') return { event, - relayStatuses: relays.map((relayUrl, idx) => { + relayStatuses: relays.map((relayUrl, _idx) => { const isSuccess = typeof result.published === 'object' && result.published.includes(relayUrl) return { relayUrl, diff --git a/lib/nostrAuth.ts b/lib/nostrAuth.ts index 14f6608..7cfb5d2 100644 --- a/lib/nostrAuth.ts +++ b/lib/nostrAuth.ts @@ -63,10 +63,13 @@ export class NostrAuthService { void this.saveStateToStorage() this.notifyListeners() - // Sync user content to IndexedDB cache (background operation) + // Sync user content via Service Worker (background operation) if (result.publicKey) { - const { syncUserContentToCache } = await import('@/lib/userContentSync') - void syncUserContentToCache(result.publicKey) + const { swClient } = await import('@/lib/swClient') + const isReady = await swClient.isReady() + if (isReady) { + void swClient.startUserSync(result.publicKey) + } } return result diff --git a/lib/writeService.ts b/lib/writeService.ts index dc33719..c94c65b 100644 --- a/lib/writeService.ts +++ b/lib/writeService.ts @@ -237,7 +237,7 @@ class WriteService { data: { type, objectType, objectId, eventId, notificationData: data }, }) }) - } else { + } // Fallback: direct write const { notificationService } = await import('./notificationService') await notificationService.createNotification({ @@ -247,7 +247,7 @@ class WriteService { eventId, data, }) - } + } catch (error) { console.error('[WriteService] Error creating notification:', error)