Skip to content

Commit d157489

Browse files
authored
Merge pull request #3 from topcoderinc/challenge_2
changes for challenge 4
2 parents 1c1d2ea + a24697d commit d157489

30 files changed

+3745
-2927
lines changed

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This project was generated with [Angular CLI](https://github.com/angular/angular
66

77
- Node 8.11.x , npm 5.6.x
88
- redis 4.x
9+
- docker
910

1011
### Redis
1112

@@ -18,22 +19,30 @@ This project was generated with [Angular CLI](https://github.com/angular/angular
1819
$ make
1920
```
2021

21-
- start up redis server `cd src` `./redis-server`
22+
- start up redis server `cd src` `./redis-server --bind 0.0.0.0`
2223
- load sample data into redis server, `cd src`, then run this `cat <submission-folder>/simple-data-txt.txt | ./redis-cli --pipe` import simple data.
2324

2425
### Local run
2526

2627
- goto submission folder, run `npm i` first
2728
- just run `npm run start`, and use browsers open http://127.0.0.1:3003
2829

30+
### Docker build and run
31+
32+
- build image, make sure your dokcer already startup
33+
- run `./build-docker-image.sh` to build docker image, the image named **tc/redis-manager**
34+
- after build succeed, run `docker run -p 3003:3003 -it tc/redis-manager` to run image
35+
- then use browsers open http://127.0.0.1:3003
36+
2937
### Configs
3038

31-
| Key | default | Description |
32-
| ---------------------------------------- | ---------------- | -------------------------------- |
33-
| config/default.js **PORT** | 3003 | the web app run port |
34-
| config/default.js **API_VERSION** | api/1.0 | the backend endpoint prefix |
35-
| config/default.js **LOG_LEVEL** | Debug | the backend log level |
36-
| src/environments/environments.ts **URI** | /backend/api/1.0 | the backend uri used in frontend |
39+
| Key | default | Description |
40+
| ----------------------------------------------- | ---------------- | -------------------------------- |
41+
| config/default.js **PORT** | 3003 | the web app run port |
42+
| config/default.js **API_VERSION** | api/1.0 | the backend endpoint prefix |
43+
| config/default.js **LOG_LEVEL** | Debug | the backend log level |
44+
| config/default.js **defaultExternalConfigLink** | None | the default external config link |
45+
| src/environments/environments.ts **URI** | /backend/api/1.0 | the backend uri used in frontend |
3746

3847

3948

app.js

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,16 @@
1-
const config = require('config');
2-
const _ = require('lodash');
1+
const config = require('./config/default');
32

43
const HApi = require('hapi');
5-
const routes = require('./lib/route');
64
const logger = require('./lib/common/logger');
5+
const redisManagerPlugin = require('./lib');
76

87
// Create a server with a host and port
9-
const server = HApi.server({
10-
port: config.PORT,
11-
});
12-
13-
14-
/**
15-
* inject cors headers
16-
*/
17-
const injectHeader = (h) => {
18-
h.header("Access-Control-Allow-Origin", "*");
19-
h.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
20-
h.header("Access-Control-Allow-Headers", "If-Modified-Since, Origin, X-Requested-With, Content-Type, Accept, Authorization");
21-
return h;
22-
};
23-
24-
/**
25-
* inject routes
26-
*/
27-
_.each(routes, (route, path) => {
28-
const newPath = '/backend/' + config.API_VERSION + path;
29-
server.route({method: 'options', path: newPath, handler: (req, h) => injectHeader(h.response('ok'))});
30-
_.each(route, (handler, method) => {
31-
32-
logger.info(`endpoint added, [${method.toUpperCase()}] ${newPath}`);
33-
server.route({
34-
method,
35-
path: newPath,
36-
handler: async (req, h) => {
37-
let result = {};
38-
let status = 200;
39-
try {
40-
result = await handler.method(req, h);
41-
} catch (e) {
42-
result = {code: e.status, message: e.message}
43-
status = e.status || 500;
44-
}
45-
return injectHeader(h.response(result).code(status));
46-
}
47-
});
48-
});
49-
});
50-
8+
const server = HApi.server({port: config.PORT});
519

5210
// Start the server
5311
async function start() {
5412
try {
55-
await server.register(require('inert'));
56-
57-
// add static folder
58-
server.route({
59-
method: 'GET',
60-
path: '/{param*}',
61-
handler: {
62-
directory: {
63-
path: 'dist',
64-
}
65-
}
66-
});
13+
await server.register(redisManagerPlugin);
6714
await server.start();
6815
}
6916
catch (err) {

config/default.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,34 @@
88
* @version 1.0
99
*/
1010

11-
module.exports = {
11+
const path = require('path');
12+
const fs = require('fs');
13+
const _ = require('lodash');
14+
15+
let config = {
1216
LOG_LEVEL: process.env.LOG_LEVEL || 'debug',
1317
PORT: process.env.PORT || 3003,
1418
API_VERSION: 'api/1.0',
19+
WORK_ROOT: path.join(__dirname, '..'),
20+
defaultExternalConfigLink: 'https://raw.githubusercontent.com/jiangliwu/static-files/master/config.json',
21+
defaultExternalConfigFileName: 'defaultExternalCache.json',
22+
externalConfigFileName: 'externalCache.json',
1523
};
24+
25+
26+
/**
27+
* merge json config to config object
28+
* @param fileName the config json file
29+
*/
30+
function mergeConfig(fileName) {
31+
console.log('start check ' + fileName); // eslint-disable-line here use console.log
32+
if (fs.existsSync(path.join(__dirname, fileName))) {
33+
const content = fs.readFileSync(path.join(__dirname, fileName));
34+
config = _.extend({}, config, JSON.parse(content.toString()));
35+
}
36+
}
37+
38+
mergeConfig(config.defaultExternalConfigFileName);
39+
mergeConfig(config.externalConfigFileName);
40+
41+
module.exports = config;

config/defaultExternalCache.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"LOG_LEVEL": "info"
3+
}

config/externalCache.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"LOG_LEVEL": "info"
3+
}

lib/common/external-config.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const request = require('request');
2+
const logger = require('../common/logger');
3+
const fs = require('fs');
4+
const config = require('config');
5+
const path = require('path');
6+
const pm2 = require('pm2');
7+
8+
/**
9+
* download config file from link
10+
* @param url the config url link
11+
* @param isDefault is it the default external config
12+
* @return {Promise<void>}
13+
*/
14+
async function downloadExternalConfigFile(url, isDefault) {
15+
logger.info('downloadExternalConfigFile ' + url);
16+
return new Promise((resolve, reject) => {
17+
request(url, function (error, response, body) {
18+
if (error) {
19+
logger.error(error);
20+
return reject(error);
21+
}
22+
if (response && response.statusCode === 200) {
23+
const filePath = path.join(config.WORK_ROOT, 'config',
24+
isDefault ? config.defaultExternalConfigFileName : config.externalConfigFileName);
25+
try {
26+
fs.writeFileSync(filePath, JSON.stringify(JSON.parse(body), null, 2));
27+
} catch (e) {
28+
fs.writeFileSync(filePath, JSON.stringify({error: JSON.stringify(e)}));
29+
}
30+
logger.info('downloadExternalConfigFile succeed');
31+
resolve();
32+
} else {
33+
logger.error('empty response from url or wrong status code');
34+
}
35+
});
36+
});
37+
}
38+
39+
40+
/**
41+
* refresh config
42+
* @param url the config file link
43+
* @return {Promise<void>}
44+
*/
45+
async function refreshConfig(url) {
46+
await downloadExternalConfigFile(url, false);
47+
return new Promise(((resolve, reject) => {
48+
pm2.connect(function (err) {
49+
if (err) {
50+
return reject(err);
51+
}
52+
resolve({message: 'OK'});
53+
pm2.reload('app', function (err) {
54+
pm2.disconnect();
55+
if (err) {
56+
reject(err);
57+
}
58+
})
59+
});
60+
}));
61+
}
62+
63+
module.exports = {
64+
refreshConfig, downloadExternalConfigFile
65+
};

lib/common/git.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const git = require('git-last-commit');
2+
const config = require('config');
3+
const fs = require('fs');
4+
const path = require('path');
5+
const logger = require('./logger');
6+
7+
const FILE_NAME = 'lastCommit.json';
8+
9+
/**
10+
* get last commit information from git command
11+
* @return {Promise<*>}
12+
*/
13+
async function getLastCommit() {
14+
return new Promise(((resolve, reject) => {
15+
git.getLastCommit(function (err, commit) {
16+
if (err) {
17+
return reject(err);
18+
}
19+
resolve({
20+
commitSha: commit.hash,
21+
author: commit.author,
22+
committedOn: new Date(commit.committedOn * 1000),
23+
subject: commit.subject,
24+
message: commit.body,
25+
notes: commit.notes,
26+
});
27+
});
28+
}))
29+
}
30+
31+
/**
32+
* create last commit json file
33+
* @return {Promise<void>}
34+
*/
35+
async function generateLastCommitJson() {
36+
try {
37+
const lastCommit = await getLastCommit();
38+
fs.writeFileSync(path.join(config.WORK_ROOT, FILE_NAME), JSON.stringify(lastCommit, null, 2));
39+
} catch (e) {
40+
logger.error('generateLastCommitJson failed');
41+
logger.error(e);
42+
}
43+
}
44+
45+
/**
46+
* get last commit from file, if not exist, it will try to create one
47+
* @return {Promise<*>}
48+
*/
49+
async function getLastCommitFromFile() {
50+
try {
51+
if (!fs.existsSync(path.join(config.WORK_ROOT, FILE_NAME))) {
52+
await generateLastCommitJson();
53+
}
54+
const content = fs.readFileSync(path.join(config.WORK_ROOT, FILE_NAME));
55+
return JSON.parse(content.toString());
56+
} catch (e) {
57+
logger.error(e);
58+
return e;
59+
}
60+
}
61+
62+
module.exports = {
63+
generateLastCommitJson, getLastCommitFromFile
64+
};

lib/index.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const config = require('config');
2+
const routes = require('./route');
3+
const _ = require('lodash');
4+
const logger = require('./common/logger');
5+
6+
module.exports = {
7+
name: 'redis-manager-hapi-service',
8+
version: '1.0.0',
9+
register: async function (server, options) {
10+
11+
logger.debug('redis-manager-hapi-service plugin start init ...');
12+
13+
/**
14+
* inject cors headers
15+
*/
16+
const injectHeader = (h) => {
17+
h.header('Access-Control-Allow-Origin', '*');
18+
h.header('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT');
19+
h.header('Access-Control-Allow-Headers', 'If-Modified-Since, Origin, X-Requested-With, Content-Type, Accept, Authorization');
20+
return h;
21+
};
22+
23+
/**
24+
* inject routes
25+
*/
26+
_.each(routes, (route, path) => {
27+
const newPath = '/backend/' + config.API_VERSION + path;
28+
server.route({method: 'options', path: newPath, handler: (req, h) => injectHeader(h.response('ok'))});
29+
_.each(route, (handler, method) => {
30+
31+
logger.info(`endpoint added, [${method.toUpperCase()}] ${newPath}`);
32+
server.route({
33+
method,
34+
path: newPath,
35+
handler: async (req, h) => {
36+
let result = {};
37+
let status = 200;
38+
try {
39+
result = await handler.method(req, h);
40+
} catch (e) {
41+
result = {code: e.status, message: e.message}
42+
status = e.status || 500;
43+
}
44+
return injectHeader(h.response(result).code(status));
45+
}
46+
});
47+
});
48+
});
49+
50+
await server.register(require('inert'));
51+
// add static folder
52+
server.route({
53+
method: 'GET',
54+
path: '/{param*}',
55+
handler: {
56+
directory: {
57+
path: 'dist',
58+
}
59+
}
60+
});
61+
}
62+
};

lib/route.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212

1313
const redis = require('./redis');
14+
const git = require('./common/git');
15+
const externalConfig = require('./common/external-config');
16+
1417
const logger = require('./common/logger');
1518

1619
logger.buildService("RedisService", redis);
@@ -30,5 +33,15 @@ module.exports = {
3033
post: {
3134
method: async req => await redis.call(req.query, req.payload),
3235
}
33-
}
36+
},
37+
'/healthCheck': {
38+
get: {
39+
method: async () => await git.getLastCommitFromFile(),
40+
}
41+
},
42+
'/refreshConfig': {
43+
post: {
44+
method: async req => await externalConfig.refreshConfig(req.payload.externalFile),
45+
}
46+
},
3447
};

lib/tools/before-build.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const git = require('../common/git');
2+
3+
(async () => {
4+
await git.generateLastCommitJson();
5+
})();

lib/tools/before-start.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const config = require('config');
2+
const externalConfig = require('../common/external-config');
3+
(async () => {
4+
await externalConfig.downloadExternalConfigFile(config.defaultExternalConfigLink, true);
5+
})();

0 commit comments

Comments
 (0)