← back to Dear Bubbe Admin

backend/server.js

973 lines

const express = require('express')
const cors = require('cors')
const helmet = require('helmet')
const compression = require('compression')
const winston = require('winston')
const fs = require('fs').promises
const path = require('path')
const jwt = require('jsonwebtoken')
const { OAuth2Client } = require('google-auth-library')
const http = require('http')
const { Server } = require('socket.io')
require('dotenv').config()

// Constants
const PORT = process.env.PORT || 5013
const ADMIN_EMAIL = 'steve@designerwallcoverings.com'
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
  throw new Error('JWT_SECRET env var required (no fallback for production safety) — audit 2026-05-04');
}
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '493480746821-mhpjc0glkkogcf845ubi2hkhksoqj39k.apps.googleusercontent.com'
const PROFILES_DIR = path.join(__dirname, '../../dear-bubbe-nextjs/data/profiles')
const LOGS_DIR = path.join(__dirname, '../logs')

// Logger setup
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: path.join(LOGS_DIR, 'error.log'), level: 'error' }),
    new winston.transports.File({ filename: path.join(LOGS_DIR, 'combined.log') }),
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    })
  ]
})

// Ensure directories exist
async function ensureDirectories() {
  try {
    await fs.mkdir(LOGS_DIR, { recursive: true })
    await fs.mkdir(path.join(__dirname, '../alerts'), { recursive: true })
    logger.info('Directories initialized')
  } catch (error) {
    logger.error('Failed to create directories:', error)
  }
}

// Initialize Express
const app = express()
const server = http.createServer(app)
const io = new Server(server, {
  cors: {
    origin: ['http://localhost:3000', 'http://45.61.58.125:5013'],
    credentials: true
  }
})

// Google OAuth client
const client = new OAuth2Client(GOOGLE_CLIENT_ID)

// Middleware
app.use(helmet({
  contentSecurityPolicy: false, // Disable CSP to allow Google OAuth
  crossOriginEmbedderPolicy: false
}))
app.use(compression())
app.use(cors({
  origin: ['http://localhost:3000', 'http://45.61.58.125:5013', 'https://accounts.google.com'],
  credentials: true
}))
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// Alert storage (in-memory for now, will migrate to DB)
const alerts = {
  antisemitic: [],
  racist: [],
  other: []
}

// Active sessions tracking
const activeSessions = new Map()

// Clean up old sessions every 5 minutes
setInterval(() => {
  const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000)
  
  for (const [userId, session] of activeSessions) {
    if (new Date(session.lastActive) < fiveMinutesAgo) {
      activeSessions.delete(userId)
    }
  }
  
  // Emit updated count to all admin clients
  io.emit('stats-update', {
    activeUsers: activeSessions.size
  })
}, 5 * 60 * 1000)

// Auth middleware - RESTRICTED TO steve@designerwallcoverings.com ONLY
async function authenticateAdmin(req, res, next) {
  try {
    const token = req.headers.authorization?.split(' ')[1]
    
    if (!token) {
      return res.status(401).json({ error: 'No token provided' })
    }

    const decoded = jwt.verify(token, JWT_SECRET)
    
    // CRITICAL: Only allow steve@designerwallcoverings.com
    if (decoded.email !== ADMIN_EMAIL) {
      logger.warn(`Unauthorized access attempt from: ${decoded.email}`)
      return res.status(403).json({ error: 'Access denied. Admin only.' })
    }

    req.admin = decoded
    next()
  } catch (error) {
    logger.error('Auth error:', error)
    res.status(401).json({ error: 'Invalid token' })
  }
}

// Routes

// Password login - RESTRICTED  
app.post('/api/auth/password', async (req, res) => {
  try {
    const { email, password } = req.body
    
    // CRITICAL: Only allow steve@designerwallcoverings.com
    if (email !== ADMIN_EMAIL) {
      logger.warn(`Login attempt blocked for: ${email}`)
      return res.status(403).json({ 
        error: 'Access denied. This admin panel is restricted.',
        message: 'Only authorized administrators can access this system.'
      })
    }
    
    // Check password (you should use bcrypt in production)
    // For now, using a simple password check
    const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'BubbeAdmin2024!'
    
    if (password !== ADMIN_PASSWORD) {
      logger.warn(`Invalid password attempt for: ${email}`)
      return res.status(401).json({ 
        error: 'Invalid password',
        message: 'Invalid password. Please try again.'
      })
    }
    
    // Create JWT token
    const token = jwt.sign(
      { 
        email: email,
        name: 'Steve',
        picture: 'https://ui-avatars.com/api/?name=Steve&background=667eea&color=fff',
        isAdmin: true
      },
      JWT_SECRET,
      { expiresIn: '24h' }
    )
    
    logger.info(`Admin logged in via password: ${email}`)
    
    res.json({
      token,
      user: {
        email: email,
        name: 'Steve',
        picture: 'https://ui-avatars.com/api/?name=Steve&background=667eea&color=fff',
        isAdmin: true
      }
    })
  } catch (error) {
    logger.error('Password auth error:', error)
    res.status(400).json({ error: 'Authentication failed' })
  }
})

// Google OAuth login - RESTRICTED
app.post('/api/auth/google', async (req, res) => {
  try {
    const { credential } = req.body
    
    // Verify Google token
    const ticket = await client.verifyIdToken({
      idToken: credential,
      audience: GOOGLE_CLIENT_ID
    })
    
    const payload = ticket.getPayload()
    const email = payload.email
    
    // CRITICAL: Only allow steve@designerwallcoverings.com
    if (email !== ADMIN_EMAIL) {
      logger.warn(`Login attempt blocked for: ${email}`)
      return res.status(403).json({ 
        error: 'Access denied. This admin panel is restricted.',
        message: 'Only authorized administrators can access this system.'
      })
    }
    
    // Create JWT token
    const token = jwt.sign(
      { 
        email: email,
        name: payload.name,
        picture: payload.picture,
        isAdmin: true
      },
      JWT_SECRET,
      { expiresIn: '24h' }
    )
    
    logger.info(`Admin logged in: ${email}`)
    
    res.json({
      token,
      user: {
        email: email,
        name: payload.name,
        picture: payload.picture,
        isAdmin: true
      }
    })
  } catch (error) {
    logger.error('Google auth error:', error)
    res.status(400).json({ error: 'Authentication failed' })
  }
})

// Get active sessions with details
app.get('/api/active-sessions', authenticateAdmin, async (req, res) => {
  const sessions = []
  
  // Clean up old sessions first
  const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000)
  for (const [userId, session] of activeSessions) {
    if (new Date(session.lastActive) < fiveMinutesAgo) {
      activeSessions.delete(userId)
    } else {
      sessions.push({
        userId,
        ...session,
        isActive: true
      })
    }
  }
  
  // Also include recent live chats
  for (const [userId, chat] of liveChats) {
    if (new Date(chat.lastActivity) > fiveMinutesAgo && !activeSessions.has(userId)) {
      sessions.push({
        userId,
        userName: chat.userName,
        ip: chat.ip,
        location: chat.location,
        mode: chat.mode,
        lastActive: chat.lastActivity,
        isActive: false
      })
    }
  }
  
  res.json({
    count: sessions.length,
    sessions: sessions
  })
})

// Dashboard stats - ADMIN ONLY
app.get('/api/dashboard/stats', authenticateAdmin, async (req, res) => {
  try {
    // Read all user profiles
    const profiles = []
    const profileFiles = await fs.readdir(PROFILES_DIR).catch(() => [])
    
    for (const file of profileFiles) {
      if (file.endsWith('.json')) {
        const data = await fs.readFile(path.join(PROFILES_DIR, file), 'utf-8')
        profiles.push(JSON.parse(data))
      }
    }
    
    // Calculate stats
    const stats = {
      totalUsers: profiles.length,
      activeToday: activeSessions.size,
      totalAlerts: alerts.antisemitic.length + alerts.racist.length + alerts.other.length,
      criticalAlerts: alerts.antisemitic.filter(a => a.severity === 'HIGH').length + 
                     alerts.racist.filter(a => a.severity === 'HIGH').length,
      demographics: {
        married: profiles.filter(p => p.relationshipStatus === 'married').length,
        single: profiles.filter(p => p.relationshipStatus === 'single').length,
        hasKids: profiles.filter(p => p.hasKids === 'yes').length,
        religious: profiles.filter(p => ['very', 'somewhat'].includes(p.religiousLevel)).length
      },
      recentAlerts: [...alerts.antisemitic, ...alerts.racist].slice(-10)
    }
    
    res.json(stats)
  } catch (error) {
    logger.error('Dashboard stats error:', error)
    res.status(500).json({ error: 'Failed to fetch stats' })
  }
})

// Get all users - ADMIN ONLY
app.get('/api/users', authenticateAdmin, async (req, res) => {
  try {
    const users = []
    const profileFiles = await fs.readdir(PROFILES_DIR).catch(() => [])
    
    for (const file of profileFiles) {
      if (file.endsWith('.json')) {
        const data = await fs.readFile(path.join(PROFILES_DIR, file), 'utf-8')
        const profile = JSON.parse(data)
        users.push({
          ...profile,
          lastActive: activeSessions.get(profile.email) || null
        })
      }
    }
    
    res.json(users)
  } catch (error) {
    logger.error('Get users error:', error)
    res.status(500).json({ error: 'Failed to fetch users' })
  }
})

// Get alerts - ADMIN ONLY
app.get('/api/alerts/:type?', authenticateAdmin, (req, res) => {
  const { type } = req.params
  
  if (type && alerts[type]) {
    res.json(alerts[type])
  } else {
    res.json({
      antisemitic: alerts.antisemitic,
      racist: alerts.racist,
      other: alerts.other,
      total: alerts.antisemitic.length + alerts.racist.length + alerts.other.length
    })
  }
})

// Report new alert (from main app)
app.post('/api/alerts', async (req, res) => {
  try {
    const { type, userId, message, severity, ip, location, timestamp } = req.body
    
    const alert = {
      id: Date.now().toString(),
      type,
      userId,
      message,
      severity: severity || 'MEDIUM',
      ip,
      location,
      timestamp: timestamp || new Date().toISOString(),
      status: 'new'
    }
    
    // Store alert
    if (alerts[type]) {
      alerts[type].push(alert)
    } else {
      alerts.other.push(alert)
    }
    
    // Emit to connected admin clients
    io.emit('new-alert', alert)
    
    // Log critical alerts
    if (severity === 'HIGH') {
      logger.error(`CRITICAL ALERT - ${type}:`, alert)
    }
    
    res.json({ success: true, alertId: alert.id })
  } catch (error) {
    logger.error('Alert creation error:', error)
    res.status(500).json({ error: 'Failed to create alert' })
  }
})

// Block/unblock user - ADMIN ONLY
app.post('/api/users/:email/block', authenticateAdmin, async (req, res) => {
  try {
    const { email } = req.params
    const { blocked, reason } = req.body
    
    // Update user profile
    const profilePath = path.join(PROFILES_DIR, `${email}.json`)
    const data = await fs.readFile(profilePath, 'utf-8')
    const profile = JSON.parse(data)
    
    profile.blocked = blocked
    profile.blockReason = reason
    profile.blockedAt = blocked ? new Date().toISOString() : null
    profile.blockedBy = req.admin.email
    
    await fs.writeFile(profilePath, JSON.stringify(profile, null, 2))
    
    logger.info(`User ${email} ${blocked ? 'blocked' : 'unblocked'} by ${req.admin.email}`)
    
    res.json({ success: true, profile })
  } catch (error) {
    logger.error('Block user error:', error)
    res.status(500).json({ error: 'Failed to update user status' })
  }
})

// Deactivate/activate user account - ADMIN ONLY
app.post('/api/users/:email/status', authenticateAdmin, async (req, res) => {
  try {
    const { email } = req.params
    const { active, reason } = req.body
    
    // Update user profile
    const profilePath = path.join(PROFILES_DIR, `${email}.json`)
    
    if (!await fs.access(profilePath).then(() => true).catch(() => false)) {
      return res.status(404).json({ error: 'User not found' })
    }
    
    const data = await fs.readFile(profilePath, 'utf-8')
    const profile = JSON.parse(data)
    
    profile.active = active
    profile.deactivatedReason = !active ? reason : null
    profile.deactivatedAt = !active ? new Date().toISOString() : null
    profile.reactivatedAt = active && profile.deactivatedAt ? new Date().toISOString() : null
    profile.modifiedBy = req.admin.email
    profile.lastModified = new Date().toISOString()
    
    await fs.writeFile(profilePath, JSON.stringify(profile, null, 2))
    
    logger.info(`User ${email} ${active ? 'activated' : 'deactivated'} by ${req.admin.email}`)
    
    // Emit status change to connected clients
    io.emit('user-status-change', {
      email,
      active,
      reason,
      modifiedBy: req.admin.email,
      timestamp: new Date().toISOString()
    })
    
    res.json({ success: true, profile })
  } catch (error) {
    logger.error('Update user status error:', error)
    res.status(500).json({ error: 'Failed to update user status' })
  }
})

// Delete user account - ADMIN ONLY  
app.delete('/api/users/:email', authenticateAdmin, async (req, res) => {
  try {
    const { email } = req.params
    const { reason } = req.body
    
    // Create deletion log before deleting
    const deletionLog = {
      email,
      deletedBy: req.admin.email,
      deletedAt: new Date().toISOString(),
      reason
    }
    
    // Save deletion log
    const deletionLogsPath = path.join(__dirname, '../logs/deletions.json')
    let deletions = []
    try {
      const existing = await fs.readFile(deletionLogsPath, 'utf-8')
      deletions = JSON.parse(existing)
    } catch (e) {
      // File doesn't exist yet
    }
    deletions.push(deletionLog)
    await fs.writeFile(deletionLogsPath, JSON.stringify(deletions, null, 2))
    
    // Delete user profile
    const profilePath = path.join(PROFILES_DIR, `${email}.json`)
    if (await fs.access(profilePath).then(() => true).catch(() => false)) {
      await fs.unlink(profilePath)
    }
    
    // Delete user emails
    const emailsPath = path.join(PROFILES_DIR, '../emails', `${email}.json`)
    if (await fs.access(emailsPath).then(() => true).catch(() => false)) {
      await fs.unlink(emailsPath)
    }
    
    // Delete user discount codes
    const discountPath = path.join(PROFILES_DIR, '../discount-codes', `${email}.json`)
    if (await fs.access(discountPath).then(() => true).catch(() => false)) {
      await fs.unlink(discountPath)
    }
    
    logger.info(`User ${email} deleted by ${req.admin.email}. Reason: ${reason}`)
    
    // Emit deletion to connected clients
    io.emit('user-deleted', {
      email,
      deletedBy: req.admin.email,
      reason,
      timestamp: new Date().toISOString()
    })
    
    res.json({ success: true, message: `User ${email} has been permanently deleted`, deletionLog })
  } catch (error) {
    logger.error('Delete user error:', error)
    res.status(500).json({ error: 'Failed to delete user' })
  }
})

// WebSocket for real-time updates
io.on('connection', (socket) => {
  logger.info('Admin client connected')
  
  socket.on('disconnect', () => {
    logger.info('Admin client disconnected')
  })
  
  // Send initial stats
  socket.emit('stats-update', {
    activeUsers: activeSessions.size,
    alertCount: alerts.antisemitic.length + alerts.racist.length + alerts.other.length
  })
})

// Track active sessions (receives updates from main app)
app.post('/api/sessions/update', (req, res) => {
  const { email, action } = req.body
  
  if (action === 'start') {
    activeSessions.set(email, new Date().toISOString())
  } else if (action === 'end') {
    activeSessions.delete(email)
  }
  
  // Emit update to admin clients
  io.emit('session-update', {
    activeUsers: activeSessions.size,
    email,
    action
  })
  
  res.json({ success: true })
})

// Live chat monitoring - receives real-time chat data
const liveChats = new Map() // Store active conversations

app.post('/api/live-chat', (req, res) => {
  const { type, userId, ip, location, message, response, mode, timestamp, filtered, userName, userEmail } = req.body
  
  // Track as active session
  if (userId && !activeSessions.has(userId)) {
    activeSessions.set(userId, {
      userName: userName || 'Anonymous',
      ip: ip || 'Unknown',
      location: location || 'Unknown',
      mode: mode || 'bubbe',
      lastActive: new Date().toISOString()
    })
  }
  
  // Get or create conversation for this user
  if (!liveChats.has(userId)) {
    liveChats.set(userId, {
      userId,
      userName: userName || 'Anonymous',
      userEmail: userEmail || '',
      ip,
      location: location || 'Unknown',
      mode,
      messages: [],
      startTime: timestamp,
      lastActivity: timestamp
    })
  }
  
  const chat = liveChats.get(userId)
  chat.lastActivity = timestamp
  // Update user info if provided
  if (userName) chat.userName = userName
  if (location) chat.location = location
  if (userEmail) chat.userEmail = userEmail
  
  if (type === 'user_message') {
    chat.messages.push({
      type: 'user',
      text: message,
      timestamp
    })
    
    // Emit to all connected admin clients with user info
    io.emit('live-chat-update', {
      userId,
      userName: chat.userName,
      userEmail: chat.userEmail,
      ip,
      type: 'user_typing',
      message,
      mode,
      timestamp
    })
  } else if (type === 'bubbe_response') {
    chat.messages.push({
      type: 'bubbe',
      text: response,
      timestamp,
      filtered
    })
    
    // Emit to all connected admin clients with user info
    io.emit('live-chat-update', {
      userId,
      userName: chat.userName,
      userEmail: chat.userEmail,
      ip,
      type: 'bubbe_response',
      response,
      mode,
      timestamp,
      filtered
    })
  }
  
  // Clean up old conversations (inactive for more than 30 minutes)
  const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString()
  for (const [id, convo] of liveChats.entries()) {
    if (convo.lastActivity < thirtyMinutesAgo) {
      liveChats.delete(id)
    }
  }
  
  res.json({ success: true })
})

// Get all active live chats
app.get('/api/live-chats', authenticateAdmin, (req, res) => {
  const activeChats = Array.from(liveChats.values()).map(chat => ({
    ...chat,
    messageCount: chat.messages.length,
    duration: Math.floor((new Date().getTime() - new Date(chat.startTime).getTime()) / 1000)
  }))
  
  res.json(activeChats)
})

// Get banned terms for civil rights security
app.get('/api/banned-terms', authenticateAdmin, async (req, res) => {
  try {
    const bannedTermsPath = path.join(__dirname, '../../dear-bubbe-nextjs/lib/moderation/banned-terms.json')
    const bannedTermsData = await fs.readFile(bannedTermsPath, 'utf-8')
    const bannedTerms = JSON.parse(bannedTermsData)
    
    // Flatten terms for easier display
    const flattenedTerms = []
    
    for (const [categoryName, category] of Object.entries(bannedTerms.categories)) {
      const categoryData = category
      for (const item of categoryData.terms) {
        const term = item.term || item.phrase
        const variants = item.variants || []
        flattenedTerms.push({
          category: categoryName,
          severity: categoryData.severity,
          term: term,
          variants: variants,
          reason: item.reason,
          replacement: item.replacement
        })
      }
    }
    
    res.json({
      version: bannedTerms.version,
      lastUpdated: bannedTerms.lastUpdated,
      description: bannedTerms.description,
      sources: bannedTerms.sources,
      terms: flattenedTerms,
      totalTerms: flattenedTerms.length,
      acceptableYiddish: bannedTerms.acceptable_yiddish?.terms || []
    })
  } catch (error) {
    logger.error('Error fetching banned terms:', error)
    res.status(500).json({ error: 'Failed to fetch banned terms' })
  }
})

// Get acceptable terms
app.get('/api/acceptable-terms', authenticateAdmin, async (req, res) => {
  try {
    const acceptableTermsPath = path.join(__dirname, '../../dear-bubbe-nextjs/lib/moderation/acceptable-terms.json')
    const acceptableTerms = JSON.parse(await fs.readFile(acceptableTermsPath, 'utf8'))
    
    res.json({
      version: acceptableTerms.version,
      lastUpdated: acceptableTerms.lastUpdated,
      description: acceptableTerms.description,
      terms: acceptableTerms.terms,
      totalTerms: acceptableTerms.terms.length
    })
  } catch (error) {
    logger.error('Error fetching acceptable terms:', error)
    res.status(500).json({ error: 'Failed to fetch acceptable terms' })
  }
})

// Add new banned term
app.post('/api/banned-terms', authenticateAdmin, async (req, res) => {
  try {
    const { term, category, severity, reason, replacement, variants } = req.body
    
    if (!term || !category || !severity) {
      return res.status(400).json({ error: 'Term, category, and severity are required' })
    }
    
    const bannedTermsPath = path.join(__dirname, '../../dear-bubbe-nextjs/lib/moderation/banned-terms.json')
    const bannedTerms = JSON.parse(await fs.readFile(bannedTermsPath, 'utf8'))
    
    // Ensure category exists
    if (!bannedTerms.categories[category]) {
      bannedTerms.categories[category] = {
        severity: severity,
        terms: []
      }
    }
    
    // Add the new term
    bannedTerms.categories[category].terms.push({
      term: term,
      variants: variants ? variants.split(',').map(v => v.trim()) : [],
      reason: reason || '',
      replacement: replacement || ''
    })
    
    // Update metadata
    bannedTerms.lastUpdated = new Date().toISOString().split('T')[0]
    
    // Save back to file
    await fs.writeFile(bannedTermsPath, JSON.stringify(bannedTerms, null, 2))
    
    logger.info('Added banned term:', { term, category, severity })
    res.json({ success: true, message: 'Term added successfully' })
  } catch (error) {
    logger.error('Error adding banned term:', error)
    res.status(500).json({ error: 'Failed to add banned term' })
  }
})

// Add new acceptable term
app.post('/api/acceptable-terms', authenticateAdmin, async (req, res) => {
  try {
    const { term, meaning, usage, variants, notes } = req.body
    
    if (!term || !meaning) {
      return res.status(400).json({ error: 'Term and meaning are required' })
    }
    
    const acceptableTermsPath = path.join(__dirname, '../../dear-bubbe-nextjs/lib/moderation/acceptable-terms.json')
    const acceptableTerms = JSON.parse(await fs.readFile(acceptableTermsPath, 'utf8'))
    
    // Add the new term
    acceptableTerms.terms.push({
      term: term,
      variants: variants ? variants.split(',').map(v => v.trim()) : [],
      meaning: meaning,
      usage: usage || '',
      notes: notes || ''
    })
    
    // Update metadata
    acceptableTerms.lastUpdated = new Date().toISOString().split('T')[0]
    
    // Save back to file
    await fs.writeFile(acceptableTermsPath, JSON.stringify(acceptableTerms, null, 2))
    
    logger.info('Added acceptable term:', { term, meaning })
    res.json({ success: true, message: 'Term added successfully' })
  } catch (error) {
    logger.error('Error adding acceptable term:', error)
    res.status(500).json({ error: 'Failed to add acceptable term' })
  }
})

// Delete banned term
app.delete('/api/banned-terms/:term', authenticateAdmin, async (req, res) => {
  try {
    const termToDelete = decodeURIComponent(req.params.term)
    
    const bannedTermsPath = path.join(__dirname, '../../dear-bubbe-nextjs/lib/moderation/banned-terms.json')
    const bannedTerms = JSON.parse(await fs.readFile(bannedTermsPath, 'utf8'))
    
    let found = false
    for (const [categoryName, category] of Object.entries(bannedTerms.categories)) {
      const initialLength = category.terms.length
      category.terms = category.terms.filter(item => item.term !== termToDelete)
      if (category.terms.length < initialLength) {
        found = true
        break
      }
    }
    
    if (!found) {
      return res.status(404).json({ error: 'Term not found' })
    }
    
    // Update metadata
    bannedTerms.lastUpdated = new Date().toISOString().split('T')[0]
    
    // Save back to file
    await fs.writeFile(bannedTermsPath, JSON.stringify(bannedTerms, null, 2))
    
    logger.info('Deleted banned term:', termToDelete)
    res.json({ success: true, message: 'Term deleted successfully' })
  } catch (error) {
    logger.error('Error deleting banned term:', error)
    res.status(500).json({ error: 'Failed to delete banned term' })
  }
})

// Delete acceptable term
app.delete('/api/acceptable-terms/:term', authenticateAdmin, async (req, res) => {
  try {
    const termToDelete = decodeURIComponent(req.params.term)
    
    const acceptableTermsPath = path.join(__dirname, '../../dear-bubbe-nextjs/lib/moderation/acceptable-terms.json')
    const acceptableTerms = JSON.parse(await fs.readFile(acceptableTermsPath, 'utf8'))
    
    const initialLength = acceptableTerms.terms.length
    acceptableTerms.terms = acceptableTerms.terms.filter(item => item.term !== termToDelete)
    
    if (acceptableTerms.terms.length === initialLength) {
      return res.status(404).json({ error: 'Term not found' })
    }
    
    // Update metadata
    acceptableTerms.lastUpdated = new Date().toISOString().split('T')[0]
    
    // Save back to file
    await fs.writeFile(acceptableTermsPath, JSON.stringify(acceptableTerms, null, 2))
    
    logger.info('Deleted acceptable term:', termToDelete)
    res.json({ success: true, message: 'Term deleted successfully' })
  } catch (error) {
    logger.error('Error deleting acceptable term:', error)
    res.status(500).json({ error: 'Failed to delete acceptable term' })
  }
})

// Import social post handler
const { handleImmediateSocialPost } = require('./social-post-handler')

// IMMEDIATE SOCIAL POSTING ENDPOINT
app.post('/api/social-post-now', async (req, res) => {
  try {
    const postData = req.body
    
    logger.info('[SOCIAL-POST-NOW] Received immediate post request:', {
      messageId: postData.messageId,
      timestamp: postData.timestamp,
      userSnippet: postData.userMessage?.substring(0, 30),
      bubbeSnippet: postData.bubbeResponse?.substring(0, 30)
    })
    
    // Handle the immediate posting
    const results = await handleImmediateSocialPost(postData)
    
    // Emit to connected admin clients
    io.emit('social-post-triggered', {
      ...postData,
      results,
      triggeredAt: new Date().toISOString()
    })
    
    res.json({ 
      success: true, 
      message: 'Social post triggered immediately',
      results 
    })
  } catch (error) {
    logger.error('[SOCIAL-POST-NOW] Error:', error)
    res.status(500).json({ error: 'Failed to trigger social post' })
  }
})

// Serve frontend HTML
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '../frontend/index.html'))
})

// Serve Twitter monitor
app.get('/twitter-monitor', (req, res) => {
  res.sendFile(path.join(__dirname, '../frontend/twitter-monitor.html'))
})

// Get Twitter posts data
app.get('/api/twitter-posts', async (req, res) => {
  try {
    const immediatePostsPath = path.join(__dirname, '../logs/immediate-posts.json')
    const statsPath = path.join(__dirname, '../logs/twitter-stats.json')
    
    let posts = []
    let stats = {}
    
    try {
      posts = JSON.parse(await fs.readFile(immediatePostsPath, 'utf8'))
    } catch (e) {
      // File doesn't exist
    }
    
    try {
      stats = JSON.parse(await fs.readFile(statsPath, 'utf8'))
    } catch (e) {
      // File doesn't exist
    }
    
    res.json({ posts, stats })
  } catch (error) {
    logger.error('Error getting Twitter posts:', error)
    res.status(500).json({ error: 'Failed to get Twitter posts' })
  }
})

// Health check
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    uptime: process.uptime(),
    adminOnly: true,
    authorizedEmail: ADMIN_EMAIL
  })
})

// Start server
async function start() {
  await ensureDirectories()
  
  server.listen(PORT, () => {
    logger.info(`
╔════════════════════════════════════════════╗
║      DEAR BUBBE ADMIN DASHBOARD           ║
║                                            ║
║  Server: http://45.61.58.125:${PORT}        ║
║  Admin:  ${ADMIN_EMAIL}  ║
║  Status: RESTRICTED ACCESS                ║
║                                            ║
║  Features:                                 ║
║  - Real-time antisemitic alerts          ║
║  - Racist content monitoring             ║
║  - User management & blocking            ║
║  - Conversation monitoring               ║
║  - Analytics & reporting                 ║
╚════════════════════════════════════════════╝
    `)
  })
}

start().catch(logger.error)