mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-23 01:01:27 +00:00
Fix Android database initialization issue with platform-specific approach
This commit is contained in:
parent
ff8851bd04
commit
3e4f304f56
14
CHANGELOG.md
14
CHANGELOG.md
@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Documentation
|
||||||
|
- Added comprehensive React Query integration plan to address authentication state transitions and hook ordering issues
|
||||||
|
- Created detailed technical documentation for integrating React Query with SQLite, NDK, and Amber signer
|
||||||
|
- Added detailed conflict resolution strategies for local-first Nostr app
|
||||||
|
- Implemented enhanced error handling patterns for React Query
|
||||||
|
- Developed executive summary for stakeholder review
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Android database initialization error (NullPointerException) by:
|
||||||
|
- Creating a platform-specific database initialization path for Android
|
||||||
|
- Implementing resilient error handling with step-by-step table creation
|
||||||
|
- Simplifying SQL statements for better Android compatibility
|
||||||
|
- Replacing dynamic imports with static imports
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Centralized Authentication System with Advanced Security
|
- Centralized Authentication System with Advanced Security
|
||||||
- Implemented new AuthService for unified authentication management
|
- Implemented new AuthService for unified authentication management
|
||||||
|
303
lib/db/schema.ts
303
lib/db/schema.ts
@ -1,6 +1,8 @@
|
|||||||
// lib/db/schema.ts
|
// lib/db/schema.ts
|
||||||
import { SQLiteDatabase } from 'expo-sqlite';
|
import { SQLiteDatabase } from 'expo-sqlite';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
|
// Import the migration functions directly to avoid dynamic imports that can fail on Android
|
||||||
|
import { addNostrFieldsToWorkouts, createNostrWorkoutsTable } from './migrations/add-nostr-fields-to-workouts';
|
||||||
|
|
||||||
export const SCHEMA_VERSION = 12;
|
export const SCHEMA_VERSION = 12;
|
||||||
|
|
||||||
@ -132,10 +134,7 @@ class Schema {
|
|||||||
try {
|
try {
|
||||||
console.log('[Schema] Running migration v11 - Adding Nostr fields to workouts');
|
console.log('[Schema] Running migration v11 - Adding Nostr fields to workouts');
|
||||||
|
|
||||||
// Import the migration functions
|
// Run the migrations using the directly imported functions
|
||||||
const { addNostrFieldsToWorkouts, createNostrWorkoutsTable } = await import('./migrations/add-nostr-fields-to-workouts');
|
|
||||||
|
|
||||||
// Run the migrations
|
|
||||||
await addNostrFieldsToWorkouts(db);
|
await addNostrFieldsToWorkouts(db);
|
||||||
await createNostrWorkoutsTable(db);
|
await createNostrWorkoutsTable(db);
|
||||||
|
|
||||||
@ -194,10 +193,306 @@ class Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method specifically for Android database initialization
|
||||||
|
async createTablesAndroid(db: SQLiteDatabase): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('[Schema] Using Android-specific database initialization');
|
||||||
|
|
||||||
|
// Create schema_version table separately without transaction
|
||||||
|
try {
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
|
version INTEGER PRIMARY KEY,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
console.log('[Schema] Schema_version table created successfully on Android');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Error creating schema_version table on Android:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current version
|
||||||
|
const currentVersion = await this.getCurrentVersion(db);
|
||||||
|
console.log(`[Schema] Android - Current version: ${currentVersion}, Target version: ${SCHEMA_VERSION}`);
|
||||||
|
|
||||||
|
// If already at current version, just check for missing tables
|
||||||
|
if (currentVersion === SCHEMA_VERSION) {
|
||||||
|
console.log(`[Schema] Android - Database already at version ${SCHEMA_VERSION}, checking for missing tables`);
|
||||||
|
await this.ensureCriticalTablesExist(db);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Android, we'll create tables one by one without a transaction
|
||||||
|
|
||||||
|
// 1. Drop all tables except schema_version (if needed)
|
||||||
|
await this.dropAllTables(db);
|
||||||
|
|
||||||
|
// 2. Create tables one by one - simplifying CHECK constraints for Android
|
||||||
|
try {
|
||||||
|
// Create exercises table - removed CHECK constraints for Android
|
||||||
|
console.log('[Schema] Android - Creating exercises table...');
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS exercises (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
equipment TEXT,
|
||||||
|
description TEXT,
|
||||||
|
format_json TEXT,
|
||||||
|
format_units_json TEXT,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
source TEXT NOT NULL DEFAULT 'local',
|
||||||
|
nostr_event_id TEXT
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
console.log('[Schema] Android - Exercises table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating exercises table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create exercise_tags table
|
||||||
|
console.log('[Schema] Android - Creating exercise_tags table...');
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS exercise_tags (
|
||||||
|
exercise_id TEXT NOT NULL,
|
||||||
|
tag TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(exercise_id) REFERENCES exercises(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(exercise_id, tag)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execAsync(`CREATE INDEX IF NOT EXISTS idx_exercise_tags ON exercise_tags(tag);`);
|
||||||
|
console.log('[Schema] Android - Exercise_tags table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating exercise_tags table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create nostr_events table
|
||||||
|
console.log('[Schema] Android - Creating nostr_events table...');
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS nostr_events (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
pubkey TEXT NOT NULL,
|
||||||
|
kind INTEGER NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
sig TEXT,
|
||||||
|
raw_event TEXT NOT NULL,
|
||||||
|
received_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
console.log('[Schema] Android - Nostr_events table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating nostr_events table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create event_tags table
|
||||||
|
console.log('[Schema] Android - Creating event_tags table...');
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS event_tags (
|
||||||
|
event_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
index_num INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(event_id) REFERENCES nostr_events(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execAsync(`CREATE INDEX IF NOT EXISTS idx_event_tags ON event_tags(name, value);`);
|
||||||
|
console.log('[Schema] Android - Event_tags table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating event_tags table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create templates table
|
||||||
|
console.log('[Schema] Android - Creating 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,
|
||||||
|
is_archived BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
author_pubkey TEXT
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execAsync(`CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates(updated_at);`);
|
||||||
|
console.log('[Schema] Android - Templates table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating templates table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create template_exercises table
|
||||||
|
console.log('[Schema] Android - Creating 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
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execAsync(`CREATE INDEX IF NOT EXISTS idx_template_exercises_template_id ON template_exercises(template_id);`);
|
||||||
|
console.log('[Schema] Android - Template_exercises table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating template_exercises table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create powr_packs table
|
||||||
|
console.log('[Schema] Android - Creating powr_packs table...');
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS powr_packs (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
author_pubkey TEXT,
|
||||||
|
nostr_event_id TEXT,
|
||||||
|
import_date INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execAsync(`CREATE INDEX IF NOT EXISTS idx_powr_packs_import_date ON powr_packs(import_date DESC);`);
|
||||||
|
console.log('[Schema] Android - Powr_packs table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating powr_packs table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create powr_pack_items table - removed CHECK constraint for Android
|
||||||
|
console.log('[Schema] Android - Creating powr_pack_items table...');
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS powr_pack_items (
|
||||||
|
pack_id TEXT NOT NULL,
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
item_type TEXT NOT NULL,
|
||||||
|
item_order INTEGER,
|
||||||
|
is_imported BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
nostr_event_id TEXT,
|
||||||
|
PRIMARY KEY (pack_id, item_id),
|
||||||
|
FOREIGN KEY (pack_id) REFERENCES powr_packs(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execAsync(`CREATE INDEX IF NOT EXISTS idx_powr_pack_items_type ON powr_pack_items(item_type);`);
|
||||||
|
console.log('[Schema] Android - Powr_pack_items table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating powr_pack_items table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create favorites table
|
||||||
|
console.log('[Schema] Android - Creating favorites table...');
|
||||||
|
await db.execAsync(`
|
||||||
|
CREATE TABLE IF NOT EXISTS favorites (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
content_type TEXT NOT NULL,
|
||||||
|
content_id TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
pubkey TEXT,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
UNIQUE(content_type, content_id)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execAsync(`CREATE INDEX IF NOT EXISTS idx_favorites_content ON favorites(content_type, content_id);`);
|
||||||
|
console.log('[Schema] Android - Favorites table created successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error creating favorites table:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run migrations one by one
|
||||||
|
if (currentVersion < 8) {
|
||||||
|
try {
|
||||||
|
await this.migrate_v8(db);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error in migrate_v8:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVersion < 9) {
|
||||||
|
try {
|
||||||
|
await this.migrate_v9(db);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error in migrate_v9:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVersion < 10) {
|
||||||
|
try {
|
||||||
|
await this.migrate_v10(db);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error in migrate_v10:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVersion < 11) {
|
||||||
|
try {
|
||||||
|
await this.migrate_v11(db);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error in migrate_v11:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVersion < 12) {
|
||||||
|
try {
|
||||||
|
await this.migrate_v12(db);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error in migrate_v12:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update schema version
|
||||||
|
try {
|
||||||
|
await this.updateSchemaVersion(db);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error updating schema version:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, make sure critical tables exist
|
||||||
|
await this.ensureCriticalTablesExist(db);
|
||||||
|
|
||||||
|
console.log('[Schema] Android - Database initialization completed');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Schema] Android - Error initializing database:', error);
|
||||||
|
// Don't throw - try to continue with partial initialization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createTables(db: SQLiteDatabase): Promise<void> {
|
async createTables(db: SQLiteDatabase): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log(`[Schema] Initializing database on ${Platform.OS}`);
|
console.log(`[Schema] Initializing database on ${Platform.OS}`);
|
||||||
|
|
||||||
|
// For Android, use a specialized initialization method
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
await this.createTablesAndroid(db);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS and other platforms - use the original implementation
|
||||||
|
|
||||||
// First, ensure schema_version table exists since we need it for version checking
|
// First, ensure schema_version table exists since we need it for version checking
|
||||||
await db.execAsync(`
|
await db.execAsync(`
|
||||||
CREATE TABLE IF NOT EXISTS schema_version (
|
CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
|
9
package-lock.json
generated
9
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/cli": "^0.22.16",
|
"@expo/cli": "^0.22.16",
|
||||||
|
"@expo/config-plugins": "^9.0.17",
|
||||||
"@noble/hashes": "^1.7.1",
|
"@noble/hashes": "^1.7.1",
|
||||||
"@noble/secp256k1": "^2.2.3",
|
"@noble/secp256k1": "^2.2.3",
|
||||||
"@nostr-dev-kit/ndk": "^2.12.0",
|
"@nostr-dev-kit/ndk": "^2.12.0",
|
||||||
@ -60,7 +61,7 @@
|
|||||||
"expo-router": "~4.0.19",
|
"expo-router": "~4.0.19",
|
||||||
"expo-secure-store": "~14.0.1",
|
"expo-secure-store": "~14.0.1",
|
||||||
"expo-splash-screen": "~0.29.22",
|
"expo-splash-screen": "~0.29.22",
|
||||||
"expo-sqlite": "~15.1.3",
|
"expo-sqlite": "~15.1.4",
|
||||||
"expo-status-bar": "~2.0.1",
|
"expo-status-bar": "~2.0.1",
|
||||||
"expo-system-ui": "~4.0.9",
|
"expo-system-ui": "~4.0.9",
|
||||||
"jest": "~29.7.0",
|
"jest": "~29.7.0",
|
||||||
@ -12388,9 +12389,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/expo-sqlite": {
|
"node_modules/expo-sqlite": {
|
||||||
"version": "15.1.3",
|
"version": "15.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-15.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-15.1.4.tgz",
|
||||||
"integrity": "sha512-YyxU4rBfSo+aLKBbRjlw4SoAkLLbUPpB2XLq+JMwIZrTdVFwr+CvtyNLsC9omevsLXBODXhVkX0Rk3gASag2eg==",
|
"integrity": "sha512-1SG5Qi6/L2SK/o5EKtvEmlMVGdra/wYYh/intI94/ovsUfZGFrDG31YGtTt4rLpE95M6FwHUVpALO8g9/G9B2Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"expo": "*",
|
"expo": "*",
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/cli": "^0.22.16",
|
"@expo/cli": "^0.22.16",
|
||||||
|
"@expo/config-plugins": "^9.0.17",
|
||||||
"@noble/hashes": "^1.7.1",
|
"@noble/hashes": "^1.7.1",
|
||||||
"@noble/secp256k1": "^2.2.3",
|
"@noble/secp256k1": "^2.2.3",
|
||||||
"@nostr-dev-kit/ndk": "^2.12.0",
|
"@nostr-dev-kit/ndk": "^2.12.0",
|
||||||
@ -74,7 +75,7 @@
|
|||||||
"expo-router": "~4.0.19",
|
"expo-router": "~4.0.19",
|
||||||
"expo-secure-store": "~14.0.1",
|
"expo-secure-store": "~14.0.1",
|
||||||
"expo-splash-screen": "~0.29.22",
|
"expo-splash-screen": "~0.29.22",
|
||||||
"expo-sqlite": "~15.1.3",
|
"expo-sqlite": "~15.1.4",
|
||||||
"expo-status-bar": "~2.0.1",
|
"expo-status-bar": "~2.0.1",
|
||||||
"expo-system-ui": "~4.0.9",
|
"expo-system-ui": "~4.0.9",
|
||||||
"jest": "~29.7.0",
|
"jest": "~29.7.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user