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
This commit is contained in:
Gigi
2025-10-07 21:49:01 +01:00
parent 70a830fb66
commit 7b390aae66
2 changed files with 75 additions and 169 deletions

View File

@@ -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<string, NostrEvent>()
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)
}

View File

@@ -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<string, NostrEvent>()
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 []