← back to Dear Bubbe Admin

backend/routes/users.js

476 lines

const express = require('express')
const User = require('../models/User')
const Session = require('../models/Session')
const Conversation = require('../models/Conversation')
const Alert = require('../models/Alert')
const fs = require('fs')
const path = require('path')
const router = express.Router()

// Get all users with filtering and pagination
router.get('/', async (req, res) => {
  try {
    const {
      page = 1,
      limit = 50,
      search,
      blocked,
      riskLevel,
      country,
      state,
      sort = '-stats.lastVisit'
    } = req.query
    
    // Build query
    const query = {}
    
    if (search) {
      query.$or = [
        { 'profile.name': { $regex: search, $options: 'i' } },
        { 'profile.preferredName': { $regex: search, $options: 'i' } },
        { email: { $regex: search, $options: 'i' } },
        { userId: search }
      ]
    }
    
    if (blocked !== undefined) {
      query['security.blocked'] = blocked === 'true'
    }
    
    if (riskLevel) {
      query['security.riskLevel'] = riskLevel
    }
    
    if (country) {
      query['location.country'] = country
    }
    
    if (state) {
      query['location.state'] = state
    }
    
    const users = await User.find(query)
      .sort(sort)
      .limit(limit * 1)
      .skip((page - 1) * limit)
      .lean()
    
    const total = await User.countDocuments(query)
    
    res.json({
      success: true,
      users,
      pagination: {
        current: parseInt(page),
        limit: parseInt(limit),
        total,
        pages: Math.ceil(total / limit)
      }
    })
    
  } catch (error) {
    console.error('Error fetching users:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to fetch users' 
    })
  }
})

// Get user statistics
router.get('/stats', async (req, res) => {
  try {
    const stats = await User.getStats()
    const geoStats = await User.getGeoDistribution()
    
    // Additional stats
    const activeUsersToday = await User.countDocuments({
      'stats.lastVisit': { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
    })
    
    const newUsersThisWeek = await User.countDocuments({
      createdAt: { $gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }
    })
    
    const topActiveUsers = await User.aggregate([
      {
        $match: {
          'stats.lastVisit': { $gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }
        }
      },
      {
        $sort: { 'stats.totalMessages': -1 }
      },
      {
        $limit: 10
      },
      {
        $project: {
          userId: 1,
          'profile.name': 1,
          'profile.preferredName': 1,
          'stats.totalMessages': 1,
          'stats.lastVisit': 1,
          'location.country': 1,
          'location.state': 1
        }
      }
    ])
    
    res.json({
      success: true,
      stats: stats[0] || {},
      geoStats,
      activeUsersToday,
      newUsersThisWeek,
      topActiveUsers
    })
    
  } catch (error) {
    console.error('Error fetching user stats:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to fetch user statistics' 
    })
  }
})

// Get specific user details
router.get('/:userId', async (req, res) => {
  try {
    const user = await User.findOne({ userId: req.params.userId }).lean()
    
    if (!user) {
      return res.status(404).json({ 
        success: false, 
        error: 'User not found' 
      })
    }
    
    // Get user's sessions
    const sessions = await Session.find({ userId: req.params.userId })
      .sort({ startTime: -1 })
      .limit(20)
      .lean()
    
    // Get recent conversations
    const conversations = await Conversation.find({ userId: req.params.userId })
      .sort({ timestamp: -1 })
      .limit(50)
      .lean()
    
    // Get alerts for this user
    const alerts = await Alert.find({ 'userInfo.userId': req.params.userId })
      .sort({ timestamp: -1 })
      .limit(20)
      .lean()
    
    // Load user memory from Dear Bubbe if it exists
    let userMemory = null
    try {
      const memoryPath = path.join(process.cwd(), '../dear-bubbe-nextjs/user-memories', `${req.params.userId.replace(/[^a-zA-Z0-9-_]/g, '_')}.md`)
      if (fs.existsSync(memoryPath)) {
        userMemory = fs.readFileSync(memoryPath, 'utf-8')
      }
    } catch (error) {
      console.log('Could not load user memory:', error.message)
    }
    
    // Load user profile from Dear Bubbe profiles directory
    let bubbeProfile = null
    try {
      const profilesDir = path.join(process.cwd(), '../dear-bubbe-nextjs/data/profiles')
      const files = fs.readdirSync(profilesDir)
      const profileFile = files.find(f => f.includes(user.userId) || f.includes(user.email))
      
      if (profileFile) {
        const profilePath = path.join(profilesDir, profileFile)
        bubbeProfile = JSON.parse(fs.readFileSync(profilePath, 'utf-8'))
      }
    } catch (error) {
      console.log('Could not load Bubbe profile:', error.message)
    }
    
    res.json({
      success: true,
      user,
      sessions,
      conversations,
      alerts,
      userMemory,
      bubbeProfile,
      summary: {
        totalSessions: sessions.length,
        totalConversations: conversations.length,
        totalAlerts: alerts.length,
        riskLevel: user.security?.riskLevel || 'low',
        lastActivity: user.stats?.lastVisit
      }
    })
    
  } catch (error) {
    console.error('Error fetching user details:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to fetch user details' 
    })
  }
})

// Update user profile/settings
router.put('/:userId', async (req, res) => {
  try {
    const { profile, security, preferences, notes } = req.body
    const adminId = req.user.userId
    
    const user = await User.findOne({ userId: req.params.userId })
    
    if (!user) {
      return res.status(404).json({ 
        success: false, 
        error: 'User not found' 
      })
    }
    
    // Update profile if provided
    if (profile) {
      user.profile = { ...user.profile, ...profile }
    }
    
    // Update security settings (admin only)
    if (security && (req.user.role === 'admin' || req.user.role === 'super_admin')) {
      user.security = { ...user.security, ...security }
    }
    
    // Update preferences
    if (preferences) {
      user.preferences = { ...user.preferences, ...preferences }
    }
    
    // Add admin note if provided
    if (notes) {
      await user.addNote(`Updated by admin: ${notes}`, adminId, 'info')
    }
    
    await user.save()
    
    console.log(`User ${req.params.userId} updated by ${adminId}`)
    
    res.json({
      success: true,
      user: user.toObject()
    })
    
  } catch (error) {
    console.error('Error updating user:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to update user' 
    })
  }
})

// Block user
router.post('/:userId/block', async (req, res) => {
  try {
    const { reason } = req.body
    const adminId = req.user.userId
    
    if (!reason) {
      return res.status(400).json({ 
        success: false, 
        error: 'Block reason required' 
      })
    }
    
    const user = await User.findOne({ userId: req.params.userId })
    
    if (!user) {
      return res.status(404).json({ 
        success: false, 
        error: 'User not found' 
      })
    }
    
    await user.block(reason, adminId)
    
    console.log(`User ${req.params.userId} blocked by ${adminId}: ${reason}`)
    
    res.json({
      success: true,
      message: 'User blocked successfully',
      user: user.toObject()
    })
    
  } catch (error) {
    console.error('Error blocking user:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to block user' 
    })
  }
})

// Unblock user
router.post('/:userId/unblock', async (req, res) => {
  try {
    const adminId = req.user.userId
    
    const user = await User.findOne({ userId: req.params.userId })
    
    if (!user) {
      return res.status(404).json({ 
        success: false, 
        error: 'User not found' 
      })
    }
    
    if (!user.security.blocked) {
      return res.status(400).json({ 
        success: false, 
        error: 'User is not blocked' 
      })
    }
    
    await user.unblock(adminId)
    
    console.log(`User ${req.params.userId} unblocked by ${adminId}`)
    
    res.json({
      success: true,
      message: 'User unblocked successfully',
      user: user.toObject()
    })
    
  } catch (error) {
    console.error('Error unblocking user:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to unblock user' 
    })
  }
})

// Add note to user
router.post('/:userId/notes', async (req, res) => {
  try {
    const { note, type = 'info' } = req.body
    const adminId = req.user.userId
    
    if (!note) {
      return res.status(400).json({ 
        success: false, 
        error: 'Note content required' 
      })
    }
    
    const user = await User.findOne({ userId: req.params.userId })
    
    if (!user) {
      return res.status(404).json({ 
        success: false, 
        error: 'User not found' 
      })
    }
    
    await user.addNote(note, adminId, type)
    
    res.json({
      success: true,
      message: 'Note added successfully',
      notes: user.notes
    })
    
  } catch (error) {
    console.error('Error adding note:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to add note' 
    })
  }
})

// Get user conversation history
router.get('/:userId/conversations', async (req, res) => {
  try {
    const { page = 1, limit = 100, topic, sentiment } = req.query
    
    const query = { userId: req.params.userId }
    
    if (topic) {
      query.topics = topic
    }
    
    if (sentiment) {
      query['message.sentiment'] = sentiment
    }
    
    const conversations = await Conversation.find(query)
      .sort({ timestamp: -1 })
      .limit(limit * 1)
      .skip((page - 1) * limit)
      .lean()
    
    const total = await Conversation.countDocuments(query)
    
    res.json({
      success: true,
      conversations,
      pagination: {
        current: parseInt(page),
        limit: parseInt(limit),
        total,
        pages: Math.ceil(total / limit)
      }
    })
    
  } catch (error) {
    console.error('Error fetching user conversations:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to fetch conversations' 
    })
  }
})

// Export user data (GDPR compliance)
router.get('/:userId/export', async (req, res) => {
  try {
    const user = await User.findOne({ userId: req.params.userId }).lean()
    
    if (!user) {
      return res.status(404).json({ 
        success: false, 
        error: 'User not found' 
      })
    }
    
    const sessions = await Session.find({ userId: req.params.userId }).lean()
    const conversations = await Conversation.find({ userId: req.params.userId }).lean()
    const alerts = await Alert.find({ 'userInfo.userId': req.params.userId }).lean()
    
    const exportData = {
      user,
      sessions,
      conversations,
      alerts,
      exportedAt: new Date(),
      exportedBy: req.user.userId
    }
    
    res.setHeader('Content-Type', 'application/json')
    res.setHeader('Content-Disposition', `attachment; filename="user_data_${req.params.userId}_${Date.now()}.json"`)
    res.json(exportData)
    
    console.log(`User data exported for ${req.params.userId} by ${req.user.userId}`)
    
  } catch (error) {
    console.error('Error exporting user data:', error)
    res.status(500).json({ 
      success: false, 
      error: 'Failed to export user data' 
    })
  }
})

module.exports = router