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.
This commit is contained in:
William Valentin
2025-09-08 11:30:58 -07:00
parent 2556250f2c
commit 8541877290
3 changed files with 17 additions and 129 deletions

View File

@@ -1,4 +1,4 @@
import { dbService } from './couchdb.factory';
import { databaseService } from './database';
import { AccountStatus } from './auth/auth.constants';
import { UserRole } from '../types';
@@ -15,7 +15,7 @@ export class DatabaseSeeder {
try {
// Check if admin already exists
const existingAdmin = await dbService.findUserByEmail(adminEmail);
const existingAdmin = await databaseService.findUserByEmail(adminEmail);
if (existingAdmin) {
console.warn('✅ Default admin user already exists');
@@ -33,7 +33,7 @@ export class DatabaseSeeder {
status: AccountStatus.ACTIVE,
emailVerified: true,
};
await dbService.updateUser(updatedAdmin);
await databaseService.updateUser(updatedAdmin);
console.warn('✅ Admin user updated successfully');
console.warn('👤 Updated admin:', updatedAdmin);
}
@@ -42,7 +42,7 @@ export class DatabaseSeeder {
console.warn('🚀 Creating new admin user...');
// Create default admin user
const adminUser = await dbService.createUserWithPassword(
const adminUser = await databaseService.createUserWithPassword(
adminEmail,
adminPassword,
'admin'
@@ -60,7 +60,7 @@ export class DatabaseSeeder {
lastLoginAt: new Date(),
};
await dbService.updateUser(updatedAdmin);
await databaseService.updateUser(updatedAdmin);
console.warn('✅ Admin user created successfully');
console.warn('👤 Final admin user:', updatedAdmin);

View File

@@ -78,43 +78,17 @@ export class DatabaseService implements DatabaseStrategy {
// Overloads for updateMedication
async updateMedication(
userId: string,
medication: Parameters<DatabaseStrategy['updateMedication']>[0]
): Promise<Parameters<DatabaseStrategy['updateMedication']>[0]>;
async updateMedication(
medication: Parameters<DatabaseStrategy['updateMedication']>[0]
): Promise<Parameters<DatabaseStrategy['updateMedication']>[0]>;
async updateMedication(
userIdOrMedication:
| string
| Parameters<DatabaseStrategy['updateMedication']>[0],
medication?: Parameters<DatabaseStrategy['updateMedication']>[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<DatabaseStrategy['updateMedication']>[0]
);
): Promise<Parameters<DatabaseStrategy['updateMedication']>[0]> {
return this.strategy.updateMedication(medication);
}
async getMedications(userId: string) {
return this.strategy.getMedications(userId);
}
// Overloads for deleteMedication
async deleteMedication(
userId: string,
medication: { _id: string }
): Promise<boolean>;
async deleteMedication(id: string): Promise<boolean>;
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);
async deleteMedication(id: string): Promise<boolean> {
return this.strategy.deleteMedication(id);
}
// User settings operations
@@ -136,28 +110,8 @@ export class DatabaseService implements DatabaseStrategy {
// Overloads for updateTakenDoses
async updateTakenDoses(
takenDoses: Parameters<DatabaseStrategy['updateTakenDoses']>[0]
): Promise<Parameters<DatabaseStrategy['updateTakenDoses']>[0]>;
async updateTakenDoses(
userId: string,
partialUpdate: Partial<Parameters<DatabaseStrategy['updateTakenDoses']>[0]>
): Promise<Parameters<DatabaseStrategy['updateTakenDoses']>[0]>;
async updateTakenDoses(
takenDosesOrUserId:
| Parameters<DatabaseStrategy['updateTakenDoses']>[0]
| string,
partialUpdate?: Partial<Parameters<DatabaseStrategy['updateTakenDoses']>[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<DatabaseStrategy['updateTakenDoses']>[0]
);
): Promise<Parameters<DatabaseStrategy['updateTakenDoses']>[0]> {
return this.strategy.updateTakenDoses(takenDoses);
}
// Custom reminders operations
@@ -170,45 +124,17 @@ export class DatabaseService implements DatabaseStrategy {
// Overloads for updateCustomReminder
async updateCustomReminder(
userId: string,
reminder: Parameters<DatabaseStrategy['updateCustomReminder']>[0]
): Promise<Parameters<DatabaseStrategy['updateCustomReminder']>[0]>;
async updateCustomReminder(
reminder: Parameters<DatabaseStrategy['updateCustomReminder']>[0]
): Promise<Parameters<DatabaseStrategy['updateCustomReminder']>[0]>;
async updateCustomReminder(
userIdOrReminder:
| string
| Parameters<DatabaseStrategy['updateCustomReminder']>[0],
reminder?: Parameters<DatabaseStrategy['updateCustomReminder']>[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]
);
): Promise<Parameters<DatabaseStrategy['updateCustomReminder']>[0]> {
return this.strategy.updateCustomReminder(reminder);
}
async getCustomReminders(userId: string) {
return this.strategy.getCustomReminders(userId);
}
// Overloads for deleteCustomReminder
async deleteCustomReminder(
userId: string,
reminder: { _id: string }
): Promise<boolean>;
async deleteCustomReminder(id: string): Promise<boolean>;
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);
async deleteCustomReminder(id: string): Promise<boolean> {
return this.strategy.deleteCustomReminder(id);
}
// User operations with password
@@ -241,42 +167,12 @@ export class DatabaseService implements DatabaseStrategy {
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<DatabaseStrategy['createMedication']>[1]
) {
return this.strategy.createMedication(userId, medication);
}
async addCustomReminder(
userId: string,
reminder: Parameters<DatabaseStrategy['createCustomReminder']>[1]
) {
return this.strategy.createCustomReminder(userId, reminder);
}
async updateSettings(
userId: string,
settings: Partial<Parameters<DatabaseStrategy['updateUserSettings']>[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,
status: AccountStatus.SUSPENDED,
});
}
@@ -285,7 +181,7 @@ export class DatabaseService implements DatabaseStrategy {
if (!user) throw new Error('User not found');
return this.strategy.updateUser({
...user,
status: 'ACTIVE' as AccountStatus,
status: AccountStatus.ACTIVE,
});
}

View File

@@ -10,11 +10,3 @@ export {
export type { DatabaseStrategy } from './types';
export { MockDatabaseStrategy } from './MockDatabaseStrategy';
export { ProductionDatabaseStrategy } from './ProductionDatabaseStrategy';
// Legacy compatibility - re-export as dbService for existing code
import { databaseService } from './DatabaseService';
export { databaseService as dbService };
// Re-export CouchDBError for backward compatibility
import { DatabaseError } from './types';
export { DatabaseError as CouchDBError };