const { LRUCache } = require('lru-cache') const Logger = require('../Logger') const Database = require('../Database') class ApiCacheManager { defaultCacheOptions = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: item => (item.body.length + JSON.stringify(item.headers).length) } defaultTtlOptions = { ttl: 30 * 60 * 1000 } constructor(cache = new LRUCache(this.defaultCacheOptions), ttlOptions = this.defaultTtlOptions) { this.cache = cache this.ttlOptions = ttlOptions } init(database = Database) { let hooks = ['afterCreate', 'afterUpdate', 'afterDestroy', 'afterBulkCreate', 'afterBulkUpdate', 'afterBulkDestroy', 'afterUpsert'] hooks.forEach(hook => database.sequelize.addHook(hook, (model) => this.clear(model, hook))) } clear(model, hook) { Logger.debug(`[ApiCacheManager] ${model.constructor.name}.${hook}: Clearing cache`) this.cache.clear() } /** * Reset hooks and clear cache. Used when applying backups */ reset() { Logger.info(`[ApiCacheManager] Resetting cache`) this.init() this.cache.clear() } get middleware() { return (req, res, next) => { const key = { user: req.user.username, url: req.url } const stringifiedKey = JSON.stringify(key) Logger.debug(`[ApiCacheManager] count: ${this.cache.size} size: ${this.cache.calculatedSize}`) const cached = this.cache.get(stringifiedKey) if (cached) { Logger.debug(`[ApiCacheManager] Cache hit: ${stringifiedKey}`) res.set(cached.headers) res.status(cached.statusCode) res.send(cached.body) return } res.originalSend = res.send res.send = (body) => { Logger.debug(`[ApiCacheManager] Cache miss: ${stringifiedKey}`) const cached = { body, headers: res.getHeaders(), statusCode: res.statusCode } if (key.url.search(/^\/libraries\/.*?\/personalized/) !== -1) { Logger.debug(`[ApiCacheManager] Caching with ${this.ttlOptions.ttl} ms TTL`) this.cache.set(stringifiedKey, cached, this.ttlOptions) } else { this.cache.set(stringifiedKey, cached) } res.originalSend(body) } next() } } } module.exports = ApiCacheManager