← back to Dear Bubbe Admin

log-all-users.js

271 lines

#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

// Color codes for terminal output
const colors = {
  reset: '\x1b[0m',
  bright: '\x1b[1m',
  red: '\x1b[31m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  magenta: '\x1b[35m',
  cyan: '\x1b[36m'
};

// Track all unique users
const allUsers = new Map();
const userSessions = new Map();

// Read all log sources
const logSources = [
  {
    path: '/root/Projects/dear-bubbe-admin/logs/combined.log',
    name: 'Admin Dashboard Logs'
  },
  {
    path: '/root/Projects/dear-bubbe-nextjs/logs/chat.log',
    name: 'Bubbe Chat Logs'
  },
  {
    path: '/root/Projects/dear-bubbe-admin/logs/access.log',
    name: 'Access Logs'
  }
];

// Parse different log formats
function parseLogLine(line) {
  // Try to parse as JSON first
  try {
    const parsed = JSON.parse(line);
    return {
      timestamp: parsed.timestamp || parsed.time,
      ip: parsed.ip || parsed.userIp,
      userName: parsed.userName || parsed.name || 'Anonymous',
      email: parsed.userEmail || parsed.email || '',
      location: parsed.location || 'Unknown',
      message: parsed.message || parsed.userMessage || '',
      mode: parsed.mode || 'unknown',
      userId: parsed.userId || parsed.sessionId || null
    };
  } catch (e) {
    // Try to extract from text format
    const ipMatch = line.match(/IP:\s*([\d\.:a-f]+)/i);
    const userMatch = line.match(/userName[":]+\s*([^",}]+)/i);
    const emailMatch = line.match(/email[":]+\s*([^",}]+)/i);
    const timeMatch = line.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
    
    if (ipMatch || userMatch) {
      return {
        timestamp: timeMatch ? timeMatch[0] : null,
        ip: ipMatch ? ipMatch[1] : 'Unknown',
        userName: userMatch ? userMatch[1] : 'Anonymous',
        email: emailMatch ? emailMatch[1] : '',
        location: 'Unknown',
        message: '',
        mode: 'unknown',
        userId: null
      };
    }
    return null;
  }
}

// Process all log files
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}`);
console.log(`${colors.bright}${colors.blue}📊 COMPREHENSIVE USER LOG ANALYSIS${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);

logSources.forEach(source => {
  if (!fs.existsSync(source.path)) {
    console.log(`${colors.yellow}⚠️  ${source.name}: File not found${colors.reset}`);
    return;
  }
  
  console.log(`${colors.green}📁 Processing: ${source.name}${colors.reset}`);
  const content = fs.readFileSync(source.path, 'utf-8');
  const lines = content.split('\n');
  
  lines.forEach(line => {
    if (!line.trim()) return;
    
    const parsed = parseLogLine(line);
    if (!parsed || !parsed.ip) return;
    
    // Create unique key for user
    const userKey = parsed.email || parsed.ip;
    
    if (!allUsers.has(userKey)) {
      allUsers.set(userKey, {
        userName: parsed.userName,
        email: parsed.email,
        ips: new Set(),
        locations: new Set(),
        firstSeen: parsed.timestamp,
        lastSeen: parsed.timestamp,
        messageCount: 0,
        modes: new Set(),
        sessions: []
      });
    }
    
    const user = allUsers.get(userKey);
    
    // Update user data
    if (parsed.ip && parsed.ip !== 'Unknown') {
      user.ips.add(parsed.ip);
    }
    if (parsed.location && parsed.location !== 'Unknown') {
      user.locations.add(parsed.location);
    }
    if (parsed.mode && parsed.mode !== 'unknown') {
      user.modes.add(parsed.mode);
    }
    if (parsed.message) {
      user.messageCount++;
    }
    
    // Update timestamps
    if (parsed.timestamp) {
      if (!user.firstSeen || parsed.timestamp < user.firstSeen) {
        user.firstSeen = parsed.timestamp;
      }
      if (!user.lastSeen || parsed.timestamp > user.lastSeen) {
        user.lastSeen = parsed.timestamp;
      }
      
      // Track session
      user.sessions.push({
        timestamp: parsed.timestamp,
        ip: parsed.ip,
        location: parsed.location
      });
    }
    
    // Update name if better one found
    if (parsed.userName && parsed.userName !== 'Anonymous' && user.userName === 'Anonymous') {
      user.userName = parsed.userName;
    }
  });
  
  console.log(`   ✓ Processed ${lines.length} lines\n`);
});

// Sort users by last activity
const sortedUsers = Array.from(allUsers.entries()).sort((a, b) => {
  const timeA = new Date(a[1].lastSeen || 0).getTime();
  const timeB = new Date(b[1].lastSeen || 0).getTime();
  return timeB - timeA;
});

// Display results
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}`);
console.log(`${colors.bright}${colors.magenta}👥 ALL USERS (${sortedUsers.length} Total)${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);

sortedUsers.forEach((([key, user], index) => {
  const isActive = user.lastSeen && 
    (new Date() - new Date(user.lastSeen)) < 3600000; // Active in last hour
  
  const statusColor = isActive ? colors.green : colors.reset;
  const status = isActive ? '🟢 ACTIVE' : '⚪ INACTIVE';
  
  console.log(`${colors.bright}${index + 1}. ${user.userName}${colors.reset} ${statusColor}${status}${colors.reset}`);
  
  if (user.email) {
    console.log(`   ${colors.blue}📧 Email:${colors.reset} ${user.email}`);
  }
  
  console.log(`   ${colors.yellow}🌐 IPs:${colors.reset} ${Array.from(user.ips).join(', ') || 'None'}`);
  console.log(`   ${colors.cyan}📍 Locations:${colors.reset} ${Array.from(user.locations).join(', ') || 'Unknown'}`);
  
  if (user.firstSeen) {
    const first = new Date(user.firstSeen);
    const last = new Date(user.lastSeen);
    console.log(`   ${colors.magenta}⏱️ First Seen:${colors.reset} ${first.toLocaleString()}`);
    console.log(`   ${colors.magenta}⏱️ Last Seen:${colors.reset} ${last.toLocaleString()}`);
  }
  
  console.log(`   ${colors.green}💬 Messages:${colors.reset} ${user.messageCount}`);
  
  if (user.modes.size > 0) {
    console.log(`   ${colors.blue}🎭 Modes Used:${colors.reset} ${Array.from(user.modes).join(', ')}`);
  }
  
  // Show recent activity
  const recentSessions = user.sessions
    .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
    .slice(0, 3);
  
  if (recentSessions.length > 0) {
    console.log(`   ${colors.yellow}📅 Recent Activity:${colors.reset}`);
    recentSessions.forEach(session => {
      const time = new Date(session.timestamp).toLocaleString();
      console.log(`      • ${time} from ${session.ip}`);
    });
  }
  
  console.log(`   ${colors.cyan}${'-'.repeat(60)}${colors.reset}`);
}));

// Summary statistics
const activeCount = sortedUsers.filter(([k, u]) => 
  u.lastSeen && (new Date() - new Date(u.lastSeen)) < 3600000
).length;

const todayCount = sortedUsers.filter(([k, u]) => 
  u.lastSeen && (new Date() - new Date(u.lastSeen)) < 86400000
).length;

console.log(`\n${colors.cyan}${'='.repeat(80)}${colors.reset}`);
console.log(`${colors.bright}${colors.green}📈 SUMMARY STATISTICS${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);

console.log(`${colors.bright}Total Users:${colors.reset} ${sortedUsers.length}`);
console.log(`${colors.green}Active (Last Hour):${colors.reset} ${activeCount}`);
console.log(`${colors.yellow}Active Today:${colors.reset} ${todayCount}`);
console.log(`${colors.blue}With Email:${colors.reset} ${sortedUsers.filter(([k, u]) => u.email).length}`);
console.log(`${colors.magenta}Anonymous:${colors.reset} ${sortedUsers.filter(([k, u]) => u.userName === 'Anonymous').length}`);

// Geographic distribution
const locations = new Map();
sortedUsers.forEach(([k, user]) => {
  user.locations.forEach(loc => {
    if (loc && loc !== 'Unknown') {
      locations.set(loc, (locations.get(loc) || 0) + 1);
    }
  });
});

if (locations.size > 0) {
  console.log(`\n${colors.bright}${colors.cyan}🌍 GEOGRAPHIC DISTRIBUTION:${colors.reset}`);
  Array.from(locations.entries())
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .forEach(([loc, count]) => {
      console.log(`   • ${loc}: ${count} users`);
    });
}

// Save detailed report
const reportPath = '/root/Projects/dear-bubbe-admin/logs/user-report.json';
const report = {
  generatedAt: new Date().toISOString(),
  totalUsers: sortedUsers.length,
  activeInLastHour: activeCount,
  activeToday: todayCount,
  users: sortedUsers.map(([key, user]) => ({
    key,
    ...user,
    ips: Array.from(user.ips),
    locations: Array.from(user.locations),
    modes: Array.from(user.modes)
  }))
};

fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));

console.log(`\n${colors.green}✅ Detailed report saved to: ${reportPath}${colors.reset}`);
console.log(`${colors.cyan}${'='.repeat(80)}${colors.reset}\n`);