UNPKG

mcp-gdrive-enhanced-markov

Version:

MCP server for Google Drive and Sheets with write capabilities, AI-powered PDF analysis, SQLite document indexing, and advanced directory exploration tools

287 lines (286 loc) 10.6 kB
/** * Unified database interface that wraps database_v2 with compatibility for old database.ts API */ import { dbV2 } from './database_v2.js'; class UnifiedDatabaseManager { async upsertDocument(doc) { // Convert old format to new format const baseDoc = { drive_id: doc.drive_id, drive_path: doc.drive_path, name: doc.name, mime_type: doc.mime_type, size: doc.size, modified_time: doc.modified_time, ai_model: doc.ai_model || 'gemini-2.5-flash-preview-05-20', doc_type: doc.doc_type || 'unknown', synopsis: doc.synopsis }; // First, upsert the base document const docId = await dbV2.upsertDocument(baseDoc); // Then insert type-specific data if we have ai_json if (doc.ai_json && doc.doc_type) { try { switch (doc.doc_type) { case 'contract': await this.upsertContract(docId, doc.ai_json); break; case 'invoice': await this.upsertInvoice(docId, doc.ai_json); break; case 'proposal': await this.upsertProposal(docId, doc.ai_json); break; // report type doesn't have specific tables in v2 } } catch (error) { console.warn(`Failed to insert type-specific data for ${doc.doc_type}:`, error); } } return docId; } async upsertContract(docId, fields) { const existing = await dbV2.getDocument(docId); if (!existing) return; try { await dbV2.insertContract(docId, { contract_number: fields.contract_number || undefined, contract_type: fields.contract_type || undefined, effective_date: fields.effective_date || undefined, expiration_date: fields.expiration_date || undefined, total_value: fields.value || fields.total_value || undefined, currency: fields.currency || undefined, payment_terms: fields.payment_terms || undefined, governing_law: fields.governing_law || undefined }); // Insert parties if available if (fields.parties && Array.isArray(fields.parties)) { const parties = fields.parties.map((party) => ({ party_name: typeof party === 'string' ? party : party.name, party_role: 'other' })); const db = dbV2.ensureConnection(); const contractResult = db.prepare('SELECT id FROM contracts WHERE document_id = ?').get(docId); if (contractResult) { await dbV2.insertContractParties(contractResult.id, parties); } } } catch (error) { // Ignore if already exists if (error instanceof Error && !error.message.includes('UNIQUE constraint failed')) { throw error; } } } async upsertInvoice(docId, fields) { try { await dbV2.insertInvoice(docId, { invoice_number: fields.invoice_number || undefined, invoice_date: fields.invoice_date || undefined, due_date: fields.due_date || undefined, supplier_name: fields.supplier || fields.supplier_name || undefined, customer_name: fields.customer || fields.customer_name || undefined, subtotal: fields.subtotal || undefined, tax_amount: fields.tax || fields.tax_amount || undefined, total_amount: fields.total || fields.total_amount || undefined, currency: fields.currency || undefined }); } catch (error) { // Ignore if already exists if (error instanceof Error && !error.message.includes('UNIQUE constraint failed')) { throw error; } } } async upsertProposal(docId, fields) { try { await dbV2.insertProposal(docId, { proposal_number: fields.proposal_number || undefined, client_name: fields.client || fields.client_name || undefined, project_name: fields.project_name || undefined, proposal_date: fields.proposal_date || undefined, total_value: fields.total_value || undefined, currency: fields.currency || undefined, timeline_days: fields.timeline ? parseInt(fields.timeline) : undefined }); } catch (error) { // Ignore if already exists if (error instanceof Error && !error.message.includes('UNIQUE constraint failed')) { throw error; } } } async getDocument(id) { const doc = await dbV2.getDocument(id); if (!doc) return null; // Convert to old format return { id: doc.id, drive_id: doc.drive_id, drive_path: doc.drive_path, name: doc.name, mime_type: doc.mime_type, doc_type: doc.doc_type === 'unknown' ? null : doc.doc_type, synopsis: doc.synopsis, ai_model: doc.ai_model, ai_ts_utc: doc.indexed_time, indexed_ts_utc: doc.indexed_time, modified_time: doc.modified_time, size: doc.size }; } async getDocumentByDriveId(driveId) { const doc = await dbV2.getDocumentByDriveId(driveId); if (!doc) return null; return { id: doc.id, drive_id: doc.drive_id, drive_path: doc.drive_path, name: doc.name, mime_type: doc.mime_type, doc_type: doc.doc_type === 'unknown' ? null : doc.doc_type, synopsis: doc.synopsis, ai_model: doc.ai_model, ai_ts_utc: doc.indexed_time, indexed_ts_utc: doc.indexed_time, modified_time: doc.modified_time, size: doc.size }; } async searchDocuments(query, limit = 25) { const results = await dbV2.searchDocuments(query); // Apply limit on results const limited = results.slice(0, limit); return limited.map(r => ({ id: r.id, drive_id: r.drive_id, drive_path: r.drive_path || '', name: r.name, doc_type: r.doc_type, synopsis: r.synopsis || '', score: r.confidence_score })); } searchByType(docType, limit = 25) { const db = this.ensureConnection(); const stmt = db.prepare(` SELECT id, drive_id, drive_path, name, doc_type, synopsis FROM documents WHERE doc_type = ? ORDER BY modified_time DESC LIMIT ? `); const results = stmt.all(docType, limit); return results.map(r => ({ id: r.id, drive_id: r.drive_id, drive_path: r.drive_path || '', name: r.name, doc_type: r.doc_type, synopsis: r.synopsis || '' })); } async getRecentDocuments(limit = 10) { const db = this.ensureConnection(); const stmt = db.prepare(` SELECT * FROM documents ORDER BY indexed_time DESC LIMIT ? `); const docs = stmt.all(limit); return docs.map(doc => ({ id: doc.id, drive_id: doc.drive_id, drive_path: doc.drive_path, name: doc.name, mime_type: doc.mime_type, doc_type: doc.doc_type === 'unknown' ? null : doc.doc_type, synopsis: doc.synopsis, ai_model: doc.ai_model, ai_ts_utc: doc.indexed_time, indexed_ts_utc: doc.indexed_time, modified_time: doc.modified_time, size: doc.size })); } async getDocumentsByType(docType, limit = 100) { const db = this.ensureConnection(); const stmt = db.prepare(` SELECT * FROM documents WHERE doc_type = ? ORDER BY indexed_time DESC LIMIT ? `); const docs = stmt.all(docType, limit); return docs.map(doc => ({ id: doc.id, drive_id: doc.drive_id, drive_path: doc.drive_path, name: doc.name, mime_type: doc.mime_type, doc_type: doc.doc_type === 'unknown' ? null : doc.doc_type, synopsis: doc.synopsis, ai_model: doc.ai_model, ai_ts_utc: doc.indexed_time, indexed_ts_utc: doc.indexed_time, modified_time: doc.modified_time, size: doc.size })); } async resetDatabase() { await dbV2.resetDatabase(); } getStats() { const db = this.ensureConnection(); const total = db.prepare('SELECT COUNT(*) as count FROM documents').get(); const byType = db.prepare(` SELECT doc_type, COUNT(*) as count FROM documents GROUP BY doc_type `).all(); const typeMap = {}; byType.forEach(row => { typeMap[row.doc_type] = row.count; }); return { total: total.count, by_type: typeMap }; } getDocumentsNeedingReindex(since) { const db = this.ensureConnection(); const stmt = db.prepare(` SELECT * FROM documents WHERE modified_time > ? OR indexed_time IS NULL ORDER BY modified_time DESC `); const docs = stmt.all(since.toISOString()); return docs.map(doc => ({ id: doc.id, drive_id: doc.drive_id, drive_path: doc.drive_path, name: doc.name, mime_type: doc.mime_type, doc_type: doc.doc_type === 'unknown' ? null : doc.doc_type, synopsis: doc.synopsis, ai_model: doc.ai_model, ai_ts_utc: doc.indexed_time, indexed_ts_utc: doc.indexed_time, modified_time: doc.modified_time, size: doc.size })); } // Direct access to v2 connection for advanced operations ensureConnection() { return dbV2.ensureConnection(); } } // Export singleton instance that's compatible with old 'db' export export const db = new UnifiedDatabaseManager();