BREAKING CHANGE(config): convert configuration management to read-only; remove updateConfiguration endpoint and client-side editing

This commit is contained in:
2026-02-03 23:26:51 +00:00
parent 5de3344905
commit 9e0e77737b
25 changed files with 2129 additions and 269 deletions

170
ts/cache/classes.cache.cleaner.ts vendored Normal file
View File

@@ -0,0 +1,170 @@
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';
import { CachedBounce } from './documents/classes.cached.bounce.js';
import { CachedSuppression } from './documents/classes.cached.suppression.js';
import { CachedDKIMKey } from './documents/classes.cached.dkim.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 {
// Clean CachedEmail documents
const emailsDeleted = await this.cleanCollection(CachedEmail, now);
results.push({ collection: 'CachedEmail', deleted: emailsDeleted });
// Clean CachedIPReputation documents
const ipReputationDeleted = await this.cleanCollection(CachedIPReputation, now);
results.push({ collection: 'CachedIPReputation', deleted: ipReputationDeleted });
// Clean CachedBounce documents
const bouncesDeleted = await this.cleanCollection(CachedBounce, now);
results.push({ collection: 'CachedBounce', deleted: bouncesDeleted });
// Clean CachedSuppression documents (but not permanent ones)
const suppressionDeleted = await this.cleanCollection(CachedSuppression, now);
results.push({ collection: 'CachedSuppression', deleted: suppressionDeleted });
// Clean CachedDKIMKey documents
const dkimDeleted = await this.cleanCollection(CachedDKIMKey, now);
results.push({ collection: 'CachedDKIMKey', deleted: dkimDeleted });
// 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
*/
private async cleanCollection<T>(
documentClass: { deleteMany: (filter: any) => Promise<any> },
now: Date
): Promise<number> {
try {
const result = await documentClass.deleteMany({
expiresAt: { $lt: now },
});
return result?.deletedCount || 0;
} 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;
}
}