import { getEnvVar, isTest } from '../../utils/env'; import { MockDatabaseStrategy } from './MockDatabaseStrategy'; import { ProductionDatabaseStrategy } from './ProductionDatabaseStrategy'; import { DatabaseStrategy } from './types'; import { AccountStatus } from '../auth/auth.constants'; /** * Consolidated Database Service * Uses strategy pattern to switch between mock and production implementations */ export class DatabaseService implements DatabaseStrategy { private strategy: DatabaseStrategy; constructor() { this.strategy = this.createStrategy(); } private createStrategy(): DatabaseStrategy { // Always use mock service in test environment if (isTest()) { return new MockDatabaseStrategy(); } // Check if we're in a Docker environment or if CouchDB URL is configured const couchdbUrl = getEnvVar('VITE_COUCHDB_URL') || getEnvVar('COUCHDB_URL'); const useProduction = !!couchdbUrl && couchdbUrl !== 'mock'; if (useProduction) { try { return new ProductionDatabaseStrategy(); } catch (error) { console.warn( 'Production CouchDB service not available, falling back to mock:', error ); return new MockDatabaseStrategy(); } } else { return new MockDatabaseStrategy(); } } // Delegate all methods to the strategy // User operations async createUser(user: Parameters[0]) { return this.strategy.createUser(user); } async updateUser(user: Parameters[0]) { return this.strategy.updateUser(user); } async getUserById(id: string) { return this.strategy.getUserById(id); } async findUserByEmail(email: string) { return this.strategy.findUserByEmail(email); } async deleteUser(id: string) { return this.strategy.deleteUser(id); } async getAllUsers() { return this.strategy.getAllUsers(); } // Medication operations async createMedication( userId: string, medication: Parameters[1] ) { return this.strategy.createMedication(userId, medication); } // Overloads for updateMedication async updateMedication( userId: string, medication: Parameters[0] ): Promise[0]>; async updateMedication( medication: Parameters[0] ): Promise[0]>; async updateMedication( userIdOrMedication: | string | Parameters[0], medication?: Parameters[0] ) { // Support both old signature (userId, medication) and new (medication) if (typeof userIdOrMedication === 'string' && medication) { return this.strategy.updateMedication(medication); } return this.strategy.updateMedication( userIdOrMedication as Parameters[0] ); } async getMedications(userId: string) { return this.strategy.getMedications(userId); } // Overloads for deleteMedication async deleteMedication( userId: string, medication: { _id: string } ): Promise; async deleteMedication(id: string): Promise; async deleteMedication(userIdOrId: string, medication?: { _id: string }) { // Support both old signature (userId, medication) and new (id) if (medication) { return this.strategy.deleteMedication(medication._id); } return this.strategy.deleteMedication(userIdOrId); } // User settings operations async getUserSettings(userId: string) { return this.strategy.getUserSettings(userId); } async updateUserSettings( settings: Parameters[0] ) { return this.strategy.updateUserSettings(settings); } // Taken doses operations async getTakenDoses(userId: string) { return this.strategy.getTakenDoses(userId); } // Overloads for updateTakenDoses async updateTakenDoses( takenDoses: Parameters[0] ): Promise[0]>; async updateTakenDoses( userId: string, partialUpdate: Partial[0]> ): Promise[0]>; async updateTakenDoses( takenDosesOrUserId: | Parameters[0] | string, partialUpdate?: Partial[0]> ) { // Support both new signature (takenDoses) and legacy (userId, partialUpdate) if (typeof takenDosesOrUserId === 'string' && partialUpdate !== undefined) { const existing = await this.strategy.getTakenDoses(takenDosesOrUserId); return this.strategy.updateTakenDoses({ ...existing, ...partialUpdate, }); } return this.strategy.updateTakenDoses( takenDosesOrUserId as Parameters[0] ); } // Custom reminders operations async createCustomReminder( userId: string, reminder: Parameters[1] ) { return this.strategy.createCustomReminder(userId, reminder); } // Overloads for updateCustomReminder async updateCustomReminder( userId: string, reminder: Parameters[0] ): Promise[0]>; async updateCustomReminder( reminder: Parameters[0] ): Promise[0]>; async updateCustomReminder( userIdOrReminder: | string | Parameters[0], reminder?: Parameters[0] ) { // Support both old signature (userId, reminder) and new (reminder) if (typeof userIdOrReminder === 'string' && reminder) { return this.strategy.updateCustomReminder(reminder); } return this.strategy.updateCustomReminder( userIdOrReminder as Parameters< DatabaseStrategy['updateCustomReminder'] >[0] ); } async getCustomReminders(userId: string) { return this.strategy.getCustomReminders(userId); } // Overloads for deleteCustomReminder async deleteCustomReminder( userId: string, reminder: { _id: string } ): Promise; async deleteCustomReminder(id: string): Promise; async deleteCustomReminder(userIdOrId: string, reminder?: { _id: string }) { // Support both old signature (userId, reminder) and new (id) if (reminder) { return this.strategy.deleteCustomReminder(reminder._id); } return this.strategy.deleteCustomReminder(userIdOrId); } // User operations with password async createUserWithPassword( email: string, hashedPassword: string, username?: string ) { return this.strategy.createUserWithPassword( email, hashedPassword, username ); } async createUserFromOAuth(email: string, username: string, provider: string) { return this.strategy.createUserFromOAuth(email, username, provider); } // Utility methods getStrategyType(): string { return this.strategy.constructor.name; } isUsingMockStrategy(): boolean { return this.strategy instanceof MockDatabaseStrategy; } isUsingProductionStrategy(): boolean { return this.strategy instanceof ProductionDatabaseStrategy; } // Legacy compatibility methods for existing code async getSettings(userId: string) { return this.strategy.getUserSettings(userId); } async addMedication( userId: string, medication: Parameters[1] ) { return this.strategy.createMedication(userId, medication); } async addCustomReminder( userId: string, reminder: Parameters[1] ) { return this.strategy.createCustomReminder(userId, reminder); } async updateSettings( userId: string, settings: Partial[0]> ) { const currentSettings = await this.strategy.getUserSettings(userId); return this.strategy.updateUserSettings({ ...currentSettings, ...settings, }); } async suspendUser(userId: string) { const user = await this.strategy.getUserById(userId); if (!user) throw new Error('User not found'); return this.strategy.updateUser({ ...user, status: 'SUSPENDED' as AccountStatus, }); } async activateUser(userId: string) { const user = await this.strategy.getUserById(userId); if (!user) throw new Error('User not found'); return this.strategy.updateUser({ ...user, status: 'ACTIVE' as AccountStatus, }); } async changeUserPassword(userId: string, newPassword: string) { const user = await this.strategy.getUserById(userId); if (!user) throw new Error('User not found'); return this.strategy.updateUser({ ...user, password: newPassword, }); } async deleteAllUserData(userId: string) { // Delete user's medications const medications = await this.strategy.getMedications(userId); for (const med of medications) { await this.strategy.deleteMedication(med._id); } // Delete user's reminders const reminders = await this.strategy.getCustomReminders(userId); for (const reminder of reminders) { await this.strategy.deleteCustomReminder(reminder._id); } // Delete user return this.strategy.deleteUser(userId); } } // Export singleton instance export const databaseService = new DatabaseService(); // Re-export types and errors export { DatabaseError } from './types'; export type { DatabaseStrategy } from './types';