From f17f0c4ff4539ec483883cb65e10c890a60bd6a2 Mon Sep 17 00:00:00 2001 From: DocNR Date: Mon, 24 Mar 2025 22:08:10 -0400 Subject: [PATCH] Add contact cache schema and service integration - Add contact_cache table schema and migration - Update useContactList hook with caching support - Enhance RelayInitializer with cache initialization - Update SocialFeedService to work with caching system --- lib/db/schema.ts | 60 +++++++++++++++++++++++++++++++- lib/hooks/useContactList.ts | 68 ++++++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 47e52ef..520c1d2 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -2,7 +2,7 @@ import { SQLiteDatabase } from 'expo-sqlite'; import { Platform } from 'react-native'; -export const SCHEMA_VERSION = 11; +export const SCHEMA_VERSION = 12; class Schema { private async getCurrentVersion(db: SQLiteDatabase): Promise { @@ -145,6 +145,54 @@ class Schema { throw error; } } + + async migrate_v12(db: SQLiteDatabase): Promise { + try { + console.log('[Schema] Running migration v12 - Adding Contact Cache table'); + + // Create contact_cache table if it doesn't exist + await db.execAsync(` + CREATE TABLE IF NOT EXISTS contact_cache ( + owner_pubkey TEXT NOT NULL, + contact_pubkey TEXT NOT NULL, + cached_at INTEGER NOT NULL, + PRIMARY KEY (owner_pubkey, contact_pubkey) + ); + + CREATE INDEX IF NOT EXISTS idx_contact_cache_owner + ON contact_cache (owner_pubkey); + `); + + console.log('[Schema] Migration v12 completed successfully'); + } catch (error) { + console.error('[Schema] Error in migration v12:', error); + throw error; + } + } + + async addContactCacheTable(db: SQLiteDatabase): Promise { + try { + console.log('[Schema] Running migration v12 - Adding Contact Cache table'); + + // Create contact_cache table if it doesn't exist + await db.execAsync(` + CREATE TABLE IF NOT EXISTS contact_cache ( + owner_pubkey TEXT NOT NULL, + contact_pubkey TEXT NOT NULL, + cached_at INTEGER NOT NULL, + PRIMARY KEY (owner_pubkey, contact_pubkey) + ); + + CREATE INDEX IF NOT EXISTS idx_contact_cache_owner + ON contact_cache (owner_pubkey); + `); + + console.log('[Schema] Migration v12 completed successfully'); + } catch (error) { + console.error('[Schema] Error in migration v12:', error); + throw error; + } + } async createTables(db: SQLiteDatabase): Promise { try { @@ -207,6 +255,11 @@ class Schema { await this.migrate_v11(db); } + if (currentVersion < 12) { + console.log(`[Schema] Running migration from version ${currentVersion} to 12`); + await this.migrate_v12(db); + } + // Update schema version at the end of the transaction await this.updateSchemaVersion(db); }); @@ -245,6 +298,11 @@ class Schema { await this.migrate_v11(db); } + if (currentVersion < 12) { + console.log(`[Schema] Running migration from version ${currentVersion} to 12`); + await this.migrate_v12(db); + } + // Update schema version await this.updateSchemaVersion(db); diff --git a/lib/hooks/useContactList.ts b/lib/hooks/useContactList.ts index 9a70e34..78ee8b7 100644 --- a/lib/hooks/useContactList.ts +++ b/lib/hooks/useContactList.ts @@ -1,20 +1,64 @@ // lib/hooks/useContactList.ts -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import { NDKEvent, NDKUser, NDKKind, NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk-mobile'; import { useNDK } from '@/lib/hooks/useNDK'; import { POWR_PUBKEY_HEX } from '@/lib/hooks/useFeedHooks'; +import { useDatabase } from '@/components/DatabaseProvider'; +import { getContactCacheService } from '@/lib/db/services/ContactCacheService'; export function useContactList(pubkey: string | undefined) { const { ndk } = useNDK(); + const db = useDatabase(); const [contacts, setContacts] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [isInitialLoad, setIsInitialLoad] = useState(true); + // Use a ref to track if we've loaded from cache + const loadedFromCacheRef = useRef(false); + + // Load contacts from cache first + useEffect(() => { + if (!pubkey || !db || loadedFromCacheRef.current) return; + + const loadCachedContacts = async () => { + try { + const contactCache = getContactCacheService(db); + const cachedContacts = await contactCache.getCachedContacts(pubkey); + + if (cachedContacts.length > 0) { + console.log(`[useContactList] Loaded ${cachedContacts.length} contacts from cache`); + + // Add self and POWR account to contacts + const contactSet = new Set(cachedContacts); + contactSet.add(pubkey); + if (POWR_PUBKEY_HEX) { + contactSet.add(POWR_PUBKEY_HEX); + } + + // Set contacts state with cached contacts + setContacts(Array.from(contactSet)); + setIsInitialLoad(false); + loadedFromCacheRef.current = true; + } + } catch (error) { + console.error('[useContactList] Error loading cached contacts:', error); + // Don't set error state here - we'll still try to fetch from network + } + }; + + loadCachedContacts(); + }, [pubkey, db]); + + // Fetch contact list from NDK const fetchContactList = useCallback(async () => { if (!ndk || !pubkey) return; setIsLoading(true); - setError(null); + if (!loadedFromCacheRef.current) { + // Only reset error if this is not a background refresh after cache load + setError(null); + } try { // Try multiple approaches to ensure reliability @@ -92,13 +136,26 @@ export function useContactList(pubkey: string | undefined) { // Convert to array and update state const contactArray = Array.from(contactSet); setContacts(contactArray); + setIsInitialLoad(false); + + // Cache contacts if we have a database connection + if (db && contactArray.length > 0) { + try { + const contactCache = getContactCacheService(db); + await contactCache.cacheContacts(pubkey, contactArray); + console.log(`[useContactList] Cached ${contactArray.length} contacts for ${pubkey}`); + } catch (cacheError) { + console.error('[useContactList] Error caching contacts:', cacheError); + // Non-fatal, we can continue even if caching fails + } + } } catch (err) { console.error('Error fetching contact list:', err); setError(err instanceof Error ? err : new Error('Failed to fetch contacts')); } finally { setIsLoading(false); } - }, [ndk, pubkey]); + }, [ndk, pubkey, db]); // Fetch on mount and when dependencies change useEffect(() => { @@ -112,6 +169,7 @@ export function useContactList(pubkey: string | undefined) { isLoading, error, refetch: fetchContactList, - hasContacts: contacts.length > 0 + hasContacts: contacts.length > 0, + isInitialLoad }; -} \ No newline at end of file +}