← 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