feat: implement comprehensive CouchDB service and migration utilities
- Add production-ready CouchDB service with connection management - Implement design documents with views and Mango indexes - Create CRUD operations with proper error handling - Add specialized helper methods for all document types - Include batch operations and conflict resolution - Create comprehensive migration script from MongoDB to CouchDB - Add test suite with graceful handling when CouchDB unavailable - Include detailed documentation and usage guide - Update environment configuration for CouchDB support - Follow existing code patterns and conventions 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
498
backend/COUCHDB_SERVICE_GUIDE.md
Normal file
498
backend/COUCHDB_SERVICE_GUIDE.md
Normal file
@@ -0,0 +1,498 @@
|
||||
# CouchDB Service Guide
|
||||
|
||||
This guide provides comprehensive documentation for the CouchDB service implementation in the Adopt-a-Street application.
|
||||
|
||||
## Overview
|
||||
|
||||
The `CouchDBService` class provides a production-ready interface for interacting with CouchDB, replacing MongoDB as the primary database. It includes connection management, CRUD operations, query helpers, and migration utilities.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add these to your `.env` file:
|
||||
|
||||
```bash
|
||||
# CouchDB Configuration
|
||||
COUCHDB_URL=http://localhost:5984
|
||||
COUCHDB_DB_NAME=adopt-a-street
|
||||
```
|
||||
|
||||
### Connection Initialization
|
||||
|
||||
```javascript
|
||||
const couchdbService = require('./services/couchdbService');
|
||||
|
||||
// Initialize the service (call once at application startup)
|
||||
await couchdbService.initialize();
|
||||
|
||||
// Check if connected
|
||||
if (couchdbService.isReady()) {
|
||||
console.log('CouchDB is ready');
|
||||
}
|
||||
```
|
||||
|
||||
## Core Features
|
||||
|
||||
### 1. Connection Management
|
||||
|
||||
- **Automatic connection**: Establishes connection on first use
|
||||
- **Database creation**: Creates database if it doesn't exist
|
||||
- **Health checks**: Validates connection status
|
||||
- **Graceful shutdown**: Properly closes connections
|
||||
|
||||
### 2. Design Documents & Indexes
|
||||
|
||||
The service automatically creates design documents with:
|
||||
|
||||
- **Views**: For common query patterns
|
||||
- **Mango Indexes**: For efficient document queries
|
||||
- **Geospatial indexes**: For location-based queries
|
||||
|
||||
#### Available Indexes
|
||||
|
||||
- `user-by-email`: Fast user authentication
|
||||
- `users-by-points`: Leaderboard queries
|
||||
- `streets-by-location`: Geospatial street searches
|
||||
- `streets-by-status`: Filter streets by adoption status
|
||||
- `posts-by-date`: Social feed ordering
|
||||
- `events-by-date-status`: Event management
|
||||
- `reports-by-status`: Report workflow management
|
||||
|
||||
### 3. Generic CRUD Operations
|
||||
|
||||
```javascript
|
||||
// Create document
|
||||
const doc = {
|
||||
_id: 'user_123',
|
||||
type: 'user',
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
};
|
||||
const created = await couchdbService.createDocument(doc);
|
||||
|
||||
// Get document
|
||||
const retrieved = await couchdbService.getDocument('user_123');
|
||||
|
||||
// Update document
|
||||
created.name = 'Jane Doe';
|
||||
const updated = await couchdbService.updateDocument(created);
|
||||
|
||||
// Delete document
|
||||
await couchdbService.deleteDocument('user_123', updated._rev);
|
||||
```
|
||||
|
||||
### 4. Query Operations
|
||||
|
||||
```javascript
|
||||
// Find with Mango query
|
||||
const users = await couchdbService.find({
|
||||
selector: {
|
||||
type: 'user',
|
||||
points: { $gt: 100 }
|
||||
},
|
||||
sort: [{ points: 'desc' }],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
// Find single document
|
||||
const user = await couchdbService.findOne({
|
||||
type: 'user',
|
||||
email: 'john@example.com'
|
||||
});
|
||||
|
||||
// Find by type
|
||||
const streets = await couchdbService.findByType('street', {
|
||||
status: 'available'
|
||||
});
|
||||
|
||||
// Use views
|
||||
const topUsers = await couchdbService.view('users', 'by-points', {
|
||||
limit: 10,
|
||||
descending: true
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Batch Operations
|
||||
|
||||
```javascript
|
||||
// Bulk create/update
|
||||
const docs = [
|
||||
{ _id: 'doc1', type: 'test', name: 'Test 1' },
|
||||
{ _id: 'doc2', type: 'test', name: 'Test 2' }
|
||||
];
|
||||
const result = await couchdbService.bulkDocs({ docs });
|
||||
```
|
||||
|
||||
### 6. Specialized Helper Methods
|
||||
|
||||
#### User Operations
|
||||
|
||||
```javascript
|
||||
// Find user by email
|
||||
const user = await couchdbService.findUserByEmail('john@example.com');
|
||||
|
||||
// Update user points with transaction
|
||||
const updatedUser = await couchdbService.updateUserPoints(
|
||||
'user_123',
|
||||
50,
|
||||
'Completed task: Clean up street',
|
||||
{
|
||||
entityType: 'Task',
|
||||
entityId: 'task_456',
|
||||
entityName: 'Clean up street'
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
#### Street Operations
|
||||
|
||||
```javascript
|
||||
// Find streets by location (geospatial)
|
||||
const bounds = [[-74.1, 40.6], [-73.9, 40.8]]; // NYC area
|
||||
const streets = await couchdbService.findStreetsByLocation(bounds);
|
||||
|
||||
// Adopt a street
|
||||
const adoptedStreet = await couchdbService.adoptStreet('user_123', 'street_456');
|
||||
```
|
||||
|
||||
#### Task Operations
|
||||
|
||||
```javascript
|
||||
// Complete a task
|
||||
const completedTask = await couchdbService.completeTask('user_123', 'task_789');
|
||||
```
|
||||
|
||||
#### Social Features
|
||||
|
||||
```javascript
|
||||
// Create a post
|
||||
const post = await couchdbService.createPost('user_123', {
|
||||
content: 'Great day cleaning up!',
|
||||
imageUrl: 'https://example.com/image.jpg',
|
||||
cloudinaryPublicId: 'post_123'
|
||||
});
|
||||
|
||||
// Toggle like on post
|
||||
const updatedPost = await couchdbService.togglePostLike('user_123', 'post_456');
|
||||
|
||||
// Add comment
|
||||
const comment = await couchdbService.addCommentToPost(
|
||||
'user_123',
|
||||
'post_456',
|
||||
'Great work! 🎉'
|
||||
);
|
||||
```
|
||||
|
||||
#### Event Management
|
||||
|
||||
```javascript
|
||||
// Join event
|
||||
const updatedEvent = await couchdbService.joinEvent('user_123', 'event_789');
|
||||
```
|
||||
|
||||
#### Leaderboard & Activity
|
||||
|
||||
```javascript
|
||||
// Get leaderboard
|
||||
const leaderboard = await couchdbService.getLeaderboard(10);
|
||||
|
||||
// Get user activity feed
|
||||
const activity = await couchdbService.getUserActivity('user_123', 20);
|
||||
|
||||
// Get social feed
|
||||
const feed = await couchdbService.getSocialFeed(20, 0);
|
||||
```
|
||||
|
||||
#### Report Management
|
||||
|
||||
```javascript
|
||||
// Create report
|
||||
const report = await couchdbService.createReport('user_123', 'street_456', {
|
||||
issue: 'Broken streetlight',
|
||||
imageUrl: 'https://example.com/report.jpg',
|
||||
cloudinaryPublicId: 'report_123'
|
||||
});
|
||||
|
||||
// Resolve report
|
||||
const resolved = await couchdbService.resolveReport('report_789');
|
||||
```
|
||||
|
||||
## Document Structure
|
||||
|
||||
### User Document
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: "user_1234567890abcdef",
|
||||
type: "user",
|
||||
name: "John Doe",
|
||||
email: "john@example.com",
|
||||
password: "hashed_password",
|
||||
isPremium: false,
|
||||
points: 150,
|
||||
profilePicture: "https://cloudinary.com/image.jpg",
|
||||
cloudinaryPublicId: "abc123",
|
||||
adoptedStreets: ["street_abc123", "street_def456"],
|
||||
completedTasks: ["task_123", "task_456"],
|
||||
posts: ["post_789", "post_012"],
|
||||
events: ["event_345", "event_678"],
|
||||
earnedBadges: [
|
||||
{
|
||||
badgeId: "badge_123",
|
||||
name: "Street Hero",
|
||||
description: "Adopted 5 streets",
|
||||
icon: "🏆",
|
||||
rarity: "rare",
|
||||
earnedAt: "2024-01-15T10:30:00Z",
|
||||
progress: 100
|
||||
}
|
||||
],
|
||||
stats: {
|
||||
streetsAdopted: 2,
|
||||
tasksCompleted: 5,
|
||||
postsCreated: 3,
|
||||
eventsParticipated: 2,
|
||||
badgesEarned: 1
|
||||
},
|
||||
createdAt: "2024-01-01T00:00:00Z",
|
||||
updatedAt: "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Street Document
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: "street_abc123def456",
|
||||
type: "street",
|
||||
name: "Main Street",
|
||||
location: {
|
||||
type: "Point",
|
||||
coordinates: [-74.0060, 40.7128]
|
||||
},
|
||||
adoptedBy: {
|
||||
userId: "user_1234567890abcdef",
|
||||
name: "John Doe",
|
||||
profilePicture: "https://cloudinary.com/image.jpg"
|
||||
},
|
||||
status: "adopted",
|
||||
stats: {
|
||||
tasksCount: 5,
|
||||
completedTasksCount: 3,
|
||||
reportsCount: 2,
|
||||
openReportsCount: 1
|
||||
},
|
||||
createdAt: "2024-01-01T00:00:00Z",
|
||||
updatedAt: "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
### Running the Migration
|
||||
|
||||
```bash
|
||||
# From the backend directory
|
||||
node scripts/migrate-to-couchdb.js
|
||||
```
|
||||
|
||||
### Migration Process
|
||||
|
||||
1. **Phase 1**: Export and transform all MongoDB documents to CouchDB format
|
||||
2. **Phase 2**: Resolve relationships and populate embedded data
|
||||
3. **Phase 3**: Calculate statistics and counters
|
||||
|
||||
### Migration Features
|
||||
|
||||
- **ID transformation**: MongoDB ObjectIds → prefixed string IDs
|
||||
- **Relationship resolution**: Populates embedded user/street data
|
||||
- **Statistics calculation**: Computes counts and aggregates
|
||||
- **Error handling**: Continues migration even if some documents fail
|
||||
- **Progress tracking**: Shows migration status and statistics
|
||||
|
||||
## Error Handling
|
||||
|
||||
The service provides comprehensive error handling:
|
||||
|
||||
```javascript
|
||||
try {
|
||||
const doc = await couchdbService.getDocument('nonexistent');
|
||||
if (!doc) {
|
||||
console.log('Document not found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Database error:', error.message);
|
||||
}
|
||||
```
|
||||
|
||||
### Common Error Scenarios
|
||||
|
||||
1. **Connection errors**: Service will retry and provide clear error messages
|
||||
2. **Document conflicts**: Use `resolveConflict()` method for handling
|
||||
3. **Validation errors**: Use `validateDocument()` before operations
|
||||
4. **Query errors**: Detailed error messages for invalid queries
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Index Usage
|
||||
|
||||
- Always use indexed fields in selectors
|
||||
- Use appropriate limit/sort combinations
|
||||
- Consider view queries for complex aggregations
|
||||
|
||||
### Batch Operations
|
||||
|
||||
- Use `bulkDocs()` for multiple document operations
|
||||
- Batch size recommendations: 100-500 documents per operation
|
||||
|
||||
### Memory Management
|
||||
|
||||
- Service maintains single connection instance
|
||||
- Automatic cleanup on shutdown
|
||||
- Efficient document streaming for large datasets
|
||||
|
||||
## Testing
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run CouchDB service tests
|
||||
bun test __tests__/services/couchdbService.test.js
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
|
||||
- Connection management
|
||||
- CRUD operations
|
||||
- Query functionality
|
||||
- Helper methods
|
||||
- Error handling
|
||||
- Migration utilities
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Document Design
|
||||
|
||||
- Include `type` field in all documents
|
||||
- Use consistent ID prefixes (`user_`, `street_`, etc.)
|
||||
- Embed frequently accessed data
|
||||
- Keep document sizes reasonable (< 1MB)
|
||||
|
||||
### 2. Query Optimization
|
||||
|
||||
- Use appropriate indexes
|
||||
- Limit result sets with pagination
|
||||
- Prefer views for complex queries
|
||||
- Use selectors efficiently
|
||||
|
||||
### 3. Error Handling
|
||||
|
||||
- Always wrap database calls in try-catch
|
||||
- Check for null returns from `getDocument()`
|
||||
- Handle conflicts gracefully
|
||||
- Log errors for debugging
|
||||
|
||||
### 4. Performance
|
||||
|
||||
- Use batch operations for bulk changes
|
||||
- Implement proper pagination
|
||||
- Cache frequently accessed data
|
||||
- Monitor query performance
|
||||
|
||||
## Integration with Existing Code
|
||||
|
||||
### Replacing MongoDB Operations
|
||||
|
||||
```javascript
|
||||
// Before (MongoDB)
|
||||
const user = await User.findOne({ email: 'john@example.com' });
|
||||
|
||||
// After (CouchDB)
|
||||
const user = await couchdbService.findUserByEmail('john@example.com');
|
||||
```
|
||||
|
||||
### Updating Routes
|
||||
|
||||
Most route handlers can be updated by replacing Mongoose calls with CouchDB service methods:
|
||||
|
||||
```javascript
|
||||
// Example route update
|
||||
router.get('/users/:id', async (req, res) => {
|
||||
try {
|
||||
const user = await couchdbService.getDocument(`user_${req.params.id}`);
|
||||
if (!user) {
|
||||
return res.status(404).json({ msg: 'User not found' });
|
||||
}
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
res.status(500).send('Server error');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Monitoring & Maintenance
|
||||
|
||||
### Health Checks
|
||||
|
||||
The service provides connection status monitoring:
|
||||
|
||||
```javascript
|
||||
if (couchdbService.isReady()) {
|
||||
// Database is available
|
||||
} else {
|
||||
// Handle database unavailability
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Monitoring
|
||||
|
||||
- Monitor query response times
|
||||
- Track document sizes
|
||||
- Watch for connection pool issues
|
||||
- Monitor index usage
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Connection refused**: Check CouchDB server status
|
||||
2. **Database not found**: Service creates automatically
|
||||
3. **Index not found**: Service creates design documents on init
|
||||
4. **Document conflicts**: Use conflict resolution methods
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging by setting environment variable:
|
||||
|
||||
```bash
|
||||
DEBUG=couchdb* node server.js
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
|
||||
1. **Change feed integration**: Real-time updates
|
||||
2. **Replication support**: Multi-instance deployment
|
||||
3. **Advanced analytics**: Complex aggregations
|
||||
4. **Caching layer**: Redis integration
|
||||
5. **Connection pooling**: High-performance scaling
|
||||
|
||||
### Extensibility
|
||||
|
||||
The service is designed to be easily extended:
|
||||
|
||||
```javascript
|
||||
// Add custom helper methods
|
||||
couchdbService.customMethod = async function(params) {
|
||||
// Custom implementation
|
||||
};
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The CouchDB service provides a robust, production-ready replacement for MongoDB in the Adopt-a-Street application. It offers comprehensive functionality, proper error handling, and migration tools to ensure a smooth transition.
|
||||
|
||||
For questions or issues, refer to the test files and implementation details in `backend/services/couchdbService.js`.
|
||||
Reference in New Issue
Block a user