mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-19 10:51:19 +00:00
improved relay system, fixed bugs
This commit is contained in:
parent
4cd62cf775
commit
704fa27950
@ -1,7 +1,7 @@
|
||||
// app/(tabs)/index.tsx
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { ScrollView, View, TouchableOpacity } from 'react-native'
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { ScrollView, View, TouchableOpacity, Platform } from 'react-native'
|
||||
import { useFocusEffect, useTheme } from '@react-navigation/native';
|
||||
import { router } from 'expo-router'
|
||||
import {
|
||||
AlertDialog,
|
||||
@ -15,8 +15,6 @@ import {
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { TabScreen } from '@/components/layout/TabScreen'
|
||||
import Header from '@/components/Header'
|
||||
import HomeWorkout from '@/components/workout/HomeWorkout'
|
||||
import FavoriteTemplate from '@/components/workout/FavoriteTemplate'
|
||||
import { useWorkoutStore } from '@/stores/workoutStore'
|
||||
import { Text } from '@/components/ui/text'
|
||||
import { getRandomWorkoutTitle } from '@/utils/workoutTitles'
|
||||
@ -63,6 +61,8 @@ export default function WorkoutScreen() {
|
||||
endWorkout
|
||||
} = useWorkoutStore();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadFavorites();
|
||||
@ -241,7 +241,10 @@ export default function WorkoutScreen() {
|
||||
onPress={() => console.log('Open notifications')}
|
||||
>
|
||||
<View className="relative">
|
||||
<Bell className="text-foreground" />
|
||||
<Bell size={24} color={Platform.select({
|
||||
ios: undefined,
|
||||
android: '#8B5CF6'
|
||||
})} />
|
||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||
</View>
|
||||
</Button>
|
||||
|
@ -161,74 +161,28 @@ export default function ProgramsScreen() {
|
||||
console.warn('[Database Reset] Error clearing keys:', keyError);
|
||||
}
|
||||
|
||||
// Define explicit type for tables
|
||||
let tables: { name: string }[] = [];
|
||||
|
||||
// Try to get existing tables
|
||||
try {
|
||||
tables = await db.getAllAsync<{ name: string }>(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
);
|
||||
console.log(`[Database Reset] Found ${tables.length} tables to drop`);
|
||||
} catch (tableListError) {
|
||||
console.warn('[Database Reset] Error listing tables:', tableListError);
|
||||
// Initialize with empty array if query fails
|
||||
tables = [];
|
||||
}
|
||||
|
||||
// Drop tables one by one
|
||||
for (const table of tables) {
|
||||
try {
|
||||
await db.execAsync(`DROP TABLE IF EXISTS "${table.name}";`);
|
||||
console.log(`[Database Reset] Dropped table: ${table.name}`);
|
||||
} catch (dropError) {
|
||||
console.error(`[Database Reset] Error dropping table ${table.name}:`, dropError);
|
||||
}
|
||||
}
|
||||
|
||||
// Use a delay to allow any pending operations to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Create a completely new database instance instead of using the existing one
|
||||
// This will bypass the "Access to closed resource" issue
|
||||
Alert.alert(
|
||||
'Database Tables Dropped',
|
||||
'All database tables have been dropped. The app needs to be restarted to complete the reset process.',
|
||||
[
|
||||
{
|
||||
text: 'Restart Now',
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
// In a production app, you would use something like RN's DevSettings.reload()
|
||||
// For Expo, we'll suggest manual restart
|
||||
Alert.alert(
|
||||
'Manual Restart Required',
|
||||
'Please completely close the app and reopen it to finish the database reset.',
|
||||
[{ text: 'OK', style: 'default' }]
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
// Use the new complete reset method
|
||||
await schema.resetDatabaseCompletely(db);
|
||||
|
||||
// Show success message
|
||||
setTestResults({
|
||||
success: true,
|
||||
message: 'Database tables dropped. Please restart the app to complete the reset.'
|
||||
message: 'Database completely reset. The app will need to be restarted to see the changes.'
|
||||
});
|
||||
|
||||
// Recommend a restart
|
||||
Alert.alert(
|
||||
'Database Reset Complete',
|
||||
'The database has been completely reset. Please restart the app for the changes to take effect fully.',
|
||||
[{ text: 'OK', style: 'default' }]
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Database Reset] Error resetting database:', error);
|
||||
setTestResults({
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Unknown error during database reset'
|
||||
});
|
||||
|
||||
// Still recommend a restart since the database might be in an inconsistent state
|
||||
Alert.alert(
|
||||
'Database Reset Error',
|
||||
'There was an error during database reset. Please restart the app and try again.',
|
||||
[{ text: 'OK', style: 'default' }]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -36,6 +36,32 @@ interface DatabaseProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// Add a DelayedInitializer component to ensure database is fully ready
|
||||
const DelayedInitializer: React.FC<{children: React.ReactNode}> = ({children}) => {
|
||||
const [ready, setReady] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Small delay to ensure database is fully ready
|
||||
const timer = setTimeout(() => {
|
||||
console.log('[Database] Delayed initialization complete');
|
||||
setReady(true);
|
||||
}, 300); // 300ms delay should be sufficient
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
if (!ready) {
|
||||
return (
|
||||
<View className="flex-1 items-center justify-center bg-background">
|
||||
<ActivityIndicator size="small" className="mb-2" />
|
||||
<Text className="text-foreground text-sm">Finishing initialization...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export function DatabaseProvider({ children }: DatabaseProviderProps) {
|
||||
const [isReady, setIsReady] = React.useState(false);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
@ -63,11 +89,19 @@ export function DatabaseProvider({ children }: DatabaseProviderProps) {
|
||||
React.useEffect(() => {
|
||||
async function initDatabase() {
|
||||
try {
|
||||
console.log('[DB] Starting database initialization...');
|
||||
|
||||
// Add a small delay to ensure system is ready (especially on Android)
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
console.log('[DB] Opening database...');
|
||||
const db = openDatabaseSync('powr.db');
|
||||
|
||||
console.log('[DB] Creating schema...');
|
||||
await schema.createTables(db);
|
||||
|
||||
// Explicitly check for critical tables after schema creation
|
||||
await schema.ensureCriticalTablesExist(db);
|
||||
|
||||
// Initialize services
|
||||
console.log('[DB] Initializing services...');
|
||||
@ -101,7 +135,12 @@ export function DatabaseProvider({ children }: DatabaseProviderProps) {
|
||||
// Seed development database
|
||||
if (__DEV__) {
|
||||
console.log('[DB] Seeding development database...');
|
||||
await devSeeder.seedDatabase();
|
||||
try {
|
||||
await devSeeder.seedDatabase();
|
||||
} catch (seedError) {
|
||||
console.error('[DB] Error seeding database:', seedError);
|
||||
// Continue even if seeding fails
|
||||
}
|
||||
await logDatabaseInfo();
|
||||
}
|
||||
|
||||
@ -137,7 +176,9 @@ export function DatabaseProvider({ children }: DatabaseProviderProps) {
|
||||
return (
|
||||
<SQLiteProvider databaseName="powr.db">
|
||||
<DatabaseServicesContext.Provider value={services}>
|
||||
{children}
|
||||
<DelayedInitializer>
|
||||
{children}
|
||||
</DelayedInitializer>
|
||||
</DatabaseServicesContext.Provider>
|
||||
</SQLiteProvider>
|
||||
);
|
||||
|
@ -77,7 +77,7 @@ export default function Header({
|
||||
onPress={() => {}}
|
||||
>
|
||||
<View className="relative">
|
||||
<Bell size={24} className="text-foreground" />
|
||||
<Bell size={24} color={theme.colors.text} />
|
||||
{/* Notification indicator - you can conditionally render this */}
|
||||
<View className="absolute -top-1 -right-1 w-2 h-2 bg-primary rounded-full" />
|
||||
</View>
|
||||
|
@ -1,23 +1,31 @@
|
||||
// components/RelayManagement.tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, Text, FlatList, TouchableOpacity, Modal, ActivityIndicator } from 'react-native';
|
||||
import { View, Text, FlatList, TouchableOpacity, Modal, ActivityIndicator, TextInput, SafeAreaView, KeyboardAvoidingView, Platform } from 'react-native';
|
||||
import { useRelayStore } from '@/lib/stores/relayStore';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { X } from 'lucide-react-native';
|
||||
import { X, RefreshCw, PlusCircle, AlertTriangle } from 'lucide-react-native';
|
||||
import { RelayWithStatus } from '@/lib/db/services/RelayService';
|
||||
|
||||
// Define proper interface for component props
|
||||
interface RelayManagementProps {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
// Simple RelayManagement component
|
||||
export default function RelayManagement({ isVisible, onClose }: RelayManagementProps) {
|
||||
const relays = useRelayStore(state => state.relays);
|
||||
const isLoading = useRelayStore(state => state.isLoading);
|
||||
const isSaving = useRelayStore(state => state.isSaving);
|
||||
const loadRelays = useRelayStore(state => state.loadRelays);
|
||||
const updateRelay = useRelayStore(state => state.updateRelay);
|
||||
const applyChanges = useRelayStore(state => state.applyChanges);
|
||||
const resetToDefaults = useRelayStore(state => state.resetToDefaults);
|
||||
const addRelay = useRelayStore(state => state.addRelay);
|
||||
|
||||
const [newRelayUrl, setNewRelayUrl] = useState('');
|
||||
const [showAddRelay, setShowAddRelay] = useState(false);
|
||||
const [confirmReset, setConfirmReset] = useState(false);
|
||||
const [hasUnappliedChanges, setHasUnappliedChanges] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
@ -25,7 +33,52 @@ export default function RelayManagement({ isVisible, onClose }: RelayManagementP
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
// Status indicator color with proper typing
|
||||
// Track if there are unapplied changes
|
||||
const handleRelayUpdate = (url: string, changes: Partial<RelayWithStatus>) => {
|
||||
updateRelay(url, changes);
|
||||
setHasUnappliedChanges(true);
|
||||
};
|
||||
|
||||
// Handle applying changes
|
||||
const handleApplyChanges = async () => {
|
||||
const success = await applyChanges();
|
||||
if (success) {
|
||||
setHasUnappliedChanges(false);
|
||||
// Success notification could be added here
|
||||
}
|
||||
};
|
||||
|
||||
// Add new relay
|
||||
const handleAddRelay = async () => {
|
||||
if (!newRelayUrl || !newRelayUrl.startsWith('wss://')) {
|
||||
alert('Please enter a valid relay URL starting with wss://');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await addRelay(newRelayUrl);
|
||||
setNewRelayUrl('');
|
||||
setShowAddRelay(false);
|
||||
setHasUnappliedChanges(true);
|
||||
} catch (error) {
|
||||
alert(`Failed to add relay: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Reset to defaults with confirmation
|
||||
const handleResetToDefaults = async () => {
|
||||
if (confirmReset) {
|
||||
await resetToDefaults();
|
||||
setConfirmReset(false);
|
||||
setHasUnappliedChanges(true);
|
||||
} else {
|
||||
setConfirmReset(true);
|
||||
// Auto-reset the confirmation after 3 seconds
|
||||
setTimeout(() => setConfirmReset(false), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
// Status indicator color
|
||||
const getStatusColor = (status: string): string => {
|
||||
switch (status) {
|
||||
case 'connected': return '#10b981'; // Green
|
||||
@ -36,58 +89,49 @@ export default function RelayManagement({ isVisible, onClose }: RelayManagementP
|
||||
}
|
||||
};
|
||||
|
||||
// Render a relay item with proper typing
|
||||
// Render a relay item
|
||||
const renderRelayItem = ({ item }: { item: RelayWithStatus }) => (
|
||||
<View style={{
|
||||
padding: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e5e7eb',
|
||||
backgroundColor: 'white',
|
||||
}}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
||||
<View style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: getStatusColor(item.status),
|
||||
marginRight: 8
|
||||
}} />
|
||||
<Text style={{ flex: 1 }}>{item.url}</Text>
|
||||
<View className="p-4 bg-card mb-2 rounded-lg border border-border">
|
||||
<View className="flex-row items-center justify-between mb-2">
|
||||
<View className="flex-row items-center flex-1">
|
||||
<View
|
||||
style={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
backgroundColor: getStatusColor(item.status),
|
||||
marginRight: 8
|
||||
}}
|
||||
/>
|
||||
<Text className="text-foreground flex-1" numberOfLines={1}>{item.url}</Text>
|
||||
</View>
|
||||
|
||||
<Text style={{ fontSize: 12, color: '#6b7280' }}>
|
||||
<Text className="text-xs text-muted-foreground capitalize">
|
||||
{item.status}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
marginTop: 8,
|
||||
paddingTop: 8,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#f3f4f6'
|
||||
}}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ marginRight: 8 }}>Read</Text>
|
||||
<View className="flex-row justify-between items-center mt-3 pt-3 border-t border-border/30">
|
||||
<View className="flex-row items-center">
|
||||
<Text className="text-foreground mr-2">Read</Text>
|
||||
<Switch
|
||||
checked={item.read}
|
||||
onCheckedChange={() => updateRelay(item.url, { read: !item.read })}
|
||||
onCheckedChange={() => handleRelayUpdate(item.url, { read: !item.read })}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ marginRight: 8 }}>Write</Text>
|
||||
<View className="flex-row items-center">
|
||||
<Text className="text-foreground mr-2">Write</Text>
|
||||
<Switch
|
||||
checked={item.write}
|
||||
onCheckedChange={() => updateRelay(item.url, { write: !item.write })}
|
||||
onCheckedChange={() => handleRelayUpdate(item.url, { write: !item.write })}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// Main Render
|
||||
return (
|
||||
<Modal
|
||||
visible={isVisible}
|
||||
@ -95,62 +139,132 @@ export default function RelayManagement({ isVisible, onClose }: RelayManagementP
|
||||
transparent={true}
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<View style={{
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
justifyContent: 'flex-end',
|
||||
}}>
|
||||
<View style={{
|
||||
backgroundColor: 'white',
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
paddingBottom: 20,
|
||||
maxHeight: '80%',
|
||||
}}>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e5e7eb',
|
||||
}}>
|
||||
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Manage Relays</Text>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SafeAreaView style={{ flex: 1 }} className="bg-background/95">
|
||||
{/* Header */}
|
||||
<View className="p-4 flex-row items-center justify-between bg-card border-b border-border">
|
||||
<Text className="text-xl font-semibold text-foreground">Relay Management</Text>
|
||||
<TouchableOpacity onPress={onClose}>
|
||||
<X size={24} />
|
||||
<X size={24} className="text-foreground" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{isLoading ? (
|
||||
<View style={{ padding: 20, alignItems: 'center' }}>
|
||||
<ActivityIndicator size="large" />
|
||||
<Text style={{ marginTop: 10 }}>Loading relays...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<FlatList
|
||||
data={relays}
|
||||
keyExtractor={(item) => item.url}
|
||||
renderItem={renderRelayItem}
|
||||
style={{ maxHeight: '70%' }}
|
||||
/>
|
||||
|
||||
<View style={{ padding: 16 }}>
|
||||
<Button onPress={loadRelays} style={{ marginBottom: 8 }}>
|
||||
<Text style={{ color: 'white' }}>Refresh Relays</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={onClose}
|
||||
>
|
||||
<Text>Close</Text>
|
||||
</Button>
|
||||
{/* Content */}
|
||||
<View className="flex-1 px-4 pt-2">
|
||||
{isLoading ? (
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<ActivityIndicator size="large" />
|
||||
<Text className="mt-2 text-foreground">Loading relays...</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
{/* Summary */}
|
||||
<View className="flex-row justify-between mb-2 items-center">
|
||||
<Text className="text-foreground">
|
||||
{relays.length} Relays ({relays.filter(r => r.status === 'connected').length} Connected)
|
||||
</Text>
|
||||
<TouchableOpacity onPress={loadRelays} className="p-2">
|
||||
<RefreshCw size={18} className="text-primary" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Relay List */}
|
||||
{relays.length === 0 ? (
|
||||
<View className="items-center justify-center py-8">
|
||||
<Text className="text-muted-foreground mb-4">No relays configured</Text>
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={handleResetToDefaults}
|
||||
>
|
||||
<Text>Reset to Defaults</Text>
|
||||
</Button>
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
data={relays}
|
||||
renderItem={renderRelayItem}
|
||||
keyExtractor={(item) => item.url}
|
||||
ListEmptyComponent={
|
||||
<View className="items-center justify-center py-8">
|
||||
<Text className="text-muted-foreground">No relays found</Text>
|
||||
</View>
|
||||
}
|
||||
contentContainerStyle={{ paddingBottom: showAddRelay ? 180 : 100 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Add Relay Form */}
|
||||
{showAddRelay && (
|
||||
<View className="bg-card p-4 rounded-lg mb-4 mt-2 border border-border">
|
||||
<Text className="text-foreground mb-2">Add New Relay</Text>
|
||||
<TextInput
|
||||
className="bg-background text-foreground p-2 rounded-md border border-border mb-2"
|
||||
placeholder="wss://relay.example.com"
|
||||
placeholderTextColor="#6b7280"
|
||||
value={newRelayUrl}
|
||||
onChangeText={setNewRelayUrl}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
<View className="flex-row justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={() => setShowAddRelay(false)}
|
||||
>
|
||||
<Text>Cancel</Text>
|
||||
</Button>
|
||||
<Button
|
||||
onPress={handleAddRelay}
|
||||
disabled={!newRelayUrl.startsWith('wss://')}
|
||||
>
|
||||
<Text className="text-primary-foreground">Add Relay</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Footer */}
|
||||
<View className="p-4 bg-card border-t border-border">
|
||||
<View className="flex-row gap-2 mb-2">
|
||||
<Button
|
||||
className="flex-1"
|
||||
onPress={handleApplyChanges}
|
||||
disabled={!hasUnappliedChanges || isSaving}
|
||||
>
|
||||
{isSaving ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
) : (
|
||||
<Text className="text-primary-foreground">Apply Changes</Text>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={() => setShowAddRelay(true)}
|
||||
disabled={showAddRelay}
|
||||
>
|
||||
<PlusCircle size={18} className="mr-1" />
|
||||
<Text>Add Relay</Text>
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
variant={confirmReset ? "destructive" : "outline"}
|
||||
onPress={handleResetToDefaults}
|
||||
>
|
||||
<Text className={confirmReset ? "text-destructive-foreground" : ""}>
|
||||
{confirmReset ? "Confirm Reset" : "Reset to Defaults"}
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
);
|
||||
}
|
178
lib/db/schema.ts
178
lib/db/schema.ts
@ -2,7 +2,7 @@
|
||||
import { SQLiteDatabase } from 'expo-sqlite';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
export const SCHEMA_VERSION = 3; // Incrementing to add the relays table
|
||||
export const SCHEMA_VERSION = 6; // Incremented from 5 to 6 for relay table removal
|
||||
|
||||
class Schema {
|
||||
private async getCurrentVersion(db: SQLiteDatabase): Promise<number> {
|
||||
@ -44,9 +44,17 @@ class Schema {
|
||||
const currentVersion = await this.getCurrentVersion(db);
|
||||
console.log(`[Schema] Current version: ${currentVersion}, Target version: ${SCHEMA_VERSION}`);
|
||||
|
||||
// If we already have the current version, no need to recreate tables
|
||||
// If we already have the current version, check for missing tables
|
||||
if (currentVersion === SCHEMA_VERSION) {
|
||||
console.log(`[Schema] Database already at version ${SCHEMA_VERSION}`);
|
||||
console.log(`[Schema] Database already at version ${SCHEMA_VERSION}, checking for missing tables`);
|
||||
await this.ensureCriticalTablesExist(db);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle higher version numbers - especially important for Android
|
||||
if (currentVersion > SCHEMA_VERSION) {
|
||||
console.log(`[Schema] Database version ${currentVersion} is newer than target ${SCHEMA_VERSION}, checking for missing tables`);
|
||||
await this.ensureCriticalTablesExist(db);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -89,6 +97,129 @@ class Schema {
|
||||
}
|
||||
}
|
||||
|
||||
// Add this method to check for and create critical tables
|
||||
async ensureCriticalTablesExist(db: SQLiteDatabase): Promise<void> {
|
||||
try {
|
||||
console.log('[Schema] Checking for missing critical tables...');
|
||||
|
||||
// Check if workouts table exists
|
||||
const workoutsTableExists = await db.getFirstAsync<{ count: number }>(
|
||||
`SELECT count(*) as count FROM sqlite_master
|
||||
WHERE type='table' AND name='workouts'`
|
||||
);
|
||||
|
||||
if (!workoutsTableExists || workoutsTableExists.count === 0) {
|
||||
console.log('[Schema] Creating missing workouts tables...');
|
||||
|
||||
// Create workouts table
|
||||
await db.execAsync(`
|
||||
CREATE TABLE IF NOT EXISTS workouts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
start_time INTEGER NOT NULL,
|
||||
end_time INTEGER,
|
||||
is_completed BOOLEAN NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
template_id TEXT,
|
||||
nostr_event_id TEXT,
|
||||
share_status TEXT NOT NULL DEFAULT 'local',
|
||||
source TEXT NOT NULL DEFAULT 'local',
|
||||
total_volume REAL,
|
||||
total_reps INTEGER,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_workouts_start_time ON workouts(start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_workouts_template_id ON workouts(template_id);
|
||||
`);
|
||||
|
||||
// Create workout_exercises table
|
||||
await db.execAsync(`
|
||||
CREATE TABLE IF NOT EXISTS workout_exercises (
|
||||
id TEXT PRIMARY KEY,
|
||||
workout_id TEXT NOT NULL,
|
||||
exercise_id TEXT NOT NULL,
|
||||
display_order INTEGER NOT NULL,
|
||||
notes TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY(workout_id) REFERENCES workouts(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_workout_exercises_workout_id ON workout_exercises(workout_id);
|
||||
`);
|
||||
|
||||
// Create workout_sets table
|
||||
await db.execAsync(`
|
||||
CREATE TABLE IF NOT EXISTS workout_sets (
|
||||
id TEXT PRIMARY KEY,
|
||||
workout_exercise_id TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'normal',
|
||||
weight REAL,
|
||||
reps INTEGER,
|
||||
rpe REAL,
|
||||
duration INTEGER,
|
||||
is_completed BOOLEAN NOT NULL DEFAULT 0,
|
||||
completed_at INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY(workout_exercise_id) REFERENCES workout_exercises(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_workout_sets_exercise_id ON workout_sets(workout_exercise_id);
|
||||
`);
|
||||
}
|
||||
|
||||
// Check if templates table exists
|
||||
const templatesTableExists = await db.getFirstAsync<{ count: number }>(
|
||||
`SELECT count(*) as count FROM sqlite_master
|
||||
WHERE type='table' AND name='templates'`
|
||||
);
|
||||
|
||||
if (!templatesTableExists || templatesTableExists.count === 0) {
|
||||
console.log('[Schema] Creating missing templates tables...');
|
||||
|
||||
// Create templates table
|
||||
await db.execAsync(`
|
||||
CREATE TABLE IF NOT EXISTS templates (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
nostr_event_id TEXT,
|
||||
source TEXT NOT NULL DEFAULT 'local',
|
||||
parent_id TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates(updated_at);
|
||||
`);
|
||||
|
||||
// Create template_exercises table
|
||||
await db.execAsync(`
|
||||
CREATE TABLE IF NOT EXISTS template_exercises (
|
||||
id TEXT PRIMARY KEY,
|
||||
template_id TEXT NOT NULL,
|
||||
exercise_id TEXT NOT NULL,
|
||||
display_order INTEGER NOT NULL,
|
||||
target_sets INTEGER,
|
||||
target_reps INTEGER,
|
||||
target_weight REAL,
|
||||
notes TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY(template_id) REFERENCES templates(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_template_exercises_template_id ON template_exercises(template_id);
|
||||
`);
|
||||
}
|
||||
|
||||
console.log('[Schema] Critical tables check complete');
|
||||
} catch (error) {
|
||||
console.error('[Schema] Error ensuring critical tables exist:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async dropAllTables(db: SQLiteDatabase): Promise<void> {
|
||||
try {
|
||||
console.log('[Schema] Getting list of tables to drop...');
|
||||
@ -288,20 +419,6 @@ class Schema {
|
||||
CREATE INDEX idx_favorites_content_id ON favorites(content_id);
|
||||
`);
|
||||
|
||||
// Create relays table
|
||||
console.log('[Schema] Creating relays table...');
|
||||
await db.execAsync(`
|
||||
CREATE TABLE relays (
|
||||
url TEXT PRIMARY KEY,
|
||||
read INTEGER NOT NULL DEFAULT 1,
|
||||
write INTEGER NOT NULL DEFAULT 1,
|
||||
priority INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX idx_relays_priority ON relays(priority DESC);
|
||||
`);
|
||||
|
||||
// === NEW TABLES === //
|
||||
|
||||
// Create workouts table
|
||||
@ -427,25 +544,36 @@ class Schema {
|
||||
}
|
||||
}
|
||||
|
||||
async resetDatabase(db: SQLiteDatabase): Promise<void> {
|
||||
async resetDatabaseCompletely(db: SQLiteDatabase): Promise<void> {
|
||||
if (!__DEV__) {
|
||||
console.log('[Schema] Database reset is only available in development mode');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[Schema] Resetting database...');
|
||||
console.log('[Schema] Completely resetting database...');
|
||||
|
||||
// Clear schema_version to force recreation of all tables
|
||||
await db.execAsync('DROP TABLE IF EXISTS schema_version');
|
||||
console.log('[Schema] Dropped schema_version table');
|
||||
// Get all tables
|
||||
const tables = await db.getAllAsync<{ name: string }>(
|
||||
"SELECT name FROM sqlite_master WHERE type='table'"
|
||||
);
|
||||
|
||||
// Now create tables from scratch
|
||||
// Drop all tables including schema_version
|
||||
for (const { name } of tables) {
|
||||
try {
|
||||
await db.execAsync(`DROP TABLE IF EXISTS ${name}`);
|
||||
console.log(`[Schema] Dropped table: ${name}`);
|
||||
} catch (dropError) {
|
||||
console.error(`[Schema] Error dropping table ${name}:`, dropError);
|
||||
}
|
||||
}
|
||||
|
||||
// Create tables from scratch
|
||||
await this.createTables(db);
|
||||
|
||||
console.log('[Schema] Database reset complete');
|
||||
console.log('[Schema] Database completely reset');
|
||||
} catch (error) {
|
||||
console.error('[Schema] Error resetting database:', error);
|
||||
console.error('[Schema] Error completely resetting database:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { logDatabaseInfo } from '../debug';
|
||||
import { mockExerciseEvents, convertNostrToExercise } from '../../mocks/exercises';
|
||||
import { DbService } from '../db-service';
|
||||
import NDK, { NDKEvent } from '@nostr-dev-kit/ndk-mobile';
|
||||
import { NostrEvent } from '@/types/nostr'; // Assuming you have this type defined
|
||||
|
||||
export class DevSeederService {
|
||||
private db: SQLiteDatabase;
|
||||
@ -181,6 +182,71 @@ export class DevSeederService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed the database with real events from Nostr relays instead of mock data
|
||||
* @param filter The filter to use when fetching events from relays
|
||||
* @param limit Maximum number of events to seed (optional)
|
||||
*/
|
||||
async seedFromNostr(filter: any, limit?: number) {
|
||||
if (!this.ndk) {
|
||||
console.log('NDK not available for seeding from Nostr');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Seeding from Nostr with filter:`, filter);
|
||||
|
||||
// Fetch events from relays
|
||||
const events = await this.ndk.fetchEvents(filter);
|
||||
|
||||
console.log(`Found ${events.size} events on Nostr`);
|
||||
|
||||
// Convert to array and limit if needed
|
||||
const eventsArray = Array.from(events);
|
||||
const eventsToProcess = limit ? eventsArray.slice(0, limit) : eventsArray;
|
||||
|
||||
// Process each event individually
|
||||
let successCount = 0;
|
||||
|
||||
for (const ndkEvent of eventsToProcess) {
|
||||
try {
|
||||
// Convert NDKEvent to your NostrEvent format
|
||||
const nostrEvent: NostrEvent = {
|
||||
id: ndkEvent.id || '',
|
||||
pubkey: ndkEvent.pubkey || '',
|
||||
created_at: ndkEvent.created_at || 0, // Set a default value of 0 if undefined
|
||||
kind: ndkEvent.kind || 0,
|
||||
tags: ndkEvent.tags || [],
|
||||
content: ndkEvent.content || '',
|
||||
sig: ndkEvent.sig || ''
|
||||
};
|
||||
|
||||
// Cache the event
|
||||
if (this.eventCache) {
|
||||
await this.eventCache.setEvent(nostrEvent, true);
|
||||
}
|
||||
|
||||
// Process based on kind
|
||||
if (ndkEvent.kind === 33401) { // Exercise
|
||||
const exercise = convertNostrToExercise(nostrEvent);
|
||||
await this.exerciseService.createExercise(exercise, true);
|
||||
successCount++;
|
||||
}
|
||||
// Add more event type processing here as needed
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error processing Nostr event:`, error);
|
||||
// Continue with next event
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Successfully seeded ${successCount} items from Nostr`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error seeding from Nostr:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async clearDatabase() {
|
||||
if (!__DEV__) return;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,58 +2,39 @@
|
||||
import 'react-native-get-random-values'; // This must be the first import
|
||||
import NDK, { NDKCacheAdapterSqlite } from '@nostr-dev-kit/ndk-mobile';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { openDatabaseSync } from 'expo-sqlite';
|
||||
import { RelayService, DEFAULT_RELAYS } from '@/lib/db/services/RelayService';
|
||||
import { NDKCommon } from '@/types/ndk-common';
|
||||
import { extendNDK } from '@/types/ndk-extensions';
|
||||
|
||||
/**
|
||||
* Initialize NDK with relays from database or defaults
|
||||
* Initialize NDK with relays
|
||||
*/
|
||||
export async function initializeNDK() {
|
||||
console.log('[NDK] Initializing NDK with mobile adapter...');
|
||||
|
||||
// Create a mobile-specific cache adapter
|
||||
const cacheAdapter = new NDKCacheAdapterSqlite('powr', 1000);
|
||||
await cacheAdapter.initialize();
|
||||
|
||||
// Initialize database and relay service
|
||||
const db = openDatabaseSync('powr.db');
|
||||
const relayService = new RelayService(db);
|
||||
|
||||
// Initialize relay service
|
||||
const relayService = new RelayService();
|
||||
relayService.enableDebug();
|
||||
|
||||
// Load relays from database or use defaults
|
||||
console.log('[NDK] Loading relay configuration...');
|
||||
let relays: string[];
|
||||
|
||||
try {
|
||||
// Try to initialize relays from database (will add defaults if none exist)
|
||||
relays = await relayService.initializeRelays();
|
||||
console.log(`[NDK] Loaded ${relays.length} relays from database:`, relays);
|
||||
} catch (error) {
|
||||
console.error('[NDK] Error loading relays from database:', error);
|
||||
console.log('[NDK] Falling back to default relays');
|
||||
relays = DEFAULT_RELAYS;
|
||||
}
|
||||
|
||||
// Create settings store
|
||||
const settingsStore = {
|
||||
get: SecureStore.getItemAsync,
|
||||
set: SecureStore.setItemAsync,
|
||||
delete: SecureStore.deleteItemAsync,
|
||||
getSync: (key: string) => {
|
||||
// This is a synchronous wrapper - for mobile we need to handle this differently
|
||||
// since SecureStore is async-only
|
||||
console.log('[Settings] Warning: getSync called but returning null, not supported in this implementation');
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize NDK with options
|
||||
console.log(`[NDK] Creating NDK instance with ${relays.length} relays`);
|
||||
// Initialize NDK with default relays first
|
||||
console.log(`[NDK] Creating NDK instance with default relays`);
|
||||
let ndk = new NDK({
|
||||
cacheAdapter,
|
||||
explicitRelayUrls: relays,
|
||||
explicitRelayUrls: DEFAULT_RELAYS,
|
||||
enableOutboxModel: true,
|
||||
autoConnectUserRelays: true,
|
||||
clientName: 'powr',
|
||||
@ -62,41 +43,8 @@ export async function initializeNDK() {
|
||||
// Extend NDK with helper methods for better compatibility
|
||||
ndk = extendNDK(ndk);
|
||||
|
||||
// Initialize cache adapter
|
||||
await cacheAdapter.initialize();
|
||||
|
||||
// Set up the RelayService with the NDK instance for future use
|
||||
relayService.setNDK(ndk as unknown as NDKCommon);
|
||||
|
||||
// Setup relay status tracking
|
||||
const relayStatus: Record<string, 'connected' | 'connecting' | 'disconnected' | 'error'> = {};
|
||||
relays.forEach(url => {
|
||||
relayStatus[url] = 'connecting';
|
||||
});
|
||||
|
||||
// Set up listeners before connecting
|
||||
relays.forEach(url => {
|
||||
const relay = ndk.pool.getRelay(url);
|
||||
if (relay) {
|
||||
// Connection success
|
||||
relay.on('connect', () => {
|
||||
console.log(`[NDK] Relay connected: ${url}`);
|
||||
relayStatus[url] = 'connected';
|
||||
});
|
||||
|
||||
// Connection closed
|
||||
relay.on('disconnect', () => {
|
||||
console.log(`[NDK] Relay disconnected: ${url}`);
|
||||
relayStatus[url] = 'disconnected';
|
||||
});
|
||||
|
||||
// For errors, use the notice event which is used for errors in NDK
|
||||
relay.on('notice', (notice: string) => {
|
||||
console.error(`[NDK] Relay notice/error for ${url}:`, notice);
|
||||
relayStatus[url] = 'error';
|
||||
});
|
||||
}
|
||||
});
|
||||
// Set the NDK instance in the RelayService
|
||||
relayService.setNDK(ndk);
|
||||
|
||||
try {
|
||||
console.log('[NDK] Connecting to relays...');
|
||||
@ -105,27 +53,24 @@ export async function initializeNDK() {
|
||||
// Wait a moment for connections to establish
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Get updated relay statuses
|
||||
const relaysWithStatus = await relayService.getAllRelaysWithStatus();
|
||||
|
||||
// Count connected relays
|
||||
const connectedRelays = Object.entries(relayStatus)
|
||||
.filter(([_, status]) => status === 'connected')
|
||||
.map(([url]) => url);
|
||||
const connectedRelays = relaysWithStatus
|
||||
.filter(relay => relay.status === 'connected')
|
||||
.map(relay => relay.url);
|
||||
|
||||
console.log(`[NDK] Connected to ${connectedRelays.length}/${relays.length} relays`);
|
||||
console.log(`[NDK] Connected to ${connectedRelays.length}/${relaysWithStatus.length} relays`);
|
||||
|
||||
// Add more detailed relay status logging
|
||||
// Log detailed relay status
|
||||
console.log('[NDK] Detailed relay status:');
|
||||
relays.forEach(url => {
|
||||
const relay = ndk.pool.getRelay(url);
|
||||
console.log(` - ${url}: ${relay ?
|
||||
(relay.status === 1 ? 'connected' :
|
||||
relay.status === 0 ? 'connecting' :
|
||||
relay.status === 3 ? 'disconnected' :
|
||||
`status=${relay.status}`) : 'not found'}`);
|
||||
relaysWithStatus.forEach(relay => {
|
||||
console.log(` - ${relay.url}: ${relay.status}`);
|
||||
});
|
||||
|
||||
return {
|
||||
ndk,
|
||||
relayStatus,
|
||||
relayService,
|
||||
connectedRelayCount: connectedRelays.length,
|
||||
connectedRelays
|
||||
@ -135,7 +80,6 @@ export async function initializeNDK() {
|
||||
// Still return the NDK instance so the app can work offline
|
||||
return {
|
||||
ndk,
|
||||
relayStatus,
|
||||
relayService,
|
||||
connectedRelayCount: 0,
|
||||
connectedRelays: []
|
||||
|
@ -6,11 +6,10 @@ import NDK, {
|
||||
NDKUser,
|
||||
NDKRelay,
|
||||
NDKPrivateKeySigner
|
||||
} from '@nostr-dev-kit/ndk';
|
||||
} from '@nostr-dev-kit/ndk-mobile';
|
||||
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { RelayService } from '@/lib/db/services/RelayService';
|
||||
import { openDatabaseSync } from 'expo-sqlite';
|
||||
|
||||
// Constants for SecureStore
|
||||
const PRIVATE_KEY_STORAGE_KEY = 'nostr_privkey';
|
||||
@ -133,85 +132,129 @@ export const useNDKStore = create<NDKStoreState & NDKStoreActions>((set, get) =>
|
||||
|
||||
login: async (privateKeyInput?: string) => {
|
||||
set({ isLoading: true, error: null });
|
||||
console.log('[NDK] Login attempt starting');
|
||||
|
||||
try {
|
||||
const { ndk } = get();
|
||||
if (!ndk) {
|
||||
console.log('[NDK] Error: NDK not initialized');
|
||||
throw new Error('NDK not initialized');
|
||||
}
|
||||
|
||||
console.log('[NDK] Processing private key input');
|
||||
|
||||
// If no private key is provided, generate one
|
||||
let privateKeyHex = privateKeyInput;
|
||||
if (!privateKeyHex) {
|
||||
console.log('[NDK] No key provided, generating new key');
|
||||
const { privateKey } = get().generateKeys();
|
||||
privateKeyHex = privateKey;
|
||||
} else {
|
||||
console.log('[NDK] Using provided key, format:',
|
||||
privateKeyHex.startsWith('nsec') ? 'nsec' : 'hex',
|
||||
'length:', privateKeyHex.length);
|
||||
}
|
||||
|
||||
// Handle nsec format
|
||||
if (privateKeyHex.startsWith('nsec')) {
|
||||
try {
|
||||
console.log('[NDK] Decoding nsec format key');
|
||||
const decoded = nip19.decode(privateKeyHex);
|
||||
console.log('[NDK] Decoded type:', decoded.type);
|
||||
if (decoded.type === 'nsec') {
|
||||
// Get the data as hex
|
||||
privateKeyHex = bytesToHex(decoded.data as any);
|
||||
console.log('[NDK] Converted to hex, new length:', privateKeyHex.length);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error decoding nsec:', error);
|
||||
console.error('[NDK] Error decoding nsec:', error);
|
||||
throw new Error('Invalid nsec format');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[NDK] Creating signer with key length:', privateKeyHex.length);
|
||||
|
||||
// Create signer with private key
|
||||
const signer = new NDKPrivateKeySigner(privateKeyHex);
|
||||
console.log('[NDK] Signer created, setting on NDK');
|
||||
ndk.signer = signer;
|
||||
|
||||
// Get user
|
||||
console.log('[NDK] Getting user from signer');
|
||||
const user = await ndk.signer.user();
|
||||
if (!user) {
|
||||
console.log('[NDK] Error: Could not get user from signer');
|
||||
throw new Error('Could not get user from signer');
|
||||
}
|
||||
|
||||
console.log('[NDK] User retrieved, pubkey:', user.pubkey ? user.pubkey.substring(0, 8) + '...' : 'undefined');
|
||||
|
||||
// Fetch user profile
|
||||
console.log('[NDK] Fetching user profile');
|
||||
await user.fetchProfile();
|
||||
try {
|
||||
await user.fetchProfile();
|
||||
console.log('[NDK] Profile fetched successfully');
|
||||
} catch (profileError) {
|
||||
console.warn('[NDK] Warning: Could not fetch user profile:', profileError);
|
||||
// Continue even if profile fetch fails
|
||||
}
|
||||
|
||||
// Process profile data to ensure image property is set
|
||||
if (user.profile) {
|
||||
console.log('[NDK] Profile data available');
|
||||
if (!user.profile.image && (user.profile as any).picture) {
|
||||
user.profile.image = (user.profile as any).picture;
|
||||
console.log('[NDK] Set image from picture property');
|
||||
}
|
||||
|
||||
console.log('[NDK] User profile loaded:', user.profile);
|
||||
console.log('[NDK] User profile loaded:',
|
||||
user.profile.name || user.profile.displayName || 'No name available');
|
||||
} else {
|
||||
console.log('[NDK] No profile data available');
|
||||
}
|
||||
|
||||
// Save the private key hex string securely
|
||||
console.log('[NDK] Saving private key to secure storage');
|
||||
await SecureStore.setItemAsync(PRIVATE_KEY_STORAGE_KEY, privateKeyHex);
|
||||
|
||||
// After successful login, import user relay preferences
|
||||
try {
|
||||
console.log('[NDK] Login successful, importing user relay preferences');
|
||||
const db = openDatabaseSync('powr.db');
|
||||
const relayService = new RelayService(db);
|
||||
console.log('[NDK] Creating RelayService to import user preferences');
|
||||
const relayService = new RelayService();
|
||||
|
||||
// Set NDK on the relay service
|
||||
relayService.setNDK(ndk as any);
|
||||
console.log('[NDK] Setting NDK on RelayService');
|
||||
relayService.setNDK(ndk as any); // Using type assertion
|
||||
|
||||
// Import and apply user relay preferences
|
||||
await relayService.importUserRelaysOnLogin(user, ndk);
|
||||
// Import user relay preferences from metadata (kind:3 events)
|
||||
if (user.pubkey) {
|
||||
console.log('[NDK] Importing relay metadata for user:', user.pubkey.substring(0, 8) + '...');
|
||||
try {
|
||||
await relayService.importFromUserMetadata(user.pubkey, ndk);
|
||||
console.log('[NDK] Successfully imported user relay preferences');
|
||||
} catch (importError) {
|
||||
console.warn('[NDK] Could not import user relay preferences:', importError);
|
||||
// Continue even if import fails
|
||||
}
|
||||
} else {
|
||||
console.log('[NDK] Cannot import relay metadata: No pubkey available');
|
||||
}
|
||||
} catch (relayError) {
|
||||
console.error('[NDK] Error importing user relay preferences:', relayError);
|
||||
console.error('[NDK] Error with RelayService:', relayError);
|
||||
// Continue with login even if relay import fails
|
||||
}
|
||||
|
||||
console.log('[NDK] Login successful, updating state');
|
||||
set({
|
||||
currentUser: user,
|
||||
isAuthenticated: true,
|
||||
isLoading: false
|
||||
});
|
||||
|
||||
console.log('[NDK] Login complete');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[NDK] Login error:', error);
|
||||
console.error('[NDK] Login error detailed:', error);
|
||||
set({
|
||||
error: error instanceof Error ? error : new Error('Failed to login'),
|
||||
isLoading: false
|
||||
|
@ -1,6 +1,5 @@
|
||||
// lib/stores/relayStore.ts
|
||||
import { create } from 'zustand';
|
||||
import { openDatabaseSync } from 'expo-sqlite';
|
||||
import type { RelayWithStatus } from '@/lib/db/services/RelayService';
|
||||
import { RelayService } from '@/lib/db/services/RelayService';
|
||||
import { useNDKStore } from './ndk';
|
||||
@ -11,8 +10,7 @@ let relayServiceInstance: RelayService | null = null;
|
||||
|
||||
const getRelayService = (): RelayService => {
|
||||
if (!relayServiceInstance) {
|
||||
const db = openDatabaseSync('powr.db');
|
||||
relayServiceInstance = new RelayService(db);
|
||||
relayServiceInstance = new RelayService();
|
||||
console.log('[RelayStore] Created RelayService instance');
|
||||
}
|
||||
return relayServiceInstance;
|
||||
@ -60,7 +58,8 @@ export const useRelayStore = create<RelayStoreState & RelayStoreActions>((set, g
|
||||
const ndk = ndkState.ndk as unknown as NDKCommon;
|
||||
|
||||
if (ndk) {
|
||||
relayService.setNDK(ndk);
|
||||
// Use type assertion to ensure compatibility
|
||||
relayService.setNDK(ndk as any);
|
||||
}
|
||||
|
||||
const relays = await relayService.getAllRelaysWithStatus();
|
||||
@ -159,7 +158,8 @@ export const useRelayStore = create<RelayStoreState & RelayStoreActions>((set, g
|
||||
}
|
||||
|
||||
// Apply relay config changes to NDK
|
||||
const success = await relayService.applyRelayConfig(ndk);
|
||||
const relays = get().relays;
|
||||
const success = await relayService.applyRelayConfig(relays);
|
||||
|
||||
// Wait a moment before reloading to give connections time to establish
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
Loading…
x
Reference in New Issue
Block a user