From 7b390aae66206147f93d43177be6129483951ca8 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 7 Oct 2025 21:49:01 +0100 Subject: [PATCH] refactor(highlights): extract event processing utilities - Create highlightEventProcessor module with eventToHighlight utility - Extract dedupeHighlights and sortHighlights functions - Reduce highlightService.ts from 346 lines to 186 lines - Eliminate repetitive event-to-highlight conversion code --- src/services/highlightEventProcessor.ts | 66 +++++++++ src/services/highlightService.ts | 178 ++---------------------- 2 files changed, 75 insertions(+), 169 deletions(-) create mode 100644 src/services/highlightEventProcessor.ts diff --git a/src/services/highlightEventProcessor.ts b/src/services/highlightEventProcessor.ts new file mode 100644 index 00000000..93aae73d --- /dev/null +++ b/src/services/highlightEventProcessor.ts @@ -0,0 +1,66 @@ +import { NostrEvent } from 'nostr-tools' +import { Helpers } from 'applesauce-core' +import { Highlight } from '../types/highlights' + +const { + getHighlightText, + getHighlightContext, + getHighlightComment, + getHighlightSourceEventPointer, + getHighlightSourceAddressPointer, + getHighlightSourceUrl, + getHighlightAttributions +} = Helpers + +/** + * Convert a NostrEvent to a Highlight object + */ +export function eventToHighlight(event: NostrEvent): Highlight { + const highlightText = getHighlightText(event) + const context = getHighlightContext(event) + const comment = getHighlightComment(event) + const sourceEventPointer = getHighlightSourceEventPointer(event) + const sourceAddressPointer = getHighlightSourceAddressPointer(event) + const sourceUrl = getHighlightSourceUrl(event) + const attributions = getHighlightAttributions(event) + + const author = attributions.find(a => a.role === 'author')?.pubkey + const eventReference = sourceEventPointer?.id || + (sourceAddressPointer ? `${sourceAddressPointer.kind}:${sourceAddressPointer.pubkey}:${sourceAddressPointer.identifier}` : undefined) + + return { + id: event.id, + pubkey: event.pubkey, + created_at: event.created_at, + content: highlightText, + tags: event.tags, + eventReference, + urlReference: sourceUrl, + author, + context, + comment + } +} + +/** + * Deduplicate highlight events by ID + */ +export function dedupeHighlights(events: NostrEvent[]): NostrEvent[] { + const byId = new Map() + + for (const event of events) { + if (event?.id && !byId.has(event.id)) { + byId.set(event.id, event) + } + } + + return Array.from(byId.values()) +} + +/** + * Sort highlights by creation time (newest first) + */ +export function sortHighlights(highlights: Highlight[]): Highlight[] { + return highlights.sort((a, b) => b.created_at - a.created_at) +} + diff --git a/src/services/highlightService.ts b/src/services/highlightService.ts index d214af51..14e91cfd 100644 --- a/src/services/highlightService.ts +++ b/src/services/highlightService.ts @@ -1,36 +1,9 @@ import { RelayPool, completeOnEose, onlyEvents } from 'applesauce-relay' import { lastValueFrom, takeUntil, timer, tap, toArray } from 'rxjs' import { NostrEvent } from 'nostr-tools' -import { Helpers } from 'applesauce-core' import { Highlight } from '../types/highlights' import { RELAYS } from '../config/relays' - -const { - getHighlightText, - getHighlightContext, - getHighlightComment, - getHighlightSourceEventPointer, - getHighlightSourceAddressPointer, - getHighlightSourceUrl, - getHighlightAttributions -} = Helpers - -/** - * Deduplicate highlight events by ID - * Since highlights can come from multiple relays, we need to ensure - * we only show each unique highlight once - */ -function dedupeHighlights(events: NostrEvent[]): NostrEvent[] { - const byId = new Map() - - for (const event of events) { - if (event?.id && !byId.has(event.id)) { - byId.set(event.id, event) - } - } - - return Array.from(byId.values()) -} +import { eventToHighlight, dedupeHighlights, sortHighlights } from './highlightEventProcessor' /** * Fetches highlights for a specific article by its address coordinate and/or event ID @@ -53,31 +26,7 @@ export const fetchHighlightsForArticle = async ( const processEvent = (event: NostrEvent): Highlight | null => { if (seenIds.has(event.id)) return null seenIds.add(event.id) - - const highlightText = getHighlightText(event) - const context = getHighlightContext(event) - const comment = getHighlightComment(event) - const sourceEventPointer = getHighlightSourceEventPointer(event) - const sourceAddressPointer = getHighlightSourceAddressPointer(event) - const sourceUrl = getHighlightSourceUrl(event) - const attributions = getHighlightAttributions(event) - - const author = attributions.find(a => a.role === 'author')?.pubkey - const eventReference = sourceEventPointer?.id || - (sourceAddressPointer ? `${sourceAddressPointer.kind}:${sourceAddressPointer.pubkey}:${sourceAddressPointer.identifier}` : undefined) - - return { - id: event.id, - pubkey: event.pubkey, - created_at: event.created_at, - content: highlightText, - tags: event.tags, - eventReference, - urlReference: sourceUrl, - author, - context, - comment - } + return eventToHighlight(event) } // Query for highlights that reference this article via the 'a' tag @@ -138,39 +87,8 @@ export const fetchHighlightsForArticle = async ( const uniqueEvents = dedupeHighlights(rawEvents) console.log('📊 Unique highlight events after deduplication:', uniqueEvents.length) - const highlights: Highlight[] = uniqueEvents.map((event: NostrEvent) => { - // Use applesauce helpers to extract highlight data - const highlightText = getHighlightText(event) - const context = getHighlightContext(event) - const comment = getHighlightComment(event) - const sourceEventPointer = getHighlightSourceEventPointer(event) - const sourceAddressPointer = getHighlightSourceAddressPointer(event) - const sourceUrl = getHighlightSourceUrl(event) - const attributions = getHighlightAttributions(event) - - // Get author from attributions - const author = attributions.find(a => a.role === 'author')?.pubkey - - // Get event reference (prefer event pointer, fallback to address pointer) - const eventReference = sourceEventPointer?.id || - (sourceAddressPointer ? `${sourceAddressPointer.kind}:${sourceAddressPointer.pubkey}:${sourceAddressPointer.identifier}` : undefined) - - return { - id: event.id, - pubkey: event.pubkey, - created_at: event.created_at, - content: highlightText, - tags: event.tags, - eventReference, - urlReference: sourceUrl, - author, - context, - comment - } - }) - - // Sort by creation time (newest first) - return highlights.sort((a, b) => b.created_at - a.created_at) + const highlights: Highlight[] = uniqueEvents.map(eventToHighlight) + return sortHighlights(highlights) } catch (error) { console.error('Failed to fetch highlights for article:', error) return [] @@ -207,34 +125,8 @@ export const fetchHighlightsForUrl = async ( console.log('📊 Highlights for URL:', rawEvents.length) const uniqueEvents = dedupeHighlights(rawEvents) - const highlights: Highlight[] = uniqueEvents.map((event: NostrEvent) => { - const highlightText = getHighlightText(event) - const context = getHighlightContext(event) - const comment = getHighlightComment(event) - const sourceEventPointer = getHighlightSourceEventPointer(event) - const sourceAddressPointer = getHighlightSourceAddressPointer(event) - const sourceUrl = getHighlightSourceUrl(event) - const attributions = getHighlightAttributions(event) - - const author = attributions.find(a => a.role === 'author')?.pubkey - const eventReference = sourceEventPointer?.id || - (sourceAddressPointer ? `${sourceAddressPointer.kind}:${sourceAddressPointer.pubkey}:${sourceAddressPointer.identifier}` : undefined) - - return { - id: event.id, - pubkey: event.pubkey, - created_at: event.created_at, - content: highlightText, - tags: event.tags, - eventReference, - urlReference: sourceUrl, - author, - context, - comment - } - }) - - return highlights.sort((a, b) => b.created_at - a.created_at) + const highlights: Highlight[] = uniqueEvents.map(eventToHighlight) + return sortHighlights(highlights) } catch (error) { console.error('Failed to fetch highlights for URL:', error) return [] @@ -266,32 +158,7 @@ export const fetchHighlights = async ( tap((event: NostrEvent) => { if (!seenIds.has(event.id)) { seenIds.add(event.id) - - const highlightText = getHighlightText(event) - const context = getHighlightContext(event) - const comment = getHighlightComment(event) - const sourceEventPointer = getHighlightSourceEventPointer(event) - const sourceAddressPointer = getHighlightSourceAddressPointer(event) - const sourceUrl = getHighlightSourceUrl(event) - const attributions = getHighlightAttributions(event) - - const author = attributions.find(a => a.role === 'author')?.pubkey - const eventReference = sourceEventPointer?.id || - (sourceAddressPointer ? `${sourceAddressPointer.kind}:${sourceAddressPointer.pubkey}:${sourceAddressPointer.identifier}` : undefined) - - const highlight: Highlight = { - id: event.id, - pubkey: event.pubkey, - created_at: event.created_at, - content: highlightText, - tags: event.tags, - eventReference, - urlReference: sourceUrl, - author, - context, - comment - } - + const highlight = eventToHighlight(event) if (onHighlight) { onHighlight(highlight) } @@ -309,35 +176,8 @@ export const fetchHighlights = async ( const uniqueEvents = dedupeHighlights(rawEvents) console.log('📊 Unique highlight events after deduplication:', uniqueEvents.length) - const highlights: Highlight[] = uniqueEvents.map((event: NostrEvent) => { - const highlightText = getHighlightText(event) - const context = getHighlightContext(event) - const comment = getHighlightComment(event) - const sourceEventPointer = getHighlightSourceEventPointer(event) - const sourceAddressPointer = getHighlightSourceAddressPointer(event) - const sourceUrl = getHighlightSourceUrl(event) - const attributions = getHighlightAttributions(event) - - const author = attributions.find(a => a.role === 'author')?.pubkey - const eventReference = sourceEventPointer?.id || - (sourceAddressPointer ? `${sourceAddressPointer.kind}:${sourceAddressPointer.pubkey}:${sourceAddressPointer.identifier}` : undefined) - - return { - id: event.id, - pubkey: event.pubkey, - created_at: event.created_at, - content: highlightText, - tags: event.tags, - eventReference, - urlReference: sourceUrl, - author, - context, - comment - } - }) - - // Sort by creation time (newest first) - return highlights.sort((a, b) => b.created_at - a.created_at) + const highlights: Highlight[] = uniqueEvents.map(eventToHighlight) + return sortHighlights(highlights) } catch (error) { console.error('Failed to fetch highlights by author:', error) return []