1.3.1: current visitors
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitblog.md",
|
"name": "gitblog.md",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"description": "A static blog using Markdown pulled from your git repository.",
|
"description": "A static blog using Markdown pulled from your git repository.",
|
||||||
"main": "src/server.js",
|
"main": "src/server.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+2
-8
@@ -182,10 +182,7 @@ module.exports = (config) => {
|
|||||||
app.get('/stats', (req, res) => {
|
app.get('/stats', (req, res) => {
|
||||||
if (config['modules']['hit_counter']) {
|
if (config['modules']['hit_counter']) {
|
||||||
hc.read('/', (data) => {
|
hc.read('/', (data) => {
|
||||||
res.json({
|
res.json(data);
|
||||||
hits: data.hits,
|
|
||||||
visitors: data.visitors,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showError(req, res, 404);
|
showError(req, res, 404);
|
||||||
@@ -268,10 +265,7 @@ module.exports = (config) => {
|
|||||||
} else if (req.path.endsWith('stats')) {
|
} else if (req.path.endsWith('stats')) {
|
||||||
if (config['modules']['hit_counter']) {
|
if (config['modules']['hit_counter']) {
|
||||||
hc.read(articlePath, (data) => {
|
hc.read(articlePath, (data) => {
|
||||||
res.json({
|
res.json(data);
|
||||||
hits: data.hits,
|
|
||||||
visitors: data.visitors,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showError(req, res, 404);
|
showError(req, res, 404);
|
||||||
|
|||||||
+21
-5
@@ -13,10 +13,10 @@ module.exports = (config, onConnect, onError) => {
|
|||||||
cb();
|
cb();
|
||||||
} else {
|
} else {
|
||||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||||
const key = path + ':' + ip;
|
visitors[path] = (visitors[path] || {});
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const isNewVisitor = (now - (visitors[key] || 0)) > config['hit_counter']['unique_visitor_timeout'];
|
const isNewVisitor = (now - (visitors[path][ip] || 0)) > config['hit_counter']['unique_visitor_timeout'];
|
||||||
visitors[key] = now;
|
visitors[path][ip] = now;
|
||||||
client
|
client
|
||||||
.multi()
|
.multi()
|
||||||
.hincrby(path, 'h', 1)
|
.hincrby(path, 'h', 1)
|
||||||
@@ -25,17 +25,33 @@ module.exports = (config, onConnect, onError) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanVisitors = (path) => {
|
||||||
|
visitors[path] = (visitors[path] || {});
|
||||||
|
const now = Date.now();
|
||||||
|
let count = 0;
|
||||||
|
for (let ip in visitors[path]) {
|
||||||
|
if ((now - visitors[path][ip]) > config['hit_counter']['unique_visitor_timeout']) {
|
||||||
|
delete visitors[path][ip];
|
||||||
|
} else {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
const read = (path, cb) => {
|
const read = (path, cb) => {
|
||||||
if (!client.connected) {
|
if (!client.connected) {
|
||||||
cb({
|
cb({
|
||||||
hits: 0,
|
hits: 0,
|
||||||
visitors: 0,
|
total_visitors: 0,
|
||||||
|
current_visitors: 0,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
client.hgetall(path, (_, value) => {
|
client.hgetall(path, (_, value) => {
|
||||||
cb({
|
cb({
|
||||||
hits: value ? value.h || 0 : 0,
|
hits: value ? value.h || 0 : 0,
|
||||||
visitors: value ? value.v || 0 : 0,
|
total_visitors: value ? value.v || 0 : 0,
|
||||||
|
current_visitors: cleanVisitors(path),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -209,7 +209,11 @@ describe('Test root path', () => {
|
|||||||
request(app).get('/stats')
|
request(app).get('/stats')
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body).toEqual({ hits: 0, visitors: 0 });
|
expect(response.body).toEqual({
|
||||||
|
hits: 0,
|
||||||
|
total_visitors: 0,
|
||||||
|
current_visitors: 0,
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -478,7 +482,11 @@ describe('Test articles rendering', () => {
|
|||||||
request(app).get('/2019/05/05/anything/stats')
|
request(app).get('/2019/05/05/anything/stats')
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body).toEqual({ hits: 0, visitors: 0 });
|
expect(response.body).toEqual({
|
||||||
|
hits: 0,
|
||||||
|
total_visitors: 0,
|
||||||
|
current_visitors: 0,
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+49
-21
@@ -37,14 +37,21 @@ test('options passed to redis', () => {
|
|||||||
expect(mockClient.options).toEqual(config['redis']);
|
expect(mockClient.options).toEqual(config['redis']);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('read()', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockClient.hgetall = (_, cb) => {
|
|
||||||
cb();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
test('read path', (done) => {
|
beforeEach(() => {
|
||||||
|
mockClient.hgetall = (_, cb) => {
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
mockClient.multi = () => mockClient;
|
||||||
|
mockClient.hincrby = () => mockClient;
|
||||||
|
mockClient.exec = (cb) => {
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
config['hit_counter']['unique_visitor_timeout'] = -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('read()', () => {
|
||||||
|
test('normal', (done) => {
|
||||||
mockClient.hgetall = (path, cb) => {
|
mockClient.hgetall = (path, cb) => {
|
||||||
expect(path).toBe('/test/path/');
|
expect(path).toBe('/test/path/');
|
||||||
cb(undefined, { h: 12, v: 34 });
|
cb(undefined, { h: 12, v: 34 });
|
||||||
@@ -52,12 +59,13 @@ describe('read()', () => {
|
|||||||
hc.read('/test/path/', (data) => {
|
hc.read('/test/path/', (data) => {
|
||||||
expect(data).toBeDefined();
|
expect(data).toBeDefined();
|
||||||
expect(data.hits).toBe(12);
|
expect(data.hits).toBe(12);
|
||||||
expect(data.visitors).toBe(34);
|
expect(data.total_visitors).toBe(34);
|
||||||
|
expect(data.current_visitors).toBe(0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('read path with error', (done) => {
|
test('with error', (done) => {
|
||||||
mockClient.hgetall = (path, cb) => {
|
mockClient.hgetall = (path, cb) => {
|
||||||
expect(path).toBe('/test/path/');
|
expect(path).toBe('/test/path/');
|
||||||
cb('error', undefined);
|
cb('error', undefined);
|
||||||
@@ -65,12 +73,13 @@ describe('read()', () => {
|
|||||||
hc.read('/test/path/', (data) => {
|
hc.read('/test/path/', (data) => {
|
||||||
expect(data).toBeDefined();
|
expect(data).toBeDefined();
|
||||||
expect(data.hits).toBe(0);
|
expect(data.hits).toBe(0);
|
||||||
expect(data.visitors).toBe(0);
|
expect(data.total_visitors).toBe(0);
|
||||||
|
expect(data.current_visitors).toBe(0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('read path with error 2', (done) => {
|
test('with error 2', (done) => {
|
||||||
mockClient.hgetall = (path, cb) => {
|
mockClient.hgetall = (path, cb) => {
|
||||||
expect(path).toBe('/test/path/');
|
expect(path).toBe('/test/path/');
|
||||||
cb(undefined, {});
|
cb(undefined, {});
|
||||||
@@ -78,22 +87,41 @@ describe('read()', () => {
|
|||||||
hc.read('/test/path/', (data) => {
|
hc.read('/test/path/', (data) => {
|
||||||
expect(data).toBeDefined();
|
expect(data).toBeDefined();
|
||||||
expect(data.hits).toBe(0);
|
expect(data.hits).toBe(0);
|
||||||
expect(data.visitors).toBe(0);
|
expect(data.total_visitors).toBe(0);
|
||||||
|
expect(data.current_visitors).toBe(0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('1 visitor', (done) => {
|
||||||
|
config['hit_counter']['unique_visitor_timeout'] = 1000;
|
||||||
|
hc.count({
|
||||||
|
headers: {},
|
||||||
|
connection: { remoteAddress: 'test1' },
|
||||||
|
}, '/test/path/5', () => {
|
||||||
|
hc.read('/test/path/5', (data) => {
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.current_visitors).toBe(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cleaned old visitor', (done) => {
|
||||||
|
hc.count({
|
||||||
|
headers: {},
|
||||||
|
connection: { remoteAddress: 'test1' },
|
||||||
|
}, '/test/path/5', () => {
|
||||||
|
hc.read('/test/path/5', (data) => {
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.current_visitors).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('count()', () => {
|
describe('count()', () => {
|
||||||
beforeEach(() => {
|
|
||||||
mockClient.multi = () => mockClient;
|
|
||||||
mockClient.hincrby = () => mockClient;
|
|
||||||
mockClient.exec = (cb) => {
|
|
||||||
cb();
|
|
||||||
};
|
|
||||||
config['hit_counter']['unique_visitor_timeout'] = -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('simple visit', (done) => {
|
test('simple visit', (done) => {
|
||||||
let multiCalled = false;
|
let multiCalled = false;
|
||||||
let execCalled = false;
|
let execCalled = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user