Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work on MVP #16

Merged
merged 19 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 352 additions & 29 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/AndreyBelym/nodejs-katas.git"
"url": "git+https://github.com/wentwrong/gh-canary/"
},
"author": "Developer Express Inc.",
"license": "MIT",
"bugs": {
"url": "https://github.com/AndreyBelym/nodejs-katas/issues"
"url": "https://github.com/wentwrong/gh-canary/issues"
},
"homepage": "https://github.com/AndreyBelym/nodejs-katas#readme",
"homepage": "https://github.com/wentwrong/gh-canary/#readme",
"devDependencies": {
"chai": "^4.2.0",
"cross-env": "^7.0.0",
Expand All @@ -31,9 +31,12 @@
"sinon": "^8.1.1"
},
"dependencies": {
"@octokit/rest": "^16.43.1",
"debug": "^4.1.1",
"dedent": "^0.7.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"globby": "^11.0.0",
"got": "^10.4.0"
}
}
22 changes: 0 additions & 22 deletions src/app.js

This file was deleted.

7 changes: 7 additions & 0 deletions src/client/public/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../../.eslintrc",

"env": {
"browser": true
}
}
16 changes: 16 additions & 0 deletions src/client/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dashboard</title>
<meta name="description" content="Dashboard for github pull-requests notifier">
<meta name="author" content="wentwrong">
<script src="script.js"></script>
</head>

<body>
<div class="pull-requests">
</div>
<script>new App()</script>
</body>
</html>
36 changes: 36 additions & 0 deletions src/client/public/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class App {
constructor () {
this.pullRequestsDiv = document.querySelector('.pull-requests');
this.renderPage();
}

async renderPage () {
try {
const response = await fetch('api/v1/pulls/list');
const { pullRequestList } = await response.json();

pullRequestList.forEach(pr => this._createNewPullRequestDiv(pr));
}
catch (err) {
console.log(err);
const errorMessage = document.createElement('div');

errorMessage.innerHTML = 'An error has occured while fetching data from server.';

this.pullRequestsDiv.appendChild(errorMessage);
}
}

_createNewPullRequestDiv (pullRequest) {
const pullRequestDiv = document.createElement('div');

pullRequestDiv.innerHTML = `<p>
<a href="${pullRequest['html_url']}">
${pullRequest['title']}
</a> by ${pullRequest['user']['login']}</p>`;

this.pullRequestsDiv.appendChild(pullRequestDiv);
}
}

module.exports = App;
6 changes: 0 additions & 6 deletions src/config.js

This file was deleted.

5 changes: 4 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const App = require('./app');
const App = require('./server/app');
const globalErrorHandlers = require('./server/globerrorhandlers');

globalErrorHandlers();

if (require.main === module) {
const app = new App();
Expand Down
16 changes: 16 additions & 0 deletions src/server/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const config = require('./config');
const ConfigExpress = require('./configexpress');
const express = require('express');

class App extends ConfigExpress {
constructor (...props) {
super(...props);
this._setMiddlewares();
}

_setMiddlewares () {
this.express.use(express.static(config.STATIC_DIR));
}
}

module.exports = App;
21 changes: 21 additions & 0 deletions src/server/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require('dotenv').config();

const [ OWNER, REPO ] = process.env.REPO_SLUG ? process.env.REPO_SLUG.split('/') : [ 'DevExpress', 'testcafe' ];

module.exports = {
PORT: process.env.PORT || 1337,
MOCK_PORT: process.env.MOCK_PORT || 1338,
HOST: process.env.HOST || '127.0.0.1',
MOCK_HOST: process.env.MOCK_HOST || '127.0.0.1',
NODE_ENV: process.env.NODE_ENV || 'production',
OWNER,
REPO,
API_VERSION: 'v1',
MOCK_GITHUB_PREFIX: 'mock-github',
STATIC_DIR: 'src/client/public',
ROUTES_DIR: 'src/server/routes',
MOCK_ROUTES_DIR: 'test/routes',
JSON_SPACES: 2,
DEFAULT_GITHUB_API_URL: 'https://api.github.com',
GITHUB_API_VAR_NAME: 'github-api-url'
};
45 changes: 45 additions & 0 deletions src/server/configexpress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const config = require('./config');
const logger = require('./middleware/logger');
const Server = require('./server');
const express = require('express');
const createRoutes = require('./createRoutes');

// TODO:
// inherit from express app when we move to Typescript
class ConfigExpress {
constructor ({ port = config.PORT, host = config.HOST, routesDir = config.ROUTES_DIR } = {}) {
this.express = express();
this.port = port;
this.host = host;
this.server = null;
this.routesDir = routesDir;
this.jsonSpaceSetting = 'json spaces';
}

async init () {
this.express.use(logger);

this.express.set(this.jsonSpaceSetting, config.JSON_SPACES);

this.express.use(await createRoutes(this.routesDir));
}

async run () {
await this.init();
this.server = new Server(this.express, this.port, this.host);
await this.server.start();
}

async close () {
if (this.server)
await this.server.stop();
else
throw new Error('Stop when application not running');
}

set (key, value) {
this.express.set(key, value);
}
}

module.exports = ConfigExpress;
39 changes: 39 additions & 0 deletions src/server/controllers/pullscontroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const config = require('../config');
const debug = require('../debug');
const { Octokit } = require('@octokit/rest');

class PullsController {
static async list (req, res) {
const octokit = Octokit({
baseUrl: req.app.get(config.GITHUB_API_VAR_NAME) || config.DEFAULT_GITHUB_API_URL
});

const options = octokit.pulls.list.endpoint.merge({
owner: config.OWNER,
repo: config.REPO
});

try {
const pullRequests = [];

for await (const page of octokit.paginate.iterator(options))
pullRequests.push(page['data'].filter(pr => pr['author_association'] === 'NONE'));

res.send({ pullRequestList: pullRequests.flat() });
}
catch (err) {
debug.error(`An error has happened when ${req.ip} requests ${req.path}`);
debug.error(err);

res
.status(500)
.json({ error: 'An error has occured while fetching pull-requests from server.' });
}
}

static async addComment (req, res) {
return res.json({ message: 'NOT IMPLEMENTED YET' });
}
}

module.exports = PullsController;
34 changes: 34 additions & 0 deletions src/server/createRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const path = require('path');
const globby = require('globby');
const debug = require('./debug');
const express = require('express');

function filenameToRoute (filename, routesDir) {
return {
routePrefix: path.dirname(filename),
Router: require(path.join('../../', routesDir, filename))
};
}

async function createRoutes (routesDir) {
const mainRouter = express.Router();

try {
const files = await globby('**/*.js', { cwd: path.resolve(routesDir) });

files
.filter(filename => !filename.endsWith('-test.js'))
.map(filename => filenameToRoute(filename, routesDir))
.filter(({ Router }) => Object.getPrototypeOf(Router) === express.Router)
.map(({ routePrefix, Router }) => mainRouter.use(`/${routePrefix}`, new Router()));

return mainRouter;
}
catch (err) {
debug.error('An error has occured when processing app routes');
debug.error(err);
throw err;
}
}

module.exports = createRoutes;
1 change: 1 addition & 0 deletions src/debug.js → src/server/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ const debug = require('debug');

module.exports = {
log: debug('gh-canary:log'),
warn: debug('gh-canary:warn'),
error: debug('gh-canary:error')
};
14 changes: 14 additions & 0 deletions src/server/globerrorhandlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const debug = require('./debug');

module.exports = () => {
process
.on('unhandledRejection', reason => {
debug.error('Unhandled promise rejection');
debug.error(reason);
})
.on('uncaughtException', err => {
debug.error('Uncaught exception');
debug.error(err);
throw err;
});
};
10 changes: 10 additions & 0 deletions src/server/middleware/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const debug = require('../debug');

module.exports = (req, res, next) => {
res.on('finish', () => {
const utfDateTime = new Date().toUTCString();

debug.log(`[${utfDateTime}] - ${req.method} ${req.originalUrl} - ${res.statusCode}`);
});
next();
};
13 changes: 13 additions & 0 deletions src/server/routes/api/v1/pulls/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const express = require('express');
const PullsController = require('../../../../controllers/pullscontroller');

class PullsRouter extends express.Router {
constructor (...props) {
super(...props);

this.get('/list', PullsController.list)
.post('/addComment', PullsController.addComment);
}
}

module.exports = PullsRouter;
15 changes: 11 additions & 4 deletions src/my-server.js → src/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,18 @@ class Server {
try {
this.server = await this._listenPromisify();

log(`Server started on ${this.host}:${this.port}`);
log(`Server '${this.host}:${this.port}' started`);
}
catch (err) {
error(`Server failed to start`);

switch (err.code) {
case 'EACCES':
wentwrong marked this conversation as resolved.
Show resolved Hide resolved
error(`Server requires elevated privileges`);
break;
case 'EADDRINUSE':
error(`Server is already in use`);
break;
}
error(err);
throw err;
}
}
Expand All @@ -42,7 +49,7 @@ class Server {
try {
await this._closePromisify();

log(`Server stoped`);
log(`Server '${this.host}:${this.port}' stopped working`);
}
catch (err) {
error('Server failed to stop');
Expand Down
9 changes: 0 additions & 9 deletions static/index.html

This file was deleted.

9 changes: 9 additions & 0 deletions test/controllers/mockpullscontroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const pulls = require('../fixtures/pulls');

class MockPullsController {
static async list (req, res) {
res.json(pulls);
}
}

module.exports = MockPullsController;
Loading