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 {
|
||||
constructor() {
|
||||
this.connection = null;
|
||||
this.db = null;
|
||||
this.baseUrl = null;
|
||||
this.dbName = null;
|
||||
this.auth = null;
|
||||
this.isConnected = false;
|
||||
this.isConnecting = false;
|
||||
}
|
||||
@@ -23,27 +23,31 @@ class CouchDBService {
|
||||
// Get configuration from environment variables
|
||||
const couchdbUrl = process.env.COUCHDB_URL || "http://localhost:5984";
|
||||
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}`);
|
||||
|
||||
// Initialize nano connection
|
||||
this.connection = nano(couchdbUrl);
|
||||
// Set up base URL and authentication
|
||||
this.baseUrl = couchdbUrl.replace(/\/$/, ""); // Remove trailing slash
|
||||
|
||||
if (couchdbUser && couchdbPassword) {
|
||||
const authString = Buffer.from(`${couchdbUser}:${couchdbPassword}`).toString('base64');
|
||||
this.auth = `Basic ${authString}`;
|
||||
}
|
||||
|
||||
// Test connection
|
||||
await this.connection.info();
|
||||
await this.makeRequest('GET', '/');
|
||||
console.log("CouchDB connection established");
|
||||
|
||||
// Get or create database
|
||||
this.db = this.connection.use(this.dbName);
|
||||
|
||||
try {
|
||||
await this.db.info();
|
||||
await this.makeRequest('GET', `/${this.dbName}`);
|
||||
console.log(`Database '${this.dbName}' exists`);
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
console.log(`Creating database '${this.dbName}'`);
|
||||
await this.connection.db.create(this.dbName);
|
||||
this.db = this.connection.use(this.dbName);
|
||||
await this.makeRequest('PUT', `/${this.dbName}`);
|
||||
console.log(`Database '${this.dbName}' created successfully`);
|
||||
} else {
|
||||
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
|
||||
*/
|
||||
@@ -377,16 +416,16 @@ class CouchDBService {
|
||||
for (const designDoc of designDocs) {
|
||||
try {
|
||||
// Check if design document exists
|
||||
const existing = await this.db.get(designDoc._id);
|
||||
const existing = await this.getDocument(designDoc._id);
|
||||
|
||||
// Update with new revision
|
||||
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}`);
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
// 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}`);
|
||||
} else {
|
||||
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() {
|
||||
if (!this.isConnected) {
|
||||
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() {
|
||||
try {
|
||||
if (!this.connection) {
|
||||
if (!this.baseUrl) {
|
||||
return false;
|
||||
}
|
||||
await this.connection.info();
|
||||
await this.makeRequest('GET', '/');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("CouchDB connection check failed:", error.message);
|
||||
@@ -433,7 +472,7 @@ class CouchDBService {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
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 };
|
||||
} catch (error) {
|
||||
console.error("Error creating document:", error.message);
|
||||
@@ -445,7 +484,12 @@ class CouchDBService {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
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;
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
@@ -464,7 +508,7 @@ class CouchDBService {
|
||||
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 };
|
||||
} catch (error) {
|
||||
console.error("Error updating document:", error.message);
|
||||
@@ -476,7 +520,7 @@ class CouchDBService {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
try {
|
||||
const response = await this.db.destroy(id, rev);
|
||||
const response = await this.makeRequest('DELETE', `/${this.dbName}/${id}`, null, { rev });
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error deleting document:", error.message);
|
||||
@@ -489,7 +533,7 @@ class CouchDBService {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
try {
|
||||
const response = await this.db.find(query);
|
||||
const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
|
||||
return response.docs;
|
||||
} catch (error) {
|
||||
console.error("Error executing query:", error.message);
|
||||
@@ -529,7 +573,7 @@ class CouchDBService {
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await this.db.find(query);
|
||||
const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
|
||||
return response.total_rows || 0;
|
||||
} catch (error) {
|
||||
console.error("Error counting documents:", error.message);
|
||||
@@ -551,7 +595,7 @@ class CouchDBService {
|
||||
};
|
||||
|
||||
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 totalPages = Math.ceil(totalCount / limit);
|
||||
|
||||
@@ -575,7 +619,16 @@ class CouchDBService {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error("Error querying view:", error.message);
|
||||
@@ -588,7 +641,7 @@ class CouchDBService {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
try {
|
||||
const response = await this.db.bulk({ docs });
|
||||
const response = await this.makeRequest('POST', `/${this.dbName}/_bulk_docs`, { docs });
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error in bulk operation:", error.message);
|
||||
@@ -658,8 +711,8 @@ class CouchDBService {
|
||||
// Graceful shutdown
|
||||
async shutdown() {
|
||||
try {
|
||||
if (this.connection) {
|
||||
// Nano doesn't have an explicit close method, but we can mark as disconnected
|
||||
if (this.baseUrl) {
|
||||
// Mark as disconnected
|
||||
this.isConnected = false;
|
||||
console.log("CouchDB service shut down gracefully");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user