Spaces:
Runtime error
Runtime error
| import { initializeApp, cert, getApps } from 'firebase-admin/app'; | |
| import { getFirestore } from 'firebase-admin/firestore'; | |
| import path from 'path'; | |
| import fs from 'fs'; | |
| import { fileURLToPath } from 'url'; | |
| import logger from './logger.js'; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| let serviceAccount = null; | |
| // 1. Try environment variable | |
| if (process.env.FIREBASE_SERVICE_ACCOUNT) { | |
| try { | |
| serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT); | |
| logger.info('Firebase credentials loaded from FIREBASE_SERVICE_ACCOUNT environment variable.'); | |
| } catch (err) { | |
| logger.error('Failed to parse FIREBASE_SERVICE_ACCOUNT environment variable:', { | |
| error: err.message || err, | |
| }); | |
| } | |
| } | |
| // 2. Try local firebase-key.json | |
| const keyPath = path.resolve(__dirname, '../../firebase-key.json'); | |
| if (!serviceAccount && fs.existsSync(keyPath)) { | |
| try { | |
| const rawKey = fs.readFileSync(keyPath, 'utf8'); | |
| serviceAccount = JSON.parse(rawKey); | |
| logger.info('Firebase credentials loaded from firebase-key.json.'); | |
| } catch (err) { | |
| logger.error('Failed to load firebase-key.json:', { error: err.message || err }); | |
| } | |
| } | |
| let db; | |
| if (serviceAccount && process.env.NODE_ENV !== 'test') { | |
| try { | |
| if (getApps().length === 0) { | |
| initializeApp({ | |
| credential: cert(serviceAccount), | |
| }); | |
| } | |
| db = getFirestore(); | |
| logger.info('EcoGuide Connected to Firebase Firestore'); | |
| } catch (err) { | |
| logger.error('Firebase initialization failed:', { error: err.message || err }); | |
| } | |
| } | |
| if (!db) { | |
| // Safe mock db for testing or local development fallback | |
| logger.info('EcoGuide Running with IN-MEMORY / MOCK Firestore'); | |
| // Minimal Firestore mock interface | |
| class MockDoc { | |
| constructor(data, id) { | |
| this.id = id; | |
| this._data = data; | |
| this.exists = !!data; | |
| } | |
| data() { | |
| return this._data || null; | |
| } | |
| } | |
| class MockQuerySnapshot { | |
| constructor(docs) { | |
| this.docs = docs; | |
| this.empty = docs.length === 0; | |
| } | |
| } | |
| class MockCollection { | |
| constructor(name, parentDoc = null) { | |
| this.name = name; | |
| this.parentDoc = parentDoc; | |
| } | |
| doc(id) { | |
| const self = this; | |
| return { | |
| id, | |
| get: async () => { | |
| // Access the globally mocked store | |
| const path = self.getPath(id); | |
| const data = mockStore[path]; | |
| return new MockDoc(data, id); | |
| }, | |
| set: async (data) => { | |
| const path = self.getPath(id); | |
| mockStore[path] = { ...data }; | |
| }, | |
| update: async (data) => { | |
| const path = self.getPath(id); | |
| mockStore[path] = { ...(mockStore[path] || {}), ...data }; | |
| }, | |
| delete: async () => { | |
| const path = self.getPath(id); | |
| delete mockStore[path]; | |
| }, | |
| collection: (subName) => { | |
| return new MockCollection(subName, { path: self.getPath(id) }); | |
| }, | |
| }; | |
| } | |
| getPath(docId) { | |
| if (this.parentDoc) { | |
| return `${this.parentDoc.path}/${this.name}/${docId}`; | |
| } | |
| return `${this.name}/${docId}`; | |
| } | |
| async get() { | |
| const prefix = this.parentDoc ? `${this.parentDoc.path}/${this.name}/` : `${this.name}/`; | |
| const matchedDocs = []; | |
| for (const [key, val] of Object.entries(mockStore)) { | |
| if (key.startsWith(prefix)) { | |
| const parts = key.slice(prefix.length).split('/'); | |
| if (parts.length === 1) { | |
| matchedDocs.push(new MockDoc(val, parts[0])); | |
| } | |
| } | |
| } | |
| return new MockQuerySnapshot(matchedDocs); | |
| } | |
| where(field, op, value) { | |
| const self = this; | |
| return { | |
| get: async () => { | |
| const snapshot = await self.get(); | |
| const filtered = snapshot.docs.filter((doc) => { | |
| const val = doc.data()?.[field]; | |
| if (op === '==') return val === value; | |
| return false; | |
| }); | |
| return new MockQuerySnapshot(filtered); | |
| }, | |
| }; | |
| } | |
| } | |
| // Global in-memory storage for the mock Firestore | |
| const mockStore = {}; | |
| db = { | |
| collection: (name) => new MockCollection(name), | |
| _mockStore: mockStore, // Expose for testing | |
| }; | |
| } | |
| export default db; | |