firebase WIP
This commit is contained in:
		
							parent
							
								
									9977387fbc
								
							
						
					
					
						commit
						fae6962297
					
				
							
								
								
									
										51
									
								
								PLAN.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								PLAN.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
# Project Plan: Firebase Integration & Encryption
 | 
			
		||||
 | 
			
		||||
This document outlines the plan to integrate Firebase Firestore as a remote database for logged-in users and implement end-to-end encryption for user data.
 | 
			
		||||
 | 
			
		||||
## Phase 1: `encryptionService` and Secure Key Management
 | 
			
		||||
 | 
			
		||||
The goal of this phase is to create a centralized service for data encryption and decryption, with secure management of encryption keys.
 | 
			
		||||
 | 
			
		||||
1.  **Install `keytar`**: Add the `keytar` package to the project to securely store encryption keys in the OS keychain.
 | 
			
		||||
2.  **Create `encryptionService.js`**:
 | 
			
		||||
    -   Location: `src/common/services/encryptionService.js`
 | 
			
		||||
    -   Implement `encrypt(text)` and `decrypt(encrypted)` functions using Node.js `crypto` with `AES-256-GCM`.
 | 
			
		||||
3.  **Implement Key Management**:
 | 
			
		||||
    -   Create an `initializeKey(userId)` function within the service.
 | 
			
		||||
    -   This function will first attempt to retrieve the encryption key from `keytar`.
 | 
			
		||||
    -   If `keytar` fails or no key is found, it will generate a secure, session-only key in memory as a fallback. It will **not** save the key to an insecure location like `electron-store`.
 | 
			
		||||
 | 
			
		||||
## Phase 2: Automatic Encryption/Decryption via Firestore Converter
 | 
			
		||||
 | 
			
		||||
This phase aims to abstract away the encryption/decryption logic from the repository layer, making it automatic.
 | 
			
		||||
 | 
			
		||||
1.  **Create `firestoreConverter.js`**:
 | 
			
		||||
    -   Location: `src/common/repositories/firestoreConverter.js`
 | 
			
		||||
    -   Implement a factory function `createEncryptedConverter(fieldsToEncrypt = [])`.
 | 
			
		||||
    -   This function will return a Firestore converter object with `toFirestore` and `fromFirestore` methods.
 | 
			
		||||
    -   `toFirestore`: Will automatically encrypt the specified fields before writing data to Firestore.
 | 
			
		||||
    -   `fromFirestore`: Will automatically decrypt the specified fields after reading data from Firestore.
 | 
			
		||||
 | 
			
		||||
## Phase 3: Implement Firebase Repositories
 | 
			
		||||
 | 
			
		||||
With the encryption layer ready, we will create the Firebase equivalents of the existing SQLite repositories.
 | 
			
		||||
 | 
			
		||||
1.  **Create `session/firebase.repository.js`**:
 | 
			
		||||
    -   Location: `src/common/repositories/session/firebase.repository.js`
 | 
			
		||||
    -   Use the `createEncryptedConverter` to encrypt fields like `title`.
 | 
			
		||||
    -   Implement all functions from the SQLite counterpart (`create`, `getById`, `getOrCreateActive`, etc.) using Firestore APIs.
 | 
			
		||||
2.  **Create `ask/repositories/firebase.repository.js`**:
 | 
			
		||||
    -   Location: `src/features/ask/repositories/firebase.repository.js`
 | 
			
		||||
    -   Use the `createEncryptedConverter` to encrypt the `content` field of AI messages.
 | 
			
		||||
    -   Implement all functions from the SQLite counterpart (`addAiMessage`, `getAllAiMessagesBySessionId`).
 | 
			
		||||
 | 
			
		||||
## Phase 4: Integrate Repository Strategy Pattern
 | 
			
		||||
 | 
			
		||||
This final phase will activate the logic that switches between local and remote databases based on user authentication status.
 | 
			
		||||
 | 
			
		||||
1.  **Update `getRepository()` functions**:
 | 
			
		||||
    -   Modify `src/common/repositories/session/index.js` and `src/features/ask/repositories/index.js`.
 | 
			
		||||
    -   In the `getRepository()` function, use `authService.getCurrentUser()` to check if the user is logged in (`user.isLoggedIn`).
 | 
			
		||||
    -   If logged in, return the `firebaseRepository`.
 | 
			
		||||
    -   Otherwise, return the `sqliteRepository`.
 | 
			
		||||
    -   Uncomment the `require` statements for the newly created Firebase repositories. 
 | 
			
		||||
							
								
								
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -24,6 +24,7 @@
 | 
			
		||||
                "firebase": "^11.10.0",
 | 
			
		||||
                "firebase-admin": "^13.4.0",
 | 
			
		||||
                "jsonwebtoken": "^9.0.2",
 | 
			
		||||
                "keytar": "^7.9.0",
 | 
			
		||||
                "node-fetch": "^2.7.0",
 | 
			
		||||
                "openai": "^4.70.0",
 | 
			
		||||
                "react-hot-toast": "^2.5.2",
 | 
			
		||||
@ -8090,6 +8091,23 @@
 | 
			
		||||
                "safe-buffer": "^5.0.1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/keytar": {
 | 
			
		||||
            "version": "7.9.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz",
 | 
			
		||||
            "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==",
 | 
			
		||||
            "hasInstallScript": true,
 | 
			
		||||
            "license": "MIT",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "node-addon-api": "^4.3.0",
 | 
			
		||||
                "prebuild-install": "^7.0.1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/keytar/node_modules/node-addon-api": {
 | 
			
		||||
            "version": "4.3.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
 | 
			
		||||
            "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==",
 | 
			
		||||
            "license": "MIT"
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/keyv": {
 | 
			
		||||
            "version": "4.5.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "pickle-glass",
 | 
			
		||||
    "productName": "Glass",
 | 
			
		||||
 | 
			
		||||
    "version": "0.2.2",
 | 
			
		||||
 | 
			
		||||
    "description": "Cl*ely for Free",
 | 
			
		||||
    "main": "src/index.js",
 | 
			
		||||
    "scripts": {
 | 
			
		||||
@ -48,6 +46,7 @@
 | 
			
		||||
        "firebase": "^11.10.0",
 | 
			
		||||
        "firebase-admin": "^13.4.0",
 | 
			
		||||
        "jsonwebtoken": "^9.0.2",
 | 
			
		||||
        "keytar": "^7.9.0",
 | 
			
		||||
        "node-fetch": "^2.7.0",
 | 
			
		||||
        "openai": "^4.70.0",
 | 
			
		||||
        "react-hot-toast": "^2.5.2",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										77
									
								
								repository_api_report.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								repository_api_report.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
			
		||||
# Repository API Report
 | 
			
		||||
 | 
			
		||||
이 문서는 각 리포지토리 모듈의 공개 API 명세를 정리합니다. 모든 서비스 레이어는 여기에 명시된 함수 시그니처를 따라야 합니다. `uid`는 어댑터 레이어에서 자동으로 주입되므로 서비스 레이어에서 전달해서는 안 됩니다.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Session Repository
 | 
			
		||||
**Path:** `src/common/repositories/session/`
 | 
			
		||||
 | 
			
		||||
- `getById(id: string)`
 | 
			
		||||
- `create(type: 'ask' | 'listen' = 'ask')`
 | 
			
		||||
- `getAllByUserId()`
 | 
			
		||||
- `updateTitle(id: string, title: string)`
 | 
			
		||||
- `deleteWithRelatedData(id:string)`
 | 
			
		||||
- `end(id: string)`
 | 
			
		||||
- `updateType(id: string, type: 'ask' | 'listen')`
 | 
			
		||||
- `touch(id: string)`
 | 
			
		||||
- `getOrCreateActive(requestedType: 'ask' | 'listen' = 'ask')`
 | 
			
		||||
- `endAllActiveSessions()`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### User Repository
 | 
			
		||||
**Path:** `src/common/repositories/user/`
 | 
			
		||||
 | 
			
		||||
- `findOrCreate(user: object)`
 | 
			
		||||
- `getById()`
 | 
			
		||||
- `saveApiKey(apiKey: string, provider: string)`
 | 
			
		||||
- `update(updateData: object)`
 | 
			
		||||
- `deleteById()`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Preset Repository
 | 
			
		||||
**Path:** `src/common/repositories/preset/`
 | 
			
		||||
 | 
			
		||||
- `getPresets()`
 | 
			
		||||
- `getPresetTemplates()`
 | 
			
		||||
- `create(options: { title: string, prompt: string })`
 | 
			
		||||
- `update(id: string, options: { title: string, prompt: string })`
 | 
			
		||||
- `delete(id: string)`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Ask Repository (AI Messages)
 | 
			
		||||
**Path:** `src/features/ask/repositories/`
 | 
			
		||||
 | 
			
		||||
- `addAiMessage(options: { sessionId: string, role: string, content: string, model?: string })`
 | 
			
		||||
- `getAllAiMessagesBySessionId(sessionId: string)`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### STT Repository (Transcripts)
 | 
			
		||||
**Path:** `src/features/listen/stt/repositories/`
 | 
			
		||||
 | 
			
		||||
- `addTranscript(options: { sessionId: string, speaker: string, text: string })`
 | 
			
		||||
- `getAllTranscriptsBySessionId(sessionId: string)`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Summary Repository
 | 
			
		||||
**Path:** `src/features/listen/summary/repositories/`
 | 
			
		||||
 | 
			
		||||
- `saveSummary(options: { sessionId: string, tldr: string, text: string, bullet_json: string, action_json: string, model?: string })`
 | 
			
		||||
- `getSummaryBySessionId(sessionId: string)`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Settings Repository (Presets)
 | 
			
		||||
**Path:** `src/features/settings/repositories/`
 | 
			
		||||
*(Note: This is largely a duplicate of the main Preset Repository and might be a candidate for future refactoring.)*
 | 
			
		||||
 | 
			
		||||
- `getPresets()`
 | 
			
		||||
- `getPresetTemplates()`
 | 
			
		||||
- `createPreset(options: { title: string, prompt: string })`
 | 
			
		||||
- `updatePreset(id: string, options: { title: string, prompt: string })`
 | 
			
		||||
- `deletePreset(id: string)` 
 | 
			
		||||
							
								
								
									
										45
									
								
								src/common/repositories/firestoreConverter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/common/repositories/firestoreConverter.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
			
		||||
const encryptionService = require('../services/encryptionService');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a Firestore converter that automatically encrypts and decrypts specified fields.
 | 
			
		||||
 * @param {string[]} fieldsToEncrypt - An array of field names to encrypt.
 | 
			
		||||
 * @returns {import('@firebase/firestore').FirestoreDataConverter<T>} A Firestore converter.
 | 
			
		||||
 * @template T
 | 
			
		||||
 */
 | 
			
		||||
function createEncryptedConverter(fieldsToEncrypt = []) {
 | 
			
		||||
    return {
 | 
			
		||||
        /**
 | 
			
		||||
         * @param {import('@firebase/firestore').DocumentData} appObject
 | 
			
		||||
         */
 | 
			
		||||
        toFirestore: (appObject) => {
 | 
			
		||||
            const firestoreData = { ...appObject };
 | 
			
		||||
            for (const field of fieldsToEncrypt) {
 | 
			
		||||
                if (Object.prototype.hasOwnProperty.call(firestoreData, field) && firestoreData[field] != null) {
 | 
			
		||||
                    firestoreData[field] = encryptionService.encrypt(firestoreData[field]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Ensure there's a timestamp for the last modification
 | 
			
		||||
            firestoreData.updated_at = Math.floor(Date.now() / 1000);
 | 
			
		||||
            return firestoreData;
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * @param {import('@firebase/firestore').QueryDocumentSnapshot} snapshot
 | 
			
		||||
         * @param {import('@firebase/firestore').SnapshotOptions} options
 | 
			
		||||
         */
 | 
			
		||||
        fromFirestore: (snapshot, options) => {
 | 
			
		||||
            const firestoreData = snapshot.data(options);
 | 
			
		||||
            const appObject = { ...firestoreData, id: snapshot.id }; // include the document ID
 | 
			
		||||
 | 
			
		||||
            for (const field of fieldsToEncrypt) {
 | 
			
		||||
                 if (Object.prototype.hasOwnProperty.call(appObject, field) && appObject[field] != null) {
 | 
			
		||||
                    appObject[field] = encryptionService.decrypt(appObject[field]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return appObject;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    createEncryptedConverter,
 | 
			
		||||
}; 
 | 
			
		||||
							
								
								
									
										94
									
								
								src/common/repositories/preset/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/common/repositories/preset/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
const { getFirestore, collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, query, where, orderBy } = require('firebase/firestore');
 | 
			
		||||
const { createEncryptedConverter } = require('../firestoreConverter');
 | 
			
		||||
 | 
			
		||||
const userPresetConverter = createEncryptedConverter(['prompt', 'title']);
 | 
			
		||||
 | 
			
		||||
const defaultPresetConverter = {
 | 
			
		||||
    toFirestore: (data) => data,
 | 
			
		||||
    fromFirestore: (snapshot, options) => {
 | 
			
		||||
        const data = snapshot.data(options);
 | 
			
		||||
        return { ...data, id: snapshot.id };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function userPresetsCol() {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, 'prompt_presets').withConverter(userPresetConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function defaultPresetsCol() {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, 'defaults/prompt_presets').withConverter(defaultPresetConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getPresets(uid) {
 | 
			
		||||
    const userPresetsQuery = query(userPresetsCol(), where('uid', '==', uid));
 | 
			
		||||
    const defaultPresetsQuery = query(defaultPresetsCol()); // Defaults have no owner
 | 
			
		||||
 | 
			
		||||
    const [userSnapshot, defaultSnapshot] = await Promise.all([
 | 
			
		||||
        getDocs(userPresetsQuery),
 | 
			
		||||
        getDocs(defaultPresetsQuery)
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const presets = [
 | 
			
		||||
        ...defaultSnapshot.docs.map(d => d.data()),
 | 
			
		||||
        ...userSnapshot.docs.map(d => d.data())
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return presets.sort((a, b) => {
 | 
			
		||||
        if (a.is_default && !b.is_default) return -1;
 | 
			
		||||
        if (!a.is_default && b.is_default) return 1;
 | 
			
		||||
        return a.title.localeCompare(b.title);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getPresetTemplates() {
 | 
			
		||||
    const q = query(defaultPresetsCol(), orderBy('title', 'asc'));
 | 
			
		||||
    const snapshot = await getDocs(q);
 | 
			
		||||
    return snapshot.docs.map(doc => doc.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function create({ uid, title, prompt }) {
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const newPreset = {
 | 
			
		||||
        uid: uid,
 | 
			
		||||
        title,
 | 
			
		||||
        prompt,
 | 
			
		||||
        is_default: false,
 | 
			
		||||
        created_at: now,
 | 
			
		||||
    };
 | 
			
		||||
    const docRef = await addDoc(userPresetsCol(), newPreset);
 | 
			
		||||
    return { id: docRef.id };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function update(id, { title, prompt }, uid) {
 | 
			
		||||
    const docRef = doc(userPresetsCol(), id);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
 | 
			
		||||
    if (!docSnap.exists() || docSnap.data().uid !== uid || docSnap.data().is_default) {
 | 
			
		||||
        throw new Error("Preset not found or permission denied to update.");
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    await updateDoc(docRef, { title, prompt });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function del(id, uid) {
 | 
			
		||||
    const docRef = doc(userPresetsCol(), id);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
 | 
			
		||||
    if (!docSnap.exists() || docSnap.data().uid !== uid || docSnap.data().is_default) {
 | 
			
		||||
        throw new Error("Preset not found or permission denied to delete.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await deleteDoc(docRef);
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getPresets,
 | 
			
		||||
    getPresetTemplates,
 | 
			
		||||
    create,
 | 
			
		||||
    update,
 | 
			
		||||
    delete: del,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,19 +1,39 @@
 | 
			
		||||
const sqliteRepository = require('./sqlite.repository');
 | 
			
		||||
// const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../../common/services/authService');
 | 
			
		||||
 | 
			
		||||
function getRepository() {
 | 
			
		||||
    // const user = authService.getCurrentUser();
 | 
			
		||||
    // if (user.isLoggedIn) {
 | 
			
		||||
    //     return firebaseRepository;
 | 
			
		||||
    // }
 | 
			
		||||
function getBaseRepository() {
 | 
			
		||||
    const user = authService.getCurrentUser();
 | 
			
		||||
    if (user && user.isLoggedIn) {
 | 
			
		||||
        return firebaseRepository;
 | 
			
		||||
    }
 | 
			
		||||
    return sqliteRepository;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getPresets: (...args) => getRepository().getPresets(...args),
 | 
			
		||||
    getPresetTemplates: (...args) => getRepository().getPresetTemplates(...args),
 | 
			
		||||
    create: (...args) => getRepository().create(...args),
 | 
			
		||||
    update: (...args) => getRepository().update(...args),
 | 
			
		||||
    delete: (...args) => getRepository().delete(...args),
 | 
			
		||||
}; 
 | 
			
		||||
const presetRepositoryAdapter = {
 | 
			
		||||
    getPresets: () => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().getPresets(uid);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getPresetTemplates: () => {
 | 
			
		||||
        return getBaseRepository().getPresetTemplates();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    create: (options) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().create({ uid, ...options });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    update: (id, options) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().update(id, options, uid);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    delete: (id) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().delete(id, uid);
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = presetRepositoryAdapter; 
 | 
			
		||||
							
								
								
									
										155
									
								
								src/common/repositories/session/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/common/repositories/session/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
			
		||||
const { getFirestore, doc, getDoc, collection, addDoc, query, where, getDocs, writeBatch, orderBy, limit, runTransaction, updateDoc } = require('firebase/firestore');
 | 
			
		||||
const { createEncryptedConverter } = require('../firestoreConverter');
 | 
			
		||||
 | 
			
		||||
const sessionConverter = createEncryptedConverter(['title']);
 | 
			
		||||
 | 
			
		||||
function sessionsCol() {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, 'sessions').withConverter(sessionConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sub-collection references are now built from the top-level
 | 
			
		||||
function subCollections(sessionId) {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    const sessionPath = `sessions/${sessionId}`;
 | 
			
		||||
    return {
 | 
			
		||||
        transcripts: collection(db, `${sessionPath}/transcripts`),
 | 
			
		||||
        ai_messages: collection(db, `${sessionPath}/ai_messages`),
 | 
			
		||||
        summary: collection(db, `${sessionPath}/summary`),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getById(id) {
 | 
			
		||||
    const docRef = doc(sessionsCol(), id);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
    return docSnap.exists() ? docSnap.data() : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function create(uid, type = 'ask') {
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const newSession = {
 | 
			
		||||
        uid: uid,
 | 
			
		||||
        members: [uid], // For future sharing functionality
 | 
			
		||||
        title: `Session @ ${new Date().toLocaleTimeString()}`,
 | 
			
		||||
        session_type: type,
 | 
			
		||||
        started_at: now,
 | 
			
		||||
        updated_at: now,
 | 
			
		||||
        ended_at: null,
 | 
			
		||||
    };
 | 
			
		||||
    const docRef = await addDoc(sessionsCol(), newSession);
 | 
			
		||||
    console.log(`Firebase: Created session ${docRef.id} for user ${uid}`);
 | 
			
		||||
    return docRef.id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getAllByUserId(uid) {
 | 
			
		||||
    const q = query(sessionsCol(), where('members', 'array-contains', uid), orderBy('started_at', 'desc'));
 | 
			
		||||
    const querySnapshot = await getDocs(q);
 | 
			
		||||
    return querySnapshot.docs.map(doc => doc.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function updateTitle(id, title) {
 | 
			
		||||
    const docRef = doc(sessionsCol(), id);
 | 
			
		||||
    await updateDoc(docRef, { title });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function deleteWithRelatedData(id) {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    const batch = writeBatch(db);
 | 
			
		||||
 | 
			
		||||
    const { transcripts, ai_messages, summary } = subCollections(id);
 | 
			
		||||
    const [transcriptsSnap, aiMessagesSnap, summarySnap] = await Promise.all([
 | 
			
		||||
        getDocs(query(transcripts)),
 | 
			
		||||
        getDocs(query(ai_messages)),
 | 
			
		||||
        getDocs(query(summary)),
 | 
			
		||||
    ]);
 | 
			
		||||
    
 | 
			
		||||
    transcriptsSnap.forEach(d => batch.delete(d.ref));
 | 
			
		||||
    aiMessagesSnap.forEach(d => batch.delete(d.ref));
 | 
			
		||||
    summarySnap.forEach(d => batch.delete(d.ref));
 | 
			
		||||
 | 
			
		||||
    const sessionRef = doc(sessionsCol(), id);
 | 
			
		||||
    batch.delete(sessionRef);
 | 
			
		||||
 | 
			
		||||
    await batch.commit();
 | 
			
		||||
    return { success: true };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function end(id) {
 | 
			
		||||
    const docRef = doc(sessionsCol(), id);
 | 
			
		||||
    await updateDoc(docRef, { ended_at: Math.floor(Date.now() / 1000) });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function updateType(id, type) {
 | 
			
		||||
    const docRef = doc(sessionsCol(), id);
 | 
			
		||||
    await updateDoc(docRef, { session_type: type });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function touch(id) {
 | 
			
		||||
    const docRef = doc(sessionsCol(), id);
 | 
			
		||||
    await updateDoc(docRef, { updated_at: Math.floor(Date.now() / 1000) });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getOrCreateActive(uid, requestedType = 'ask') {
 | 
			
		||||
    const findQuery = query(
 | 
			
		||||
        sessionsCol(),
 | 
			
		||||
        where('uid', '==', uid),
 | 
			
		||||
        where('ended_at', '==', null),
 | 
			
		||||
        orderBy('session_type', 'desc'),
 | 
			
		||||
        limit(1)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const activeSessionSnap = await getDocs(findQuery);
 | 
			
		||||
    
 | 
			
		||||
    if (!activeSessionSnap.empty) {
 | 
			
		||||
        const activeSessionDoc = activeSessionSnap.docs[0];
 | 
			
		||||
        const sessionRef = doc(sessionsCol(), activeSessionDoc.id);
 | 
			
		||||
        const activeSession = activeSessionDoc.data();
 | 
			
		||||
 | 
			
		||||
        console.log(`[Repo] Found active Firebase session ${activeSession.id}`);
 | 
			
		||||
        
 | 
			
		||||
        const updates = { updated_at: Math.floor(Date.now() / 1000) };
 | 
			
		||||
        if (activeSession.session_type === 'ask' && requestedType === 'listen') {
 | 
			
		||||
            updates.session_type = 'listen';
 | 
			
		||||
            console.log(`[Repo] Promoted Firebase session ${activeSession.id} to 'listen' type.`);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        await updateDoc(sessionRef, updates);
 | 
			
		||||
        return activeSessionDoc.id;
 | 
			
		||||
    } else {
 | 
			
		||||
        console.log(`[Repo] No active Firebase session for user ${uid}. Creating new.`);
 | 
			
		||||
        return create(uid, requestedType);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function endAllActiveSessions(uid) {
 | 
			
		||||
    const q = query(sessionsCol(), where('uid', '==', uid), where('ended_at', '==', null));
 | 
			
		||||
    const snapshot = await getDocs(q);
 | 
			
		||||
 | 
			
		||||
    if (snapshot.empty) return { changes: 0 };
 | 
			
		||||
 | 
			
		||||
    const batch = writeBatch(getFirestore());
 | 
			
		||||
    snapshot.forEach(d => {
 | 
			
		||||
        batch.update(d.ref, { ended_at: Math.floor(Date.now() / 1000) });
 | 
			
		||||
    });
 | 
			
		||||
    await batch.commit();
 | 
			
		||||
 | 
			
		||||
    console.log(`[Repo] Ended ${snapshot.size} active session(s) for user ${uid}.`);
 | 
			
		||||
    return { changes: snapshot.size };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getById,
 | 
			
		||||
    create,
 | 
			
		||||
    getAllByUserId,
 | 
			
		||||
    updateTitle,
 | 
			
		||||
    deleteWithRelatedData,
 | 
			
		||||
    end,
 | 
			
		||||
    updateType,
 | 
			
		||||
    touch,
 | 
			
		||||
    getOrCreateActive,
 | 
			
		||||
    endAllActiveSessions,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,26 +1,48 @@
 | 
			
		||||
const sqliteRepository = require('./sqlite.repository');
 | 
			
		||||
// const firebaseRepository = require('./firebase.repository'); // Future implementation
 | 
			
		||||
const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../../common/services/authService');
 | 
			
		||||
 | 
			
		||||
function getRepository() {
 | 
			
		||||
    // In the future, we can check the user's login status from authService
 | 
			
		||||
    // const user = authService.getCurrentUser();
 | 
			
		||||
    // if (user.isLoggedIn) {
 | 
			
		||||
    //     return firebaseRepository;
 | 
			
		||||
    // }
 | 
			
		||||
function getBaseRepository() {
 | 
			
		||||
    const user = authService.getCurrentUser();
 | 
			
		||||
    if (user && user.isLoggedIn) {
 | 
			
		||||
        return firebaseRepository;
 | 
			
		||||
    }
 | 
			
		||||
    return sqliteRepository;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Directly export functions for ease of use, decided by the strategy
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getById: (...args) => getRepository().getById(...args),
 | 
			
		||||
    create: (...args) => getRepository().create(...args),
 | 
			
		||||
    getAllByUserId: (...args) => getRepository().getAllByUserId(...args),
 | 
			
		||||
    updateTitle: (...args) => getRepository().updateTitle(...args),
 | 
			
		||||
    deleteWithRelatedData: (...args) => getRepository().deleteWithRelatedData(...args),
 | 
			
		||||
    end: (...args) => getRepository().end(...args),
 | 
			
		||||
    updateType: (...args) => getRepository().updateType(...args),
 | 
			
		||||
    touch: (...args) => getRepository().touch(...args),
 | 
			
		||||
    getOrCreateActive: (...args) => getRepository().getOrCreateActive(...args),
 | 
			
		||||
    endAllActiveSessions: (...args) => getRepository().endAllActiveSessions(...args),
 | 
			
		||||
}; 
 | 
			
		||||
// The adapter layer that injects the UID
 | 
			
		||||
const sessionRepositoryAdapter = {
 | 
			
		||||
    getById: (id) => getBaseRepository().getById(id),
 | 
			
		||||
    
 | 
			
		||||
    create: (type = 'ask') => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().create(uid, type);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    getAllByUserId: () => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().getAllByUserId(uid);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    updateTitle: (id, title) => getBaseRepository().updateTitle(id, title),
 | 
			
		||||
    
 | 
			
		||||
    deleteWithRelatedData: (id) => getBaseRepository().deleteWithRelatedData(id),
 | 
			
		||||
 | 
			
		||||
    end: (id) => getBaseRepository().end(id),
 | 
			
		||||
 | 
			
		||||
    updateType: (id, type) => getBaseRepository().updateType(id, type),
 | 
			
		||||
 | 
			
		||||
    touch: (id) => getBaseRepository().touch(id),
 | 
			
		||||
 | 
			
		||||
    getOrCreateActive: (requestedType = 'ask') => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().getOrCreateActive(uid, requestedType);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    endAllActiveSessions: () => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().endAllActiveSessions(uid);
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = sessionRepositoryAdapter; 
 | 
			
		||||
@ -108,14 +108,15 @@ function getOrCreateActive(uid, requestedType = 'ask') {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function endAllActiveSessions() {
 | 
			
		||||
function endAllActiveSessions(uid) {
 | 
			
		||||
    const db = sqliteClient.getDb();
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE ended_at IS NULL`;
 | 
			
		||||
    // Filter by uid to match the Firebase repository's behavior.
 | 
			
		||||
    const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE ended_at IS NULL AND uid = ?`;
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
        const result = db.prepare(query).run(now, now);
 | 
			
		||||
        console.log(`[Repo] Ended ${result.changes} active session(s).`);
 | 
			
		||||
        const result = db.prepare(query).run(now, now, uid);
 | 
			
		||||
        console.log(`[Repo] Ended ${result.changes} active SQLite session(s) for user ${uid}.`);
 | 
			
		||||
        return { changes: result.changes };
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        console.error('SQLite: Failed to end all active sessions:', err);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										89
									
								
								src/common/repositories/user/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/common/repositories/user/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
const { getFirestore, doc, getDoc, setDoc, deleteDoc, writeBatch, query, where, getDocs, collection } = require('firebase/firestore');
 | 
			
		||||
const { createEncryptedConverter } = require('../firestoreConverter');
 | 
			
		||||
 | 
			
		||||
const userConverter = createEncryptedConverter(['api_key']);
 | 
			
		||||
 | 
			
		||||
function usersCol() {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, 'users').withConverter(userConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// These functions are mostly correct as they already operate on a top-level collection.
 | 
			
		||||
// We just need to ensure the signatures are consistent.
 | 
			
		||||
 | 
			
		||||
async function findOrCreate(user) {
 | 
			
		||||
    if (!user || !user.uid) throw new Error('User object and uid are required');
 | 
			
		||||
    const { uid, displayName, email } = user;
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const docRef = doc(usersCol(), uid);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
 | 
			
		||||
    if (docSnap.exists()) {
 | 
			
		||||
        await setDoc(docRef, { 
 | 
			
		||||
            display_name: displayName || docSnap.data().display_name || 'User',
 | 
			
		||||
            email: email || docSnap.data().email || 'no-email@example.com'
 | 
			
		||||
        }, { merge: true });
 | 
			
		||||
    } else {
 | 
			
		||||
        await setDoc(docRef, { uid, display_name: displayName || 'User', email: email || 'no-email@example.com', created_at: now });
 | 
			
		||||
    }
 | 
			
		||||
    const finalDoc = await getDoc(docRef);
 | 
			
		||||
    return finalDoc.data();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getById(uid) {
 | 
			
		||||
    const docRef = doc(usersCol(), uid);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
    return docSnap.exists() ? docSnap.data() : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function saveApiKey(uid, apiKey, provider = 'openai') {
 | 
			
		||||
    const docRef = doc(usersCol(), uid);
 | 
			
		||||
    await setDoc(docRef, { api_key: apiKey, provider }, { merge: true });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function update({ uid, displayName }) {
 | 
			
		||||
    const docRef = doc(usersCol(), uid);
 | 
			
		||||
    await setDoc(docRef, { display_name: displayName }, { merge: true });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function deleteById(uid) {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    const batch = writeBatch(db);
 | 
			
		||||
 | 
			
		||||
    // 1. Delete all sessions owned by the user
 | 
			
		||||
    const sessionsQuery = query(collection(db, 'sessions'), where('uid', '==', uid));
 | 
			
		||||
    const sessionsSnapshot = await getDocs(sessionsQuery);
 | 
			
		||||
    
 | 
			
		||||
    for (const sessionDoc of sessionsSnapshot.docs) {
 | 
			
		||||
        // Recursively delete sub-collections
 | 
			
		||||
        const subcollectionsToDelete = ['transcripts', 'ai_messages', 'summary'];
 | 
			
		||||
        for (const sub of subcollectionsToDelete) {
 | 
			
		||||
            const subColPath = `sessions/${sessionDoc.id}/${sub}`;
 | 
			
		||||
            const subSnapshot = await getDocs(query(collection(db, subColPath)));
 | 
			
		||||
            subSnapshot.forEach(d => batch.delete(d.ref));
 | 
			
		||||
        }
 | 
			
		||||
        batch.delete(sessionDoc.ref);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 2. Delete all presets owned by the user
 | 
			
		||||
    const presetsQuery = query(collection(db, 'prompt_presets'), where('uid', '==', uid));
 | 
			
		||||
    const presetsSnapshot = await getDocs(presetsQuery);
 | 
			
		||||
    presetsSnapshot.forEach(doc => batch.delete(doc.ref));
 | 
			
		||||
 | 
			
		||||
    // 3. Delete the user document itself
 | 
			
		||||
    const userRef = doc(usersCol(), uid);
 | 
			
		||||
    batch.delete(userRef);
 | 
			
		||||
 | 
			
		||||
    await batch.commit();
 | 
			
		||||
    return { success: true };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    findOrCreate,
 | 
			
		||||
    getById,
 | 
			
		||||
    saveApiKey,
 | 
			
		||||
    update,
 | 
			
		||||
    deleteById,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,19 +1,40 @@
 | 
			
		||||
const sqliteRepository = require('./sqlite.repository');
 | 
			
		||||
// const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../../common/services/authService');
 | 
			
		||||
const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../services/authService');
 | 
			
		||||
 | 
			
		||||
function getRepository() {
 | 
			
		||||
    // const user = authService.getCurrentUser();
 | 
			
		||||
    // if (user.isLoggedIn) {
 | 
			
		||||
    //     return firebaseRepository;
 | 
			
		||||
    // }
 | 
			
		||||
function getBaseRepository() {
 | 
			
		||||
    const user = authService.getCurrentUser();
 | 
			
		||||
    if (user && user.isLoggedIn) {
 | 
			
		||||
        return firebaseRepository;
 | 
			
		||||
    }
 | 
			
		||||
    return sqliteRepository;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    findOrCreate: (...args) => getRepository().findOrCreate(...args),
 | 
			
		||||
    getById: (...args) => getRepository().getById(...args),
 | 
			
		||||
    saveApiKey: (...args) => getRepository().saveApiKey(...args),
 | 
			
		||||
    update: (...args) => getRepository().update(...args),
 | 
			
		||||
    deleteById: (...args) => getRepository().deleteById(...args),
 | 
			
		||||
}; 
 | 
			
		||||
const userRepositoryAdapter = {
 | 
			
		||||
    findOrCreate: (user) => {
 | 
			
		||||
        // This function receives the full user object, which includes the uid. No need to inject.
 | 
			
		||||
        return getBaseRepository().findOrCreate(user);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    getById: () => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().getById(uid);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    saveApiKey: (apiKey, provider) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().saveApiKey(uid, apiKey, provider);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    update: (updateData) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().update({ uid, ...updateData });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    deleteById: () => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().deleteById(uid);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = userRepositoryAdapter; 
 | 
			
		||||
@ -40,7 +40,7 @@ function getById(uid) {
 | 
			
		||||
    return db.prepare('SELECT * FROM users WHERE uid = ?').get(uid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function saveApiKey(apiKey, uid, provider = 'openai') {
 | 
			
		||||
function saveApiKey(uid, apiKey, provider = 'openai') {
 | 
			
		||||
    const db = sqliteClient.getDb();
 | 
			
		||||
    try {
 | 
			
		||||
        const result = db.prepare('UPDATE users SET api_key = ?, provider = ? WHERE uid = ?').run(apiKey, provider, uid);
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
const { onAuthStateChanged, signInWithCustomToken, signOut } = require('firebase/auth');
 | 
			
		||||
const { BrowserWindow } = require('electron');
 | 
			
		||||
const { getFirebaseAuth } = require('./firebaseClient');
 | 
			
		||||
const userRepository = require('../repositories/user');
 | 
			
		||||
const fetch = require('node-fetch');
 | 
			
		||||
const encryptionService = require('./encryptionService');
 | 
			
		||||
 | 
			
		||||
async function getVirtualKeyByEmail(email, idToken) {
 | 
			
		||||
    if (!idToken) {
 | 
			
		||||
@ -37,6 +37,10 @@ class AuthService {
 | 
			
		||||
        this.currentUserMode = 'local'; // 'local' or 'firebase'
 | 
			
		||||
        this.currentUser = null;
 | 
			
		||||
        this.isInitialized = false;
 | 
			
		||||
 | 
			
		||||
        // Initialize immediately for the default local user on startup.
 | 
			
		||||
        // This ensures the key is ready before any login/logout state change.
 | 
			
		||||
        encryptionService.initializeKey(this.currentUserId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialize() {
 | 
			
		||||
@ -53,6 +57,10 @@ class AuthService {
 | 
			
		||||
                this.currentUserId = user.uid;
 | 
			
		||||
                this.currentUserMode = 'firebase';
 | 
			
		||||
 | 
			
		||||
                // ** Initialize encryption key for the logged-in user **
 | 
			
		||||
                await encryptionService.initializeKey(user.uid);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                // Start background task to fetch and save virtual key
 | 
			
		||||
                (async () => {
 | 
			
		||||
                    try {
 | 
			
		||||
@ -81,6 +89,9 @@ class AuthService {
 | 
			
		||||
                this.currentUser = null;
 | 
			
		||||
                this.currentUserId = 'default_user';
 | 
			
		||||
                this.currentUserMode = 'local';
 | 
			
		||||
                
 | 
			
		||||
                // ** Initialize encryption key for the default/local user **
 | 
			
		||||
                await encryptionService.initializeKey(this.currentUserId);
 | 
			
		||||
            }
 | 
			
		||||
            this.broadcastUserState();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										140
									
								
								src/common/services/encryptionService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/common/services/encryptionService.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
			
		||||
const crypto = require('crypto');
 | 
			
		||||
let keytar;
 | 
			
		||||
 | 
			
		||||
// Dynamically import keytar, as it's an optional dependency.
 | 
			
		||||
try {
 | 
			
		||||
    keytar = require('keytar');
 | 
			
		||||
} catch (error) {
 | 
			
		||||
    console.warn('[EncryptionService] keytar is not available. Will use in-memory key for this session. Restarting the app might be required for data persistence after login.');
 | 
			
		||||
    keytar = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SERVICE_NAME = 'com.pickle.glass'; // A unique identifier for the app in the keychain
 | 
			
		||||
let sessionKey = null; // In-memory fallback key
 | 
			
		||||
 | 
			
		||||
const ALGORITHM = 'aes-256-gcm';
 | 
			
		||||
const IV_LENGTH = 16; // For AES, this is always 16
 | 
			
		||||
const AUTH_TAG_LENGTH = 16;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Initializes the encryption key for a given user.
 | 
			
		||||
 * It first tries to get the key from the OS keychain.
 | 
			
		||||
 * If that fails, it generates a new key.
 | 
			
		||||
 * If keytar is available, it saves the new key.
 | 
			
		||||
 * Otherwise, it uses an in-memory key for the session.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} userId - The unique identifier for the user (e.g., Firebase UID).
 | 
			
		||||
 */
 | 
			
		||||
async function initializeKey(userId) {
 | 
			
		||||
    if (!userId) {
 | 
			
		||||
        throw new Error('A user ID must be provided to initialize the encryption key.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (keytar) {
 | 
			
		||||
        try {
 | 
			
		||||
            let key = await keytar.getPassword(SERVICE_NAME, userId);
 | 
			
		||||
            if (!key) {
 | 
			
		||||
                console.log(`[EncryptionService] No key found for ${userId}. Creating a new one.`);
 | 
			
		||||
                key = crypto.randomBytes(32).toString('hex');
 | 
			
		||||
                await keytar.setPassword(SERVICE_NAME, userId, key);
 | 
			
		||||
                console.log(`[EncryptionService] New key securely stored in keychain for ${userId}.`);
 | 
			
		||||
            } else {
 | 
			
		||||
                console.log(`[EncryptionService] Encryption key successfully retrieved from keychain for ${userId}.`);
 | 
			
		||||
            }
 | 
			
		||||
            sessionKey = key;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error('[EncryptionService] keytar failed. Falling back to in-memory key for this session.', error);
 | 
			
		||||
            keytar = null; // Disable keytar for the rest of the session to avoid repeated errors
 | 
			
		||||
            sessionKey = crypto.randomBytes(32).toString('hex');
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        // keytar is not available
 | 
			
		||||
        if (!sessionKey) {
 | 
			
		||||
            console.warn('[EncryptionService] Using in-memory session key. Data will not persist across restarts without keytar.');
 | 
			
		||||
            sessionKey = crypto.randomBytes(32).toString('hex');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (!sessionKey) {
 | 
			
		||||
        throw new Error('Failed to initialize encryption key.');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Encrypts a given text using AES-256-GCM.
 | 
			
		||||
 * @param {string} text The text to encrypt.
 | 
			
		||||
 * @returns {string | null} The encrypted data, as a base64 string containing iv, authTag, and content, or the original value if it cannot be encrypted.
 | 
			
		||||
 */
 | 
			
		||||
function encrypt(text) {
 | 
			
		||||
    if (!sessionKey) {
 | 
			
		||||
        console.error('[EncryptionService] Encryption key is not initialized. Cannot encrypt.');
 | 
			
		||||
        return text; // Return original if key is missing
 | 
			
		||||
    }
 | 
			
		||||
    if (text == null) { // checks for null or undefined
 | 
			
		||||
        return text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const key = Buffer.from(sessionKey, 'hex');
 | 
			
		||||
        const iv = crypto.randomBytes(IV_LENGTH);
 | 
			
		||||
        const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
 | 
			
		||||
        
 | 
			
		||||
        let encrypted = cipher.update(String(text), 'utf8', 'hex');
 | 
			
		||||
        encrypted += cipher.final('hex');
 | 
			
		||||
        
 | 
			
		||||
        const authTag = cipher.getAuthTag();
 | 
			
		||||
 | 
			
		||||
        // Prepend IV and AuthTag to the encrypted content, then encode as base64.
 | 
			
		||||
        return Buffer.concat([iv, authTag, Buffer.from(encrypted, 'hex')]).toString('base64');
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error('[EncryptionService] Encryption failed:', error);
 | 
			
		||||
        return text; // Return original on error
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Decrypts a given encrypted string.
 | 
			
		||||
 * @param {string} encryptedText The base64 encrypted text.
 | 
			
		||||
 * @returns {string | null} The decrypted text, or the original value if it cannot be decrypted.
 | 
			
		||||
 */
 | 
			
		||||
function decrypt(encryptedText) {
 | 
			
		||||
    if (!sessionKey) {
 | 
			
		||||
        console.error('[EncryptionService] Encryption key is not initialized. Cannot decrypt.');
 | 
			
		||||
        return encryptedText; // Return original if key is missing
 | 
			
		||||
    }
 | 
			
		||||
    if (encryptedText == null || typeof encryptedText !== 'string') {
 | 
			
		||||
        return encryptedText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const data = Buffer.from(encryptedText, 'base64');
 | 
			
		||||
        if (data.length < IV_LENGTH + AUTH_TAG_LENGTH) {
 | 
			
		||||
            // This is not a valid encrypted string, likely plain text.
 | 
			
		||||
            return encryptedText;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const key = Buffer.from(sessionKey, 'hex');
 | 
			
		||||
        const iv = data.slice(0, IV_LENGTH);
 | 
			
		||||
        const authTag = data.slice(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
 | 
			
		||||
        const encryptedContent = data.slice(IV_LENGTH + AUTH_TAG_LENGTH);
 | 
			
		||||
 | 
			
		||||
        const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
 | 
			
		||||
        decipher.setAuthTag(authTag);
 | 
			
		||||
        
 | 
			
		||||
        let decrypted = decipher.update(encryptedContent, 'hex', 'utf8');
 | 
			
		||||
        decrypted += decipher.final('utf8');
 | 
			
		||||
        
 | 
			
		||||
        return decrypted;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        // It's common for this to fail if the data is not encrypted (e.g., legacy data).
 | 
			
		||||
        // In that case, we return the original value.
 | 
			
		||||
        return encryptedText;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    initializeKey,
 | 
			
		||||
    encrypt,
 | 
			
		||||
    decrypt,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,6 +1,9 @@
 | 
			
		||||
const { initializeApp } = require('firebase/app');
 | 
			
		||||
const { initializeAuth } = require('firebase/auth');
 | 
			
		||||
const Store = require('electron-store');
 | 
			
		||||
const { setLogLevel } = require('firebase/firestore');
 | 
			
		||||
 | 
			
		||||
setLogLevel('debug');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Firebase Auth expects the `persistence` option passed to `initializeAuth()` to be *classes*,
 | 
			
		||||
 | 
			
		||||
@ -101,9 +101,8 @@ async function sendMessage(userPrompt) {
 | 
			
		||||
                        
 | 
			
		||||
                        // Save to DB
 | 
			
		||||
                        try {
 | 
			
		||||
                            const uid = authService.getCurrentUserId();
 | 
			
		||||
                            if (!uid) throw new Error("User not logged in, cannot save message.");
 | 
			
		||||
                            const sessionId = await sessionRepository.getOrCreateActive(uid, 'ask');
 | 
			
		||||
                            // The repository adapter will now handle the UID internally.
 | 
			
		||||
                            const sessionId = await sessionRepository.getOrCreateActive('ask');
 | 
			
		||||
                            await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
 | 
			
		||||
                            await askRepository.addAiMessage({ sessionId, role: 'assistant', content: fullResponse });
 | 
			
		||||
                            console.log(`[AskService] DB: Saved ask/answer pair to session ${sessionId}`);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								src/features/ask/repositories/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/features/ask/repositories/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
const { getFirestore, collection, addDoc, query, getDocs, orderBy } = require('firebase/firestore');
 | 
			
		||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
 | 
			
		||||
 | 
			
		||||
const aiMessageConverter = createEncryptedConverter(['content']);
 | 
			
		||||
 | 
			
		||||
function aiMessagesCol(sessionId) {
 | 
			
		||||
    if (!sessionId) throw new Error("Session ID is required to access AI messages.");
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, `sessions/${sessionId}/ai_messages`).withConverter(aiMessageConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function addAiMessage({ uid, sessionId, role, content, model = 'unknown' }) {
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const newMessage = {
 | 
			
		||||
        uid, // To identify the author of the message
 | 
			
		||||
        session_id: sessionId,
 | 
			
		||||
        sent_at: now,
 | 
			
		||||
        role,
 | 
			
		||||
        content,
 | 
			
		||||
        model,
 | 
			
		||||
        created_at: now,
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    const docRef = await addDoc(aiMessagesCol(sessionId), newMessage);
 | 
			
		||||
    return { id: docRef.id };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getAllAiMessagesBySessionId(sessionId) {
 | 
			
		||||
    const q = query(aiMessagesCol(sessionId), orderBy('sent_at', 'asc'));
 | 
			
		||||
    const querySnapshot = await getDocs(q);
 | 
			
		||||
    return querySnapshot.docs.map(doc => doc.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    addAiMessage,
 | 
			
		||||
    getAllAiMessagesBySessionId,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,18 +1,25 @@
 | 
			
		||||
const sqliteRepository = require('./sqlite.repository');
 | 
			
		||||
// const firebaseRepository = require('./firebase.repository'); // Future implementation
 | 
			
		||||
const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../../common/services/authService');
 | 
			
		||||
 | 
			
		||||
function getRepository() {
 | 
			
		||||
    // In the future, we can check the user's login status from authService
 | 
			
		||||
    // const user = authService.getCurrentUser();
 | 
			
		||||
    // if (user.isLoggedIn) {
 | 
			
		||||
    //     return firebaseRepository;
 | 
			
		||||
    // }
 | 
			
		||||
function getBaseRepository() {
 | 
			
		||||
    const user = authService.getCurrentUser();
 | 
			
		||||
    if (user && user.isLoggedIn) {
 | 
			
		||||
        return firebaseRepository;
 | 
			
		||||
    }
 | 
			
		||||
    return sqliteRepository;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Directly export functions for ease of use, decided by the strategy
 | 
			
		||||
module.exports = {
 | 
			
		||||
    addAiMessage: (...args) => getRepository().addAiMessage(...args),
 | 
			
		||||
    getAllAiMessagesBySessionId: (...args) => getRepository().getAllAiMessagesBySessionId(...args),
 | 
			
		||||
}; 
 | 
			
		||||
// The adapter layer that injects the UID
 | 
			
		||||
const askRepositoryAdapter = {
 | 
			
		||||
    addAiMessage: ({ sessionId, role, content, model }) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().addAiMessage({ uid, sessionId, role, content, model });
 | 
			
		||||
    },
 | 
			
		||||
    getAllAiMessagesBySessionId: (sessionId) => {
 | 
			
		||||
        // This function does not require a UID at the service level.
 | 
			
		||||
        return getBaseRepository().getAllAiMessagesBySessionId(sessionId);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = askRepositoryAdapter; 
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
const sqliteClient = require('../../../common/services/sqliteClient');
 | 
			
		||||
 | 
			
		||||
function addAiMessage({ sessionId, role, content, model = 'gpt-4.1' }) {
 | 
			
		||||
function addAiMessage({ uid, sessionId, role, content, model = 'unknown' }) {
 | 
			
		||||
    // uid is ignored in the SQLite implementation
 | 
			
		||||
    const db = sqliteClient.getDb();
 | 
			
		||||
    const messageId = require('crypto').randomUUID();
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
 | 
			
		||||
@ -77,12 +77,15 @@ class ListenService {
 | 
			
		||||
 | 
			
		||||
    async initializeNewSession() {
 | 
			
		||||
        try {
 | 
			
		||||
            const uid = authService.getCurrentUserId();
 | 
			
		||||
            if (!uid) {
 | 
			
		||||
                throw new Error("Cannot initialize session: user not logged in.");
 | 
			
		||||
            // The UID is no longer passed to the repository method directly.
 | 
			
		||||
            // The adapter layer handles UID injection. We just ensure a user is available.
 | 
			
		||||
            const user = authService.getCurrentUser();
 | 
			
		||||
            if (!user) {
 | 
			
		||||
                // This case should ideally not happen as authService initializes a default user.
 | 
			
		||||
                throw new Error("Cannot initialize session: auth service not ready.");
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            this.currentSessionId = await sessionRepository.getOrCreateActive(uid, 'listen');
 | 
			
		||||
            this.currentSessionId = await sessionRepository.getOrCreateActive('listen');
 | 
			
		||||
            console.log(`[DB] New listen session ensured: ${this.currentSessionId}`);
 | 
			
		||||
 | 
			
		||||
            // Set session ID for summary service
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								src/features/listen/stt/repositories/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/features/listen/stt/repositories/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
const { getFirestore, collection, addDoc, query, getDocs, orderBy } = require('firebase/firestore');
 | 
			
		||||
const { createEncryptedConverter } = require('../../../../common/repositories/firestoreConverter');
 | 
			
		||||
 | 
			
		||||
const transcriptConverter = createEncryptedConverter(['text']);
 | 
			
		||||
 | 
			
		||||
function transcriptsCol(sessionId) {
 | 
			
		||||
    if (!sessionId) throw new Error("Session ID is required to access transcripts.");
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, `sessions/${sessionId}/transcripts`).withConverter(transcriptConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function addTranscript({ uid, sessionId, speaker, text }) {
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const newTranscript = {
 | 
			
		||||
        uid, // To identify the author/source of the transcript
 | 
			
		||||
        session_id: sessionId,
 | 
			
		||||
        start_at: now,
 | 
			
		||||
        speaker,
 | 
			
		||||
        text,
 | 
			
		||||
        created_at: now,
 | 
			
		||||
    };
 | 
			
		||||
    const docRef = await addDoc(transcriptsCol(sessionId), newTranscript);
 | 
			
		||||
    return { id: docRef.id };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getAllTranscriptsBySessionId(sessionId) {
 | 
			
		||||
    const q = query(transcriptsCol(sessionId), orderBy('start_at', 'asc'));
 | 
			
		||||
    const querySnapshot = await getDocs(q);
 | 
			
		||||
    return querySnapshot.docs.map(doc => doc.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    addTranscript,
 | 
			
		||||
    getAllTranscriptsBySessionId,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,5 +1,23 @@
 | 
			
		||||
const sttRepository = require('./sqlite.repository');
 | 
			
		||||
const sqliteRepository = require('./sqlite.repository');
 | 
			
		||||
const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../../../common/services/authService');
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    ...sttRepository,
 | 
			
		||||
}; 
 | 
			
		||||
function getBaseRepository() {
 | 
			
		||||
    const user = authService.getCurrentUser();
 | 
			
		||||
    if (user && user.isLoggedIn) {
 | 
			
		||||
        return firebaseRepository;
 | 
			
		||||
    }
 | 
			
		||||
    return sqliteRepository;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const sttRepositoryAdapter = {
 | 
			
		||||
    addTranscript: ({ sessionId, speaker, text }) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().addTranscript({ uid, sessionId, speaker, text });
 | 
			
		||||
    },
 | 
			
		||||
    getAllTranscriptsBySessionId: (sessionId) => {
 | 
			
		||||
        return getBaseRepository().getAllTranscriptsBySessionId(sessionId);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = sttRepositoryAdapter; 
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
const sqliteClient = require('../../../../common/services/sqliteClient');
 | 
			
		||||
 | 
			
		||||
function addTranscript({ sessionId, speaker, text }) {
 | 
			
		||||
function addTranscript({ uid, sessionId, speaker, text }) {
 | 
			
		||||
    // uid is ignored in the SQLite implementation
 | 
			
		||||
    const db = sqliteClient.getDb();
 | 
			
		||||
    const transcriptId = require('crypto').randomUUID();
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
const { getFirestore, collection, doc, setDoc, getDoc } = require('firebase/firestore');
 | 
			
		||||
const { createEncryptedConverter } = require('../../../../common/repositories/firestoreConverter');
 | 
			
		||||
 | 
			
		||||
const fieldsToEncrypt = ['tldr', 'text', 'bullet_json', 'action_json'];
 | 
			
		||||
const summaryConverter = createEncryptedConverter(fieldsToEncrypt);
 | 
			
		||||
 | 
			
		||||
function summaryDocRef(sessionId) {
 | 
			
		||||
    if (!sessionId) throw new Error("Session ID is required to access summary.");
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    const path = `sessions/${sessionId}/summary`;
 | 
			
		||||
    return doc(collection(db, path).withConverter(summaryConverter), 'data');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function saveSummary({ uid, sessionId, tldr, text, bullet_json, action_json, model = 'unknown' }) {
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const summaryData = {
 | 
			
		||||
        uid, // To know who generated the summary
 | 
			
		||||
        generated_at: now,
 | 
			
		||||
        model,
 | 
			
		||||
        text,
 | 
			
		||||
        tldr,
 | 
			
		||||
        bullet_json,
 | 
			
		||||
        action_json,
 | 
			
		||||
        updated_at: now,
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    const docRef = summaryDocRef(sessionId);
 | 
			
		||||
    await setDoc(docRef, summaryData, { merge: true });
 | 
			
		||||
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getSummaryBySessionId(sessionId) {
 | 
			
		||||
    const docRef = summaryDocRef(sessionId);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
    return docSnap.exists() ? docSnap.data() : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    saveSummary,
 | 
			
		||||
    getSummaryBySessionId,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,5 +1,23 @@
 | 
			
		||||
const summaryRepository = require('./sqlite.repository');
 | 
			
		||||
const sqliteRepository = require('./sqlite.repository');
 | 
			
		||||
const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../../../common/services/authService');
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    ...summaryRepository,
 | 
			
		||||
}; 
 | 
			
		||||
function getBaseRepository() {
 | 
			
		||||
    const user = authService.getCurrentUser();
 | 
			
		||||
    if (user && user.isLoggedIn) {
 | 
			
		||||
        return firebaseRepository;
 | 
			
		||||
    }
 | 
			
		||||
    return sqliteRepository;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const summaryRepositoryAdapter = {
 | 
			
		||||
    saveSummary: ({ sessionId, tldr, text, bullet_json, action_json, model }) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().saveSummary({ uid, sessionId, tldr, text, bullet_json, action_json, model });
 | 
			
		||||
    },
 | 
			
		||||
    getSummaryBySessionId: (sessionId) => {
 | 
			
		||||
        return getBaseRepository().getSummaryBySessionId(sessionId);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = summaryRepositoryAdapter; 
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
const sqliteClient = require('../../../../common/services/sqliteClient');
 | 
			
		||||
 | 
			
		||||
function saveSummary({ sessionId, tldr, text, bullet_json, action_json, model = 'gpt-4.1' }) {
 | 
			
		||||
function saveSummary({ uid, sessionId, tldr, text, bullet_json, action_json, model = 'unknown' }) {
 | 
			
		||||
    // uid is ignored in the SQLite implementation
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        try {
 | 
			
		||||
            const db = sqliteClient.getDb();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										94
									
								
								src/features/settings/repositories/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/features/settings/repositories/firebase.repository.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
const { getFirestore, collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, query, where, orderBy } = require('firebase/firestore');
 | 
			
		||||
const { createEncryptedConverter } = require('../../../common/repositories/firestoreConverter');
 | 
			
		||||
 | 
			
		||||
const userPresetConverter = createEncryptedConverter(['prompt']);
 | 
			
		||||
 | 
			
		||||
const defaultPresetConverter = {
 | 
			
		||||
    toFirestore: (data) => data,
 | 
			
		||||
    fromFirestore: (snapshot, options) => {
 | 
			
		||||
        const data = snapshot.data(options);
 | 
			
		||||
        return { ...data, id: snapshot.id };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function userPresetsCol() {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, 'prompt_presets').withConverter(userPresetConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function defaultPresetsCol() {
 | 
			
		||||
    const db = getFirestore();
 | 
			
		||||
    return collection(db, 'defaults/prompt_presets').withConverter(defaultPresetConverter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getPresets(uid) {
 | 
			
		||||
    const userPresetsQuery = query(userPresetsCol(), where('uid', '==', uid));
 | 
			
		||||
    const defaultPresetsQuery = query(defaultPresetsCol());
 | 
			
		||||
 | 
			
		||||
    const [userSnapshot, defaultSnapshot] = await Promise.all([
 | 
			
		||||
        getDocs(userPresetsQuery),
 | 
			
		||||
        getDocs(defaultPresetsQuery)
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const presets = [
 | 
			
		||||
        ...defaultSnapshot.docs.map(d => d.data()),
 | 
			
		||||
        ...userSnapshot.docs.map(d => d.data())
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return presets.sort((a, b) => {
 | 
			
		||||
        if (a.is_default && !b.is_default) return -1;
 | 
			
		||||
        if (!a.is_default && b.is_default) return 1;
 | 
			
		||||
        return a.title.localeCompare(b.title);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getPresetTemplates() {
 | 
			
		||||
    const q = query(defaultPresetsCol(), orderBy('title', 'asc'));
 | 
			
		||||
    const snapshot = await getDocs(q);
 | 
			
		||||
    return snapshot.docs.map(doc => doc.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function createPreset({ uid, title, prompt }) {
 | 
			
		||||
    const now = Math.floor(Date.now() / 1000);
 | 
			
		||||
    const newPreset = {
 | 
			
		||||
        uid: uid,
 | 
			
		||||
        title,
 | 
			
		||||
        prompt,
 | 
			
		||||
        is_default: false,
 | 
			
		||||
        created_at: now,
 | 
			
		||||
    };
 | 
			
		||||
    const docRef = await addDoc(userPresetsCol(), newPreset);
 | 
			
		||||
    return { id: docRef.id };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function updatePreset(id, { title, prompt }, uid) {
 | 
			
		||||
    const docRef = doc(userPresetsCol(), id);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
 | 
			
		||||
    if (!docSnap.exists() || docSnap.data().uid !== uid || docSnap.data().is_default) {
 | 
			
		||||
        throw new Error("Preset not found or permission denied to update.");
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    await updateDoc(docRef, { title, prompt });
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function deletePreset(id, uid) {
 | 
			
		||||
    const docRef = doc(userPresetsCol(), id);
 | 
			
		||||
    const docSnap = await getDoc(docRef);
 | 
			
		||||
 | 
			
		||||
    if (!docSnap.exists() || docSnap.data().uid !== uid || docSnap.data().is_default) {
 | 
			
		||||
        throw new Error("Preset not found or permission denied to delete.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await deleteDoc(docRef);
 | 
			
		||||
    return { changes: 1 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getPresets,
 | 
			
		||||
    getPresetTemplates,
 | 
			
		||||
    createPreset,
 | 
			
		||||
    updatePreset,
 | 
			
		||||
    deletePreset,
 | 
			
		||||
}; 
 | 
			
		||||
@ -1,21 +1,39 @@
 | 
			
		||||
const sqliteRepository = require('./sqlite.repository');
 | 
			
		||||
// const firebaseRepository = require('./firebase.repository'); // Future implementation
 | 
			
		||||
const firebaseRepository = require('./firebase.repository');
 | 
			
		||||
const authService = require('../../../common/services/authService');
 | 
			
		||||
 | 
			
		||||
function getRepository() {
 | 
			
		||||
    // In the future, we can check the user's login status from authService
 | 
			
		||||
    // const user = authService.getCurrentUser();
 | 
			
		||||
    // if (user.isLoggedIn) {
 | 
			
		||||
    //     return firebaseRepository;
 | 
			
		||||
    // }
 | 
			
		||||
function getBaseRepository() {
 | 
			
		||||
    const user = authService.getCurrentUser();
 | 
			
		||||
    if (user && user.isLoggedIn) {
 | 
			
		||||
        return firebaseRepository;
 | 
			
		||||
    }
 | 
			
		||||
    return sqliteRepository;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Directly export functions for ease of use, decided by the strategy
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getPresets: (...args) => getRepository().getPresets(...args),
 | 
			
		||||
    getPresetTemplates: (...args) => getRepository().getPresetTemplates(...args),
 | 
			
		||||
    createPreset: (...args) => getRepository().createPreset(...args),
 | 
			
		||||
    updatePreset: (...args) => getRepository().updatePreset(...args),
 | 
			
		||||
    deletePreset: (...args) => getRepository().deletePreset(...args),
 | 
			
		||||
};
 | 
			
		||||
const settingsRepositoryAdapter = {
 | 
			
		||||
    getPresets: () => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().getPresets(uid);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getPresetTemplates: () => {
 | 
			
		||||
        return getBaseRepository().getPresetTemplates();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    createPreset: (options) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().createPreset({ uid, ...options });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    updatePreset: (id, options) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().updatePreset(id, options, uid);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    deletePreset: (id) => {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        return getBaseRepository().deletePreset(id, uid);
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = settingsRepositoryAdapter;
 | 
			
		||||
@ -208,13 +208,8 @@ async function saveSettings(settings) {
 | 
			
		||||
 | 
			
		||||
async function getPresets() {
 | 
			
		||||
    try {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        if (!uid) {
 | 
			
		||||
            // Logged out users only see default presets
 | 
			
		||||
            return await settingsRepository.getPresetTemplates();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const presets = await settingsRepository.getPresets(uid);
 | 
			
		||||
        // The adapter now handles which presets to return based on login state.
 | 
			
		||||
        const presets = await settingsRepository.getPresets();
 | 
			
		||||
        return presets;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error('[SettingsService] Error getting presets:', error);
 | 
			
		||||
@ -234,12 +229,8 @@ async function getPresetTemplates() {
 | 
			
		||||
 | 
			
		||||
async function createPreset(title, prompt) {
 | 
			
		||||
    try {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        if (!uid) {
 | 
			
		||||
            throw new Error("User not logged in, cannot create preset.");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const result = await settingsRepository.createPreset({ uid, title, prompt });
 | 
			
		||||
        // The adapter injects the UID.
 | 
			
		||||
        const result = await settingsRepository.createPreset({ title, prompt });
 | 
			
		||||
        
 | 
			
		||||
        windowNotificationManager.notifyRelevantWindows('presets-updated', {
 | 
			
		||||
            action: 'created',
 | 
			
		||||
@ -256,12 +247,8 @@ async function createPreset(title, prompt) {
 | 
			
		||||
 | 
			
		||||
async function updatePreset(id, title, prompt) {
 | 
			
		||||
    try {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        if (!uid) {
 | 
			
		||||
            throw new Error("User not logged in, cannot update preset.");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        await settingsRepository.updatePreset(id, { title, prompt }, uid);
 | 
			
		||||
        // The adapter injects the UID.
 | 
			
		||||
        await settingsRepository.updatePreset(id, { title, prompt });
 | 
			
		||||
        
 | 
			
		||||
        windowNotificationManager.notifyRelevantWindows('presets-updated', {
 | 
			
		||||
            action: 'updated',
 | 
			
		||||
@ -278,12 +265,8 @@ async function updatePreset(id, title, prompt) {
 | 
			
		||||
 | 
			
		||||
async function deletePreset(id) {
 | 
			
		||||
    try {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        if (!uid) {
 | 
			
		||||
            throw new Error("User not logged in, cannot delete preset.");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        await settingsRepository.deletePreset(id, uid);
 | 
			
		||||
        // The adapter injects the UID.
 | 
			
		||||
        await settingsRepository.deletePreset(id);
 | 
			
		||||
        
 | 
			
		||||
        windowNotificationManager.notifyRelevantWindows('presets-updated', {
 | 
			
		||||
            action: 'deleted',
 | 
			
		||||
@ -299,10 +282,9 @@ async function deletePreset(id) {
 | 
			
		||||
 | 
			
		||||
async function saveApiKey(apiKey, provider = 'openai') {
 | 
			
		||||
    try {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        if (!uid) {
 | 
			
		||||
        const user = authService.getCurrentUser();
 | 
			
		||||
        if (!user.isLoggedIn) {
 | 
			
		||||
            // For non-logged-in users, save to local storage
 | 
			
		||||
            const { app } = require('electron');
 | 
			
		||||
            const Store = require('electron-store');
 | 
			
		||||
            const store = new Store();
 | 
			
		||||
            store.set('apiKey', apiKey);
 | 
			
		||||
@ -318,8 +300,8 @@ async function saveApiKey(apiKey, provider = 'openai') {
 | 
			
		||||
            return { success: true };
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // For logged-in users, save to database
 | 
			
		||||
        await userRepository.saveApiKey(apiKey, uid, provider);
 | 
			
		||||
        // For logged-in users, use the repository adapter which injects the UID.
 | 
			
		||||
        await userRepository.saveApiKey(apiKey, provider);
 | 
			
		||||
        
 | 
			
		||||
        // Notify windows
 | 
			
		||||
        BrowserWindow.getAllWindows().forEach(win => {
 | 
			
		||||
@ -337,17 +319,16 @@ async function saveApiKey(apiKey, provider = 'openai') {
 | 
			
		||||
 | 
			
		||||
async function removeApiKey() {
 | 
			
		||||
    try {
 | 
			
		||||
        const uid = authService.getCurrentUserId();
 | 
			
		||||
        if (!uid) {
 | 
			
		||||
        const user = authService.getCurrentUser();
 | 
			
		||||
        if (!user.isLoggedIn) {
 | 
			
		||||
            // For non-logged-in users, remove from local storage
 | 
			
		||||
            const { app } = require('electron');
 | 
			
		||||
            const Store = require('electron-store');
 | 
			
		||||
            const store = new Store();
 | 
			
		||||
            store.delete('apiKey');
 | 
			
		||||
            store.delete('provider');
 | 
			
		||||
        } else {
 | 
			
		||||
            // For logged-in users, remove from database
 | 
			
		||||
            await userRepository.saveApiKey(null, uid, null);
 | 
			
		||||
            // For logged-in users, use the repository adapter.
 | 
			
		||||
            await userRepository.saveApiKey(null, null);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Notify windows
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										80
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								src/index.js
									
									
									
									
									
								
							@ -251,7 +251,9 @@ function setupGeneralIpcHandlers() {
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('save-api-key', (event, apiKey) => {
 | 
			
		||||
        try {
 | 
			
		||||
            userRepository.saveApiKey(apiKey, authService.getCurrentUserId());
 | 
			
		||||
            // The adapter injects the UID and handles local/firebase logic.
 | 
			
		||||
            // Assuming a default provider if not specified.
 | 
			
		||||
            userRepository.saveApiKey(apiKey, 'openai');
 | 
			
		||||
            BrowserWindow.getAllWindows().forEach(win => {
 | 
			
		||||
                win.webContents.send('api-key-updated');
 | 
			
		||||
            });
 | 
			
		||||
@ -263,7 +265,8 @@ function setupGeneralIpcHandlers() {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('get-user-presets', () => {
 | 
			
		||||
        return presetRepository.getPresets(authService.getCurrentUserId());
 | 
			
		||||
        // The adapter injects the UID.
 | 
			
		||||
        return presetRepository.getPresets();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('get-preset-templates', () => {
 | 
			
		||||
@ -302,89 +305,112 @@ function setupWebDataHandlers() {
 | 
			
		||||
    const userRepository = require('./common/repositories/user');
 | 
			
		||||
    const presetRepository = require('./common/repositories/preset');
 | 
			
		||||
 | 
			
		||||
    const handleRequest = (channel, responseChannel, payload) => {
 | 
			
		||||
    const handleRequest = async (channel, responseChannel, payload) => {
 | 
			
		||||
        let result;
 | 
			
		||||
        const currentUserId = authService.getCurrentUserId();
 | 
			
		||||
        // const currentUserId = authService.getCurrentUserId(); // No longer needed here
 | 
			
		||||
        try {
 | 
			
		||||
            switch (channel) {
 | 
			
		||||
                // SESSION
 | 
			
		||||
                case 'get-sessions':
 | 
			
		||||
                    result = sessionRepository.getAllByUserId(currentUserId);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    result = await sessionRepository.getAllByUserId();
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'get-session-details':
 | 
			
		||||
                    const session = sessionRepository.getById(payload);
 | 
			
		||||
                    const session = await sessionRepository.getById(payload);
 | 
			
		||||
                    if (!session) {
 | 
			
		||||
                        result = null;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    const transcripts = sttRepository.getAllTranscriptsBySessionId(payload);
 | 
			
		||||
                    const ai_messages = askRepository.getAllAiMessagesBySessionId(payload);
 | 
			
		||||
                    const summary = summaryRepository.getSummaryBySessionId(payload);
 | 
			
		||||
                    const [transcripts, ai_messages, summary] = await Promise.all([
 | 
			
		||||
                        sttRepository.getAllTranscriptsBySessionId(payload),
 | 
			
		||||
                        askRepository.getAllAiMessagesBySessionId(payload),
 | 
			
		||||
                        summaryRepository.getSummaryBySessionId(payload)
 | 
			
		||||
                    ]);
 | 
			
		||||
                    result = { session, transcripts, ai_messages, summary };
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'delete-session':
 | 
			
		||||
                    result = sessionRepository.deleteWithRelatedData(payload);
 | 
			
		||||
                    result = await sessionRepository.deleteWithRelatedData(payload);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'create-session':
 | 
			
		||||
                    const id = sessionRepository.create(currentUserId, 'ask');
 | 
			
		||||
                    if (payload.title) {
 | 
			
		||||
                        sessionRepository.updateTitle(id, payload.title);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    const id = await sessionRepository.create('ask');
 | 
			
		||||
                    if (payload && payload.title) {
 | 
			
		||||
                        await sessionRepository.updateTitle(id, payload.title);
 | 
			
		||||
                    }
 | 
			
		||||
                    result = { id };
 | 
			
		||||
                    break;
 | 
			
		||||
                
 | 
			
		||||
                // USER
 | 
			
		||||
                case 'get-user-profile':
 | 
			
		||||
                    result = userRepository.getById(currentUserId);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    result = await userRepository.getById();
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'update-user-profile':
 | 
			
		||||
                    result = userRepository.update({ uid: currentUserId, ...payload });
 | 
			
		||||
                     // Adapter injects UID
 | 
			
		||||
                    result = await userRepository.update(payload);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'find-or-create-user':
 | 
			
		||||
                    result = userRepository.findOrCreate(payload);
 | 
			
		||||
                    result = await userRepository.findOrCreate(payload);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'save-api-key':
 | 
			
		||||
                    result = userRepository.saveApiKey(payload, currentUserId);
 | 
			
		||||
                    // Assuming payload is { apiKey, provider }
 | 
			
		||||
                    result = await userRepository.saveApiKey(payload.apiKey, payload.provider);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'check-api-key-status':
 | 
			
		||||
                    const user = userRepository.getById(currentUserId);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    const user = await userRepository.getById();
 | 
			
		||||
                    result = { hasApiKey: !!user?.api_key && user.api_key.length > 0 };
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'delete-account':
 | 
			
		||||
                    result = userRepository.deleteById(currentUserId);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    result = await userRepository.deleteById();
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                // PRESET
 | 
			
		||||
                case 'get-presets':
 | 
			
		||||
                    result = presetRepository.getPresets(currentUserId);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    result = await presetRepository.getPresets();
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'create-preset':
 | 
			
		||||
                    result = presetRepository.create({ ...payload, uid: currentUserId });
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    result = await presetRepository.create(payload);
 | 
			
		||||
                    settingsService.notifyPresetUpdate('created', result.id, payload.title);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'update-preset':
 | 
			
		||||
                    result = presetRepository.update(payload.id, payload.data, currentUserId);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    result = await presetRepository.update(payload.id, payload.data);
 | 
			
		||||
                    settingsService.notifyPresetUpdate('updated', payload.id, payload.data.title);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'delete-preset':
 | 
			
		||||
                    result = presetRepository.delete(payload, currentUserId);
 | 
			
		||||
                    // Adapter injects UID
 | 
			
		||||
                    result = await presetRepository.delete(payload);
 | 
			
		||||
                    settingsService.notifyPresetUpdate('deleted', payload);
 | 
			
		||||
                    break;
 | 
			
		||||
                
 | 
			
		||||
                // BATCH
 | 
			
		||||
                case 'get-batch-data':
 | 
			
		||||
                    const includes = payload ? payload.split(',').map(item => item.trim()) : ['profile', 'presets', 'sessions'];
 | 
			
		||||
                    const batchResult = {};
 | 
			
		||||
                    const promises = {};
 | 
			
		||||
            
 | 
			
		||||
                    if (includes.includes('profile')) {
 | 
			
		||||
                        batchResult.profile = userRepository.getById(currentUserId);
 | 
			
		||||
                        // Adapter injects UID
 | 
			
		||||
                        promises.profile = userRepository.getById();
 | 
			
		||||
                    }
 | 
			
		||||
                    if (includes.includes('presets')) {
 | 
			
		||||
                        batchResult.presets = presetRepository.getPresets(currentUserId);
 | 
			
		||||
                        // Adapter injects UID
 | 
			
		||||
                        promises.presets = presetRepository.getPresets();
 | 
			
		||||
                    }
 | 
			
		||||
                    if (includes.includes('sessions')) {
 | 
			
		||||
                        batchResult.sessions = sessionRepository.getAllByUserId(currentUserId);
 | 
			
		||||
                        // Adapter injects UID
 | 
			
		||||
                        promises.sessions = sessionRepository.getAllByUserId();
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    const batchResult = {};
 | 
			
		||||
                    const promiseResults = await Promise.all(Object.values(promises));
 | 
			
		||||
                    Object.keys(promises).forEach((key, index) => {
 | 
			
		||||
                        batchResult[key] = promiseResults[index];
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    result = batchResult;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user