# CouchDB Migration Guide

## Overview

This guide provides comprehensive documentation for migrating the Adopt-a-Street application from MongoDB to CouchDB. This migration improves scalability, provides better offline capabilities, and simplifies deployment.

## Migration Benefits

### Why CouchDB?

1. **Better Scalability**: CouchDB's master-master replication allows for easier scaling across multiple nodes
2. **Offline-First**: Built-in sync capabilities enable offline functionality
3. **Simpler Deployment**: No complex schema migrations required
4. **HTTP API**: Native REST API simplifies client-server communication
5. **Document Validation**: Built-in validation functions ensure data integrity
6. **MapReduce Views**: Powerful querying capabilities for complex data analysis

## Architecture Changes

### Before (MongoDB)
```
Backend (Node.js/Express)
├── Mongoose ODM
├── MongoDB Database
└── Schema Definitions
```

### After (CouchDB)
```
Backend (Node.js/Express)
├── Nano Client
├── CouchDB Database
└── Document Models
```

## Migration Steps

### Phase 1: Setup CouchDB

#### 1. Install CouchDB
```bash
# Ubuntu/Debian
sudo apt-get install couchdb

# macOS
brew install couchdb

# Docker
docker run -d -p 5984:5984 --name couchdb couchdb:latest
```

#### 2. Configure CouchDB
```bash
# Create admin user
curl -X PUT http://localhost:5984/_config/admins/admin -d '"password"'

# Create database
curl -X PUT http://admin:password@localhost:5984/adopt-a-street
```

#### 3. Install Nano Client
```bash
cd backend
bun add nano
```

### Phase 2: Update Backend Configuration

#### 1. Environment Variables
Update `.env` file:
```env
# Remove MongoDB
# MONGO_URI=mongodb://localhost:27017/adopt-a-street

# Add CouchDB
COUCHDB_URL=http://localhost:5984
COUCHDB_DB_NAME=adopt-a-street
COUCHDB_USERNAME=admin
COUCHDB_PASSWORD=password
```

#### 2. Create CouchDB Service
Create `backend/services/couchdbService.js`:
```javascript
const nano = require('nano')(
  `${process.env.COUCHDB_URL}/${process.env.COUCHDB_DB_NAME}`
);

class CouchDBService {
  constructor() {
    this.db = nano;
  }

  async create(doc) {
    return await this.db.insert(doc);
  }

  async get(id) {
    return await this.db.get(id);
  }

  async update(id, doc) {
    const existing = await this.get(id);
    doc._rev = existing._rev;
    return await this.db.insert(doc);
  }

  async delete(id) {
    const doc = await this.get(id);
    return await this.db.destroy(id, doc._rev);
  }

  async find(view, params = {}) {
    return await this.db.view('design_doc', view, params);
  }

  async all(params = {}) {
    return await this.db.list(params);
  }
}

module.exports = new CouchDBService();
```

### Phase 3: Migrate Data Models

#### 1. User Model
Create `backend/models/User.js`:
```javascript
const couchdbService = require('../services/couchdbService');

class User {
  static async create(userData) {
    const user = {
      _id: `user:${userData.email}`,
      type: 'user',
      name: userData.name,
      email: userData.email,
      password: userData.password, // Hashed
      points: 0,
      isPremium: false,
      adoptedStreets: [],
      completedTasks: [],
      earnedBadges: [],
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };

    return await couchdbService.create(user);
  }

  static async findById(id) {
    try {
      return await couchdbService.get(id);
    } catch (error) {
      if (error.statusCode === 404) return null;
      throw error;
    }
  }

  static async findByEmail(email) {
    const result = await couchdbService.find('by_email', { key: email });
    return result.rows.length > 0 ? result.rows[0].value : null;
  }

  static async update(id, updateData) {
    const user = await this.findById(id);
    if (!user) throw new Error('User not found');

    Object.assign(user, updateData);
    user.updatedAt = new Date().toISOString();

    return await couchdbService.update(id, user);
  }

  static async delete(id) {
    return await couchdbService.delete(id);
  }
}

module.exports = User;
```

#### 2. Street Model
Create `backend/models/Street.js`:
```javascript
const couchdbService = require('../services/couchdbService');

class Street {
  static async create(streetData) {
    const street = {
      _id: `street:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`,
      type: 'street',
      name: streetData.name,
      location: streetData.location, // GeoJSON
      description: streetData.description,
      status: 'active',
      adoptedBy: null,
      adoptionDate: null,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };

    return await couchdbService.create(street);
  }

  static async findById(id) {
    try {
      return await couchdbService.get(id);
    } catch (error) {
      if (error.statusCode === 404) return null;
      throw error;
    }
  }

  static async findAll(params = {}) {
    const result = await couchdbService.find('all_streets', params);
    return result.rows.map(row => row.value);
  }

  static async findNearby(coordinates, maxDistance = 1000) {
    const result = await couchdbService.find('nearby_streets', {
      lat: coordinates[1],
      lon: coordinates[0],
      radius: maxDistance
    });
    return result.rows.map(row => row.value);
  }

  static async update(id, updateData) {
    const street = await this.findById(id);
    if (!street) throw new Error('Street not found');

    Object.assign(street, updateData);
    street.updatedAt = new Date().toISOString();

    return await couchdbService.update(id, street);
  }

  static async delete(id) {
    return await couchdbService.delete(id);
  }
}

module.exports = Street;
```

### Phase 4: Create Design Documents

#### 1. Create Design Document
Create `backend/couchdb/design-doc.json`:
```json
{
  "_id": "_design/adopt_a_street",
  "views": {
    "by_email": {
      "map": "function(doc) { if (doc.type === 'user' && doc.email) { emit(doc.email, doc); } }"
    },
    "all_streets": {
      "map": "function(doc) { if (doc.type === 'street') { emit(doc._id, doc); } }"
    },
    "streets_by_user": {
      "map": "function(doc) { if (doc.type === 'street' && doc.adoptedBy) { emit(doc.adoptedBy, doc); } }"
    },
    "tasks_by_street": {
      "map": "function(doc) { if (doc.type === 'task' && doc.street) { emit(doc.street, doc); } }"
    },
    "posts_by_user": {
      "map": "function(doc) { if (doc.type === 'post' && doc.user) { emit(doc.user, doc); } }"
    },
    "events_by_date": {
      "map": "function(doc) { if (doc.type === 'event' && doc.date) { emit(doc.date, doc); } }"
    },
    "nearby_streets": {
      "map": "function(doc) { if (doc.type === 'street' && doc.location && doc.location.coordinates) { emit([doc.location.coordinates[1], doc.location.coordinates[0]], doc); } }"
    }
  },
  "validate_doc_update": "function(newDoc, oldDoc, userCtx) { if (newDoc.type && !['user', 'street', 'task', 'post', 'event', 'reward', 'report'].includes(newDoc.type)) { throw({forbidden: 'Invalid document type'}); } }"
}
```

#### 2. Install Design Document
```bash
curl -X PUT http://admin:password@localhost:5984/adopt-a-street/_design/adopt_a_street \
  -H "Content-Type: application/json" \
  -d @backend/couchdb/design-doc.json
```

### Phase 5: Update Routes

#### 1. Auth Routes
Update `backend/routes/auth.js`:
```javascript
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// Register
router.post('/register', async (req, res) => {
  try {
    const { name, email, password } = req.body;

    // Check if user exists
    const existingUser = await User.findByEmail(email);
    if (existingUser) {
      return res.status(400).json({ msg: 'User already exists' });
    }

    // Hash password
    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(password, salt);

    // Create user
    const user = await User.create({
      name,
      email,
      password: hashedPassword
    });

    // Create JWT
    const token = jwt.sign(
      { userId: user._id },
      process.env.JWT_SECRET,
      { expiresIn: '24h' }
    );

    res.json({ token, user: { id: user._id, name: user.name, email: user.email } });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
});

// Login
router.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;

    // Find user
    const user = await User.findByEmail(email);
    if (!user) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    // Check password
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    // Create JWT
    const token = jwt.sign(
      { userId: user._id },
      process.env.JWT_SECRET,
      { expiresIn: '24h' }
    );

    res.json({ token, user: { id: user._id, name: user.name, email: user.email } });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
});
```

### Phase 6: Data Migration Script

#### 1. Create Migration Script
Create `scripts/migrate-to-couchdb.js`:
```javascript
const mongoose = require('mongoose');
const nano = require('nano')('http://localhost:5984/adopt-a-street');

// MongoDB Models (old)
const User = require('../backend/models/User');
const Street = require('../backend/models/Street');
const Task = require('../backend/models/Task');
const Post = require('../backend/models/Post');
const Event = require('../backend/models/Event');
const Reward = require('../backend/models/Reward');
const Report = require('../backend/models/Report');

async function migrateUsers() {
  console.log('Migrating users...');
  const users = await User.find();
  
  for (const user of users) {
    const couchUser = {
      _id: `user:${user.email}`,
      type: 'user',
      name: user.name,
      email: user.email,
      password: user.password,
      points: user.points || 0,
      isPremium: user.isPremium || false,
      adoptedStreets: user.adoptedStreets || [],
      completedTasks: user.completedTasks || [],
      earnedBadges: user.earnedBadges || [],
      createdAt: user.createdAt,
      updatedAt: user.updatedAt
    };
    
    await nano.insert(couchUser);
  }
  console.log(`Migrated ${users.length} users`);
}

async function migrateStreets() {
  console.log('Migrating streets...');
  const streets = await Street.find();
  
  for (const street of streets) {
    const couchStreet = {
      _id: `street:${street._id}`,
      type: 'street',
      name: street.name,
      location: street.location,
      description: street.description,
      status: street.status || 'active',
      adoptedBy: street.adoptedBy,
      adoptionDate: street.adoptionDate,
      createdAt: street.createdAt,
      updatedAt: street.updatedAt
    };
    
    await nano.insert(couchStreet);
  }
  console.log(`Migrated ${streets.length} streets`);
}

// Add similar functions for other models...

async function runMigration() {
  try {
    // Connect to MongoDB
    await mongoose.connect('mongodb://localhost:27017/adopt-a-street');
    
    // Run migrations
    await migrateUsers();
    await migrateStreets();
    // await migrateTasks();
    // await migratePosts();
    // await migrateEvents();
    // await migrateRewards();
    // await migrateReports();
    
    console.log('Migration completed successfully!');
    process.exit(0);
  } catch (error) {
    console.error('Migration failed:', error);
    process.exit(1);
  }
}

runMigration();
```

#### 2. Run Migration
```bash
cd scripts
node migrate-to-couchdb.js
```

### Phase 7: Update Tests

#### 1. Update Test Setup
Update `backend/__tests__/setup.js`:
```javascript
const { CouchDBMem } = require('@couchdb/test-helpers');

let couchdb;

beforeAll(async () => {
  couchdb = new CouchDBMem();
  await couchdb.start();
  
  // Create test database
  await couchdb.createDb('adopt-a-street');
  
  // Install design document
  const designDoc = require('../../couchdb/design-doc.json');
  await couchdb.insertDoc('adopt-a-street', designDoc);
});

afterAll(async () => {
  await couchdb.stop();
});

beforeEach(async () => {
  // Clean up test data
  await couchdb.clearDb('adopt-a-street');
});
```

#### 2. Update Test Helpers
Update `backend/__tests__/utils/testHelpers.js`:
```javascript
const User = require('../../models/User');
const Street = require('../../models/Street');
const Task = require('../../models/Task');
const Post = require('../../models/Post');
const Event = require('../../models/Event');
const Reward = require('../../models/Reward');
const Report = require('../../models/Report');
const jwt = require('jsonwebtoken');

async function createTestUser(userData = {}) {
  const defaultUser = {
    name: 'Test User',
    email: 'test@example.com',
    password: 'password123'
  };
  
  const user = await User.create({ ...defaultUser, ...userData });
  const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET);
  
  return { user, token };
}

async function createTestStreet(streetData = {}) {
  const defaultStreet = {
    name: 'Test Street',
    location: {
      type: 'Point',
      coordinates: [-74.0060, 40.7128]
    },
    description: 'A test street'
  };
  
  return await Street.create({ ...defaultStreet, ...streetData });
}

// Add similar functions for other models...

module.exports = {
  createTestUser,
  createTestStreet,
  createTestTask,
  createTestPost,
  createTestEvent,
  createTestReward,
  createTestReport
};
```

## Deployment Considerations

### 1. Docker Configuration
Update `docker-compose.yml`:
```yaml
version: '3.8'
services:
  couchdb:
    image: couchdb:latest
    ports:
      - "5984:5984"
    environment:
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=password
    volumes:
      - couchdb_data:/opt/couchdb/data
    networks:
      - adopt-a-street

  backend:
    build: ./backend
    ports:
      - "5000:5000"
    environment:
      - COUCHDB_URL=http://couchdb:5984
      - COUCHDB_DB_NAME=adopt-a-street
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=password
    depends_on:
      - couchdb
    networks:
      - adopt-a-street

volumes:
  couchdb_data:

networks:
  adopt-a-street:
    driver: bridge
```

### 2. Kubernetes Deployment
Update `deploy/k8s/couchdb-statefulset.yaml`:
```yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: couchdb
spec:
  serviceName: couchdb
  replicas: 1
  selector:
    matchLabels:
      app: couchdb
  template:
    metadata:
      labels:
        app: couchdb
    spec:
      containers:
      - name: couchdb
        image: couchdb:latest
        ports:
        - containerPort: 5984
        env:
        - name: COUCHDB_USER
          valueFrom:
            secretKeyRef:
              name: couchdb-secret
              key: username
        - name: COUCHDB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: couchdb-secret
              key: password
        volumeMounts:
        - name: couchdb-data
          mountPath: /opt/couchdb/data
  volumeClaimTemplates:
  - metadata:
      name: couchdb-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi
```

## Performance Optimizations

### 1. Indexing
Create additional indexes for common queries:
```javascript
// Add to design document
"indexes": {
  "streets_by_status": {
    "map": "function(doc) { if (doc.type === 'street') { emit(doc.status, doc); } }"
  },
  "tasks_by_assignee": {
    "map": "function(doc) { if (doc.type === 'task' && doc.assignedTo) { emit(doc.assignedTo, doc); } }"
  },
  "events_by_organizer": {
    "map": "function(doc) { if (doc.type === 'event' && doc.organizer) { emit(doc.organizer, doc); } }"
  }
}
```

### 2. Caching
Implement Redis caching for frequently accessed data:
```javascript
const redis = require('redis');
const client = redis.createClient();

async function getCachedUser(id) {
  const cached = await client.get(`user:${id}`);
  if (cached) return JSON.parse(cached);
  
  const user = await User.findById(id);
  await client.setex(`user:${id}`, 3600, JSON.stringify(user));
  return user;
}
```

## Monitoring and Maintenance

### 1. Health Checks
```javascript
// Add to backend/routes/health.js
router.get('/couchdb', async (req, res) => {
  try {
    const response = await couchdbService.db.info();
    res.json({ status: 'healthy', couchdb: response });
  } catch (error) {
    res.status(500).json({ status: 'unhealthy', error: error.message });
  }
});
```

### 2. Backup Strategy
```bash
# Create backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/couchdb"

# Create backup
curl -X GET http://admin:password@localhost:5984/adopt-a-street/_all_docs?include_docs=true \
  -o "$BACKUP_DIR/backup_$DATE.json"

# Compress backup
gzip "$BACKUP_DIR/backup_$DATE.json"

# Clean old backups (keep last 7 days)
find $BACKUP_DIR -name "backup_*.json.gz" -mtime +7 -delete
```

## Troubleshooting

### Common Issues

1. **Connection Errors**
   - Check CouchDB is running: `curl http://localhost:5984`
   - Verify credentials in environment variables
   - Check network connectivity

2. **Document Conflicts**
   - Use revision numbers when updating documents
   - Implement conflict resolution strategies
   - Consider using bulk operations for multiple updates

3. **Performance Issues**
   - Add appropriate indexes
   - Use pagination for large result sets
   - Consider view caching for frequently accessed data

4. **Migration Failures**
   - Check MongoDB connection
   - Verify data format compatibility
   - Run migration in smaller batches

## Rollback Plan

If you need to rollback to MongoDB:

1. **Stop Application**
   ```bash
   docker-compose down
   ```

2. **Restore MongoDB Data**
   ```bash
   mongorestore --db adopt-a-street /path/to/mongodb/backup
   ```

3. **Update Configuration**
   - Restore original `.env` file
   - Revert code changes to use Mongoose

4. **Restart Application**
   ```bash
   docker-compose up -d
   ```

## Conclusion

This migration guide provides a comprehensive approach to migrating from MongoDB to CouchDB. The key benefits include improved scalability, offline capabilities, and simplified deployment. Take time to test thoroughly in a staging environment before deploying to production.

For additional support:
- [CouchDB Documentation](https://docs.couchdb.org/)
- [Nano Client Documentation](https://github.com/apache/couchdb-nano)
- [Community Forums](https://couchdb.apache.org/#mailing-lists)

---

**Migration Date**: 2025-11-03
**CouchDB Version**: 3.3+
**Node.js Version**: 18+
**Test Coverage**: Maintained at 55%+