import { v4 as uuidv4 } from 'uuid'; import { User, Medication, UserSettings, TakenDoses, CustomReminder, CouchDBDocument, UserRole, } from '../types'; import { AccountStatus } from './auth/auth.constants'; import { CouchDBError } from './couchdb'; // Production CouchDB Service that connects to a real CouchDB instance export class CouchDBService { private baseUrl: string; private auth: string; constructor() { // Get CouchDB configuration from environment const couchdbUrl = process.env.VITE_COUCHDB_URL || 'http://localhost:5984'; const couchdbUser = process.env.VITE_COUCHDB_USER || 'admin'; const couchdbPassword = process.env.VITE_COUCHDB_PASSWORD || 'password'; this.baseUrl = couchdbUrl; this.auth = btoa(`${couchdbUser}:${couchdbPassword}`); // 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) { console.error(`Failed to initialize database ${dbName}:`, error); } } } private async createDatabaseIfNotExists(dbName: string): Promise { try { 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 Error(`Failed to create database ${dbName}`); } console.warn(`✅ Created CouchDB database: ${dbName}`); } } catch (error) { console.error(`Error checking/creating database ${dbName}:`, error); throw error; } } private async makeRequest( method: string, path: string, body?: Record ): Promise> { const url = `${this.baseUrl}${path}`; const options: RequestInit = { method, headers: { Authorization: `Basic ${this.auth}`, 'Content-Type': 'application/json', }, }; if (body) { options.body = JSON.stringify(body); } const response = await fetch(url, options); if (!response.ok) { const errorText = await response.text(); throw new CouchDBError(`CouchDB error: ${errorText}`, response.status); } return response.json(); } private async getDoc( dbName: string, id: string ): Promise { try { const doc = await this.makeRequest('GET', `/${dbName}/${id}`); return doc as T; } catch (error) { if (error instanceof CouchDBError && error.status === 404) { return null; } throw error; } } private async putDoc( dbName: string, doc: Omit & { _rev?: string } ): Promise { const response = await this.makeRequest( 'PUT', `/${dbName}/${doc._id}`, doc ); return { ...doc, _rev: response.rev } as T; } private async query( dbName: string, selector: Record ): Promise { const response = await this.makeRequest('POST', `/${dbName}/_find`, { selector, limit: 1000, }); return response.docs as T[]; } // User Management Methods async findUserByUsername(username: string): Promise { const users = await this.query('users', { username }); return users[0] || null; } async findUserByEmail(email: string): Promise { const users = await this.query('users', { email }); return users[0] || null; } async createUser(username: string): Promise { const existingUser = await this.findUserByUsername(username); if (existingUser) { throw new CouchDBError('User already exists', 409); } const newUser: Omit = { _id: uuidv4(), username }; return this.putDoc('users', newUser); } async createUserWithPassword( email: string, password: string, username?: string ): Promise { const existingUser = await this.findUserByEmail(email); if (existingUser) { throw new CouchDBError('User already exists', 409); } const newUser: Omit = { _id: uuidv4(), username: username || email.split('@')[0], email, password, emailVerified: false, status: AccountStatus.PENDING, role: UserRole.USER, createdAt: new Date(), lastLoginAt: new Date(), }; return this.putDoc('users', newUser); } async createUserFromOAuth(userData: { email: string; username: string; avatar?: string; }): Promise { const existingUser = await this.findUserByEmail(userData.email); if (existingUser) { throw new CouchDBError('User already exists', 409); } const newUser: Omit = { _id: uuidv4(), username: userData.username, email: userData.email, avatar: userData.avatar, emailVerified: true, status: AccountStatus.ACTIVE, role: UserRole.USER, createdAt: new Date(), lastLoginAt: new Date(), }; return this.putDoc('users', newUser); } async getUserById(id: string): Promise { return this.getDoc('users', id); } async updateUser(user: User): Promise { return this.putDoc('users', user); } async deleteUser(id: string): Promise { const user = await this.getDoc('users', id); if (!user) { throw new CouchDBError('User not found', 404); } await this.makeRequest('DELETE', `/users/${id}?rev=${user._rev}`); } // Medication Methods async getMedications(userId: string): Promise { return this.query('medications', { userId }); } async createMedication( medication: Omit ): Promise { const newMedication = { ...medication, _id: uuidv4() }; return this.putDoc('medications', newMedication); } async updateMedication(medication: Medication): Promise { return this.putDoc('medications', medication); } async deleteMedication(id: string): Promise { const medication = await this.getDoc('medications', id); if (!medication) { throw new CouchDBError('Medication not found', 404); } await this.makeRequest( 'DELETE', `/medications/${id}?rev=${medication._rev}` ); } // Settings Methods async getSettings(userId: string): Promise { const settings = await this.getDoc('settings', userId); if (!settings) { const defaultSettings: Omit = { _id: userId, notificationsEnabled: true, hasCompletedOnboarding: false, }; return this.putDoc('settings', defaultSettings); } return settings; } async updateSettings(settings: UserSettings): Promise { return this.putDoc('settings', settings); } // Taken Doses Methods async getTakenDoses(userId: string): Promise { const doses = await this.getDoc('taken_doses', userId); if (!doses) { const defaultDoses: Omit = { _id: userId, doses: {}, }; return this.putDoc('taken_doses', defaultDoses); } return doses; } async updateTakenDoses(takenDoses: TakenDoses): Promise { return this.putDoc('taken_doses', takenDoses); } // Reminder Methods async getReminders(userId: string): Promise { return this.query('reminders', { userId }); } async createReminder( reminder: Omit ): Promise { const newReminder = { ...reminder, _id: uuidv4() }; return this.putDoc('reminders', newReminder); } async updateReminder(reminder: CustomReminder): Promise { return this.putDoc('reminders', reminder); } async deleteReminder(id: string): Promise { const reminder = await this.getDoc('reminders', id); if (!reminder) { throw new CouchDBError('Reminder not found', 404); } await this.makeRequest('DELETE', `/reminders/${id}?rev=${reminder._rev}`); } // Admin Methods async getAllUsers(): Promise { return this.query('users', {}); } async updateUserStatus(userId: string, status: AccountStatus): Promise { const user = await this.getUserById(userId); if (!user) { throw new CouchDBError('User not found', 404); } const updatedUser = { ...user, status }; return this.updateUser(updatedUser); } async changeUserPassword(userId: string, newPassword: string): Promise { const user = await this.getUserById(userId); if (!user) { throw new CouchDBError('User not found', 404); } const updatedUser = { ...user, password: newPassword }; return this.updateUser(updatedUser); } // Cleanup Methods async deleteAllUserData(userId: string): Promise { // Delete user medications, settings, doses, and reminders const [medications, reminders] = await Promise.all([ this.getMedications(userId), this.getReminders(userId), ]); // Delete all user data const deletePromises = [ ...medications.map(med => this.deleteMedication(med._id)), ...reminders.map(rem => this.deleteReminder(rem._id)), ]; // Delete settings and taken doses try { const settings = await this.getDoc('settings', userId); if (settings) { deletePromises.push( this.makeRequest( 'DELETE', `/settings/${userId}?rev=${settings._rev}` ).then(() => undefined) ); } } catch (_error) { // Settings might not exist } try { const takenDoses = await this.getDoc('taken_doses', userId); if (takenDoses) { deletePromises.push( this.makeRequest( 'DELETE', `/taken_doses/${userId}?rev=${takenDoses._rev}` ).then(() => undefined) ); } } catch (_error) { // Taken doses might not exist } await Promise.all(deletePromises); // Finally delete the user await this.deleteUser(userId); } }