fix: replace nano library with axios for CouchDB authentication
- Replace all nano operations with direct HTTP requests using axios - Implement proper Basic Auth header generation for CouchDB 3.3.3 - Maintain same interface and method signatures for compatibility - Add comprehensive error handling with proper status codes - Support all CRUD operations, queries, views, and bulk operations - Remove dependency on nano library to resolve authentication issues 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
const nano = require("nano");
|
const axios = require("axios");
|
||||||
|
|
||||||
class CouchDBService {
|
class CouchDBService {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.connection = null;
|
this.baseUrl = null;
|
||||||
this.db = null;
|
|
||||||
this.dbName = null;
|
this.dbName = null;
|
||||||
|
this.auth = null;
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.isConnecting = false;
|
this.isConnecting = false;
|
||||||
}
|
}
|
||||||
@@ -23,27 +23,31 @@ class CouchDBService {
|
|||||||
// Get configuration from environment variables
|
// Get configuration from environment variables
|
||||||
const couchdbUrl = process.env.COUCHDB_URL || "http://localhost:5984";
|
const couchdbUrl = process.env.COUCHDB_URL || "http://localhost:5984";
|
||||||
this.dbName = process.env.COUCHDB_DB_NAME || "adopt-a-street";
|
this.dbName = process.env.COUCHDB_DB_NAME || "adopt-a-street";
|
||||||
|
const couchdbUser = process.env.COUCHDB_USER;
|
||||||
|
const couchdbPassword = process.env.COUCHDB_PASSWORD;
|
||||||
|
|
||||||
console.log(`Connecting to CouchDB at ${couchdbUrl}`);
|
console.log(`Connecting to CouchDB at ${couchdbUrl}`);
|
||||||
|
|
||||||
// Initialize nano connection
|
// Set up base URL and authentication
|
||||||
this.connection = nano(couchdbUrl);
|
this.baseUrl = couchdbUrl.replace(/\/$/, ""); // Remove trailing slash
|
||||||
|
|
||||||
|
if (couchdbUser && couchdbPassword) {
|
||||||
|
const authString = Buffer.from(`${couchdbUser}:${couchdbPassword}`).toString('base64');
|
||||||
|
this.auth = `Basic ${authString}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Test connection
|
// Test connection
|
||||||
await this.connection.info();
|
await this.makeRequest('GET', '/');
|
||||||
console.log("CouchDB connection established");
|
console.log("CouchDB connection established");
|
||||||
|
|
||||||
// Get or create database
|
// Get or create database
|
||||||
this.db = this.connection.use(this.dbName);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.db.info();
|
await this.makeRequest('GET', `/${this.dbName}`);
|
||||||
console.log(`Database '${this.dbName}' exists`);
|
console.log(`Database '${this.dbName}' exists`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.statusCode === 404) {
|
if (error.response && error.response.status === 404) {
|
||||||
console.log(`Creating database '${this.dbName}'`);
|
console.log(`Creating database '${this.dbName}'`);
|
||||||
await this.connection.db.create(this.dbName);
|
await this.makeRequest('PUT', `/${this.dbName}`);
|
||||||
this.db = this.connection.use(this.dbName);
|
|
||||||
console.log(`Database '${this.dbName}' created successfully`);
|
console.log(`Database '${this.dbName}' created successfully`);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -67,6 +71,41 @@ class CouchDBService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make HTTP request to CouchDB with proper authentication
|
||||||
|
*/
|
||||||
|
async makeRequest(method, path, data = null, params = {}) {
|
||||||
|
const config = {
|
||||||
|
method,
|
||||||
|
url: `${this.baseUrl}${path}`,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.auth) {
|
||||||
|
config.headers.Authorization = this.auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
config.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios(config);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
const couchError = new Error(error.response.data.reason || error.message);
|
||||||
|
couchError.statusCode = error.response.status;
|
||||||
|
couchError.response = error.response.data;
|
||||||
|
throw couchError;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize design documents with indexes and views
|
* Initialize design documents with indexes and views
|
||||||
*/
|
*/
|
||||||
@@ -377,16 +416,16 @@ class CouchDBService {
|
|||||||
for (const designDoc of designDocs) {
|
for (const designDoc of designDocs) {
|
||||||
try {
|
try {
|
||||||
// Check if design document exists
|
// Check if design document exists
|
||||||
const existing = await this.db.get(designDoc._id);
|
const existing = await this.getDocument(designDoc._id);
|
||||||
|
|
||||||
// Update with new revision
|
// Update with new revision
|
||||||
designDoc._rev = existing._rev;
|
designDoc._rev = existing._rev;
|
||||||
await this.db.insert(designDoc);
|
await this.makeRequest('PUT', `/${this.dbName}/${designDoc._id}`, designDoc);
|
||||||
console.log(`Updated design document: ${designDoc._id}`);
|
console.log(`Updated design document: ${designDoc._id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.statusCode === 404) {
|
if (error.statusCode === 404) {
|
||||||
// Create new design document
|
// Create new design document
|
||||||
await this.db.insert(designDoc);
|
await this.makeRequest('PUT', `/${this.dbName}/${designDoc._id}`, designDoc);
|
||||||
console.log(`Created design document: ${designDoc._id}`);
|
console.log(`Created design document: ${designDoc._id}`);
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error creating design document ${designDoc._id}:`, error.message);
|
console.error(`Error creating design document ${designDoc._id}:`, error.message);
|
||||||
@@ -396,13 +435,13 @@ class CouchDBService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get database instance
|
* Get database instance (for compatibility)
|
||||||
*/
|
*/
|
||||||
getDB() {
|
getDB() {
|
||||||
if (!this.isConnected) {
|
if (!this.isConnected) {
|
||||||
throw new Error("CouchDB not connected. Call initialize() first.");
|
throw new Error("CouchDB not connected. Call initialize() first.");
|
||||||
}
|
}
|
||||||
return this.db;
|
return this; // Return this instance for method chaining
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -417,10 +456,10 @@ class CouchDBService {
|
|||||||
*/
|
*/
|
||||||
async checkConnection() {
|
async checkConnection() {
|
||||||
try {
|
try {
|
||||||
if (!this.connection) {
|
if (!this.baseUrl) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await this.connection.info();
|
await this.makeRequest('GET', '/');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("CouchDB connection check failed:", error.message);
|
console.error("CouchDB connection check failed:", error.message);
|
||||||
@@ -433,7 +472,7 @@ class CouchDBService {
|
|||||||
if (!this.isConnected) await this.initialize();
|
if (!this.isConnected) await this.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.db.insert(doc);
|
const response = await this.makeRequest('POST', `/${this.dbName}`, doc);
|
||||||
return { ...doc, _id: response.id, _rev: response.rev };
|
return { ...doc, _id: response.id, _rev: response.rev };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating document:", error.message);
|
console.error("Error creating document:", error.message);
|
||||||
@@ -445,7 +484,12 @@ class CouchDBService {
|
|||||||
if (!this.isConnected) await this.initialize();
|
if (!this.isConnected) await this.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const doc = await this.db.get(id, options);
|
const params = new URLSearchParams();
|
||||||
|
if (options.rev) params.append('rev', options.rev);
|
||||||
|
if (options.revs) params.append('revs', 'true');
|
||||||
|
if (options.open_revs) params.append('open_revs', options.open_revs);
|
||||||
|
|
||||||
|
const doc = await this.makeRequest('GET', `/${this.dbName}/${id}`, null, params.toString());
|
||||||
return doc;
|
return doc;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.statusCode === 404) {
|
if (error.statusCode === 404) {
|
||||||
@@ -464,7 +508,7 @@ class CouchDBService {
|
|||||||
throw new Error("Document must have _id and _rev for update");
|
throw new Error("Document must have _id and _rev for update");
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.db.insert(doc);
|
const response = await this.makeRequest('PUT', `/${this.dbName}/${doc._id}`, doc);
|
||||||
return { ...doc, _rev: response.rev };
|
return { ...doc, _rev: response.rev };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating document:", error.message);
|
console.error("Error updating document:", error.message);
|
||||||
@@ -476,7 +520,7 @@ class CouchDBService {
|
|||||||
if (!this.isConnected) await this.initialize();
|
if (!this.isConnected) await this.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.db.destroy(id, rev);
|
const response = await this.makeRequest('DELETE', `/${this.dbName}/${id}`, null, { rev });
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting document:", error.message);
|
console.error("Error deleting document:", error.message);
|
||||||
@@ -489,7 +533,7 @@ class CouchDBService {
|
|||||||
if (!this.isConnected) await this.initialize();
|
if (!this.isConnected) await this.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.db.find(query);
|
const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
|
||||||
return response.docs;
|
return response.docs;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error executing query:", error.message);
|
console.error("Error executing query:", error.message);
|
||||||
@@ -529,7 +573,7 @@ class CouchDBService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.db.find(query);
|
const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
|
||||||
return response.total_rows || 0;
|
return response.total_rows || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error counting documents:", error.message);
|
console.error("Error counting documents:", error.message);
|
||||||
@@ -551,7 +595,7 @@ class CouchDBService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.db.find(query);
|
const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
|
||||||
const totalCount = response.total_rows || 0;
|
const totalCount = response.total_rows || 0;
|
||||||
const totalPages = Math.ceil(totalCount / limit);
|
const totalPages = Math.ceil(totalCount / limit);
|
||||||
|
|
||||||
@@ -575,7 +619,16 @@ class CouchDBService {
|
|||||||
if (!this.isConnected) await this.initialize();
|
if (!this.isConnected) await this.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.db.view(designDoc, viewName, params);
|
const queryParams = new URLSearchParams();
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
queryParams.append(key, JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
queryParams.append(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.makeRequest('GET', `/${this.dbName}/_design/${designDoc}/_view/${viewName}`, null, queryParams.toString());
|
||||||
return response.rows.map(row => row.value);
|
return response.rows.map(row => row.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error querying view:", error.message);
|
console.error("Error querying view:", error.message);
|
||||||
@@ -588,7 +641,7 @@ class CouchDBService {
|
|||||||
if (!this.isConnected) await this.initialize();
|
if (!this.isConnected) await this.initialize();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.db.bulk({ docs });
|
const response = await this.makeRequest('POST', `/${this.dbName}/_bulk_docs`, { docs });
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in bulk operation:", error.message);
|
console.error("Error in bulk operation:", error.message);
|
||||||
@@ -658,8 +711,8 @@ class CouchDBService {
|
|||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
async shutdown() {
|
async shutdown() {
|
||||||
try {
|
try {
|
||||||
if (this.connection) {
|
if (this.baseUrl) {
|
||||||
// Nano doesn't have an explicit close method, but we can mark as disconnected
|
// Mark as disconnected
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
console.log("CouchDB service shut down gracefully");
|
console.log("CouchDB service shut down gracefully");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user