Files
rxminder/services/database/DatabaseService.ts
William Valentin 8541877290 refactor: implement database service with strategy pattern
- Add DatabaseService with MockDatabaseStrategy and ProductionDatabaseStrategy
- Use strategy pattern to switch between test and production database implementations
- Improve type safety and testability of database operations
- Update database seeder to use new database service architecture

This is the foundation for modernizing the database layer.
2025-09-08 11:30:58 -07:00

221 lines
6.1 KiB
TypeScript

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<DatabaseStrategy['createUser']>[0]) {
return this.strategy.createUser(user);
}
async updateUser(user: Parameters<DatabaseStrategy['updateUser']>[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<DatabaseStrategy['createMedication']>[1]
) {
return this.strategy.createMedication(userId, medication);
}
// Overloads for updateMedication
async updateMedication(
medication: Parameters<DatabaseStrategy['updateMedication']>[0]
): Promise<Parameters<DatabaseStrategy['updateMedication']>[0]> {
return this.strategy.updateMedication(medication);
}
async getMedications(userId: string) {
return this.strategy.getMedications(userId);
}
async deleteMedication(id: string): Promise<boolean> {
return this.strategy.deleteMedication(id);
}
// User settings operations
async getUserSettings(userId: string) {
return this.strategy.getUserSettings(userId);
}
async updateUserSettings(
settings: Parameters<DatabaseStrategy['updateUserSettings']>[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<DatabaseStrategy['updateTakenDoses']>[0]
): Promise<Parameters<DatabaseStrategy['updateTakenDoses']>[0]> {
return this.strategy.updateTakenDoses(takenDoses);
}
// Custom reminders operations
async createCustomReminder(
userId: string,
reminder: Parameters<DatabaseStrategy['createCustomReminder']>[1]
) {
return this.strategy.createCustomReminder(userId, reminder);
}
// Overloads for updateCustomReminder
async updateCustomReminder(
reminder: Parameters<DatabaseStrategy['updateCustomReminder']>[0]
): Promise<Parameters<DatabaseStrategy['updateCustomReminder']>[0]> {
return this.strategy.updateCustomReminder(reminder);
}
async getCustomReminders(userId: string) {
return this.strategy.getCustomReminders(userId);
}
async deleteCustomReminder(id: string): Promise<boolean> {
return this.strategy.deleteCustomReminder(id);
}
// 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;
}
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: AccountStatus.SUSPENDED,
});
}
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: AccountStatus.ACTIVE,
});
}
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';