167 lines
4.6 KiB
TypeScript
167 lines
4.6 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import { logger } from '../logger.js';
|
|
import { CacheDb } from './classes.cachedb.js';
|
|
|
|
// Import document classes for cleanup
|
|
import { CachedEmail } from './documents/classes.cached.email.js';
|
|
import { CachedIPReputation } from './documents/classes.cached.ip.reputation.js';
|
|
|
|
/**
|
|
* Configuration for the cache cleaner
|
|
*/
|
|
export interface ICacheCleanerOptions {
|
|
/** Cleanup interval in milliseconds (default: 1 hour) */
|
|
intervalMs?: number;
|
|
/** Enable verbose logging */
|
|
verbose?: boolean;
|
|
}
|
|
|
|
/**
|
|
* CacheCleaner - Periodically removes expired documents from the cache
|
|
*
|
|
* Runs on a configurable interval (default: hourly) and queries each
|
|
* collection for documents where expiresAt < now(), then deletes them.
|
|
*/
|
|
export class CacheCleaner {
|
|
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
|
private isRunning: boolean = false;
|
|
private options: Required<ICacheCleanerOptions>;
|
|
private cacheDb: CacheDb;
|
|
|
|
constructor(cacheDb: CacheDb, options: ICacheCleanerOptions = {}) {
|
|
this.cacheDb = cacheDb;
|
|
this.options = {
|
|
intervalMs: options.intervalMs || 60 * 60 * 1000, // 1 hour default
|
|
verbose: options.verbose || false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Start the periodic cleanup process
|
|
*/
|
|
public start(): void {
|
|
if (this.isRunning) {
|
|
logger.log('warn', 'CacheCleaner already running');
|
|
return;
|
|
}
|
|
|
|
this.isRunning = true;
|
|
|
|
// Run cleanup immediately on start
|
|
this.runCleanup().catch((error) => {
|
|
logger.log('error', `Initial cache cleanup failed: ${error.message}`);
|
|
});
|
|
|
|
// Schedule periodic cleanup
|
|
this.cleanupInterval = setInterval(() => {
|
|
this.runCleanup().catch((error) => {
|
|
logger.log('error', `Cache cleanup failed: ${error.message}`);
|
|
});
|
|
}, this.options.intervalMs);
|
|
|
|
logger.log(
|
|
'info',
|
|
`CacheCleaner started with interval: ${this.options.intervalMs / 1000 / 60} minutes`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Stop the periodic cleanup process
|
|
*/
|
|
public stop(): void {
|
|
if (!this.isRunning) {
|
|
return;
|
|
}
|
|
|
|
if (this.cleanupInterval) {
|
|
clearInterval(this.cleanupInterval);
|
|
this.cleanupInterval = null;
|
|
}
|
|
|
|
this.isRunning = false;
|
|
logger.log('info', 'CacheCleaner stopped');
|
|
}
|
|
|
|
/**
|
|
* Run a single cleanup cycle
|
|
*/
|
|
public async runCleanup(): Promise<void> {
|
|
if (!this.cacheDb.isReady()) {
|
|
logger.log('warn', 'CacheDb not ready, skipping cleanup');
|
|
return;
|
|
}
|
|
|
|
const now = new Date();
|
|
const results: { collection: string; deleted: number }[] = [];
|
|
|
|
try {
|
|
const emailsDeleted = await this.cleanExpiredDocuments(CachedEmail, now);
|
|
results.push({ collection: 'CachedEmail', deleted: emailsDeleted });
|
|
|
|
const ipReputationDeleted = await this.cleanExpiredDocuments(CachedIPReputation, now);
|
|
results.push({ collection: 'CachedIPReputation', deleted: ipReputationDeleted });
|
|
|
|
// Log results
|
|
const totalDeleted = results.reduce((sum, r) => sum + r.deleted, 0);
|
|
if (totalDeleted > 0 || this.options.verbose) {
|
|
const summary = results
|
|
.filter((r) => r.deleted > 0)
|
|
.map((r) => `${r.collection}: ${r.deleted}`)
|
|
.join(', ');
|
|
logger.log(
|
|
'info',
|
|
`Cache cleanup completed. Deleted ${totalDeleted} expired documents. ${summary || 'No deletions.'}`
|
|
);
|
|
}
|
|
} catch (error) {
|
|
logger.log('error', `Cache cleanup error: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean expired documents from a specific collection using smartdata API
|
|
*/
|
|
private async cleanExpiredDocuments<T extends { delete: () => Promise<void> }>(
|
|
documentClass: { getInstances: (filter: any) => Promise<T[]> },
|
|
now: Date
|
|
): Promise<number> {
|
|
try {
|
|
// Find all expired documents
|
|
const expiredDocs = await documentClass.getInstances({
|
|
expiresAt: { $lt: now },
|
|
});
|
|
|
|
// Delete each expired document
|
|
let deletedCount = 0;
|
|
for (const doc of expiredDocs) {
|
|
try {
|
|
await doc.delete();
|
|
deletedCount++;
|
|
} catch (deleteError) {
|
|
logger.log('warn', `Failed to delete expired document: ${deleteError.message}`);
|
|
}
|
|
}
|
|
|
|
return deletedCount;
|
|
} catch (error) {
|
|
logger.log('error', `Error cleaning collection: ${error.message}`);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the cleaner is running
|
|
*/
|
|
public isActive(): boolean {
|
|
return this.isRunning;
|
|
}
|
|
|
|
/**
|
|
* Get the cleanup interval in milliseconds
|
|
*/
|
|
public getIntervalMs(): number {
|
|
return this.options.intervalMs;
|
|
}
|
|
}
|