Files
adopt-a-street/backend/COUCHDB_SERVICE_GUIDE.md
William Valentin 2961107136 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>
2025-11-01 13:05:18 -07:00

11 KiB

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:

# CouchDB Configuration
COUCHDB_URL=http://localhost:5984
COUCHDB_DB_NAME=adopt-a-street

Connection Initialization

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

// 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

// 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

// 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

// 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

// 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

// Complete a task
const completedTask = await couchdbService.completeTask('user_123', 'task_789');

Social Features

// 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

// Join event
const updatedEvent = await couchdbService.joinEvent('user_123', 'event_789');

Leaderboard & Activity

// 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

// 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

{
  _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

{
  _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

# 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:

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

# 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

// 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:

// 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:

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:

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:

// 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.