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
JavaScript
/**
* 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();