#!/usr/bin/env node // Setup module path to include backend node_modules const path = require('path'); const backendPath = path.join(__dirname, '..', 'backend'); process.env.NODE_PATH = path.join(backendPath, 'node_modules') + ':' + (process.env.NODE_PATH || ''); require('module').Module._initPaths(); const Nano = require('nano'); require('dotenv').config({ path: path.join(backendPath, '.env') }); // Configuration const COUCHDB_URL = process.env.COUCHDB_URL || 'http://localhost:5984'; const COUCHDB_USER = process.env.COUCHDB_USER || 'admin'; const COUCHDB_PASSWORD = process.env.COUCHDB_PASSWORD || 'admin'; const COUCHDB_DB_NAME = process.env.COUCHDB_DB_NAME || 'adopt-a-street'; class CouchDBSetup { constructor() { this.nano = Nano({ url: COUCHDB_URL, auth: { username: COUCHDB_USER, password: COUCHDB_PASSWORD } }); } async initialize() { console.log('šŸš€ Initializing CouchDB setup...'); try { // Test connection await this.nano.info(); console.log('āœ… Connected to CouchDB'); } catch (error) { console.error('āŒ Failed to connect to CouchDB:', error.message); process.exit(1); } } async createDatabase() { console.log(`šŸ“¦ Creating database: ${COUCHDB_DB_NAME}`); try { await this.nano.db.create(COUCHDB_DB_NAME); console.log('āœ… Database created successfully'); } catch (error) { if (error.statusCode === 412) { console.log('ā„¹ļø Database already exists'); } else { console.error('āŒ Failed to create database:', error.message); throw error; } } } async createIndexes() { console.log('šŸ” Creating indexes...'); const db = this.nano.use(COUCHDB_DB_NAME); // Create design document with indexes const designDoc = { _id: '_design/adopt-a-street', views: { users_by_email: { map: "function(doc) { if (doc.type === 'user') { emit(doc.email, doc); } }" }, streets_by_location: { map: "function(doc) { if (doc.type === 'street') { emit(doc.location, doc); } }" }, by_user: { map: "function(doc) { if (doc.user && doc.user.userId) { emit(doc.user.userId, doc); } }" }, users_by_points: { map: "function(doc) { if (doc.type === 'user') { emit(doc.points, doc); } }" }, posts_by_date: { map: "function(doc) { if (doc.type === 'post') { emit(doc.createdAt, doc); } }" }, streets_by_status: { map: "function(doc) { if (doc.type === 'street') { emit(doc.status, doc); } }" }, events_by_date_status: { map: "function(doc) { if (doc.type === 'event') { emit([doc.date, doc.status], doc); } }" }, comments_by_post: { map: "function(doc) { if (doc.type === 'comment' && doc.post && doc.post.postId) { emit(doc.post.postId, doc); } }" }, transactions_by_user_date: { map: "function(doc) { if (doc.type === 'point_transaction' && doc.user && doc.user.userId) { emit([doc.user.userId, doc.createdAt], doc); } }" } }, indexes: { users_by_email: { index: { fields: ["type", "email"] }, name: "user-by-email", type: "json" }, streets_by_location: { index: { fields: ["type", "location"] }, name: "streets-by-location", type: "json" }, by_user: { index: { fields: ["type", "user.userId"] }, name: "by-user", type: "json" }, users_by_points: { index: { fields: ["type", "points"] }, name: "users-by-points", type: "json" }, posts_by_date: { index: { fields: ["type", "createdAt"] }, name: "posts-by-date", type: "json" }, streets_by_status: { index: { fields: ["type", "status"] }, name: "streets-by-status", type: "json" }, events_by_date_status: { index: { fields: ["type", "date", "status"] }, name: "events-by-date-status", type: "json" }, comments_by_post: { index: { fields: ["type", "post.postId"] }, name: "comments-by-post", type: "json" }, transactions_by_user_date: { index: { fields: ["type", "user.userId", "createdAt"] }, name: "transactions-by-user-date", type: "json" } }, language: "javascript" }; try { await db.insert(designDoc); console.log('āœ… Design document and indexes created successfully'); } catch (error) { if (error.statusCode === 409) { // Document already exists, update it const existing = await db.get('_design/adopt-a-street'); designDoc._rev = existing._rev; await db.insert(designDoc); console.log('āœ… Design document and indexes updated successfully'); } else { console.error('āŒ Failed to create indexes:', error.message); throw error; } } } async createSecurityDocument() { console.log('šŸ”’ Setting up security document...'); const db = this.nano.use(COUCHDB_DB_NAME); const securityDoc = { admins: { names: [COUCHDB_USER], roles: [] }, members: { names: [], roles: [] } }; try { await db.insert(securityDoc, '_security'); console.log('āœ… Security document created successfully'); } catch (error) { console.warn('āš ļø Failed to create security document:', error.message); } } async seedBadges() { console.log('šŸ† Seeding badges...'); const db = this.nano.use(COUCHDB_DB_NAME); // Check if badges already exist try { const existingBadges = await db.find({ selector: { type: 'badge' }, limit: 1 }); if (existingBadges.docs.length > 0) { console.log('ā„¹ļø Badges already exist, skipping seeding'); return; } } catch (error) { // Continue with seeding } const badges = [ // Street Adoption Badges { _id: 'badge_first_adoption', type: 'badge', name: 'First Adoption', description: 'Adopted your first street', icon: 'šŸ”', criteria: { type: 'street_adoptions', threshold: 1 }, rarity: 'common', order: 1, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, { _id: 'badge_street_adopter', type: 'badge', name: 'Street Adopter', description: 'Adopted 5 streets', icon: 'šŸ˜ļø', criteria: { type: 'street_adoptions', threshold: 5 }, rarity: 'rare', order: 2, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, { _id: 'badge_neighborhood_champion', type: 'badge', name: 'Neighborhood Champion', description: 'Adopted 10 streets', icon: 'šŸŒ†', criteria: { type: 'street_adoptions', threshold: 10 }, rarity: 'epic', order: 3, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, { _id: 'badge_city_guardian', type: 'badge', name: 'City Guardian', description: 'Adopted 25 streets', icon: 'šŸ™ļø', criteria: { type: 'street_adoptions', threshold: 25 }, rarity: 'legendary', order: 4, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, // Task Completion Badges { _id: 'badge_first_task', type: 'badge', name: 'First Task', description: 'Completed your first task', icon: 'āœ…', criteria: { type: 'task_completions', threshold: 1 }, rarity: 'common', order: 5, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, { _id: 'badge_task_master', type: 'badge', name: 'Task Master', description: 'Completed 10 tasks', icon: 'šŸŽÆ', criteria: { type: 'task_completions', threshold: 10 }, rarity: 'rare', order: 6, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, { _id: 'badge_dedicated_worker', type: 'badge', name: 'Dedicated Worker', description: 'Completed 50 tasks', icon: 'šŸ› ļø', criteria: { type: 'task_completions', threshold: 50 }, rarity: 'epic', order: 7, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, { _id: 'badge_maintenance_legend', type: 'badge', name: 'Maintenance Legend', description: 'Completed 100 tasks', icon: '⚔', criteria: { type: 'task_completions', threshold: 100 }, rarity: 'legendary', order: 8, isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } ]; try { const results = await db.bulk({ docs: badges }); const successCount = results.filter(r => !r.error).length; console.log(`āœ… Successfully seeded ${successCount} badges`); } catch (error) { console.error('āŒ Failed to seed badges:', error.message); throw error; } } async verifySetup() { console.log('šŸ” Verifying setup...'); const db = this.nano.use(COUCHDB_DB_NAME); try { // Check database info const info = await db.info(); console.log(`āœ… Database "${info.db_name}" is ready`); console.log(` - Doc count: ${info.doc_count}`); console.log(` - Update seq: ${info.update_seq}`); // Check indexes const designDoc = await db.get('_design/adopt-a-street'); const indexCount = Object.keys(designDoc.indexes || {}).length; console.log(`āœ… ${indexCount} indexes created`); // Check badges const badges = await db.find({ selector: { type: 'badge' }, fields: ['name'] }); console.log(`āœ… ${badges.docs.length} badges available`); } catch (error) { console.error('āŒ Setup verification failed:', error.message); throw error; } } async run() { try { await this.initialize(); await this.createDatabase(); await this.createIndexes(); await this.createSecurityDocument(); await this.seedBadges(); await this.verifySetup(); console.log('\nšŸŽ‰ CouchDB setup completed successfully!'); console.log(`\nšŸ“‹ Connection Details:`); console.log(` URL: ${COUCHDB_URL}`); console.log(` Database: ${COUCHDB_DB_NAME}`); console.log(` User: ${COUCHDB_USER}`); console.log(`\n🌐 Access CouchDB at: ${COUCHDB_URL}/_utils`); } catch (error) { console.error('\nāŒ Setup failed:', error.message); process.exit(1); } } } // Run setup if called directly if (require.main === module) { const setup = new CouchDBSetup(); setup.run().catch(console.error); } module.exports = CouchDBSetup;