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 13 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.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"start-debug": "cross-env DEBUG=gh-canary:* npm run start",
"start-debug": "cross-env DEBUG=gh-canary:*,morgan:* npm run start",
wentwrong marked this conversation as resolved.
Show resolved Hide resolved
"lint": "eslint src test",
"test": "npm run lint && mocha test/**/*-test.js",
"test-debug": "cross-env DEBUG=gh-canary:* npm test",
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.

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

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

class App {
constructor ({ port = config.PORT, host = config.HOST, mockGithubUrl = '' } = {}) {
this.port = port;
this.host = host;
this.mockGithubUrl = mockGithubUrl;
}

async _configExpress (e) {
e.use(logger);
e.use(express.static(config.STATIC_DIR));

e.set('mock-github', this.mockGithubUrl);
e.set('json spaces', 2);

const routes = await createRoutes(config.ROUTES_DIR);

e.use(routes);

return e;
}

async run () {
const configuredExpress = await this._configExpress(express());

this.server = new Server(configuredExpress, 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');
}
}

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

process
wentwrong marked this conversation as resolved.
Show resolved Hide resolved
.on('unhandledRejection', reason => {
debug.error('Unhandled promise rejection');
debug.error(reason);
})
.on('uncaughtException', err => {
debug.error('Uncaught exception');
debug.error(err);
throw err;
});

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',
GITHUB_API_URL: 'https://api.github.com'
};
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('mock-github') || config.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;
32 changes: 32 additions & 0 deletions src/server/createRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const path = require('path');
const globby = require('globby');
const debug = require('./debug');
const express = require('express');
const mainRouter = express.Router();

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

files
.filter(filename => !filename.endsWith('-test.js'))
.map(filename => ({
routePrefix: path.dirname(filename),
Router: require(path.join('../../', routesDir, filename))
}))
.filter(({ Router }) => Object.getPrototypeOf(Router) === express.Router)
.map(({
routePrefix,
wentwrong marked this conversation as resolved.
Show resolved Hide resolved
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')
};
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;
14 changes: 10 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,17 @@ 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;
}
throw err;
}
}
Expand All @@ -42,7 +48,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