**Motivations:** - total_sponsoring should be calculated from cache, not stored in tags - Author searches should use cache first, not query Nostr directly - All elements should be read/written from/to database - Background sync should scan all notes with service='zapwall.fr' tag and update cache - Sync should run at startup and resume on each page navigation **Root causes:** - total_sponsoring was stored in tags which required updates on every sponsoring payment - Author queries were querying Nostr directly instead of using cache - No background sync service to keep cache up to date with new notes **Correctifs:** - Removed totalSponsoring from AuthorTags interface and buildAuthorTags - Modified getAuthorSponsoring to calculate from cache (sponsoring queries) instead of tags - Modified parsePresentationEvent to set totalSponsoring to 0 (calculated on demand from cache) - Modified fetchAuthorByHashId and fetchAuthorPresentationFromPool to use cache first and calculate totalSponsoring from cache - Created platformSyncService that scans all notes with service='zapwall.fr' tag and caches them - Modified _app.tsx to start continuous sync on mount and resume on page navigation - All author presentations now calculate totalSponsoring from cache when loaded **Evolutions:** - Cache-first architecture: all queries check cache before querying Nostr - Background sync service keeps cache up to date automatically - totalSponsoring is always calculated from actual sponsoring data in cache - Better performance: cache queries are faster than Nostr queries - Non-blocking sync: background sync doesn't block UI **Pages affectées:** - lib/nostrTagSystemTypes.ts - lib/nostrTagSystemBuild.ts - lib/articlePublisherHelpersPresentation.ts - lib/sponsoring.ts - lib/authorQueries.ts - lib/platformSync.ts (new) - pages/_app.tsx
118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
/**
|
|
* Query authors by hash ID or pubkey (for backward compatibility)
|
|
*/
|
|
|
|
import type { Event } from 'nostr-tools'
|
|
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
|
|
import { buildTagFilter, extractTagsFromEvent } from './nostrTagSystem'
|
|
import { getPrimaryRelaySync } from './config'
|
|
import { PLATFORM_SERVICE, MIN_EVENT_DATE } from './platformConfig'
|
|
import { parsePresentationEvent, fetchAuthorPresentationFromPool } from './articlePublisherHelpersPresentation'
|
|
import { getLatestVersion } from './versionManager'
|
|
import { objectCache } from './objectCache'
|
|
|
|
/**
|
|
* Fetch author presentation by hash ID or pubkey
|
|
* If the parameter looks like a pubkey (64 hex chars), it uses pubkey lookup
|
|
* Otherwise, it uses hash ID lookup
|
|
*/
|
|
export async function fetchAuthorByHashId(
|
|
pool: SimplePoolWithSub,
|
|
hashIdOrPubkey: string
|
|
): Promise<import('@/types/nostr').AuthorPresentationArticle | null> {
|
|
// Check if it's a pubkey (64 hex characters) for backward compatibility
|
|
if (/^[a-f0-9]{64}$/i.test(hashIdOrPubkey)) {
|
|
return fetchAuthorPresentationFromPool(pool, hashIdOrPubkey)
|
|
}
|
|
|
|
// Otherwise, treat as hash ID
|
|
const hashId = hashIdOrPubkey
|
|
// Check cache first - this is the primary source
|
|
const cached = await objectCache.get('author', hashId)
|
|
if (cached) {
|
|
const presentation = cached as import('@/types/nostr').AuthorPresentationArticle
|
|
// Calculate totalSponsoring from cache
|
|
const { getAuthorSponsoring } = await import('./sponsoring')
|
|
presentation.totalSponsoring = await getAuthorSponsoring(presentation.pubkey)
|
|
return presentation
|
|
}
|
|
|
|
const filters = [
|
|
{
|
|
...buildTagFilter({
|
|
type: 'author',
|
|
id: hashId,
|
|
service: PLATFORM_SERVICE,
|
|
}),
|
|
since: MIN_EVENT_DATE,
|
|
limit: 100, // Get all versions to find the latest
|
|
},
|
|
]
|
|
|
|
return new Promise<import('@/types/nostr').AuthorPresentationArticle | null>((resolve) => {
|
|
let resolved = false
|
|
const relayUrl = getPrimaryRelaySync()
|
|
const { createSubscription } = require('@/types/nostr-tools-extended')
|
|
const sub = createSubscription(pool, [relayUrl], filters)
|
|
|
|
const events: Event[] = []
|
|
|
|
const finalize = async (value: import('@/types/nostr').AuthorPresentationArticle | null): Promise<void> => {
|
|
if (resolved) {
|
|
return
|
|
}
|
|
resolved = true
|
|
sub.unsub()
|
|
|
|
// Cache the result if found
|
|
if (value && events.length > 0) {
|
|
const event = events.find(e => e.id === value.id) || events[0]
|
|
if (event) {
|
|
const tags = extractTagsFromEvent(event)
|
|
if (value.hash) {
|
|
// Calculate totalSponsoring from cache before storing
|
|
const { getAuthorSponsoring } = await import('./sponsoring')
|
|
value.totalSponsoring = await getAuthorSponsoring(value.pubkey)
|
|
await objectCache.set('author', value.hash, event, value, tags.version ?? 0, tags.hidden, value.index)
|
|
}
|
|
}
|
|
}
|
|
|
|
resolve(value)
|
|
}
|
|
|
|
sub.on('event', (event: Event): void => {
|
|
// Collect all events first
|
|
const tags = extractTagsFromEvent(event)
|
|
if (tags.type === 'author' && !tags.hidden && tags.id === hashId) {
|
|
events.push(event)
|
|
}
|
|
})
|
|
|
|
sub.on('eose', async (): Promise<void> => {
|
|
// Get the latest version from all collected events
|
|
const latestEvent = getLatestVersion(events)
|
|
if (latestEvent) {
|
|
const parsed = await parsePresentationEvent(latestEvent)
|
|
if (parsed) {
|
|
await finalize(parsed)
|
|
return
|
|
}
|
|
}
|
|
await finalize(null)
|
|
})
|
|
setTimeout(async (): Promise<void> => {
|
|
// Get the latest version from all collected events
|
|
const latestEvent = getLatestVersion(events)
|
|
if (latestEvent) {
|
|
const parsed = await parsePresentationEvent(latestEvent)
|
|
if (parsed) {
|
|
await finalize(parsed)
|
|
return
|
|
}
|
|
}
|
|
await finalize(null)
|
|
}, 5000).unref?.()
|
|
})
|
|
}
|