import { v4 as uuidv4 } from 'uuid'; import { User, Medication, UserSettings, TakenDoses, CustomReminder, CouchDBDocument, UserRole, } from '../../types'; import { AccountStatus } from '../auth/auth.constants'; import { DatabaseStrategy, DatabaseError } from './types'; import { databaseConfig } from '../../config/unified.config'; import { logger } from '../logging'; export class ProductionDatabaseStrategy implements DatabaseStrategy { private baseUrl: string; private auth: string; constructor() { // Get CouchDB configuration from unified config const dbConfig = databaseConfig; this.baseUrl = dbConfig.url; this.auth = btoa(`${dbConfig.username}:${dbConfig.password}`); logger.db.query('Initializing production database strategy', { url: dbConfig.url, username: dbConfig.username, }); // Initialize databases this.initializeDatabases(); } private async initializeDatabases(): Promise { const databases = [ 'users', 'medications', 'settings', 'taken_doses', 'reminders', ]; for (const dbName of databases) { try { await this.createDatabaseIfNotExists(dbName); } catch (error) { logger.db.error( `Failed to initialize database ${dbName}`, error as Error ); } } } private async createDatabaseIfNotExists(dbName: string): Promise { try { // Check if database exists const response = await fetch(`${this.baseUrl}/${dbName}`, { method: 'HEAD', headers: { Authorization: `Basic ${this.auth}`, }, }); if (response.status === 404) { // Database doesn't exist, create it const createResponse = await fetch(`${this.baseUrl}/${dbName}`, { method: 'PUT', headers: { Authorization: `Basic ${this.auth}`, 'Content-Type': 'application/json', }, }); if (!createResponse.ok) { throw new DatabaseError( `Failed to create database ${dbName}`, createResponse.status ); } } } catch (error) { if (error instanceof DatabaseError) { throw error; } throw new DatabaseError( `Database initialization failed for ${dbName}`, 500 ); } } private async makeRequest( method: string, path: string, body?: unknown ): Promise { const url = `${this.baseUrl}${path}`; const headers: Record = { Authorization: `Basic ${this.auth}`, 'Content-Type': 'application/json', }; const config: RequestInit = { method, headers, }; if (body) { config.body = JSON.stringify(body); } try { const response = await fetch(url, config); if (!response.ok) { const errorText = await response.text(); throw new DatabaseError( `HTTP ${response.status}: ${errorText}`, response.status ); } return await response.json(); } catch (error) { if (error instanceof DatabaseError) { throw error; } throw new DatabaseError(`Network error: ${error}`, 500); } } private async getDoc( dbName: string, id: string ): Promise { try { return await this.makeRequest('GET', `/${dbName}/${id}`); } catch (error) { if (error instanceof DatabaseError && error.status === 404) { return null; } throw error; } } private async putDoc( dbName: string, doc: T ): Promise { const response = await this.makeRequest<{ id: string; rev: string }>( 'PUT', `/${dbName}/${doc._id}`, doc ); return { ...doc, _rev: response.rev, }; } private async deleteDoc( dbName: string, id: string, rev: string ): Promise { try { await this.makeRequest('DELETE', `/${dbName}/${id}?rev=${rev}`); return true; } catch (error) { if (error instanceof DatabaseError && error.status === 404) { return false; } throw error; } } private async queryByKey( dbName: string, startKey: string, endKey?: string ): Promise { const params = new URLSearchParams({ startkey: JSON.stringify(startKey), include_docs: 'true', }); if (endKey) { params.append('endkey', JSON.stringify(endKey)); } const response = await this.makeRequest<{ rows: Array<{ doc: T }>; }>('GET', `/${dbName}/_all_docs?${params}`); return response.rows.map(row => row.doc); } // User operations async createUser(user: Omit): Promise { const newUser: User = { ...user, _id: uuidv4(), _rev: '', // Will be set by CouchDB status: user.status || AccountStatus.ACTIVE, role: user.role || UserRole.USER, createdAt: user.createdAt || new Date(), }; return this.putDoc('users', newUser); } async updateUser(user: User): Promise { return this.putDoc('users', user); } async getUserById(id: string): Promise { return this.getDoc('users', id); } async findUserByEmail(email: string): Promise { const response = await this.makeRequest<{ rows: Array<{ doc: User }>; }>('POST', '/users/_find', { selector: { email }, limit: 1, }); return response.rows[0]?.doc || null; } async deleteUser(id: string): Promise { const user = await this.getDoc('users', id); if (!user) { return false; } return this.deleteDoc('users', id, user._rev); } async getAllUsers(): Promise { const response = await this.makeRequest<{ rows: Array<{ doc: User }>; }>('GET', '/users/_all_docs?include_docs=true'); return response.rows.map(row => row.doc); } // Medication operations async createMedication( userId: string, medication: Omit ): Promise { const newMedication: Medication = { ...medication, _id: `${userId}-med-${uuidv4()}`, _rev: '', }; return this.putDoc('medications', newMedication); } async updateMedication(medication: Medication): Promise { return this.putDoc('medications', medication); } async getMedications(userId: string): Promise { return this.queryByKey( 'medications', `${userId}-med-`, `${userId}-med-\ufff0` ); } async deleteMedication(id: string): Promise { const medication = await this.getDoc('medications', id); if (!medication) { return false; } return this.deleteDoc('medications', id, medication._rev); } // User settings operations async getUserSettings(userId: string): Promise { const existing = await this.getDoc('settings', userId); if (existing) { return existing; } // Create default settings if none exist const defaultSettings: UserSettings = { _id: userId, _rev: '', notificationsEnabled: true, hasCompletedOnboarding: false, }; return this.putDoc('settings', defaultSettings); } async updateUserSettings(settings: UserSettings): Promise { return this.putDoc('settings', settings); } // Taken doses operations async getTakenDoses(userId: string): Promise { const existing = await this.getDoc('taken_doses', userId); if (existing) { return existing; } // Create default taken doses record if none exists const defaultTakenDoses: TakenDoses = { _id: userId, _rev: '', doses: {}, }; return this.putDoc('taken_doses', defaultTakenDoses); } async updateTakenDoses(takenDoses: TakenDoses): Promise { return this.putDoc('taken_doses', takenDoses); } // Custom reminders operations async createCustomReminder( userId: string, reminder: Omit ): Promise { const newReminder: CustomReminder = { ...reminder, _id: `${userId}-reminder-${uuidv4()}`, _rev: '', }; return this.putDoc('reminders', newReminder); } async updateCustomReminder( reminder: CustomReminder ): Promise { return this.putDoc('reminders', reminder); } async getCustomReminders(userId: string): Promise { return this.queryByKey( 'reminders', `${userId}-reminder-`, `${userId}-reminder-\ufff0` ); } async deleteCustomReminder(id: string): Promise { const reminder = await this.getDoc('reminders', id); if (!reminder) { return false; } return this.deleteDoc('reminders', id, reminder._rev); } // User operations with password async createUserWithPassword( email: string, hashedPassword: string, username?: string ): Promise { // Check if user already exists const existingUser = await this.findUserByEmail(email); if (existingUser) { throw new DatabaseError('User already exists with this email', 409); } return this.createUser({ username: username || email.split('@')[0], email, password: hashedPassword, emailVerified: false, status: AccountStatus.PENDING, role: UserRole.USER, createdAt: new Date(), }); } async createUserFromOAuth( email: string, username: string, _provider: string ): Promise { // Check if user already exists const existingUser = await this.findUserByEmail(email); if (existingUser) { // Update last login and return existing user return this.updateUser({ ...existingUser, lastLoginAt: new Date(), }); } return this.createUser({ username, email, emailVerified: true, // OAuth emails are considered verified status: AccountStatus.ACTIVE, role: UserRole.USER, createdAt: new Date(), lastLoginAt: new Date(), }); } }