← back to Dear Bubbe Admin

backend/models/Alert.js

174 lines

const mongoose = require('mongoose')

const alertSchema = new mongoose.Schema({
  timestamp: {
    type: Date,
    default: Date.now,
    index: true
  },
  type: {
    type: String,
    required: true,
    enum: ['antisemitism', 'racism', 'hate_speech', 'harassment', 'spam', 'system', 'security'],
    index: true
  },
  severity: {
    type: String,
    required: true,
    enum: ['low', 'medium', 'high', 'critical'],
    index: true
  },
  userInfo: {
    userId: { type: String, index: true },
    ip: { type: String, index: true },
    sessionId: { type: String },
    userAgent: { type: String },
    email: { type: String }
  },
  location: {
    country: { type: String },
    state: { type: String },
    city: { type: String },
    coordinates: {
      lat: { type: Number },
      lon: { type: Number }
    },
    isp: { type: String }
  },
  content: {
    message: { type: String, required: true },
    detectedAt: { type: Date, default: Date.now },
    context: { type: String }, // Additional context about the conversation
    responseGiven: { type: String } // Bubbe's response if any
  },
  violation: {
    ruleId: { type: String, required: true },
    pattern: { type: String, required: true },
    match: { type: String, required: true }, // The exact text that matched
    category: { type: String }, // e.g., 'racial_slur', 'conspiracy', 'stereotype'
    confidence: { type: Number, min: 0, max: 1, default: 1 }
  },
  action: {
    type: String,
    enum: ['block', 'review', 'warn', 'monitor'],
    default: 'review'
  },
  status: {
    type: String,
    enum: ['active', 'reviewed', 'resolved', 'false_positive'],
    default: 'active',
    index: true
  },
  reviewInfo: {
    reviewedBy: { type: String }, // Admin user ID
    reviewedAt: { type: Date },
    reviewNotes: { type: String },
    resolution: { type: String }
  },
  threatResponseSent: {
    type: Boolean,
    default: false
  },
  escalated: {
    type: Boolean,
    default: false
  },
  reportedToAuthorities: {
    type: Boolean,
    default: false
  },
  additionalData: {
    type: mongoose.Schema.Types.Mixed // For any additional data specific to alert type
  }
}, {
  timestamps: true
})

// Indexes for fast queries
alertSchema.index({ timestamp: -1 })
alertSchema.index({ 'userInfo.ip': 1, timestamp: -1 })
alertSchema.index({ type: 1, severity: 1 })
alertSchema.index({ status: 1, timestamp: -1 })

// Method to get alert summary
alertSchema.methods.getSummary = function() {
  return {
    id: this._id,
    type: this.type,
    severity: this.severity,
    timestamp: this.timestamp,
    userInfo: {
      userId: this.userInfo.userId,
      ip: this.userInfo.ip
    },
    location: this.location,
    violation: {
      category: this.violation.category,
      match: this.violation.match
    },
    status: this.status,
    threatResponseSent: this.threatResponseSent
  }
}

// Static method to get recent high-severity alerts
alertSchema.statics.getRecentCritical = function(limit = 10) {
  return this.find({
    severity: { $in: ['high', 'critical'] },
    timestamp: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) } // Last 24 hours
  })
  .sort({ timestamp: -1 })
  .limit(limit)
  .lean()
}

// Static method to get alert statistics
alertSchema.statics.getStats = function(timeframe = '24h') {
  const now = new Date()
  let startTime
  
  switch (timeframe) {
    case '1h':
      startTime = new Date(now - 60 * 60 * 1000)
      break
    case '24h':
      startTime = new Date(now - 24 * 60 * 60 * 1000)
      break
    case '7d':
      startTime = new Date(now - 7 * 24 * 60 * 60 * 1000)
      break
    case '30d':
      startTime = new Date(now - 30 * 24 * 60 * 60 * 1000)
      break
    default:
      startTime = new Date(now - 24 * 60 * 60 * 1000)
  }
  
  return this.aggregate([
    {
      $match: {
        timestamp: { $gte: startTime }
      }
    },
    {
      $group: {
        _id: {
          type: '$type',
          severity: '$severity'
        },
        count: { $sum: 1 },
        latestAlert: { $max: '$timestamp' },
        uniqueIPs: { $addToSet: '$userInfo.ip' },
        uniqueUsers: { $addToSet: '$userInfo.userId' }
      }
    },
    {
      $addFields: {
        uniqueIPCount: { $size: '$uniqueIPs' },
        uniqueUserCount: { $size: '$uniqueUsers' }
      }
    }
  ])
}

module.exports = mongoose.model('Alert', alertSchema)