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