# CouchDB Document Structure Design ## Overview This document outlines the comprehensive CouchDB document structure to replace the existing MongoDB models for the Adopt-a-Street application. The design prioritizes query performance, data consistency, and the denormalization requirements of a document-oriented database. ## Design Principles 1. **Denormalization over Normalization**: Since CouchDB doesn't support joins, we'll embed frequently accessed data 2. **Document Type Identification**: Each document includes a `type` field for easy filtering 3. **String IDs**: Convert MongoDB ObjectIds to strings for consistency 4. **Timestamp Handling**: Use CouchDB's built-in timestamps plus custom `createdAt`/`updatedAt` fields 5. **Query-First Design**: Structure documents based on common access patterns ## Document Types and Structures ### 1. User Documents (`type: "user"`) ```json { "_id": "user_1234567890abcdef", "type": "user", "name": "John Doe", "email": "john@example.com", "password": "hashed_password_here", "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" } ``` **Design Decisions:** - Embedded badge data to avoid additional lookups in profile views - Stats field for quick dashboard queries - Keep arrays of IDs for detailed queries when needed ### 2. Street Documents (`type: "street"`) ```json { "_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" } ``` **Design Decisions:** - Embedded adopter info for map display without additional queries - Stats field for quick overview on street details - Keep GeoJSON format for geospatial queries ### 3. Task Documents (`type: "task"`) ```json { "_id": "task_1234567890abcdef", "type": "task", "street": { "streetId": "street_abc123def456", "name": "Main Street", "location": { "type": "Point", "coordinates": [-74.0060, 40.7128] } }, "description": "Clean up litter on sidewalk", "completedBy": { "userId": "user_1234567890abcdef", "name": "John Doe", "profilePicture": "https://cloudinary.com/image.jpg" }, "status": "completed", "completedAt": "2024-01-15T10:30:00Z", "pointsAwarded": 10, "createdAt": "2024-01-10T00:00:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` **Design Decisions:** - Embedded street info for task list display - Embedded completer info for activity feeds - Added `completedAt` and `pointsAwarded` for gamification tracking ### 4. Post Documents (`type: "post"`) ```json { "_id": "post_1234567890abcdef", "type": "post", "user": { "userId": "user_1234567890abcdef", "name": "John Doe", "profilePicture": "https://cloudinary.com/image.jpg" }, "content": "Great day cleaning up Main Street!", "imageUrl": "https://cloudinary.com/post_image.jpg", "cloudinaryPublicId": "post_abc123", "likes": ["user_456", "user_789"], "likesCount": 2, "commentsCount": 5, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` **Design Decisions:** - Embedded user info for social feed display - Denormalized `likesCount` for quick sorting - Keep `likes` as array of user IDs for like/unlike operations ### 5. Comment Documents (`type: "comment"`) ```json { "_id": "comment_1234567890abcdef", "type": "comment", "post": { "postId": "post_1234567890abcdef", "content": "Great day cleaning up Main Street!", "userId": "user_1234567890abcdef" }, "user": { "userId": "user_1234567890abcdef", "name": "John Doe", "profilePicture": "https://cloudinary.com/image.jpg" }, "content": "Awesome work! 🎉", "createdAt": "2024-01-15T11:00:00Z", "updatedAt": "2024-01-15T11:00:00Z" } ``` **Design Decisions:** - Embedded both post and user info for comment display - Post reference for updating comment counts ### 6. Event Documents (`type: "event"`) ```json { "_id": "event_1234567890abcdef", "type": "event", "title": "Community Cleanup Day", "description": "Join us for a neighborhood cleanup event", "date": "2024-02-01T09:00:00Z", "location": "Central Park", "participants": [ { "userId": "user_1234567890abcdef", "name": "John Doe", "profilePicture": "https://cloudinary.com/image.jpg", "joinedAt": "2024-01-15T10:30:00Z" } ], "participantsCount": 1, "status": "upcoming", "createdAt": "2024-01-10T00:00:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` **Design Decisions:** - Embedded participant objects with join timestamps - Denormalized `participantsCount` for quick display - Rich participant data for event management ### 7. Report Documents (`type: "report"`) ```json { "_id": "report_1234567890abcdef", "type": "report", "street": { "streetId": "street_abc123def456", "name": "Main Street", "location": { "type": "Point", "coordinates": [-74.0060, 40.7128] } }, "user": { "userId": "user_1234567890abcdef", "name": "John Doe", "profilePicture": "https://cloudinary.com/image.jpg" }, "issue": "Broken streetlight needs repair", "imageUrl": "https://cloudinary.com/report_image.jpg", "cloudinaryPublicId": "report_abc123", "status": "open", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` **Design Decisions:** - Embedded street and user info for report management - Keep status for filtering and workflow management ### 8. Badge Documents (`type: "badge"`) ```json { "_id": "badge_1234567890abcdef", "type": "badge", "name": "Street Hero", "description": "Adopted 5 streets", "icon": "🏆", "criteria": { "type": "street_adoptions", "threshold": 5 }, "rarity": "rare", "order": 10, "isActive": true, "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z" } ``` **Design Decisions:** - Static badge definitions with criteria for gamification engine - Added `isActive` flag for badge management - `order` field for display sorting ### 9. Point Transaction Documents (`type: "point_transaction"`) ```json { "_id": "transaction_1234567890abcdef", "type": "point_transaction", "user": { "userId": "user_1234567890abcdef", "name": "John Doe" }, "amount": 10, "type": "task_completion", "description": "Completed task: Clean up litter on sidewalk", "relatedEntity": { "entityType": "Task", "entityId": "task_1234567890abcdef", "entityName": "Clean up litter on sidewalk" }, "balanceAfter": 150, "createdAt": "2024-01-15T10:30:00Z" } ``` **Design Decisions:** - Embedded user info for transaction history - Rich related entity data for audit trail - No updates needed - transactions are immutable ## Mango Index Strategy ### Primary Indexes ```javascript // User authentication { "index": { "fields": ["type", "email"] }, "name": "user-by-email", "type": "json" } // Geospatial queries for streets { "index": { "fields": ["type", "location"] }, "name": "streets-by-location", "type": "json" } // User's related data { "index": { "fields": ["type", "user.userId"] }, "name": "by-user", "type": "json" } // Leaderboards { "index": { "fields": ["type", "points"] }, "name": "users-by-points", "type": "json" } // Social feed { "index": { "fields": ["type", "createdAt"] }, "name": "posts-by-date", "type": "json" } // Street status { "index": { "fields": ["type", "status"] }, "name": "streets-by-status", "type": "json" } // Event queries { "index": { "fields": ["type", "date", "status"] }, "name": "events-by-date-status", "type": "json" } ``` ## Common Query Patterns ### 1. User Authentication ```javascript // Find user by email { "selector": { "type": "user", "email": "john@example.com" }, "limit": 1 } ``` ### 2. Geospatial Street Search ```javascript // Find streets within bounding box { "selector": { "type": "street", "status": "available", "location": { "$geoWithin": { "$box": [[-74.1, 40.6], [-73.9, 40.8]] } } } } ``` ### 3. User's Activity Feed ```javascript // Get user's posts, tasks, and events { "selector": { "$or": [ { "type": "post", "user.userId": "user_123" }, { "type": "task", "completedBy.userId": "user_123" }, { "type": "event", "participants": { "$elemMatch": { "userId": "user_123" } } } ] }, "sort": [{"createdAt": "desc"}] } ``` ### 4. Leaderboard ```javascript // Top users by points { "selector": { "type": "user", "points": {"$gt": 0} }, "sort": [{"points": "desc"}], "limit": 10 } ``` ### 5. Social Feed with Comments ```javascript // Get posts with user info and comment counts { "selector": { "type": "post" }, "sort": [{"createdAt": "desc"}], "limit": 20 } // Then fetch comments for each post { "selector": { "type": "comment", "post.postId": {"$in": ["post_123", "post_456"]} }, "sort": [{"createdAt": "asc"}] } ``` ## Data Consistency Strategy ### Update Patterns 1. **User Points Update**: - Update user document points - Create point transaction document - Check and award badges if thresholds met 2. **Post Creation**: - Create post document - Update user document (add to posts array, increment stats) - Update user's post creation count for badge criteria 3. **Task Completion**: - Update task document (status, completedBy, completedAt) - Update user document (add to completedTasks, increment stats, add points) - Update street document (increment completedTasksCount) - Create point transaction - Check for badge awards 4. **Street Adoption**: - Update street document (adoptedBy, status) - Update user document (add to adoptedStreets, increment stats) - Create point transaction - Check for badge awards ### Cascade Deletion Handling Since CouchDB doesn't have transactions, use a "soft delete" approach: ```javascript // Mark document as deleted { "_id": "post_123", "type": "post", "isDeleted": true, "deletedAt": "2024-01-15T10:30:00Z" } // Background cleanup job can periodically remove truly deleted documents ``` ### Counter Management For counters like `commentsCount`, `likesCount`, etc.: 1. **Optimistic Updates**: Update counter immediately, rollback if needed 2. **Periodic Reconciliation**: Background job to recalculate counts from actual data 3. **Event-Driven Updates**: Use CouchDB changes feed to trigger counter updates ## Migration Strategy ### Phase 1: Data Export and Transformation ```javascript // MongoDB to CouchDB transformation script const transformUser = (mongoUser) => ({ _id: `user_${mongoUser._id}`, type: "user", name: mongoUser.name, email: mongoUser.email, password: mongoUser.password, isPremium: mongoUser.isPremium, points: mongoUser.points, profilePicture: mongoUser.profilePicture, cloudinaryPublicId: mongoUser.cloudinaryPublicId, adoptedStreets: mongoUser.adoptedStreets.map(id => `street_${id}`), completedTasks: mongoUser.completedTasks.map(id => `task_${id}`), posts: mongoUser.posts.map(id => `post_${id}`), events: mongoUser.events.map(id => `event_${id}`), stats: { streetsAdopted: mongoUser.adoptedStreets.length, tasksCompleted: mongoUser.completedTasks.length, postsCreated: mongoUser.posts.length, eventsParticipated: mongoUser.events.length, badgesEarned: 0 // Will be populated from UserBadge collection }, createdAt: mongoUser.createdAt, updatedAt: mongoUser.updatedAt }); ``` ### Phase 2: Relationship Resolution 1. **First Pass**: Migrate all documents with ID references 2. **Second Pass**: Resolve relationships and embed data 3. **Third Pass**: Calculate and populate stats and counters ### Phase 3: Validation and Testing 1. **Data Integrity**: Verify all relationships are maintained 2. **Query Performance**: Test common query patterns 3. **Functionality**: Ensure all application features work ## Benefits of This Design 1. **Query Performance**: Most common queries require single document lookups 2. **Reduced Network Calls**: Embedded data eliminates multiple round trips 3. **Offline Capability**: Rich documents support better offline functionality 4. **Scalability**: Denormalized design scales well with read-heavy workloads 5. **Flexibility**: Document structure can evolve without schema migrations ## Trade-offs 1. **Data Duplication**: User data appears in multiple documents 2. **Update Complexity**: Changes to user data require updating multiple documents 3. **Storage Overhead**: Larger documents due to embedded data 4. **Consistency Challenges**: Eventual consistency for related data updates This design prioritizes read performance and user experience over write efficiency, which aligns well with the social community nature of the Adopt-a-Street application.