← 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)