POWR/docs/features/history/migration_guide.md
DocNR 969163313a fix(auth): Improve authentication state handling and avatar display
* Add style prop to UserAvatar component for better customization
* Refactor UserAvatar to use getAvatarSeed utility for consistent avatar generation
* Fix React hook ordering issues in profile/overview.tsx to prevent crashes during auth state changes
* Add proper state initialization and cleanup during authentication transitions
* Ensure consistent fallback avatar display for unauthenticated users

These changes improve stability during login/logout operations and provide better visual continuity with Robohash avatars when profile images aren't available.
2025-04-02 21:11:25 -04:00

9.4 KiB

Workout History API Migration Guide

Last Updated: 2025-04-02
Status: Active
Related To: History Overview, UnifiedWorkoutHistoryService

Overview

We've consolidated our workout history services and hooks to provide a more consistent and maintainable API. This guide helps developers migrate from the legacy APIs to the new unified API.

Why We Consolidated

The previous implementation had several overlapping services:

  1. WorkoutHistoryService - Basic service for local database operations
  2. EnhancedWorkoutHistoryService - Extended service with advanced features
  3. NostrWorkoutHistoryService - Service for Nostr integration
  4. useNostrWorkoutHistory Hook - Hook for fetching workouts from Nostr

This led to:

  • Duplicate code and functionality
  • Inconsistent APIs
  • Confusion about which service to use for what purpose
  • Difficulty maintaining and extending the codebase

New Architecture

The new architecture consists of two primary components:

  1. UnifiedWorkoutHistoryService - A comprehensive service that combines all functionality from previous services
  2. useWorkoutHistory - A unified hook that provides access to all workout history features

Feature Comparison

Feature Legacy Implementation Unified Implementation
Local Workouts WorkoutHistoryService UnifiedWorkoutHistoryService
Nostr Workouts NostrWorkoutHistoryService UnifiedWorkoutHistoryService
Filtering EnhancedWorkoutHistoryService UnifiedWorkoutHistoryService
Calendar View Separate implementation Integrated in service
Real-time Updates Not available Built-in with subscriptions
Export Functionality Not available Built-in (CSV/JSON)
Streak Tracking Not available Built-in analytics

Service Migration

Before:

// Using WorkoutHistoryService
const workoutHistoryService = new WorkoutHistoryService(db);
const localWorkouts = await workoutHistoryService.getAllWorkouts();

// Using NostrWorkoutHistoryService
const nostrWorkoutHistoryService = new NostrWorkoutHistoryService(db);
const allWorkouts = await nostrWorkoutHistoryService.getAllWorkouts({
  includeNostr: true,
  isAuthenticated: true
});

After:

// Using unified UnifiedWorkoutHistoryService
const workoutHistoryService = new UnifiedWorkoutHistoryService(db);

// For local workouts only
const localWorkouts = await workoutHistoryService.getAllWorkouts({
  includeNostr: false
});

// For all workouts (local + Nostr)
const allWorkouts = await workoutHistoryService.getAllWorkouts({
  includeNostr: true,
  isAuthenticated: true
});

Hook Migration

Before:

// Using useNostrWorkoutHistory
const { workouts, loading } = useNostrWorkoutHistory();

// Or manually creating a service in a component
const db = useSQLiteContext();
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);

// Then loading workouts
const loadWorkouts = async () => {
  const workouts = await workoutHistoryService.getAllWorkouts();
  setWorkouts(workouts);
};

After:

// Using unified useWorkoutHistory
const { 
  workouts, 
  loading, 
  refresh, 
  getWorkoutsByDate,
  publishWorkoutToNostr 
} = useWorkoutHistory({
  includeNostr: true,
  realtime: true,
  filters: { type: ['strength'] } // Optional filters
});

API Reference

UnifiedWorkoutHistoryService

export class UnifiedWorkoutHistoryService {
  constructor(database: SQLiteDatabase);
  
  // Core operations
  getAllWorkouts(options?: {
    limit?: number;
    offset?: number;
    includeNostr?: boolean;
    isAuthenticated?: boolean;
  }): Promise<Workout[]>;
  
  getWorkoutDetails(workoutId: string): Promise<Workout | null>;
  
  // Filtering and Search
  filterWorkouts(filters: WorkoutFilters): Promise<Workout[]>;
  searchWorkouts(query: string): Promise<Workout[]>;
  getWorkoutsByExercise(exerciseId: string): Promise<Workout[]>;
  
  // Calendar operations
  getWorkoutsByDate(date: Date): Promise<Workout[]>;
  getWorkoutDatesInMonth(year: number, month: number): Promise<Date[]>;
  
  // Nostr operations
  publishWorkoutToNostr(workoutId: string): Promise<string>;
  importNostrWorkoutToLocal(eventId: string): Promise<string>;
  subscribeToNostrWorkouts(pubkey: string, callback: (workout: Workout) => void): string;
  unsubscribeFromNostrWorkouts(subId: string): void;
  
  // Analytics
  getWorkoutStreak(): Promise<{ current: number; longest: number; lastWorkoutDate: Date | null }>;
  getWorkoutCount(): Promise<number>;
  
  // Export
  exportWorkoutHistory(format: 'csv' | 'json'): Promise<string>;
  
  // Status
  getWorkoutSyncStatus(workoutId: string): Promise<WorkoutSyncStatus>;
  updateWorkoutNostrStatus(workoutId: string, eventId: string, relayCount: number): Promise<boolean>;
}

useWorkoutHistory

export function useWorkoutHistory(options: {
  includeNostr?: boolean;
  filters?: WorkoutFilters;
  realtime?: boolean;
} = {}): {
  workouts: Workout[];
  loading: boolean;
  error: Error | null;
  refreshing: boolean;
  refresh: () => Promise<Workout[]>;
  getWorkoutsByDate: (date: Date) => Promise<Workout[]>;
  getWorkoutDetails: (workoutId: string) => Promise<Workout | null>;
  publishWorkoutToNostr: (workoutId: string) => Promise<string>;
  importNostrWorkoutToLocal: (eventId: string) => Promise<string>;
  service: UnifiedWorkoutHistoryService;
}

Migration Examples

Calendar View

// Before
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
const loadWorkouts = async () => {
  const allWorkouts = await workoutHistoryService.getAllWorkouts();
  setWorkouts(allWorkouts);
};

// After
const { 
  workouts: allWorkouts, 
  loading, 
  refresh, 
  getWorkoutsByDate 
} = useWorkoutHistory({
  includeNostr: true,
  realtime: true
});

Workout History Screen

// Before
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
const loadWorkouts = async () => {
  let allWorkouts = [];
  if (includeNostr) {
    allWorkouts = await workoutHistoryService.getAllWorkouts();
  } else {
    allWorkouts = await workoutHistoryService.filterWorkouts({
      source: ['local']
    });
  }
  setWorkouts(allWorkouts);
};

// After
const { 
  workouts: allWorkouts, 
  loading, 
  refresh 
} = useWorkoutHistory({
  includeNostr,
  filters: includeNostr ? undefined : { source: ['local'] },
  realtime: true
});

Workout Detail Screen

// Before
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
const loadWorkout = async () => {
  const workoutDetails = await workoutHistoryService.getWorkoutDetails(id);
  setWorkout(workoutDetails);
};

// After
const { getWorkoutDetails, publishWorkoutToNostr } = useWorkoutHistory();
const loadWorkout = async () => {
  const workoutDetails = await getWorkoutDetails(id);
  setWorkout(workoutDetails);
};

Key Benefits of the New API

  1. Simplified Interface: One service and one hook to learn and use
  2. Real-time Updates: Built-in support for real-time Nostr updates
  3. Consistent Filtering: Unified filtering across local and Nostr workouts
  4. Better Type Safety: Improved TypeScript types and interfaces
  5. Reduced Boilerplate: Less code needed in components
  6. Offline Support: Better handling of offline scenarios
  7. Enhanced Performance: Optimized queries and caching

Migration Timeline

  • The legacy services (EnhancedWorkoutHistoryService, NostrWorkoutHistoryService) and hooks (useNostrWorkoutHistory) are now deprecated
  • These services and hooks will be removed in a future release
  • All components should migrate to the new unified API as soon as possible

Troubleshooting

Common Issues

  1. Subscription Management

If you encounter issues with Nostr subscriptions:

// Make sure to clean up subscriptions when the component unmounts
useEffect(() => {
  // Component setup...
  
  return () => {
    // Always clean up subscriptions to prevent memory leaks
    if (subId) {
      workoutHistoryService.unsubscribeFromNostrWorkouts(subId);
    }
  };
}, [workoutHistoryService]);
  1. Filtering Workouts

If filtering isn't working as expected:

// Use proper typing for filter properties
const filters: WorkoutFilters = {
  type: ['strength'], // Explicitly typed array of strings
  source: ['local', 'nostr'], // Use valid source values
  dateRange: {
    start: new Date('2025-01-01'),
    end: new Date('2025-04-01')
  }
};
  1. Calendar Date Issues

If calendar dates aren't showing workouts:

// Make sure to use the getWorkoutsByDate method
const workoutsForSelectedDate = await getWorkoutsByDate(selectedDate);

// If that returns empty results, you can use a fallback:
if (workoutsForSelectedDate.length === 0) {
  const filteredWorkouts = allWorkouts.filter(workout => 
    isSameDay(new Date(workout.startTime), selectedDate)
  );
  setSelectedDateWorkouts(filteredWorkouts);
}