POWR/lib/db/services/DevSeederService.ts
DocNR 29c4dd1675 feat(database): Add workout and template persistence
Implements database tables and services for workout and template storage:
- Updates schema to version 5 with new workout and template tables
- Adds WorkoutService for CRUD operations on workouts
- Enhances TemplateService for template management
- Creates NostrWorkoutService for bidirectional Nostr event handling
- Implements React hooks for database access
- Connects workout store to database layer for persistence
- Improves offline support with publication queue

This change ensures workouts and templates are properly saved to SQLite
and can be referenced across app sessions, while maintaining Nostr
integration for social sharing.
2025-03-08 15:48:07 -05:00

231 lines
7.1 KiB
TypeScript

// lib/db/services/DevSeederService.ts
import { SQLiteDatabase } from 'expo-sqlite';
import { ExerciseService } from './ExerciseService';
import { EventCache } from '@/lib/db/services/EventCache';
import { WorkoutService } from './WorkoutService';
import { TemplateService } from './TemplateService';
import { logDatabaseInfo } from '../debug';
import { mockExerciseEvents, convertNostrToExercise } from '../../mocks/exercises';
import { DbService } from '../db-service';
import NDK, { NDKEvent } from '@nostr-dev-kit/ndk-mobile';
export class DevSeederService {
private db: SQLiteDatabase;
private dbService: DbService;
private exerciseService: ExerciseService;
private workoutService: WorkoutService | null = null;
private templateService: TemplateService | null = null;
private eventCache: EventCache | null = null;
private ndk: NDK | null = null;
constructor(
db: SQLiteDatabase,
exerciseService: ExerciseService
) {
this.db = db;
this.dbService = new DbService(db);
this.exerciseService = exerciseService;
// Try to initialize other services if needed
try {
this.workoutService = new WorkoutService(db);
this.templateService = new TemplateService(db);
this.eventCache = new EventCache(db);
} catch (error) {
console.log('Some services not available yet:', error);
}
}
setNDK(ndk: NDK) {
this.ndk = ndk;
}
async seedDatabase() {
if (!__DEV__) return;
try {
console.log('Starting development database seeding...');
// Log initial database state
await logDatabaseInfo();
// Check if we already have exercises
const existingCount = (await this.exerciseService.getAllExercises()).length;
if (existingCount > 0) {
console.log('Database already seeded with', existingCount, 'exercises');
} else {
// Start transaction for all seeding operations
await this.db.withTransactionAsync(async () => {
console.log('Seeding mock exercises...');
// Process all events within the same transaction
for (const eventData of mockExerciseEvents) {
if (this.ndk) {
// If NDK is available, use it to cache the event
const event = new NDKEvent(this.ndk);
Object.assign(event, eventData);
// Cache the event in NDK
const ndkEvent = new NDKEvent(this.ndk);
// Copy event properties
ndkEvent.kind = eventData.kind;
ndkEvent.content = eventData.content;
ndkEvent.created_at = eventData.created_at;
ndkEvent.tags = eventData.tags;
// If we have mock signatures, use them
if (eventData.sig) {
ndkEvent.sig = eventData.sig;
ndkEvent.id = eventData.id || '';
ndkEvent.pubkey = eventData.pubkey || '';
} else if (this.ndk.signer) {
// Otherwise sign if possible
await ndkEvent.sign();
}
}
// Cache the event if possible
if (this.eventCache) {
try {
await this.eventCache.setEvent(eventData, true);
} catch (error) {
console.log('Error caching event:', error);
}
}
// Create exercise from the mock data regardless of NDK availability
const exercise = convertNostrToExercise(eventData);
await this.exerciseService.createExercise(exercise, true);
}
console.log('Successfully seeded', mockExerciseEvents.length, 'exercises');
});
}
// Seed workout and template tables
await this.seedWorkoutTables();
await this.seedTemplates();
// Log final database state
await logDatabaseInfo();
} catch (error) {
console.error('Error seeding database:', error);
throw error;
}
}
async seedWorkoutTables() {
if (!__DEV__) return;
try {
console.log('Checking workout tables seeding...');
// Check if we already have workout data
try {
const hasWorkouts = await this.dbService.getFirstAsync<{ count: number }>(
'SELECT COUNT(*) as count FROM workouts'
);
if (hasWorkouts && hasWorkouts.count > 0) {
console.log('Workout tables already seeded with', hasWorkouts.count, 'workouts');
return;
}
console.log('No workout data found, but tables should be created');
// Optional: Add mock workout data here
// if (this.workoutService) {
// // Create mock workouts
// // await this.workoutService.saveWorkout(mockWorkout);
// }
} catch (error) {
console.log('Workout tables may not exist yet - will be created in schema update');
}
} catch (error) {
console.error('Error checking workout tables:', error);
}
}
async seedTemplates() {
if (!__DEV__) return;
try {
console.log('Checking template tables seeding...');
// Check if templates table exists and has data
try {
const hasTemplates = await this.dbService.getFirstAsync<{ count: number }>(
'SELECT COUNT(*) as count FROM templates'
);
if (hasTemplates && hasTemplates.count > 0) {
console.log('Template tables already seeded with', hasTemplates.count, 'templates');
return;
}
console.log('No template data found, but tables should be created');
// Optional: Add mock template data here
// if (this.templateService) {
// // Create mock templates
// // await this.templateService.createTemplate(mockTemplate);
// }
} catch (error) {
console.log('Template tables may not exist yet - will be created in schema update');
}
} catch (error) {
console.error('Error checking template tables:', error);
}
}
async clearDatabase() {
if (!__DEV__) return;
try {
console.log('Clearing development database...');
await this.db.withTransactionAsync(async () => {
const tables = [
// Original tables
'exercises',
'exercise_tags',
'nostr_events',
'event_tags',
'cache_metadata',
'ndk_cache',
// New tables
'workouts',
'workout_exercises',
'workout_sets',
'templates',
'template_exercises'
];
for (const table of tables) {
try {
await this.db.runAsync(`DELETE FROM ${table}`);
console.log(`Cleared table: ${table}`);
} catch (error) {
console.log(`Table ${table} might not exist yet, skipping`);
}
}
});
console.log('Successfully cleared database');
} catch (error) {
console.error('Error clearing database:', error);
throw error;
}
}
async resetDatabase() {
if (!__DEV__) return;
await this.clearDatabase();
await this.seedDatabase();
}
}