Compare commits

..

7 Commits

Author SHA1 Message Date
Klemek f7167a85a8 1.3.1: current visitors 2021-03-30 20:16:15 +02:00
Klemek 4a3b8267ec update Dockerfile 2021-03-30 19:56:08 +02:00
Klemek d0bebcba87 fix Dockerfile 2021-03-30 19:53:26 +02:00
Klemek 56d7993116 bump to node 15 2021-03-30 19:47:18 +02:00
Klemek 99a19edb93 updated README.md 2021-03-30 19:19:55 +02:00
Klemek da900d2d02 update version to 1.3.0 2021-03-30 19:19:29 +02:00
Klemek 51c1afb4d6 Merge pull request #53 from Klemek/f-hit-counter
Hit counter #47
2021-03-30 19:17:30 +02:00
7 changed files with 88 additions and 39 deletions
+5 -1
View File
@@ -1,12 +1,16 @@
FROM node:14
FROM node:15
# Create app directory
WORKDIR /usr/src/app
VOLUME [ "/usr/src/app/data" ]
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
COPY src/postinstall.js ./src/postinstall.js
COPY src/config.default.json ./src/config.default.json
RUN npm install
# If you are building your code for production
-1
View File
@@ -1,4 +1,3 @@
[![Build Status](https://img.shields.io/travis/Klemek/GitBlog.md.svg?branch=master)](https://travis-ci.org/Klemek/GitBlog.md)
[![Scc Count Badge](https://sloc.xyz/github/klemek/gitblog.md/?category=code)](https://github.com/boyter/scc/#badges-beta)
[![Coverage Status](https://img.shields.io/coveralls/github/Klemek/GitBlog.md.svg?branch=master)](https://coveralls.io/github/Klemek/GitBlog.md?branch=master)
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/Klemek/GitBlog.md.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Klemek/GitBlog.md/context:javascript)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gitblog.md",
"version": "1.2.8",
"version": "1.3.1",
"description": "A static blog using Markdown pulled from your git repository.",
"main": "src/server.js",
"dependencies": {
+2 -8
View File
@@ -182,10 +182,7 @@ module.exports = (config) => {
app.get('/stats', (req, res) => {
if (config['modules']['hit_counter']) {
hc.read('/', (data) => {
res.json({
hits: data.hits,
visitors: data.visitors,
});
res.json(data);
});
} else {
showError(req, res, 404);
@@ -268,10 +265,7 @@ module.exports = (config) => {
} else if (req.path.endsWith('stats')) {
if (config['modules']['hit_counter']) {
hc.read(articlePath, (data) => {
res.json({
hits: data.hits,
visitors: data.visitors,
});
res.json(data);
});
} else {
showError(req, res, 404);
+21 -5
View File
@@ -13,10 +13,10 @@ module.exports = (config, onConnect, onError) => {
cb();
} else {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const key = path + ':' + ip;
visitors[path] = (visitors[path] || {});
const now = Date.now();
const isNewVisitor = (now - (visitors[key] || 0)) > config['hit_counter']['unique_visitor_timeout'];
visitors[key] = now;
const isNewVisitor = (now - (visitors[path][ip] || 0)) > config['hit_counter']['unique_visitor_timeout'];
visitors[path][ip] = now;
client
.multi()
.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) => {
if (!client.connected) {
cb({
hits: 0,
visitors: 0,
total_visitors: 0,
current_visitors: 0,
});
} else {
client.hgetall(path, (_, value) => {
cb({
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
View File
@@ -209,7 +209,11 @@ describe('Test root path', () => {
request(app).get('/stats')
.then((response) => {
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();
});
});
@@ -478,7 +482,11 @@ describe('Test articles rendering', () => {
request(app).get('/2019/05/05/anything/stats')
.then((response) => {
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();
});
});
+49 -21
View File
@@ -37,14 +37,21 @@ test('options passed to 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) => {
expect(path).toBe('/test/path/');
cb(undefined, { h: 12, v: 34 });
@@ -52,12 +59,13 @@ describe('read()', () => {
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(12);
expect(data.visitors).toBe(34);
expect(data.total_visitors).toBe(34);
expect(data.current_visitors).toBe(0);
done();
});
});
test('read path with error', (done) => {
test('with error', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb('error', undefined);
@@ -65,12 +73,13 @@ describe('read()', () => {
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(0);
expect(data.visitors).toBe(0);
expect(data.total_visitors).toBe(0);
expect(data.current_visitors).toBe(0);
done();
});
});
test('read path with error 2', (done) => {
test('with error 2', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb(undefined, {});
@@ -78,22 +87,41 @@ describe('read()', () => {
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(0);
expect(data.visitors).toBe(0);
expect(data.total_visitors).toBe(0);
expect(data.current_visitors).toBe(0);
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()', () => {
beforeEach(() => {
mockClient.multi = () => mockClient;
mockClient.hincrby = () => mockClient;
mockClient.exec = (cb) => {
cb();
};
config['hit_counter']['unique_visitor_timeout'] = -1;
});
test('simple visit', (done) => {
let multiCalled = false;
let execCalled = false;