mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2026-01-09 11:19:36 +01:00
190 lines
5.4 KiB
JavaScript
190 lines
5.4 KiB
JavaScript
const debug = require('debug')('48hr-email:stats-store')
|
|
|
|
/**
|
|
* Statistics Store - Tracks email metrics and historical data
|
|
* Stores 24-hour rolling statistics for receives, deletes, and forwards
|
|
*/
|
|
class StatisticsStore {
|
|
constructor() {
|
|
// Current totals
|
|
this.currentCount = 0
|
|
this.historicalTotal = 0
|
|
|
|
// 24-hour rolling data (one entry per minute = 1440 entries)
|
|
this.hourlyData = []
|
|
this.maxDataPoints = 24 * 60 // 24 hours * 60 minutes
|
|
|
|
// Track last cleanup to avoid too frequent operations
|
|
this.lastCleanup = Date.now()
|
|
|
|
debug('Statistics store initialized')
|
|
}
|
|
|
|
/**
|
|
* Initialize with current email count
|
|
* @param {number} count - Current email count
|
|
*/
|
|
initialize(count) {
|
|
this.currentCount = count
|
|
this.historicalTotal = count
|
|
debug(`Initialized with ${count} emails`)
|
|
}
|
|
|
|
/**
|
|
* Record an email received event
|
|
*/
|
|
recordReceive() {
|
|
this.currentCount++
|
|
this.historicalTotal++
|
|
this._addDataPoint('receive')
|
|
debug(`Email received. Current: ${this.currentCount}, Historical: ${this.historicalTotal}`)
|
|
}
|
|
|
|
/**
|
|
* Record an email deleted event
|
|
*/
|
|
recordDelete() {
|
|
this.currentCount = Math.max(0, this.currentCount - 1)
|
|
this._addDataPoint('delete')
|
|
debug(`Email deleted. Current: ${this.currentCount}`)
|
|
}
|
|
|
|
/**
|
|
* Record an email forwarded event
|
|
*/
|
|
recordForward() {
|
|
this._addDataPoint('forward')
|
|
debug(`Email forwarded`)
|
|
}
|
|
|
|
/**
|
|
* Update current count (for bulk operations like purge)
|
|
* @param {number} count - New current count
|
|
*/
|
|
updateCurrentCount(count) {
|
|
const diff = count - this.currentCount
|
|
if (diff < 0) {
|
|
// Bulk delete occurred
|
|
for (let i = 0; i < Math.abs(diff); i++) {
|
|
this._addDataPoint('delete')
|
|
}
|
|
}
|
|
this.currentCount = count
|
|
debug(`Current count updated to ${count}`)
|
|
}
|
|
|
|
/**
|
|
* Get current statistics
|
|
* @returns {Object} Current stats
|
|
*/
|
|
getStats() {
|
|
this._cleanup()
|
|
|
|
const last24h = this._getLast24Hours()
|
|
|
|
return {
|
|
currentCount: this.currentCount,
|
|
historicalTotal: this.historicalTotal,
|
|
last24Hours: {
|
|
receives: last24h.receives,
|
|
deletes: last24h.deletes,
|
|
forwards: last24h.forwards,
|
|
timeline: this._getTimeline()
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a data point to the rolling history
|
|
* @param {string} type - Type of event (receive, delete, forward)
|
|
* @private
|
|
*/
|
|
_addDataPoint(type) {
|
|
const now = Date.now()
|
|
const minute = Math.floor(now / 60000) * 60000 // Round to minute
|
|
|
|
// Find or create entry for this minute
|
|
let entry = this.hourlyData.find(e => e.timestamp === minute)
|
|
if (!entry) {
|
|
entry = {
|
|
timestamp: minute,
|
|
receives: 0,
|
|
deletes: 0,
|
|
forwards: 0
|
|
}
|
|
this.hourlyData.push(entry)
|
|
}
|
|
|
|
entry[type + 's']++
|
|
|
|
this._cleanup()
|
|
}
|
|
|
|
/**
|
|
* Clean up old data points (older than 24 hours)
|
|
* @private
|
|
*/
|
|
_cleanup() {
|
|
const now = Date.now()
|
|
|
|
// Only cleanup every 5 minutes to avoid constant filtering
|
|
if (now - this.lastCleanup < 5 * 60 * 1000) {
|
|
return
|
|
}
|
|
|
|
const cutoff = now - (24 * 60 * 60 * 1000)
|
|
const beforeCount = this.hourlyData.length
|
|
this.hourlyData = this.hourlyData.filter(entry => entry.timestamp >= cutoff)
|
|
|
|
if (beforeCount !== this.hourlyData.length) {
|
|
debug(`Cleaned up ${beforeCount - this.hourlyData.length} old data points`)
|
|
}
|
|
|
|
this.lastCleanup = now
|
|
}
|
|
|
|
/**
|
|
* Get aggregated stats for last 24 hours
|
|
* @returns {Object} Aggregated counts
|
|
* @private
|
|
*/
|
|
_getLast24Hours() {
|
|
const cutoff = Date.now() - (24 * 60 * 60 * 1000)
|
|
const recent = this.hourlyData.filter(e => e.timestamp >= cutoff)
|
|
|
|
return {
|
|
receives: recent.reduce((sum, e) => sum + e.receives, 0),
|
|
deletes: recent.reduce((sum, e) => sum + e.deletes, 0),
|
|
forwards: recent.reduce((sum, e) => sum + e.forwards, 0)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get timeline data for graphing (hourly aggregates)
|
|
* @returns {Array} Array of hourly data points
|
|
* @private
|
|
*/
|
|
_getTimeline() {
|
|
const now = Date.now()
|
|
const cutoff = now - (24 * 60 * 60 * 1000)
|
|
const hourly = {}
|
|
|
|
// Aggregate by hour
|
|
this.hourlyData
|
|
.filter(e => e.timestamp >= cutoff)
|
|
.forEach(entry => {
|
|
const hour = Math.floor(entry.timestamp / 3600000) * 3600000
|
|
if (!hourly[hour]) {
|
|
hourly[hour] = { timestamp: hour, receives: 0, deletes: 0, forwards: 0 }
|
|
}
|
|
hourly[hour].receives += entry.receives
|
|
hourly[hour].deletes += entry.deletes
|
|
hourly[hour].forwards += entry.forwards
|
|
})
|
|
|
|
// Convert to sorted array
|
|
return Object.values(hourly).sort((a, b) => a.timestamp - b.timestamp)
|
|
}
|
|
}
|
|
|
|
module.exports = StatisticsStore
|