Files
rxminder/docs/development/DATABASE.md
William Valentin 598c1da17b Complete database service consolidation and documentation
 Features:
- Add comprehensive database service documentation
- Create detailed module README with usage examples
- Expand main documentation index with database links
- Add component test support to Jest configuration

🔧 Improvements:
- Fix AvatarDropdown test failures (dark mode classes and rapid clicking)
- Update documentation version to 2.1
- Include migration guide and troubleshooting sections
- Add performance considerations and security notes

📚 Documentation:
- Complete API reference with code examples
- Architecture overview with Strategy pattern explanation
- Environment configuration and strategy selection guide
- Best practices and development guidelines
- Comprehensive refactoring summary

🧪 Testing:
- All 292 tests passing across all modules
- Component tests now properly integrated with Jest
- Fixed TypeScript compatibility issues in tests
- Verified database service functionality in all environments

📋 Summary:
- Removed deprecated CouchDB service files
- Consolidated database operations under unified service
- Enhanced documentation structure and content
- Improved test coverage and reliability
- Maintained backward compatibility where possible
2025-09-08 18:59:08 -07:00

12 KiB

Database Service Documentation

Overview

The RxMinder application uses a unified database service that implements the Strategy pattern to provide a flexible data access layer. This service can seamlessly switch between different database implementations based on the environment configuration.

Architecture

Strategy Pattern Implementation

The database service uses the Strategy pattern to abstract database operations:

DatabaseService (Context)
├── DatabaseStrategy (Interface)
├── MockDatabaseStrategy (Concrete Strategy)
└── ProductionDatabaseStrategy (Concrete Strategy)

Key Components

  • DatabaseService: Main service class that delegates operations to the selected strategy
  • DatabaseStrategy: Interface defining all database operations
  • MockDatabaseStrategy: In-memory implementation for development and testing
  • ProductionDatabaseStrategy: CouchDB implementation for production environments

Configuration

Environment-Based Strategy Selection

The service automatically selects the appropriate strategy based on environment variables:

  1. Test Environment: Always uses MockDatabaseStrategy
  2. Production Environment: Uses ProductionDatabaseStrategy if CouchDB URL is configured
  3. Fallback: Uses MockDatabaseStrategy if production strategy fails to initialize

Environment Variables

# CouchDB Configuration (for production)
VITE_COUCHDB_URL=http://localhost:5984
COUCHDB_URL=http://localhost:5984

# Test Environment Detection
NODE_ENV=test

Usage

Basic Usage

import { databaseService } from './services/database';

// The service automatically selects the appropriate strategy
const users = await databaseService.getAllUsers();

Strategy Information

// Check which strategy is being used
console.log(databaseService.getStrategyType()); // "MockDatabaseStrategy" or "ProductionDatabaseStrategy"

// Check if using mock strategy
if (databaseService.isUsingMockStrategy()) {
  console.log('Using in-memory database');
}

// Check if using production strategy
if (databaseService.isUsingProductionStrategy()) {
  console.log('Using CouchDB database');
}

API Reference

User Operations

createUser(user)

Creates a new user in the database.

const user = await databaseService.createUser({
  _id: 'user1',
  _rev: '1-abc123',
  username: 'john_doe',
  email: 'john@example.com',
  role: UserRole.USER,
  status: AccountStatus.ACTIVE,
});

updateUser(user)

Updates an existing user.

const updatedUser = await databaseService.updateUser({
  ...existingUser,
  username: 'new_username',
});

getUserById(id)

Retrieves a user by their ID.

const user = await databaseService.getUserById('user1');

findUserByEmail(email)

Finds a user by their email address.

const user = await databaseService.findUserByEmail('john@example.com');

deleteUser(id)

Deletes a user from the database.

const success = await databaseService.deleteUser('user1');

getAllUsers()

Retrieves all users from the database.

const users = await databaseService.getAllUsers();

Authentication Operations

createUserWithPassword(email, hashedPassword, username?)

Creates a user with password-based authentication.

const user = await databaseService.createUserWithPassword('user@example.com', 'hashed_password', 'username');

createUserFromOAuth(email, username, provider)

Creates a user from OAuth authentication.

const user = await databaseService.createUserFromOAuth('user@example.com', 'username', 'google');

Medication Operations

createMedication(userId, medication)

Creates a new medication for a user.

const medication = await databaseService.createMedication('user1', {
  name: 'Aspirin',
  dosage: '100mg',
  frequency: Frequency.Daily,
  startTime: '08:00',
  notes: 'Take with food',
});

updateMedication(medication)

Updates an existing medication.

const updatedMedication = await databaseService.updateMedication({
  ...existingMedication,
  dosage: '200mg',
});

getMedications(userId)

Retrieves all medications for a user.

const medications = await databaseService.getMedications('user1');

deleteMedication(id)

Deletes a medication.

const success = await databaseService.deleteMedication('medication1');

User Settings Operations

getUserSettings(userId)

Retrieves user settings.

const settings = await databaseService.getUserSettings('user1');

updateUserSettings(settings)

Updates user settings.

const updatedSettings = await databaseService.updateUserSettings({
  ...existingSettings,
  notificationsEnabled: false,
});

Taken Doses Operations

getTakenDoses(userId)

Retrieves taken doses record for a user.

const takenDoses = await databaseService.getTakenDoses('user1');

updateTakenDoses(takenDoses)

Updates the taken doses record.

const updatedTakenDoses = await databaseService.updateTakenDoses({
  ...existingTakenDoses,
  doses: { ...existingTakenDoses.doses, dose1: new Date().toISOString() },
});

Custom Reminders Operations

createCustomReminder(userId, reminder)

Creates a custom reminder for a user.

const reminder = await databaseService.createCustomReminder('user1', {
  title: 'Doctor Appointment',
  icon: '🏥',
  frequencyMinutes: 60,
  startTime: '09:00',
  endTime: '17:00',
});

updateCustomReminder(reminder)

Updates a custom reminder.

const updatedReminder = await databaseService.updateCustomReminder({
  ...existingReminder,
  title: 'Updated Appointment',
});

getCustomReminders(userId)

Retrieves all custom reminders for a user.

const reminders = await databaseService.getCustomReminders('user1');

deleteCustomReminder(id)

Deletes a custom reminder.

const success = await databaseService.deleteCustomReminder('reminder1');

Administrative Operations

suspendUser(userId)

Suspends a user account.

const suspendedUser = await databaseService.suspendUser('user1');

activateUser(userId)

Activates a suspended user account.

const activatedUser = await databaseService.activateUser('user1');

changeUserPassword(userId, newPassword)

Changes a user's password.

const updatedUser = await databaseService.changeUserPassword('user1', 'new_hashed_password');

deleteAllUserData(userId)

Deletes all data associated with a user.

const success = await databaseService.deleteAllUserData('user1');

Testing

Mock Strategy for Testing

The MockDatabaseStrategy provides an in-memory implementation that's perfect for testing:

import { DatabaseService } from './services/database';

// In test environment, automatically uses MockDatabaseStrategy
const service = new DatabaseService();

// All operations work the same way but use in-memory storage
const user = await service.createUser(mockUser);

Test Utilities

The test setup provides utilities for creating mock data:

import { testUtils } from './tests/setup';

const mockUser = testUtils.createMockUser();
const mockMedication = testUtils.createMockMedication();

Error Handling

DatabaseError

All database operations can throw DatabaseError for various failure scenarios:

import { DatabaseError } from './services/database';

try {
  const user = await databaseService.getUserById('nonexistent');
} catch (error) {
  if (error instanceof DatabaseError) {
    console.error('Database operation failed:', error.message);
  }
}

Fallback Behavior

The service automatically falls back to the mock strategy if the production strategy fails to initialize:

// If CouchDB is not available, this will log a warning and use MockDatabaseStrategy
const service = new DatabaseService();

Migration from Legacy Implementation

Removed Files

The following deprecated files have been removed:

  • services/couchdb.ts - Legacy CouchDB service
  • services/couchdb.factory.ts - CouchDB factory
  • services/couchdb.production.ts - Production CouchDB configuration
  • scripts/migrate-to-unified-config.ts - Migration script

Migration Guide

  1. Update imports: Change from legacy CouchDB imports to unified database service:
// Old
import { couchdbService } from './services/couchdb';

// New
import { databaseService } from './services/database';
  1. Update method calls: The API remains largely the same, but some methods have been standardized:
// Old
await couchdbService.updateMedication(userId, medication);

// New
await databaseService.updateMedication(medication);
  1. Environment configuration: Update environment variables to use the new configuration format.

Best Practices

1. Error Handling

Always handle potential database errors:

try {
  const user = await databaseService.getUserById(userId);
  if (!user) {
    throw new Error('User not found');
  }
  // Process user
} catch (error) {
  console.error('Failed to retrieve user:', error);
  // Handle error appropriately
}

2. Transaction-like Operations

For operations that affect multiple entities, use proper error handling:

try {
  const medications = await databaseService.getMedications(userId);
  const reminders = await databaseService.getCustomReminders(userId);

  // Process both together
  return { medications, reminders };
} catch (error) {
  console.error('Failed to load user data:', error);
  throw error;
}

3. Strategy-Agnostic Code

Write code that works with any strategy:

// Good - works with any strategy
const user = await databaseService.getUserById(userId);

// Avoid - strategy-specific checks unless necessary
if (databaseService.isUsingMockStrategy()) {
  // Only do this if absolutely necessary
}

Performance Considerations

Mock Strategy

  • In-memory storage for fast operations
  • Perfect for development and testing
  • Data is lost when the application restarts

Production Strategy

  • Persistent storage with CouchDB
  • Network latency considerations
  • Proper error handling for network failures

Security

Data Validation

All data is validated before database operations:

  • User input sanitization
  • Type checking with TypeScript interfaces
  • Required field validation

Access Control

  • User authentication required for all operations
  • Role-based access control for administrative operations
  • User isolation (users can only access their own data)

Troubleshooting

Common Issues

  1. CouchDB Connection Failed

    • Check VITE_COUCHDB_URL or COUCHDB_URL environment variables
    • Verify CouchDB is running and accessible
    • Check network connectivity
  2. Strategy Selection Issues

    • Verify environment variable configuration
    • Check console logs for strategy selection messages
    • Use getStrategyType() to confirm active strategy
  3. Test Environment Issues

    • Ensure NODE_ENV=test is set for test runs
    • Verify test setup files are properly configured
    • Check that mock data is being used

Debug Information

Enable debug logging to troubleshoot issues:

console.log('Active strategy:', databaseService.getStrategyType());
console.log('Using mock strategy:', databaseService.isUsingMockStrategy());
console.log('Using production strategy:', databaseService.isUsingProductionStrategy());


Last Updated: January 2024
Version: 2.0.0