This commit is contained in:
Tommy Parnell
2021-03-22 17:09:40 -04:00
parent 95b1f5b67e
commit 162aae18a5
65 changed files with 7633 additions and 2 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

41
server/app/pixel/app.js Normal file
View File

@@ -0,0 +1,41 @@
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;

90
server/app/pixel/bin/www Executable file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('pixel:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

View File

@@ -0,0 +1,16 @@
{
"name": "pixel",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
"morgan": "~1.9.1"
}
}

View File

@@ -0,0 +1,8 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

View File

@@ -0,0 +1,9 @@
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;

View File

@@ -0,0 +1,9 @@
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;

View File

@@ -0,0 +1,6 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}

View File

@@ -0,0 +1,5 @@
extends layout
block content
h1= title
p Welcome to #{title}

View File

@@ -0,0 +1,7 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content

View File

@@ -0,0 +1,13 @@
import { IWorkerMessage } from "domain-worker/IWorkerMessage";
const worker: IWorkerMessage<any> = {
name: "yo",
payload: {
type: "message",
value: {
data: true
}
}
}
console.log(worker);

View File

@@ -0,0 +1,14 @@
{
"name": "worker-cli",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "ts-node index.ts"
},
"dependencies": {
"domain-worker": "1.0.0",
"ts-node": "^9.1.1",
"typescript": "^4.2.3"
}
}

View File

@@ -0,0 +1,63 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
source-map-support@^0.5.17:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
ts-node@^9.1.1:
version "9.1.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==
dependencies:
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
typescript@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==

View File

@@ -0,0 +1,63 @@
/**
* Remove old files, copy front-end ones.
*/
import fs from 'fs-extra';
import Logger from 'jet-logger';
import childProcess from 'child_process';
// Setup logger
const logger = new Logger();
logger.timestamp = false;
(async () => {
try {
// Remove current build
await remove('./dist/');
// Copy front-end files
await copy('./src/public', './dist/public');
await copy('./src/views', './dist/views');
// Copy production env file
await copy('./src/pre-start/env/production.env', './dist/pre-start/env/production.env');
// Copy back-end files
await exec('tsc --build tsconfig.prod.json', './')
} catch (err) {
logger.err(err);
}
})();
function remove(loc: string): Promise<void> {
return new Promise((res, rej) => {
return fs.remove(loc, (err) => {
return (!!err ? rej(err) : res());
});
});
}
function copy(src: string, dest: string): Promise<void> {
return new Promise((res, rej) => {
return fs.copy(src, dest, (err) => {
return (!!err ? rej(err) : res());
});
});
}
function exec(cmd: string, loc: string): Promise<void> {
return new Promise((res, rej) => {
return childProcess.exec(cmd, {cwd: loc}, (err, stdout, stderr) => {
if (!!stdout) {
logger.info(stdout);
}
if (!!stderr) {
logger.warn(stderr);
}
return (!!err ? rej(err) : res());
});
});
}

52
server/app/worker/dist/Server.js vendored Normal file
View File

@@ -0,0 +1,52 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const cookie_parser_1 = __importDefault(require("cookie-parser"));
const morgan_1 = __importDefault(require("morgan"));
const path_1 = __importDefault(require("path"));
const helmet_1 = __importDefault(require("helmet"));
const express_1 = __importDefault(require("express"));
const http_status_codes_1 = __importDefault(require("http-status-codes"));
require("express-async-errors");
const routes_1 = __importDefault(require("./routes"));
const Logger_1 = __importDefault(require("@shared/Logger"));
const app = express_1.default();
const { BAD_REQUEST } = http_status_codes_1.default;
/************************************************************************************
* Set basic express settings
***********************************************************************************/
app.use(express_1.default.json());
app.use(express_1.default.urlencoded({ extended: true }));
app.use(cookie_parser_1.default());
// Show routes called in console during development
if (process.env.NODE_ENV === 'development') {
app.use(morgan_1.default('dev'));
}
// Security
if (process.env.NODE_ENV === 'production') {
app.use(helmet_1.default());
}
// Add APIs
app.use('/api', routes_1.default);
// Print API errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err, req, res, next) => {
Logger_1.default.err(err, true);
return res.status(BAD_REQUEST).json({
error: err.message,
});
});
/************************************************************************************
* Serve front-end content
***********************************************************************************/
const viewsDir = path_1.default.join(__dirname, 'views');
app.set('views', viewsDir);
const staticDir = path_1.default.join(__dirname, 'public');
app.use(express_1.default.static(staticDir));
app.get('*', (req, res) => {
res.sendFile('index.html', { root: viewsDir });
});
// Export express instance
exports.default = app;

View File

@@ -0,0 +1,18 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const jsonfile_1 = __importDefault(require("jsonfile"));
class MockDaoMock {
constructor() {
this.dbFilePath = 'src/daos/MockDb/MockDb.json';
}
openDb() {
return jsonfile_1.default.readFile(this.dbFilePath);
}
saveDb(db) {
return jsonfile_1.default.writeFile(this.dbFilePath, db);
}
}
exports.default = MockDaoMock;

View File

@@ -0,0 +1,58 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
class UserDao {
/**
* @param email
*/
getOne(email) {
// TODO
return Promise.resolve(null);
}
/**
*
*/
getAll() {
// TODO
return Promise.resolve([]);
}
/**
*
* @param user
*/
add(user) {
return __awaiter(this, void 0, void 0, function* () {
// TODO
return Promise.resolve(undefined);
});
}
/**
*
* @param user
*/
update(user) {
return __awaiter(this, void 0, void 0, function* () {
// TODO
return Promise.resolve(undefined);
});
}
/**
*
* @param id
*/
delete(id) {
return __awaiter(this, void 0, void 0, function* () {
// TODO
return Promise.resolve(undefined);
});
}
}
exports.default = UserDao;

View File

@@ -0,0 +1,88 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const functions_1 = require("@shared/functions");
const MockDao_mock_1 = __importDefault(require("../MockDb/MockDao.mock"));
class UserDao extends MockDao_mock_1.default {
getOne(email) {
const _super = Object.create(null, {
openDb: { get: () => super.openDb }
});
return __awaiter(this, void 0, void 0, function* () {
const db = yield _super.openDb.call(this);
for (const user of db.users) {
if (user.email === email) {
return user;
}
}
return null;
});
}
getAll() {
const _super = Object.create(null, {
openDb: { get: () => super.openDb }
});
return __awaiter(this, void 0, void 0, function* () {
const db = yield _super.openDb.call(this);
return db.users;
});
}
add(user) {
const _super = Object.create(null, {
openDb: { get: () => super.openDb },
saveDb: { get: () => super.saveDb }
});
return __awaiter(this, void 0, void 0, function* () {
const db = yield _super.openDb.call(this);
user.id = functions_1.getRandomInt();
db.users.push(user);
yield _super.saveDb.call(this, db);
});
}
update(user) {
const _super = Object.create(null, {
openDb: { get: () => super.openDb },
saveDb: { get: () => super.saveDb }
});
return __awaiter(this, void 0, void 0, function* () {
const db = yield _super.openDb.call(this);
for (let i = 0; i < db.users.length; i++) {
if (db.users[i].id === user.id) {
db.users[i] = user;
yield _super.saveDb.call(this, db);
return;
}
}
throw new Error('User not found');
});
}
delete(id) {
const _super = Object.create(null, {
openDb: { get: () => super.openDb },
saveDb: { get: () => super.saveDb }
});
return __awaiter(this, void 0, void 0, function* () {
const db = yield _super.openDb.call(this);
for (let i = 0; i < db.users.length; i++) {
if (db.users[i].id === id) {
db.users.splice(i, 1);
yield _super.saveDb.call(this, db);
return;
}
}
throw new Error('User not found');
});
}
}
exports.default = UserDao;

17
server/app/worker/dist/entities/User.js vendored Normal file
View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class User {
constructor(nameOrUser, email, id) {
if (typeof nameOrUser === 'string') {
this.name = nameOrUser;
this.email = email || '';
this.id = id || -1;
}
else {
this.name = nameOrUser.name;
this.email = nameOrUser.email;
this.id = nameOrUser.id;
}
}
}
exports.default = User;

13
server/app/worker/dist/index.js vendored Normal file
View File

@@ -0,0 +1,13 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
require("./pre-start"); // Must be the first import
const _server_1 = __importDefault(require("@server"));
const Logger_1 = __importDefault(require("@shared/Logger"));
// Start the server
const port = Number(process.env.PORT || 3000);
_server_1.default.listen(port, () => {
Logger_1.default.info('Express server started on port: ' + port);
});

View File

@@ -0,0 +1,14 @@
## Environment ##
NODE_ENV=production
## Server ##
PORT=8081
HOST=localhost
## Setup jet-logger ##
JET_LOGGER_MODE=FILE
JET_LOGGER_FILEPATH=jet-logger.log
JET_LOGGER_TIMESTAMP=TRUE
JET_LOGGER_FORMAT=LINE

View File

@@ -0,0 +1,30 @@
"use strict";
/**
* Pre-start is where we want to place things that must run BEFORE the express server is started.
* This is useful for environment variables, command-line arguments, and cron-jobs.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const dotenv_1 = __importDefault(require("dotenv"));
const command_line_args_1 = __importDefault(require("command-line-args"));
(() => {
// Setup command line options
const options = command_line_args_1.default([
{
name: 'env',
alias: 'e',
defaultValue: 'development',
type: String,
},
]);
// Set the env file
const result2 = dotenv_1.default.config({
path: path_1.default.join(__dirname, `env/${options.env}.env`),
});
if (result2.error) {
throw result2.error;
}
})();

View File

@@ -0,0 +1,171 @@
/******************************************************************************
* Fetch and display users
******************************************************************************/
displayUsers();
function displayUsers() {
httpGet('/api/users/all')
.then(response => response.json())
.then((response) => {
var allUsers = response.users;
// Empty the anchor
var allUsersAnchor = document.getElementById('all-users-anchor');
allUsersAnchor.innerHTML = '';
// Append users to anchor
allUsers.forEach((user) => {
allUsersAnchor.innerHTML += getUserDisplayEle(user);
});
});
};
function getUserDisplayEle(user) {
return `<div class="user-display-ele">
<div class="normal-view">
<div>Name: ${user.name}</div>
<div>Email: ${user.email}</div>
<button class="edit-user-btn" data-user-id="${user.id}">
Edit
</button>
<button class="delete-user-btn" data-user-id="${user.id}">
Delete
</button>
</div>
<div class="edit-view">
<div>
Name: <input class="name-edit-input" value="${user.name}">
</div>
<div>
Email: <input class="email-edit-input" value="${user.email}">
</div>
<button class="submit-edit-btn" data-user-id="${user.id}">
Submit
</button>
<button class="cancel-edit-btn" data-user-id="${user.id}">
Cancel
</button>
</div>
</div>`;
}
/******************************************************************************
* Add, Edit, and Delete Users
******************************************************************************/
document.addEventListener('click', function (event) {
event.preventDefault();
var ele = event.target;
if (ele.matches('#add-user-btn')) {
addUser();
} else if (ele.matches('.edit-user-btn')) {
showEditView(ele.parentNode.parentNode);
} else if (ele.matches('.cancel-edit-btn')) {
cancelEdit(ele.parentNode.parentNode);
} else if (ele.matches('.submit-edit-btn')) {
submitEdit(ele);
} else if (ele.matches('.delete-user-btn')) {
deleteUser(ele);
}
}, false)
function addUser() {
var nameInput = document.getElementById('name-input');
var emailInput = document.getElementById('email-input');
var data = {
user: {
name: nameInput.value,
email: emailInput.value
},
};
httpPost('/api/users/add', data)
.then(() => {
displayUsers();
})
}
function showEditView(userEle) {
var normalView = userEle.getElementsByClassName('normal-view')[0];
var editView = userEle.getElementsByClassName('edit-view')[0];
normalView.style.display = 'none';
editView.style.display = 'block';
}
function cancelEdit(userEle) {
var normalView = userEle.getElementsByClassName('normal-view')[0];
var editView = userEle.getElementsByClassName('edit-view')[0];
normalView.style.display = 'block';
editView.style.display = 'none';
}
function submitEdit(ele) {
var userEle = ele.parentNode.parentNode;
var nameInput = userEle.getElementsByClassName('name-edit-input')[0];
var emailInput = userEle.getElementsByClassName('email-edit-input')[0];
var id = ele.getAttribute('data-user-id');
var data = {
user: {
name: nameInput.value,
email: emailInput.value,
id: id
}
};
httpPut('/api/users/update', data)
.then(() => {
displayUsers();
})
}
function deleteUser(ele) {
var id = ele.getAttribute('data-user-id');
httpDelete('/api/users/delete/' + id)
.then(() => {
displayUsers();
})
}
function httpGet(path) {
return fetch(path, getOptions('GET'))
}
function httpPost(path, data) {
return fetch(path, getOptions('POST', data));
}
function httpPut(path, data) {
return fetch(path, getOptions('PUT', data));
}
function httpDelete(path) {
return fetch(path, getOptions('DELETE'));
}
function getOptions(verb, data) {
var options = {
dataType: 'json',
method: verb,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
if (data) {
options.body = JSON.stringify(data);
}
return options;
}

View File

@@ -0,0 +1,33 @@
body {
padding: 100px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
body .users-column {
display: inline-block;
margin-right: 2em;
vertical-align: top;
}
body .users-column .column-header {
padding-bottom: 5px;
font-weight: 700;
font-size: 1.2em;
}
body .add-user-col input {
margin-bottom: 10px;
}
body .users-column .user-display-ele {
padding-bottom: 10px;
}
body .users-column .user-display-ele button {
margin-top: 2px;
margin-bottom: 10px;
}
body .users-column .user-display-ele .edit-view {
display: none;
}

67
server/app/worker/dist/routes/Users.js vendored Normal file
View File

@@ -0,0 +1,67 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_status_codes_1 = __importDefault(require("http-status-codes"));
const express_1 = require("express");
const UserDao_mock_1 = __importDefault(require("@daos/User/UserDao.mock"));
const constants_1 = require("@shared/constants");
const router = express_1.Router();
const userDao = new UserDao_mock_1.default();
const { BAD_REQUEST, CREATED, OK } = http_status_codes_1.default;
/******************************************************************************
* Get All Users - "GET /api/users/all"
******************************************************************************/
router.get('/all', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const users = yield userDao.getAll();
return res.status(OK).json({ users });
}));
/******************************************************************************
* Add One - "POST /api/users/add"
******************************************************************************/
router.post('/add', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { user } = req.body;
if (!user) {
return res.status(BAD_REQUEST).json({
error: constants_1.paramMissingError,
});
}
yield userDao.add(user);
return res.status(CREATED).end();
}));
/******************************************************************************
* Update - "PUT /api/users/update"
******************************************************************************/
router.put('/update', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { user } = req.body;
if (!user) {
return res.status(BAD_REQUEST).json({
error: constants_1.paramMissingError,
});
}
user.id = Number(user.id);
yield userDao.update(user);
return res.status(OK).end();
}));
/******************************************************************************
* Delete - "DELETE /api/users/delete/:id"
******************************************************************************/
router.delete('/delete/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
const { id } = req.params;
yield userDao.delete(Number(id));
return res.status(OK).end();
}));
/******************************************************************************
* Export
******************************************************************************/
exports.default = router;

13
server/app/worker/dist/routes/index.js vendored Normal file
View File

@@ -0,0 +1,13 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const Users_1 = __importDefault(require("./Users"));
// Init router and path
const router = express_1.Router();
// Add sub-routes
router.use('/users', Users_1.default);
// Export the base-router
exports.default = router;

13
server/app/worker/dist/shared/Logger.js vendored Normal file
View File

@@ -0,0 +1,13 @@
"use strict";
/**
* Setup the jet-logger.
*
* Documentation: https://github.com/seanpmaxwell/jet-logger
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const jet_logger_1 = __importDefault(require("jet-logger"));
const logger = new jet_logger_1.default();
exports.default = logger;

View File

@@ -0,0 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.paramMissingError = void 0;
exports.paramMissingError = 'One or more of the required parameters was missing.';

View File

@@ -0,0 +1,17 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRandomInt = exports.pErr = void 0;
const Logger_1 = __importDefault(require("./Logger"));
const pErr = (err) => {
if (err) {
Logger_1.default.err(err);
}
};
exports.pErr = pErr;
const getRandomInt = () => {
return Math.floor(Math.random() * 1000000000000);
};
exports.getRandomInt = getRandomInt;

34
server/app/worker/dist/views/index.html vendored Normal file
View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ExpressGeneratorTypeScriptApp</title>
<link rel="stylesheet" type="text/css" href="/stylesheets/style.css" />
</head>
<body>
<!-- Add Users -->
<div class="users-column add-user-col">
<div class="column-header">Add User:</div>
<div>
<input id="name-input" placeholder="Name">
</div>
<div>
<input id="email-input" placeholder="Email">
</div>
<div>
<button id="add-user-btn">Add</button>
</div>
</div>
<!-- Display Users -->
<div class="users-column">
<div class="column-header">Users:</div>
<div id="all-users-anchor"></div>
</div>
</body>
<script src="scripts/index.js"></script>
</html>

View File

@@ -0,0 +1,98 @@
{
"name": "worker",
"version": "1.0.0",
"scripts": {
"build": "./node_modules/.bin/ts-node build.ts",
"lint": "eslint . --ext .ts",
"start": "node -r module-alias/register ./dist --env=production",
"start:dev": "nodemon",
"test": "nodemon --config ./spec/nodemon.json"
},
"nodemonConfig": {
"watch": [
"src"
],
"ext": "ts, html",
"ignore": [
"src/public"
],
"exec": "./node_modules/.bin/ts-node -r tsconfig-paths/register ./src"
},
"_moduleAliases": {
"@daos": "dist/daos",
"@entities": "dist/entities",
"@shared": "dist/shared",
"@server": "dist/Server"
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"max-len": [
"error",
{
"code": 100
}
],
"no-console": 1,
"no-extra-boolean-cast": 0,
"@typescript-eslint/restrict-plus-operands": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-floating-promises": 0,
"@typescript-eslint/no-unsafe-member-access": 0,
"@typescript-eslint/no-unsafe-assignment": 0
}
},
"eslintIgnore": [
"src/public/",
"build.ts"
],
"dependencies": {
"command-line-args": "^5.1.1",
"cookie-parser": "^1.4.5",
"domain-worker": "1.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"helmet": "^4.4.1",
"http-status-codes": "^2.1.4",
"jet-logger": "^1.0.4",
"jsonfile": "^6.1.0",
"module-alias": "^2.2.2",
"morgan": "^1.10.0"
},
"devDependencies": {
"@types/command-line-args": "^5.0.0",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.11",
"@types/find": "^0.2.1",
"@types/fs-extra": "^9.0.8",
"@types/jasmine": "^3.6.7",
"@types/jsonfile": "^6.0.0",
"@types/morgan": "^1.9.2",
"@types/node": "^14.14.35",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"eslint": "^7.22.0",
"find": "^0.3.0",
"fs-extra": "^9.1.0",
"jasmine": "^3.7.0",
"nodemon": "^2.0.7",
"supertest": "^6.1.3",
"ts-node": "^9.1.1",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.2.3"
}
}

View File

@@ -0,0 +1,54 @@
import './loadEnv';
import find from 'find';
import Jasmine from 'jasmine';
import commandLineArgs from 'command-line-args';
import logger from '@shared/Logger';
// Setup command line options
const options = commandLineArgs([
{
name: 'testFile',
alias: 'f',
type: String,
},
]);
// Init Jasmine
const jasmine = new Jasmine(null);
// Set location of test files
jasmine.loadConfig({
random: true,
spec_dir: 'spec',
spec_files: [
'./tests/**/*.spec.ts',
],
stopSpecOnExpectationFailure: false,
});
// On complete callback function
jasmine.onComplete((passed: boolean) => {
if (passed) {
logger.info('All tests have passed :)');
} else {
logger.err('At least one test has failed :(');
}
});
// Run all or a single unit-test
if (options.testFile) {
const testFile = options.testFile as string;
find.file(testFile + '.spec.ts', './spec', (files) => {
if (files.length === 1) {
jasmine.specFiles = [files[0]];
jasmine.execute();
} else {
logger.err('Test file not found!');
}
});
} else {
jasmine.execute();
}

View File

@@ -0,0 +1,10 @@
// Set the env file, must be first
import dotenv from 'dotenv';
const result2 = dotenv.config({
path: `./src/pre-start/env/test.env`,
});
if (result2.error) {
throw result2.error;
}

View File

@@ -0,0 +1,6 @@
{
"watch": ["spec"],
"ext": "spec.ts",
"ignore": ["spec/support"],
"exec": "./node_modules/.bin/ts-node -r tsconfig-paths/register ./spec"
}

View File

@@ -0,0 +1,11 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.ts"
],
"helpers": [
"helpers/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": true
}

View File

@@ -0,0 +1,14 @@
import { Response } from 'supertest';
import { IUser } from '@entities/User';
export interface IResponse extends Response {
body: {
users: IUser[];
error: string;
};
}
export interface IReqBody {
user?: IUser;
}

View File

@@ -0,0 +1,210 @@
import supertest from 'supertest';
import StatusCodes from 'http-status-codes';
import { SuperTest, Test } from 'supertest';
import app from '@server';
import UserDao from '@daos/User/UserDao.mock';
import User, { IUser } from '@entities/User';
import { pErr } from '@shared/functions';
import { paramMissingError } from '@shared/constants';
import { IReqBody, IResponse } from '../support/types';
describe('Users Routes', () => {
const usersPath = '/api/users';
const getUsersPath = `${usersPath}/all`;
const addUsersPath = `${usersPath}/add`;
const updateUserPath = `${usersPath}/update`;
const deleteUserPath = `${usersPath}/delete/:id`;
const { BAD_REQUEST, CREATED, OK } = StatusCodes;
let agent: SuperTest<Test>;
beforeAll((done) => {
agent = supertest.agent(app);
done();
});
describe(`"GET:${getUsersPath}"`, () => {
it(`should return a JSON object with all the users and a status code of "${OK}" if the
request was successful.`, (done) => {
// Setup spy
const users = [
new User('Sean Maxwell', 'sean.maxwell@gmail.com'),
new User('John Smith', 'john.smith@gmail.com'),
new User('Gordan Freeman', 'gordan.freeman@gmail.com'),
];
spyOn(UserDao.prototype, 'getAll').and.returnValue(Promise.resolve(users));
// Call API
agent.get(getUsersPath)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(OK);
// Caste instance-objects to 'User' objects
const respUsers = res.body.users;
const retUsers: User[] = respUsers.map((user: IUser) => {
return new User(user);
});
expect(retUsers).toEqual(users);
expect(res.body.error).toBeUndefined();
done();
});
});
it(`should return a JSON object containing an error message and a status code of
"${BAD_REQUEST}" if the request was unsuccessful.`, (done) => {
// Setup spy
const errMsg = 'Could not fetch users.';
spyOn(UserDao.prototype, 'getAll').and.throwError(errMsg);
// Call API
agent.get(getUsersPath)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(BAD_REQUEST);
expect(res.body.error).toBe(errMsg);
done();
});
});
});
describe(`"POST:${addUsersPath}"`, () => {
const callApi = (reqBody: IReqBody) => {
return agent.post(addUsersPath).type('form').send(reqBody);
};
const userData = {
user: new User('Gordan Freeman', 'gordan.freeman@gmail.com'),
};
it(`should return a status code of "${CREATED}" if the request was successful.`, (done) => {
// Setup Spy
spyOn(UserDao.prototype, 'add').and.returnValue(Promise.resolve());
// Call API
agent.post(addUsersPath).type('form').send(userData)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(CREATED);
expect(res.body.error).toBeUndefined();
done();
});
});
it(`should return a JSON object with an error message of "${paramMissingError}" and a status
code of "${BAD_REQUEST}" if the user param was missing.`, (done) => {
// Call API
callApi({})
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(BAD_REQUEST);
expect(res.body.error).toBe(paramMissingError);
done();
});
});
it(`should return a JSON object with an error message and a status code of "${BAD_REQUEST}"
if the request was unsuccessful.`, (done) => {
// Setup spy
const errMsg = 'Could not add user.';
spyOn(UserDao.prototype, 'add').and.throwError(errMsg);
// Call API
callApi(userData)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(BAD_REQUEST);
expect(res.body.error).toBe(errMsg);
done();
});
});
});
describe(`"PUT:${updateUserPath}"`, () => {
const callApi = (reqBody: IReqBody) => {
return agent.put(updateUserPath).type('form').send(reqBody);
};
const userData = {
user: new User('Gordan Freeman', 'gordan.freeman@gmail.com'),
};
it(`should return a status code of "${OK}" if the request was successful.`, (done) => {
// Setup spy
spyOn(UserDao.prototype, 'update').and.returnValue(Promise.resolve());
// Call Api
callApi(userData)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(OK);
expect(res.body.error).toBeUndefined();
done();
});
});
it(`should return a JSON object with an error message of "${paramMissingError}" and a
status code of "${BAD_REQUEST}" if the user param was missing.`, (done) => {
// Call api
callApi({})
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(BAD_REQUEST);
expect(res.body.error).toBe(paramMissingError);
done();
});
});
it(`should return a JSON object with an error message and a status code of "${BAD_REQUEST}"
if the request was unsuccessful.`, (done) => {
// Setup spy
const updateErrMsg = 'Could not update user.';
spyOn(UserDao.prototype, 'update').and.throwError(updateErrMsg);
// Call API
callApi(userData)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(BAD_REQUEST);
expect(res.body.error).toBe(updateErrMsg);
done();
});
});
});
describe(`"DELETE:${deleteUserPath}"`, () => {
const callApi = (id: number) => {
return agent.delete(deleteUserPath.replace(':id', id.toString()));
};
it(`should return a status code of "${OK}" if the request was successful.`, (done) => {
// Setup spy
spyOn(UserDao.prototype, 'delete').and.returnValue(Promise.resolve());
// Call api
callApi(5)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(OK);
expect(res.body.error).toBeUndefined();
done();
});
});
it(`should return a JSON object with an error message and a status code of "${BAD_REQUEST}"
if the request was unsuccessful.`, (done) => {
// Setup spy
const deleteErrMsg = 'Could not delete user.';
spyOn(UserDao.prototype, 'delete').and.throwError(deleteErrMsg);
// Call Api
callApi(1)
.end((err: Error, res: IResponse) => {
pErr(err);
expect(res.status).toBe(BAD_REQUEST);
expect(res.body.error).toBe(deleteErrMsg);
done();
});
});
});
});

View File

@@ -0,0 +1,63 @@
import cookieParser from 'cookie-parser';
import morgan from 'morgan';
import path from 'path';
import helmet from 'helmet';
import express, { NextFunction, Request, Response } from 'express';
import StatusCodes from 'http-status-codes';
import 'express-async-errors';
import BaseRouter from './routes';
import logger from '@shared/Logger';
const app = express();
const { BAD_REQUEST } = StatusCodes;
/************************************************************************************
* Set basic express settings
***********************************************************************************/
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use(cookieParser());
// Show routes called in console during development
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
}
// Security
if (process.env.NODE_ENV === 'production') {
app.use(helmet());
}
// Add APIs
app.use('/api', BaseRouter);
// Print API errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
logger.err(err, true);
return res.status(BAD_REQUEST).json({
error: err.message,
});
});
/************************************************************************************
* Serve front-end content
***********************************************************************************/
const viewsDir = path.join(__dirname, 'views');
app.set('views', viewsDir);
const staticDir = path.join(__dirname, 'public');
app.use(express.static(staticDir));
app.get('*', (req: Request, res: Response) => {
res.sendFile('index.html', {root: viewsDir});
});
// Export express instance
export default app;

View File

@@ -0,0 +1,25 @@
import jsonfile from 'jsonfile';
import { IUser } from '@entities/User';
interface IDatabase {
users: IUser[];
}
class MockDaoMock {
private readonly dbFilePath = 'src/daos/MockDb/MockDb.json';
protected openDb(): Promise<IDatabase> {
return jsonfile.readFile(this.dbFilePath) as Promise<IDatabase>;
}
protected saveDb(db: IDatabase): Promise<void> {
return jsonfile.writeFile(this.dbFilePath, db);
}
}
export default MockDaoMock;

View File

@@ -0,0 +1,19 @@
{
"users": [
{
"name": "Sean Maxwell",
"email": "sean.maxwell@gmail.com",
"id": 159123164363
},
{
"name": "Gordan Freeman",
"email": "gordan.freeman@halflife.com",
"id": 906524522143
},
{
"name": "John Smith",
"email": "jsmith@yahoo.com",
"id": 357437875835
}
]
}

View File

@@ -0,0 +1,19 @@
{
"users": [
{
"name": "Sean Maxwell",
"email": "sean.maxwell@gmail.com",
"id": 159123164363
},
{
"name": "Gordan Freeman",
"email": "gordan.freeman@halflife.com",
"id": 906524522143
},
{
"name": "John Smith",
"email": "jsmith@yahoo.com",
"id": 357437875835
}
]
}

View File

@@ -0,0 +1,62 @@
import { IUser } from '@entities/User';
import { getRandomInt } from '@shared/functions';
import { IUserDao } from './UserDao';
import MockDaoMock from '../MockDb/MockDao.mock';
class UserDao extends MockDaoMock implements IUserDao {
public async getOne(email: string): Promise<IUser | null> {
const db = await super.openDb();
for (const user of db.users) {
if (user.email === email) {
return user;
}
}
return null;
}
public async getAll(): Promise<IUser[]> {
const db = await super.openDb();
return db.users;
}
public async add(user: IUser): Promise<void> {
const db = await super.openDb();
user.id = getRandomInt();
db.users.push(user);
await super.saveDb(db);
}
public async update(user: IUser): Promise<void> {
const db = await super.openDb();
for (let i = 0; i < db.users.length; i++) {
if (db.users[i].id === user.id) {
db.users[i] = user;
await super.saveDb(db);
return;
}
}
throw new Error('User not found');
}
public async delete(id: number): Promise<void> {
const db = await super.openDb();
for (let i = 0; i < db.users.length; i++) {
if (db.users[i].id === id) {
db.users.splice(i, 1);
await super.saveDb(db);
return;
}
}
throw new Error('User not found');
}
}
export default UserDao;

View File

@@ -0,0 +1,64 @@
import { IUser } from '@entities/User';
export interface IUserDao {
getOne: (email: string) => Promise<IUser | null>;
getAll: () => Promise<IUser[]>;
add: (user: IUser) => Promise<void>;
update: (user: IUser) => Promise<void>;
delete: (id: number) => Promise<void>;
}
class UserDao implements IUserDao {
/**
* @param email
*/
public getOne(email: string): Promise<IUser | null> {
// TODO
return Promise.resolve(null);
}
/**
*
*/
public getAll(): Promise<IUser[]> {
// TODO
return Promise.resolve([]);
}
/**
*
* @param user
*/
public async add(user: IUser): Promise<void> {
// TODO
return Promise.resolve(undefined);
}
/**
*
* @param user
*/
public async update(user: IUser): Promise<void> {
// TODO
return Promise.resolve(undefined);
}
/**
*
* @param id
*/
public async delete(id: number): Promise<void> {
// TODO
return Promise.resolve(undefined);
}
}
export default UserDao;

View File

@@ -0,0 +1,26 @@
export interface IUser {
id: number;
name: string;
email: string;
}
class User implements IUser {
public id: number;
public name: string;
public email: string;
constructor(nameOrUser: string | IUser, email?: string, id?: number) {
if (typeof nameOrUser === 'string') {
this.name = nameOrUser;
this.email = email || '';
this.id = id || -1;
} else {
this.name = nameOrUser.name;
this.email = nameOrUser.email;
this.id = nameOrUser.id;
}
}
}
export default User;

View File

@@ -0,0 +1,10 @@
import './pre-start'; // Must be the first import
import app from '@server';
import logger from '@shared/Logger';
// Start the server
const port = Number(process.env.PORT || 3000);
app.listen(port, () => {
logger.info('Express server started on port: ' + port);
});

View File

@@ -0,0 +1,14 @@
## Environment ##
NODE_ENV=development
## Server ##
PORT=3000
HOST=localhost
## Setup jet-logger ##
JET_LOGGER_MODE=CONSOLE
JET_LOGGER_FILEPATH=jet-logger.log
JET_LOGGER_TIMESTAMP=TRUE
JET_LOGGER_FORMAT=LINE

View File

@@ -0,0 +1,14 @@
## Environment ##
NODE_ENV=production
## Server ##
PORT=8081
HOST=localhost
## Setup jet-logger ##
JET_LOGGER_MODE=FILE
JET_LOGGER_FILEPATH=jet-logger.log
JET_LOGGER_TIMESTAMP=TRUE
JET_LOGGER_FORMAT=LINE

View File

@@ -0,0 +1,14 @@
## Environment ##
NODE_ENV=test
## Server ##
PORT=4000
HOST=localhost
## Setup jet-logger ##
JET_LOGGER_MODE=CONSOLE
JET_LOGGER_FILEPATH=jet-logger.log
JET_LOGGER_TIMESTAMP=TRUE
JET_LOGGER_FORMAT=LINE

View File

@@ -0,0 +1,29 @@
/**
* Pre-start is where we want to place things that must run BEFORE the express server is started.
* This is useful for environment variables, command-line arguments, and cron-jobs.
*/
import path from 'path';
import dotenv from 'dotenv';
import commandLineArgs from 'command-line-args';
(() => {
// Setup command line options
const options = commandLineArgs([
{
name: 'env',
alias: 'e',
defaultValue: 'development',
type: String,
},
]);
// Set the env file
const result2 = dotenv.config({
path: path.join(__dirname, `env/${options.env}.env`),
});
if (result2.error) {
throw result2.error;
}
})();

View File

@@ -0,0 +1,171 @@
/******************************************************************************
* Fetch and display users
******************************************************************************/
displayUsers();
function displayUsers() {
httpGet('/api/users/all')
.then(response => response.json())
.then((response) => {
var allUsers = response.users;
// Empty the anchor
var allUsersAnchor = document.getElementById('all-users-anchor');
allUsersAnchor.innerHTML = '';
// Append users to anchor
allUsers.forEach((user) => {
allUsersAnchor.innerHTML += getUserDisplayEle(user);
});
});
};
function getUserDisplayEle(user) {
return `<div class="user-display-ele">
<div class="normal-view">
<div>Name: ${user.name}</div>
<div>Email: ${user.email}</div>
<button class="edit-user-btn" data-user-id="${user.id}">
Edit
</button>
<button class="delete-user-btn" data-user-id="${user.id}">
Delete
</button>
</div>
<div class="edit-view">
<div>
Name: <input class="name-edit-input" value="${user.name}">
</div>
<div>
Email: <input class="email-edit-input" value="${user.email}">
</div>
<button class="submit-edit-btn" data-user-id="${user.id}">
Submit
</button>
<button class="cancel-edit-btn" data-user-id="${user.id}">
Cancel
</button>
</div>
</div>`;
}
/******************************************************************************
* Add, Edit, and Delete Users
******************************************************************************/
document.addEventListener('click', function (event) {
event.preventDefault();
var ele = event.target;
if (ele.matches('#add-user-btn')) {
addUser();
} else if (ele.matches('.edit-user-btn')) {
showEditView(ele.parentNode.parentNode);
} else if (ele.matches('.cancel-edit-btn')) {
cancelEdit(ele.parentNode.parentNode);
} else if (ele.matches('.submit-edit-btn')) {
submitEdit(ele);
} else if (ele.matches('.delete-user-btn')) {
deleteUser(ele);
}
}, false)
function addUser() {
var nameInput = document.getElementById('name-input');
var emailInput = document.getElementById('email-input');
var data = {
user: {
name: nameInput.value,
email: emailInput.value
},
};
httpPost('/api/users/add', data)
.then(() => {
displayUsers();
})
}
function showEditView(userEle) {
var normalView = userEle.getElementsByClassName('normal-view')[0];
var editView = userEle.getElementsByClassName('edit-view')[0];
normalView.style.display = 'none';
editView.style.display = 'block';
}
function cancelEdit(userEle) {
var normalView = userEle.getElementsByClassName('normal-view')[0];
var editView = userEle.getElementsByClassName('edit-view')[0];
normalView.style.display = 'block';
editView.style.display = 'none';
}
function submitEdit(ele) {
var userEle = ele.parentNode.parentNode;
var nameInput = userEle.getElementsByClassName('name-edit-input')[0];
var emailInput = userEle.getElementsByClassName('email-edit-input')[0];
var id = ele.getAttribute('data-user-id');
var data = {
user: {
name: nameInput.value,
email: emailInput.value,
id: id
}
};
httpPut('/api/users/update', data)
.then(() => {
displayUsers();
})
}
function deleteUser(ele) {
var id = ele.getAttribute('data-user-id');
httpDelete('/api/users/delete/' + id)
.then(() => {
displayUsers();
})
}
function httpGet(path) {
return fetch(path, getOptions('GET'))
}
function httpPost(path, data) {
return fetch(path, getOptions('POST', data));
}
function httpPut(path, data) {
return fetch(path, getOptions('PUT', data));
}
function httpDelete(path) {
return fetch(path, getOptions('DELETE'));
}
function getOptions(verb, data) {
var options = {
dataType: 'json',
method: verb,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
if (data) {
options.body = JSON.stringify(data);
}
return options;
}

View File

@@ -0,0 +1,33 @@
body {
padding: 100px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
body .users-column {
display: inline-block;
margin-right: 2em;
vertical-align: top;
}
body .users-column .column-header {
padding-bottom: 5px;
font-weight: 700;
font-size: 1.2em;
}
body .add-user-col input {
margin-bottom: 10px;
}
body .users-column .user-display-ele {
padding-bottom: 10px;
}
body .users-column .user-display-ele button {
margin-top: 2px;
margin-bottom: 10px;
}
body .users-column .user-display-ele .edit-view {
display: none;
}

View File

@@ -0,0 +1,75 @@
import StatusCodes from 'http-status-codes';
import { Request, Response, Router } from 'express';
import UserDao from '@daos/User/UserDao.mock';
import { paramMissingError, IRequest } from '@shared/constants';
const router = Router();
const userDao = new UserDao();
const { BAD_REQUEST, CREATED, OK } = StatusCodes;
/******************************************************************************
* Get All Users - "GET /api/users/all"
******************************************************************************/
router.get('/all', async (req: Request, res: Response) => {
const users = await userDao.getAll();
return res.status(OK).json({users});
});
/******************************************************************************
* Add One - "POST /api/users/add"
******************************************************************************/
router.post('/add', async (req: IRequest, res: Response) => {
const { user } = req.body;
if (!user) {
return res.status(BAD_REQUEST).json({
error: paramMissingError,
});
}
await userDao.add(user);
return res.status(CREATED).end();
});
/******************************************************************************
* Update - "PUT /api/users/update"
******************************************************************************/
router.put('/update', async (req: IRequest, res: Response) => {
const { user } = req.body;
if (!user) {
return res.status(BAD_REQUEST).json({
error: paramMissingError,
});
}
user.id = Number(user.id);
await userDao.update(user);
return res.status(OK).end();
});
/******************************************************************************
* Delete - "DELETE /api/users/delete/:id"
******************************************************************************/
router.delete('/delete/:id', async (req: IRequest, res: Response) => {
const { id } = req.params;
await userDao.delete(Number(id));
return res.status(OK).end();
});
/******************************************************************************
* Export
******************************************************************************/
export default router;

View File

@@ -0,0 +1,21 @@
import StatusCodes from 'http-status-codes';
import { Request, Response, Router } from 'express';
import { IWorkerMessage } from "domain-worker/IWorkerMessage";
const router = Router();
const { OK } = StatusCodes;
router.get('/all', async (req: Request, res: Response<Array<IWorkerMessage<any>>>) => {
console.log('hit')
return res.status(OK).json([{
name: "yo",
payload: {
type: "message",
value: {
data: true
}
}
}]);
});
export default router;

View File

@@ -0,0 +1,13 @@
import { Router } from 'express';
import UserRouter from './Users';
import WorkerRouter from './Worker';
// Init router and path
const router = Router();
// Add sub-routes
router.use('/users', UserRouter);
router.use('/worker', WorkerRouter);
// Export the base-router
export default router;

View File

@@ -0,0 +1,12 @@
/**
* Setup the jet-logger.
*
* Documentation: https://github.com/seanpmaxwell/jet-logger
*/
import Logger from 'jet-logger';
const logger = new Logger();
export default logger;

View File

@@ -0,0 +1,11 @@
import { Request } from 'express';
import { IUser } from '@entities/User';
export const paramMissingError = 'One or more of the required parameters was missing.';
export interface IRequest extends Request {
body: {
user: IUser;
}
}

View File

@@ -0,0 +1,11 @@
import logger from './Logger';
export const pErr = (err: Error) => {
if (err) {
logger.err(err);
}
};
export const getRandomInt = () => {
return Math.floor(Math.random() * 1_000_000_000_000);
};

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ExpressGeneratorTypeScriptApp</title>
<link rel="stylesheet" type="text/css" href="/stylesheets/style.css" />
</head>
<body>
<!-- Add Users -->
<div class="users-column add-user-col">
<div class="column-header">Add User:</div>
<div>
<input id="name-input" placeholder="Name">
</div>
<div>
<input id="email-input" placeholder="Email">
</div>
<div>
<button id="add-user-btn">Add</button>
</div>
</div>
<!-- Display Users -->
<div class="users-column">
<div class="column-header">Users:</div>
<div id="all-users-anchor"></div>
</div>
</body>
<script src="scripts/index.js"></script>
</html>

View File

@@ -0,0 +1,90 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"paths": {
"@daos/*": [
"src/daos/*"
],
"@entities/*": [
"src/entities/*"
],
"@shared/*": [
"src/shared/*"
],
"@server": [
"src/Server"
]
},
},
"include": [
"src/**/*.ts",
"spec/**/*.ts"
],
"exclude": [
"src/public/"
]
}

View File

@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false
},
"exclude": [
"spec",
"src/**/*.mock.ts",
"src/public/"
]
}

2458
server/app/worker/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
export interface IWorkerMessage<T> {
name: String,
payload: {
type: String,
value: T
}
}

View File

@@ -0,0 +1,6 @@
{
"name": "domain-worker",
"description": "",
"authors": "",
"version": "1.0.0"
}

View File

@@ -2,10 +2,11 @@
"name": "server", "name": "server",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"private": true,
"workspaces": { "workspaces": {
"packages": [ "packages": [
"lib/*", "app/*",
"apps/*" "lib/*"
] ]
} }
} }

2928
server/yarn.lock Normal file

File diff suppressed because it is too large Load Diff