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

@@ -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,
});
}