Nicolas Cantu f5d9033183 Refactor to use cache-first architecture with background sync
**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
2026-01-06 15:04:14 +01:00

73 lines
2.2 KiB
TypeScript

import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { useI18n } from '@/hooks/useI18n'
import React from 'react'
import { platformSyncService } from '@/lib/platformSync'
function I18nProvider({ children }: { children: React.ReactNode }) {
// Get saved locale from localStorage or default to French
const getInitialLocale = (): 'fr' | 'en' => {
if (typeof window === 'undefined') {
return 'fr'
}
try {
const savedLocale = localStorage.getItem('zapwall-locale') as 'fr' | 'en' | null
if (savedLocale === 'fr' || savedLocale === 'en') {
return savedLocale
}
} catch {
// Fallback to browser locale detection
}
// Try to detect browser locale
const browserLocale = navigator.language.split('-')[0]
return browserLocale === 'en' ? 'en' : 'fr'
}
const [initialLocale, setInitialLocale] = React.useState<'fr' | 'en'>('fr')
const [localeLoaded, setLocaleLoaded] = React.useState(false)
React.useEffect(() => {
const locale = getInitialLocale()
setInitialLocale(locale)
setLocaleLoaded(true)
}, [])
const { loaded } = useI18n(initialLocale)
if (!localeLoaded || !loaded) {
return <div className="min-h-screen bg-cyber-darker flex items-center justify-center text-neon-cyan">Loading...</div>
}
return <>{children}</>
}
export default function App({ Component, pageProps }: AppProps): React.ReactElement {
// Start platform sync on app mount and resume on each page navigation
React.useEffect(() => {
// Start continuous sync (runs periodically in background)
platformSyncService.startContinuousSync()
// Also trigger a sync on each page navigation
const handleRouteChange = (): void => {
if (!platformSyncService.isSyncing()) {
void platformSyncService.startSync()
}
}
// Listen to route changes
const router = require('next/router').default
router.events?.on('routeChangeComplete', handleRouteChange)
return () => {
router.events?.off('routeChangeComplete', handleRouteChange)
platformSyncService.stopSync()
}
}, [])
return (
<I18nProvider>
<Component {...pageProps} />
</I18nProvider>
)
}