Add cache, test
This commit is contained in:
parent
d61af3f9d5
commit
5985c8d280
|
@ -362,6 +362,7 @@ describe('KickbanModule', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: for whatever reason, this test is flaky
|
||||||
it('inserts a valid IPv6 ban', done => {
|
it('inserts a valid IPv6 ban', done => {
|
||||||
const longIP = require('../../lib/utilities').expandIPv6('::abcd');
|
const longIP = require('../../lib/utilities').expandIPv6('::abcd');
|
||||||
|
|
||||||
|
|
109
integration_test/controller/banned-channels.js
Normal file
109
integration_test/controller/banned-channels.js
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const { BannedChannelsController } = require('../../lib/controller/banned-channels');
|
||||||
|
const dbChannels = require('../../lib/database/channels');
|
||||||
|
const testDB = require('../testutil/db').testDB;
|
||||||
|
const { EventEmitter } = require('events');
|
||||||
|
|
||||||
|
require('../../lib/database').init(testDB);
|
||||||
|
|
||||||
|
const testBan = {
|
||||||
|
name: 'ban_test_1',
|
||||||
|
externalReason: 'because I said so',
|
||||||
|
internalReason: 'illegal content',
|
||||||
|
bannedBy: 'admin'
|
||||||
|
};
|
||||||
|
|
||||||
|
async function cleanupTestBan() {
|
||||||
|
return dbChannels.removeBannedChannel(testBan.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BannedChannelsController', () => {
|
||||||
|
let controller;
|
||||||
|
let messages;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await cleanupTestBan();
|
||||||
|
messages = new EventEmitter();
|
||||||
|
controller = new BannedChannelsController(
|
||||||
|
dbChannels,
|
||||||
|
messages
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await cleanupTestBan();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bans a channel', async () => {
|
||||||
|
assert.strictEqual(await controller.getBannedChannel(testBan.name), null);
|
||||||
|
|
||||||
|
let received = null;
|
||||||
|
messages.once('ChannelBanned', cb => {
|
||||||
|
received = cb;
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.banChannel(testBan);
|
||||||
|
let info = await controller.getBannedChannel(testBan.name);
|
||||||
|
for (let field of Object.keys(testBan)) {
|
||||||
|
// Consider renaming parameter to avoid this branch
|
||||||
|
if (field === 'name') {
|
||||||
|
assert.strictEqual(info.channelName, testBan.name);
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(info[field], testBan[field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.notEqual(received, null);
|
||||||
|
assert.strictEqual(received.channel, testBan.name);
|
||||||
|
assert.strictEqual(received.externalReason, testBan.externalReason);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates an existing ban', async () => {
|
||||||
|
let received = [];
|
||||||
|
messages.on('ChannelBanned', cb => {
|
||||||
|
received.push(cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.banChannel(testBan);
|
||||||
|
|
||||||
|
let testBan2 = { ...testBan, externalReason: 'because of reasons' };
|
||||||
|
await controller.banChannel(testBan2);
|
||||||
|
|
||||||
|
let info = await controller.getBannedChannel(testBan2.name);
|
||||||
|
for (let field of Object.keys(testBan2)) {
|
||||||
|
// Consider renaming parameter to avoid this branch
|
||||||
|
if (field === 'name') {
|
||||||
|
assert.strictEqual(info.channelName, testBan2.name);
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(info[field], testBan2[field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepStrictEqual(received, [
|
||||||
|
{
|
||||||
|
channel: testBan.name,
|
||||||
|
externalReason: testBan.externalReason
|
||||||
|
},
|
||||||
|
{
|
||||||
|
channel: testBan2.name,
|
||||||
|
externalReason: testBan2.externalReason
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unbans a channel', async () => {
|
||||||
|
let received = null;
|
||||||
|
messages.once('ChannelUnbanned', cb => {
|
||||||
|
received = cb;
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.banChannel(testBan);
|
||||||
|
await controller.unbanChannel(testBan.name, testBan.bannedBy);
|
||||||
|
|
||||||
|
let info = await controller.getBannedChannel(testBan.name);
|
||||||
|
assert.strictEqual(info, null);
|
||||||
|
|
||||||
|
assert.notEqual(received, null);
|
||||||
|
assert.strictEqual(received.channel, testBan.name);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,10 +1,15 @@
|
||||||
import { eventlog } from '../logger';
|
import { eventlog } from '../logger';
|
||||||
|
import { SimpleCache } from '../util/simple-cache';
|
||||||
const LOGGER = require('@calzoneman/jsli')('BannedChannelsController');
|
const LOGGER = require('@calzoneman/jsli')('BannedChannelsController');
|
||||||
|
|
||||||
export class BannedChannelsController {
|
export class BannedChannelsController {
|
||||||
constructor(dbChannels, globalMessageBus) {
|
constructor(dbChannels, globalMessageBus) {
|
||||||
this.dbChannels = dbChannels;
|
this.dbChannels = dbChannels;
|
||||||
this.globalMessageBus = globalMessageBus;
|
this.globalMessageBus = globalMessageBus;
|
||||||
|
this.cache = new SimpleCache({
|
||||||
|
maxElem: 1000,
|
||||||
|
maxAge: 5 * 60_000
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -20,6 +25,8 @@ export class BannedChannelsController {
|
||||||
LOGGER.warn(`Channel ${name} is already banned, updating ban reason`);
|
LOGGER.warn(`Channel ${name} is already banned, updating ban reason`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.cache.delete(name);
|
||||||
|
|
||||||
await this.dbChannels.putBannedChannel({
|
await this.dbChannels.putBannedChannel({
|
||||||
name,
|
name,
|
||||||
externalReason,
|
externalReason,
|
||||||
|
@ -36,6 +43,7 @@ export class BannedChannelsController {
|
||||||
async unbanChannel(name, unbannedBy) {
|
async unbanChannel(name, unbannedBy) {
|
||||||
LOGGER.info(`Unbanning channel ${name}`);
|
LOGGER.info(`Unbanning channel ${name}`);
|
||||||
eventlog.log(`[acp] ${unbannedBy} unbanned channel ${name}`);
|
eventlog.log(`[acp] ${unbannedBy} unbanned channel ${name}`);
|
||||||
|
this.cache.delete(name);
|
||||||
|
|
||||||
this.globalMessageBus.emit(
|
this.globalMessageBus.emit(
|
||||||
'ChannelUnbanned',
|
'ChannelUnbanned',
|
||||||
|
@ -46,47 +54,14 @@ export class BannedChannelsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBannedChannel(name) {
|
async getBannedChannel(name) {
|
||||||
// TODO: cache
|
name = name.toLowerCase();
|
||||||
return this.dbChannels.getBannedChannel(name);
|
|
||||||
}
|
let info = this.cache.get(name);
|
||||||
}
|
if (info === null) {
|
||||||
|
info = await this.dbChannels.getBannedChannel(name);
|
||||||
class Cache {
|
this.cache.put(name, info);
|
||||||
constructor({ maxElem, maxAge }) {
|
}
|
||||||
this.maxElem = maxElem;
|
|
||||||
this.maxAge = maxAge;
|
return info;
|
||||||
this.cache = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
put(key, value) {
|
|
||||||
this.cache.set(key, { value: value, at: Date.now() });
|
|
||||||
|
|
||||||
if (this.cache.size > this.maxElem) {
|
|
||||||
this.cache.delete(this.cache.keys().next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key) {
|
|
||||||
let val = this.cache.get(key);
|
|
||||||
|
|
||||||
if (val != null) {
|
|
||||||
return val.value;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(key) {
|
|
||||||
this.cache.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
let now = Date.now();
|
|
||||||
|
|
||||||
for (let [key, value] of this.cache) {
|
|
||||||
if (value.at < now - this.maxAge) {
|
|
||||||
this.cache.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ function handleLine(line, client) {
|
||||||
client.write('{"status":"error","error":"internal error"}\n');
|
client.write('{"status":"error","error":"internal error"}\n');
|
||||||
});
|
});
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
|
// eslint no-empty: off
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line === '/reload') {
|
if (line === '/reload') {
|
||||||
|
|
|
@ -141,7 +141,8 @@ var Server = function () {
|
||||||
Config.getEmailConfig(),
|
Config.getEmailConfig(),
|
||||||
emailController,
|
emailController,
|
||||||
Config.getCaptchaConfig(),
|
Config.getCaptchaConfig(),
|
||||||
captchaController
|
captchaController,
|
||||||
|
self.bannedChannelsController
|
||||||
);
|
);
|
||||||
|
|
||||||
// http/https/sio server init -----------------------------------------
|
// http/https/sio server init -----------------------------------------
|
||||||
|
|
45
src/util/simple-cache.js
Normal file
45
src/util/simple-cache.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
class SimpleCache {
|
||||||
|
constructor({ maxElem, maxAge }) {
|
||||||
|
this.maxElem = maxElem;
|
||||||
|
this.maxAge = maxAge;
|
||||||
|
this.cache = new Map();
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
this.cleanup();
|
||||||
|
}, maxAge).unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
put(key, value) {
|
||||||
|
this.cache.set(key, { value: value, at: Date.now() });
|
||||||
|
|
||||||
|
if (this.cache.size > this.maxElem) {
|
||||||
|
this.cache.delete(this.cache.keys().next().value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
let val = this.cache.get(key);
|
||||||
|
|
||||||
|
if (val != null && Date.now() < val.at + this.maxAge) {
|
||||||
|
return val.value;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
let now = Date.now();
|
||||||
|
|
||||||
|
for (let [key, value] of this.cache) {
|
||||||
|
if (value.at < now - this.maxAge) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SimpleCache };
|
|
@ -11,7 +11,6 @@ export default function initialize(app, ioConfig, chanPath, getBannedChannel) {
|
||||||
'channel name.', { status: HTTPStatus.NOT_FOUND });
|
'channel name.', { status: HTTPStatus.NOT_FOUND });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add a cache
|
|
||||||
let banInfo = await getBannedChannel(req.params.channel);
|
let banInfo = await getBannedChannel(req.params.channel);
|
||||||
if (banInfo !== null) {
|
if (banInfo !== null) {
|
||||||
sendPug(res, 'banned_channel', {
|
sendPug(res, 'banned_channel', {
|
||||||
|
|
|
@ -143,7 +143,8 @@ module.exports = {
|
||||||
emailConfig,
|
emailConfig,
|
||||||
emailController,
|
emailController,
|
||||||
captchaConfig,
|
captchaConfig,
|
||||||
captchaController
|
captchaController,
|
||||||
|
bannedChannelsController
|
||||||
) {
|
) {
|
||||||
patchExpressToHandleAsync();
|
patchExpressToHandleAsync();
|
||||||
const chanPath = Config.get('channel-path');
|
const chanPath = Config.get('channel-path');
|
||||||
|
@ -198,8 +199,7 @@ module.exports = {
|
||||||
app,
|
app,
|
||||||
ioConfig,
|
ioConfig,
|
||||||
chanPath,
|
chanPath,
|
||||||
// TODO: banController
|
async name => bannedChannelsController.getBannedChannel(name)
|
||||||
require('../database/channels').getBannedChannel
|
|
||||||
);
|
);
|
||||||
require('./routes/index')(app, channelIndex, webConfig.getMaxIndexEntries());
|
require('./routes/index')(app, channelIndex, webConfig.getMaxIndexEntries());
|
||||||
require('./routes/socketconfig')(app, clusterClient);
|
require('./routes/socketconfig')(app, clusterClient);
|
||||||
|
|
52
test/util/simple-cache.js
Normal file
52
test/util/simple-cache.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const { SimpleCache } = require('../../lib/util/simple-cache');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
describe('SimpleCache', () => {
|
||||||
|
const CACHE_MAX_ELEM = 5;
|
||||||
|
const CACHE_MAX_AGE = 5;
|
||||||
|
let cache;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cache = new SimpleCache({
|
||||||
|
maxElem: CACHE_MAX_ELEM,
|
||||||
|
maxAge: CACHE_MAX_AGE
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets, gets, and deletes a value', () => {
|
||||||
|
assert.strictEqual(cache.get('foo'), null);
|
||||||
|
|
||||||
|
cache.put('foo', 'bar');
|
||||||
|
assert.strictEqual(cache.get('foo'), 'bar');
|
||||||
|
|
||||||
|
cache.delete('foo');
|
||||||
|
assert.strictEqual(cache.get('foo'), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return an expired value', done => {
|
||||||
|
cache.put('foo', 'bar');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(cache.get('foo'), null);
|
||||||
|
done();
|
||||||
|
}, CACHE_MAX_AGE + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cleans up old values', done => {
|
||||||
|
cache.put('foo', 'bar');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(cache.get('foo'), null);
|
||||||
|
done();
|
||||||
|
}, CACHE_MAX_AGE * 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes the oldest entry if max elem is reached', () => {
|
||||||
|
for (let i = 0; i < CACHE_MAX_ELEM + 1; i++) {
|
||||||
|
cache.put(`foo${i}`, 'bar');
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.strictEqual(cache.get('foo0'), null);
|
||||||
|
assert.strictEqual(cache.get('foo1'), 'bar');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue