mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-23 01:01:27 +00:00
update to add new exercise UI and database testing
This commit is contained in:
parent
90ea708e9b
commit
76433b93e6
14
CHANGELOG.md
14
CHANGELOG.md
@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Alphabetical quick scroll in exercise library
|
||||||
|
- Dynamic letter highlighting for available sections
|
||||||
|
- Smooth scrolling to selected sections
|
||||||
|
- Sticky section headers for better navigation
|
||||||
- Basic exercise template creation functionality
|
- Basic exercise template creation functionality
|
||||||
- Input validation for required fields
|
- Input validation for required fields
|
||||||
- Schema-compliant field constraints
|
- Schema-compliant field constraints
|
||||||
@ -18,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added proper error types and propagation
|
- Added proper error types and propagation
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Improved exercise library interface
|
||||||
|
- Removed "Recent Exercises" section for cleaner UI
|
||||||
|
- Added alphabetical section organization
|
||||||
|
- Enhanced keyboard handling for input fields
|
||||||
|
- Increased description text area size
|
||||||
- Updated NewExerciseScreen with constrained inputs
|
- Updated NewExerciseScreen with constrained inputs
|
||||||
- Added dropdowns for equipment selection
|
- Added dropdowns for equipment selection
|
||||||
- Added movement pattern selection
|
- Added movement pattern selection
|
||||||
@ -28,6 +37,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Enhanced transaction rollback handling
|
- Enhanced transaction rollback handling
|
||||||
- Added detailed debug logging
|
- Added detailed debug logging
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Exercise deletion functionality
|
||||||
|
- Keyboard overlap issues in exercise creation form
|
||||||
|
- SQLite transaction handling for exercise operations
|
||||||
|
|
||||||
### Technical Details
|
### Technical Details
|
||||||
1. Database Schema Enforcement:
|
1. Database Schema Enforcement:
|
||||||
- Added CHECK constraints for equipment types
|
- Added CHECK constraints for equipment types
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// app/(tabs)/library/exercises.tsx
|
// app/(tabs)/library/exercises.tsx
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { View, ScrollView, SectionList } from 'react-native';
|
import { View, SectionList, TouchableOpacity, SectionListData } from 'react-native';
|
||||||
import { Text } from '@/components/ui/text';
|
import { Text } from '@/components/ui/text';
|
||||||
import { ExerciseCard } from '@/components/exercises/ExerciseCard';
|
import { ExerciseCard } from '@/components/exercises/ExerciseCard';
|
||||||
import { FloatingActionButton } from '@/components/shared/FloatingActionButton';
|
import { FloatingActionButton } from '@/components/shared/FloatingActionButton';
|
||||||
@ -10,17 +10,46 @@ import { Exercise, BaseExercise } from '@/types/exercise';
|
|||||||
import { useSQLiteContext } from 'expo-sqlite';
|
import { useSQLiteContext } from 'expo-sqlite';
|
||||||
import { ExerciseService } from '@/lib/db/services/ExerciseService';
|
import { ExerciseService } from '@/lib/db/services/ExerciseService';
|
||||||
|
|
||||||
|
interface ExerciseSection {
|
||||||
|
title: string;
|
||||||
|
data: Exercise[];
|
||||||
|
}
|
||||||
|
|
||||||
export default function ExercisesScreen() {
|
export default function ExercisesScreen() {
|
||||||
const db = useSQLiteContext();
|
const db = useSQLiteContext();
|
||||||
const exerciseService = React.useMemo(() => new ExerciseService(db), [db]);
|
const exerciseService = React.useMemo(() => new ExerciseService(db), [db]);
|
||||||
|
const sectionListRef = useRef<SectionList>(null);
|
||||||
|
|
||||||
const [exercises, setExercises] = useState<Exercise[]>([]);
|
const [exercises, setExercises] = useState<Exercise[]>([]);
|
||||||
|
const [sections, setSections] = useState<ExerciseSection[]>([]);
|
||||||
const [showNewExercise, setShowNewExercise] = useState(false);
|
const [showNewExercise, setShowNewExercise] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadExercises();
|
loadExercises();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Organize exercises into sections when exercises array changes
|
||||||
|
const exercisesByLetter = exercises.reduce((acc, exercise) => {
|
||||||
|
const firstLetter = exercise.title[0].toUpperCase();
|
||||||
|
if (!acc[firstLetter]) {
|
||||||
|
acc[firstLetter] = [];
|
||||||
|
}
|
||||||
|
acc[firstLetter].push(exercise);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, Exercise[]>);
|
||||||
|
|
||||||
|
// Create sections array sorted alphabetically
|
||||||
|
const newSections = Object.entries(exercisesByLetter)
|
||||||
|
.map(([letter, exercises]) => ({
|
||||||
|
title: letter,
|
||||||
|
data: exercises.sort((a, b) => a.title.localeCompare(b.title))
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
|
||||||
|
setSections(newSections);
|
||||||
|
}, [exercises]);
|
||||||
|
|
||||||
const loadExercises = async () => {
|
const loadExercises = async () => {
|
||||||
try {
|
try {
|
||||||
const loadedExercises = await exerciseService.getAllExercises();
|
const loadedExercises = await exerciseService.getAllExercises();
|
||||||
@ -57,40 +86,64 @@ export default function ExercisesScreen() {
|
|||||||
console.log('Selected exercise:', exerciseId);
|
console.log('Selected exercise:', exerciseId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollToSection = (letter: string) => {
|
||||||
|
const sectionIndex = sections.findIndex(section => section.title === letter);
|
||||||
|
if (sectionIndex !== -1) {
|
||||||
|
sectionListRef.current?.scrollToLocation({
|
||||||
|
sectionIndex,
|
||||||
|
itemIndex: 0,
|
||||||
|
animated: true,
|
||||||
|
viewOffset: 20
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||||
|
const availableLetters = new Set(sections.map(section => section.title));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 bg-background">
|
<View className="flex-1 bg-background">
|
||||||
<View className="absolute right-0 top-0 bottom-0 w-6 z-10 justify-center bg-transparent">
|
<View className="absolute right-0 top-0 bottom-0 w-6 z-10 justify-center bg-transparent">
|
||||||
{alphabet.map((letter) => (
|
{alphabet.map((letter) => (
|
||||||
<Text
|
<TouchableOpacity
|
||||||
key={letter}
|
key={letter}
|
||||||
className="text-xs text-muted-foreground text-center"
|
onPress={() => scrollToSection(letter)}
|
||||||
onPress={() => {
|
className="py-0.5"
|
||||||
// TODO: Implement scroll to section
|
>
|
||||||
console.log('Scroll to:', letter);
|
<Text
|
||||||
}}
|
className={`text-xs text-center ${
|
||||||
|
availableLetters.has(letter)
|
||||||
|
? 'text-primary font-medium'
|
||||||
|
: 'text-muted-foreground'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{letter}
|
{letter}
|
||||||
</Text>
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView className="flex-1 mr-6">
|
<SectionList
|
||||||
<View className="py-4">
|
ref={sectionListRef}
|
||||||
<Text className="text-lg font-semibold mb-4 px-4">All Exercises</Text>
|
sections={sections}
|
||||||
<View className="gap-3">
|
keyExtractor={(item) => item.id}
|
||||||
{exercises.map(exercise => (
|
renderSectionHeader={({ section }) => (
|
||||||
|
<View className="py-2 px-4 bg-background/80">
|
||||||
|
<Text className="text-lg font-semibold text-foreground">{section.title}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<View className="px-4 py-1">
|
||||||
<ExerciseCard
|
<ExerciseCard
|
||||||
key={exercise.id}
|
{...item}
|
||||||
{...exercise}
|
onPress={() => handleExercisePress(item.id)}
|
||||||
onPress={() => handleExercisePress(exercise.id)}
|
onDelete={() => handleDelete(item.id)}
|
||||||
onDelete={() => handleDelete(exercise.id)}
|
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
)}
|
||||||
</ScrollView>
|
stickySectionHeadersEnabled
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
icon={Dumbbell}
|
icon={Dumbbell}
|
||||||
|
@ -4,16 +4,22 @@ import { View, ScrollView } from 'react-native';
|
|||||||
import { Text } from '@/components/ui/text';
|
import { Text } from '@/components/ui/text';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { AlertCircle, CheckCircle2, Database, RefreshCcw, Trash2 } from 'lucide-react-native';
|
import { AlertCircle, CheckCircle2, Database, RefreshCcw, Trash2, Code } from 'lucide-react-native';
|
||||||
import { useSQLiteContext } from 'expo-sqlite';
|
import { useSQLiteContext } from 'expo-sqlite';
|
||||||
import { ExerciseType, ExerciseCategory, Equipment } from '@/types/exercise';
|
import { ExerciseType, ExerciseCategory, Equipment } from '@/types/exercise';
|
||||||
import { SQLTransaction, SQLResultSet, SQLError } from '@/lib/db/types';
|
import { SQLTransaction, SQLResultSet, SQLError } from '@/lib/db/types';
|
||||||
import { schema } from '@/lib/db/schema';
|
import { schema } from '@/lib/db/schema';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
interface TableInfo {
|
interface TableInfo {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TableSchema {
|
||||||
|
name: string;
|
||||||
|
sql: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface SchemaVersion {
|
interface SchemaVersion {
|
||||||
version: number;
|
version: number;
|
||||||
}
|
}
|
||||||
@ -41,7 +47,7 @@ export default function ProgramsScreen() {
|
|||||||
initialized: false,
|
initialized: false,
|
||||||
tables: [],
|
tables: [],
|
||||||
});
|
});
|
||||||
|
const [schemas, setSchemas] = useState<TableSchema[]>([]);
|
||||||
const [testResults, setTestResults] = useState<{
|
const [testResults, setTestResults] = useState<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
@ -49,8 +55,20 @@ export default function ProgramsScreen() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkDatabase();
|
checkDatabase();
|
||||||
|
inspectDatabase();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const inspectDatabase = async () => {
|
||||||
|
try {
|
||||||
|
const result = await db.getAllAsync<TableSchema>(
|
||||||
|
"SELECT name, sql FROM sqlite_master WHERE type='table'"
|
||||||
|
);
|
||||||
|
setSchemas(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error inspecting database:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const checkDatabase = async () => {
|
const checkDatabase = async () => {
|
||||||
try {
|
try {
|
||||||
// Check schema_version table
|
// Check schema_version table
|
||||||
@ -99,6 +117,7 @@ export default function ProgramsScreen() {
|
|||||||
|
|
||||||
// Refresh database status
|
// Refresh database status
|
||||||
checkDatabase();
|
checkDatabase();
|
||||||
|
inspectDatabase();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error resetting database:', error);
|
console.error('Error resetting database:', error);
|
||||||
setTestResults({
|
setTestResults({
|
||||||
@ -186,6 +205,35 @@ export default function ProgramsScreen() {
|
|||||||
<View className="py-4 space-y-4">
|
<View className="py-4 space-y-4">
|
||||||
<Text className="text-lg font-semibold text-center mb-4">Database Debug Panel</Text>
|
<Text className="text-lg font-semibold text-center mb-4">Database Debug Panel</Text>
|
||||||
|
|
||||||
|
{/* Schema Inspector Card */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex-row items-center gap-2">
|
||||||
|
<Code size={20} className="text-foreground" />
|
||||||
|
<Text className="text-lg font-semibold">Database Schema ({Platform.OS})</Text>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<View className="space-y-4">
|
||||||
|
{schemas.map((table) => (
|
||||||
|
<View key={table.name} className="space-y-2">
|
||||||
|
<Text className="font-semibold">{table.name}</Text>
|
||||||
|
<Text className="text-muted-foreground text-sm">
|
||||||
|
{table.sql}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<Button
|
||||||
|
className="mt-4"
|
||||||
|
onPress={inspectDatabase}
|
||||||
|
>
|
||||||
|
<Text className="text-primary-foreground">Refresh Schema</Text>
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Status Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex-row items-center gap-2">
|
<CardTitle className="flex-row items-center gap-2">
|
||||||
@ -215,6 +263,7 @@ export default function ProgramsScreen() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Operations Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex-row items-center gap-2">
|
<CardTitle className="flex-row items-center gap-2">
|
||||||
|
@ -150,7 +150,7 @@ export default function DatabaseDebug() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="p-4 mb-4">
|
<View className="p-4 mb-4">
|
||||||
<Card>
|
<Card className="mt-4"> {/* Add mt-4 to create spacing */}
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<Text className="text-xl font-semibold">Database Status</Text>
|
<Text className="text-xl font-semibold">Database Status</Text>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
// components/library/NewExerciseSheet.tsx
|
// components/library/NewExerciseSheet.tsx
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
||||||
import { Text } from '@/components/ui/text';
|
import { Text } from '@/components/ui/text';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
||||||
import { generateId } from '@/utils/ids';
|
|
||||||
import { BaseExercise, ExerciseType, ExerciseCategory, Equipment } from '@/types/exercise';
|
import { BaseExercise, ExerciseType, ExerciseCategory, Equipment } from '@/types/exercise';
|
||||||
import { StorageSource } from '@/types/shared';
|
import { StorageSource } from '@/types/shared';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { generateId } from '@/utils/ids';
|
||||||
|
|
||||||
interface NewExerciseSheetProps {
|
interface NewExerciseSheetProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -93,12 +94,16 @@ export function NewExerciseSheet({ isOpen, onClose, onSubmit }: NewExerciseSheet
|
|||||||
<SheetTitle>New Exercise</SheetTitle>
|
<SheetTitle>New Exercise</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<SheetContent>
|
<SheetContent>
|
||||||
<View className="gap-4">
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
<ScrollView className="gap-4">
|
||||||
<View>
|
<View>
|
||||||
<Text className="text-base font-medium mb-2">Exercise Name</Text>
|
<Text className="text-base font-medium mb-2">Exercise Name</Text>
|
||||||
<Input
|
<Input
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
onChangeText={(text) => setFormData(prev => ({ ...prev, title: text }))}
|
onChangeText={(text: string) => setFormData(prev => ({ ...prev, title: text }))}
|
||||||
placeholder="e.g., Barbell Back Squat"
|
placeholder="e.g., Barbell Back Squat"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@ -156,12 +161,13 @@ export function NewExerciseSheet({ isOpen, onClose, onSubmit }: NewExerciseSheet
|
|||||||
|
|
||||||
<View>
|
<View>
|
||||||
<Text className="text-base font-medium mb-2">Description</Text>
|
<Text className="text-base font-medium mb-2">Description</Text>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChangeText={(text) => setFormData(prev => ({ ...prev, description: text }))}
|
onChangeText={(text: string) => setFormData(prev => ({ ...prev, description: text }))}
|
||||||
placeholder="Exercise description..."
|
placeholder="Exercise description..."
|
||||||
multiline
|
numberOfLines={6}
|
||||||
numberOfLines={4}
|
className="min-h-[120px]"
|
||||||
|
style={{ maxHeight: 200 }}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -173,7 +179,8 @@ export function NewExerciseSheet({ isOpen, onClose, onSubmit }: NewExerciseSheet
|
|||||||
>
|
>
|
||||||
<Text className="text-white font-semibold">Create Exercise</Text>
|
<Text className="text-white font-semibold">Create Exercise</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</ScrollView>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
|
@ -1,22 +1,38 @@
|
|||||||
// lib/db/schema.ts
|
// lib/db/schema.ts
|
||||||
import { SQLiteDatabase } from 'expo-sqlite';
|
import { SQLiteDatabase } from 'expo-sqlite';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
export const SCHEMA_VERSION = 2;
|
export const SCHEMA_VERSION = 2;
|
||||||
|
|
||||||
class Schema {
|
class Schema {
|
||||||
private async getCurrentVersion(db: SQLiteDatabase): Promise<number> {
|
private async getCurrentVersion(db: SQLiteDatabase): Promise<number> {
|
||||||
try {
|
try {
|
||||||
|
// First check if the table exists
|
||||||
|
const tableExists = await db.getFirstAsync<{ count: number }>(
|
||||||
|
`SELECT count(*) as count FROM sqlite_master
|
||||||
|
WHERE type='table' AND name='schema_version'`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!tableExists || tableExists.count === 0) {
|
||||||
|
console.log('[Schema] No schema_version table found');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const version = await db.getFirstAsync<{ version: number }>(
|
const version = await db.getFirstAsync<{ version: number }>(
|
||||||
'SELECT version FROM schema_version ORDER BY version DESC LIMIT 1'
|
'SELECT version FROM schema_version ORDER BY version DESC LIMIT 1'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`[Schema] Current version: ${version?.version ?? 0}`);
|
||||||
return version?.version ?? 0;
|
return version?.version ?? 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log('[Schema] Error getting version:', error);
|
||||||
return 0; // If table doesn't exist yet
|
return 0; // If table doesn't exist yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTables(db: SQLiteDatabase): Promise<void> {
|
async createTables(db: SQLiteDatabase): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
console.log(`[Schema] Initializing database on ${Platform.OS}`);
|
||||||
const currentVersion = await this.getCurrentVersion(db);
|
const currentVersion = await this.getCurrentVersion(db);
|
||||||
|
|
||||||
// Schema version tracking
|
// Schema version tracking
|
||||||
@ -28,6 +44,8 @@ class Schema {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
if (currentVersion === 0) {
|
if (currentVersion === 0) {
|
||||||
|
console.log('[Schema] Performing fresh install');
|
||||||
|
|
||||||
// Drop existing tables if they exist
|
// Drop existing tables if they exist
|
||||||
await db.execAsync(`DROP TABLE IF EXISTS exercise_tags`);
|
await db.execAsync(`DROP TABLE IF EXISTS exercise_tags`);
|
||||||
await db.execAsync(`DROP TABLE IF EXISTS exercises`);
|
await db.execAsync(`DROP TABLE IF EXISTS exercises`);
|
||||||
@ -66,10 +84,14 @@ class Schema {
|
|||||||
'INSERT INTO schema_version (version, updated_at) VALUES (?, ?)',
|
'INSERT INTO schema_version (version, updated_at) VALUES (?, ?)',
|
||||||
[1, Date.now()]
|
[1, Date.now()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('[Schema] Base tables created successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update to version 2 if needed
|
// Update to version 2 if needed
|
||||||
if (currentVersion < 2) {
|
if (currentVersion < 2) {
|
||||||
|
console.log('[Schema] Upgrading to version 2');
|
||||||
|
|
||||||
await db.execAsync(`
|
await db.execAsync(`
|
||||||
CREATE TABLE IF NOT EXISTS nostr_events (
|
CREATE TABLE IF NOT EXISTS nostr_events (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
@ -98,15 +120,22 @@ class Schema {
|
|||||||
try {
|
try {
|
||||||
await db.execAsync(`ALTER TABLE exercises ADD COLUMN nostr_event_id TEXT REFERENCES nostr_events(id)`);
|
await db.execAsync(`ALTER TABLE exercises ADD COLUMN nostr_event_id TEXT REFERENCES nostr_events(id)`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Column might already exist
|
console.log('[Schema] Note: nostr_event_id column may already exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.runAsync(
|
await db.runAsync(
|
||||||
'INSERT INTO schema_version (version, updated_at) VALUES (?, ?)',
|
'INSERT INTO schema_version (version, updated_at) VALUES (?, ?)',
|
||||||
[2, Date.now()]
|
[2, Date.now()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('[Schema] Version 2 upgrade completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify final schema
|
||||||
|
const tables = await db.getAllAsync<{ name: string }>(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table'"
|
||||||
|
);
|
||||||
|
console.log('[Schema] Final tables:', tables.map(t => t.name).join(', '));
|
||||||
console.log(`[Schema] Database initialized at version ${await this.getCurrentVersion(db)}`);
|
console.log(`[Schema] Database initialized at version ${await this.getCurrentVersion(db)}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Schema] Error creating tables:', error);
|
console.error('[Schema] Error creating tables:', error);
|
||||||
|
24
package-lock.json
generated
24
package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/cli": "^0.22.16",
|
"@expo/cli": "^0.22.16",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||||
|
"@react-native-clipboard/clipboard": "^1.16.1",
|
||||||
"@react-navigation/material-top-tabs": "^7.1.0",
|
"@react-navigation/material-top-tabs": "^7.1.0",
|
||||||
"@react-navigation/native": "^7.0.0",
|
"@react-navigation/native": "^7.0.0",
|
||||||
"@rn-primitives/accordion": "^1.1.0",
|
"@rn-primitives/accordion": "^1.1.0",
|
||||||
@ -4258,6 +4259,29 @@
|
|||||||
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
|
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-clipboard/clipboard": {
|
||||||
|
"version": "1.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.16.1.tgz",
|
||||||
|
"integrity": "sha512-YdSwSS3P4IiJq5nW0iv3qpntDAzBf/xoew2zRPGJ6SJZr/Lhk4aWyR506EWl6BID+iQy7xQmzHXZYR5H4apM6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"example"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.9.0",
|
||||||
|
"react-native": ">= 0.61.5",
|
||||||
|
"react-native-macos": ">= 0.61.0",
|
||||||
|
"react-native-windows": ">= 0.61.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-native-macos": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native-windows": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native/assets-registry": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.76.7",
|
"version": "0.76.7",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.7.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.7.tgz",
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/cli": "^0.22.16",
|
"@expo/cli": "^0.22.16",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||||
|
"@react-native-clipboard/clipboard": "^1.16.1",
|
||||||
"@react-navigation/material-top-tabs": "^7.1.0",
|
"@react-navigation/material-top-tabs": "^7.1.0",
|
||||||
"@react-navigation/native": "^7.0.0",
|
"@react-navigation/native": "^7.0.0",
|
||||||
"@rn-primitives/accordion": "^1.1.0",
|
"@rn-primitives/accordion": "^1.1.0",
|
||||||
@ -57,6 +58,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"expo": "^52.0.35",
|
"expo": "^52.0.35",
|
||||||
|
"expo-file-system": "~18.0.10",
|
||||||
"expo-linking": "~7.0.4",
|
"expo-linking": "~7.0.4",
|
||||||
"expo-navigation-bar": "~4.0.8",
|
"expo-navigation-bar": "~4.0.8",
|
||||||
"expo-router": "~4.0.16",
|
"expo-router": "~4.0.16",
|
||||||
@ -82,8 +84,7 @@
|
|||||||
"tailwind-merge": "^2.2.1",
|
"tailwind-merge": "^2.2.1",
|
||||||
"tailwindcss": "3.3.5",
|
"tailwindcss": "3.3.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zustand": "^4.4.7",
|
"zustand": "^4.4.7"
|
||||||
"expo-file-system": "~18.0.10"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user