feat: refactored server to use ts and improved folder structure

This commit is contained in:
Roberto Tonino
2021-04-03 19:46:54 +02:00
parent f98abb384c
commit 8e4e2ff5eb
43 changed files with 4125 additions and 452 deletions

2
server/.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
bin/www
dist/

15
server/.eslintrc.yml Normal file
View File

@@ -0,0 +1,15 @@
---
extends:
- "@nuxtjs"
- plugin:prettier/recommended
plugins:
- "@typescript-eslint"
parserOptions:
parser: "@typescript-eslint/parser"
rules:
"@typescript-eslint/no-unused-vars":
- error
- args: all
argsIgnorePattern: ^_
no-unused-vars: off
no-console: off

65
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,65 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# IDE
.vscode
.idea

8
server/.prettierrc.yml Normal file
View File

@@ -0,0 +1,8 @@
tabWidth: 2
printWidth: 120
useTabs: true
semi: false
singleQuote: true
bracketSpacing: true
arrowParens: avoid
trailingComma: none

7
server/Makefile Normal file
View File

@@ -0,0 +1,7 @@
NODE_BIN ?= .\node_modules\.bin
lint:
@$(NODE_BIN)\eslint ./src/** --fix
build: lint
@$(NODE_BIN)\tsc

90
server/bin/www Normal file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../src/app');
var debug = require('debug')('deemix-gui: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);
}

32
server/dist/app.js vendored Normal file
View File

@@ -0,0 +1,32 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = __importDefault(require("http"));
const express_1 = __importDefault(require("express"));
const debug_1 = __importDefault(require("debug"));
const middlewares_1 = require("./middlewares");
const routes_1 = __importDefault(require("./routes"));
const users_1 = __importDefault(require("./routes/users"));
const port_1 = require("./helpers/port");
const server_callbacks_1 = require("./helpers/server-callbacks");
const register_1 = require("./routes/api/register");
const PORT = port_1.normalizePort(process.env.PORT || '6595');
const debug = debug_1.default('deemix-gui:server');
const app = express_1.default();
const server = http_1.default.createServer(app);
/* === Middlewares === */
middlewares_1.registerMiddlewares(app);
/* === Routes === */
app.use('/', routes_1.default);
app.use('/users', users_1.default);
/* === APIs === */
register_1.registerApis(app);
/* === Config === */
app.set('port', PORT);
/* === Server port === */
server.listen(PORT);
/* === Server callbacks === */
server.on('error', server_callbacks_1.getErrorCb(PORT));
server.on('listening', server_callbacks_1.getListeningCb(server, debug));

9
server/dist/helpers/paths.js vendored Normal file
View File

@@ -0,0 +1,9 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WEBUI_DIR = exports.ROOT_DIR = void 0;
const path_1 = __importDefault(require("path"));
exports.ROOT_DIR = path_1.default.resolve('../');
exports.WEBUI_DIR = path_1.default.join(exports.ROOT_DIR, 'webui', 'public');

21
server/dist/helpers/port.js vendored Normal file
View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizePort = void 0;
/**
* Normalize a port into a number, string, or false.
*
* @since 0.0.0
*/
function normalizePort(portString) {
const port = parseInt(portString, 10);
if (isNaN(port)) {
// named pipe
return portString;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
exports.normalizePort = normalizePort;

45
server/dist/helpers/server-callbacks.js vendored Normal file
View File

@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getListeningCb = exports.getErrorCb = void 0;
/**
* Event listener for HTTP server "error" event.
*
* @since 0.0.0
*/
function getErrorCb(port) {
return (error) => {
if (error.syscall !== 'listen') {
throw error;
}
const 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);
}
case 'EADDRINUSE': {
console.error(bind + ' is already in use');
process.exit(1);
}
default:
throw error;
}
};
}
exports.getErrorCb = getErrorCb;
/**
* Event listener for HTTP server "listening" event.
*
* @since 0.0.0
*/
function getListeningCb(server, debug) {
return () => {
const addr = server.address();
if (addr) {
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
debug('Listening on ' + bind);
}
};
}
exports.getListeningCb = getListeningCb;

18
server/dist/middlewares.js vendored Normal file
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 });
exports.registerMiddlewares = void 0;
const morgan_1 = __importDefault(require("morgan"));
const express_1 = __importDefault(require("express"));
const cookie_parser_1 = __importDefault(require("cookie-parser"));
const paths_1 = require("./helpers/paths");
function registerMiddlewares(app) {
app.use(morgan_1.default('dev'));
app.use(express_1.default.json());
app.use(express_1.default.urlencoded({ extended: false }));
app.use(cookie_parser_1.default());
app.use(express_1.default.static(paths_1.WEBUI_DIR));
}
exports.registerMiddlewares = registerMiddlewares;

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = [];

7
server/dist/routes/api/get/index.js vendored Normal file
View File

@@ -0,0 +1,7 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const sample_1 = __importDefault(require("./sample"));
exports.default = [sample_1.default];

8
server/dist/routes/api/get/sample.js vendored Normal file
View File

@@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = '/sample';
const handler = (_, res) => {
res.send('Mandi');
};
const apiHandler = { path, handler };
exports.default = apiHandler;

3
server/dist/routes/api/patch/index.js vendored Normal file
View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = [];

3
server/dist/routes/api/post/index.js vendored Normal file
View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = [];

38
server/dist/routes/api/register.js vendored Normal file
View File

@@ -0,0 +1,38 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerApis = void 0;
const get_1 = __importDefault(require("./get"));
const delete_1 = __importDefault(require("./delete"));
const post_1 = __importDefault(require("./post"));
const patch_1 = __importDefault(require("./patch"));
const prependApiPath = (path) => `/api${path}`;
const methods = [
{
method: 'get',
endpoints: get_1.default
},
{
method: 'delete',
endpoints: delete_1.default
},
{
method: 'post',
endpoints: post_1.default
},
{
method: 'patch',
endpoints: patch_1.default
}
];
function registerApis(app) {
methods.forEach(({ method, endpoints }) => {
endpoints.forEach(endpoint => {
// @ts-ignore
app[method](prependApiPath(endpoint.path), endpoint.handler);
});
});
}
exports.registerApis = registerApis;

16
server/dist/routes/index.js vendored Normal file
View File

@@ -0,0 +1,16 @@
"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 = __importDefault(require("express"));
const router = express_1.default.Router();
/**
* GET home page
*
* @since 0.0.0
*/
router.get('/', (_, res) => {
res.render('index', { title: 'Express' });
});
exports.default = router;

16
server/dist/routes/users.js vendored Normal file
View File

@@ -0,0 +1,16 @@
"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 = __importDefault(require("express"));
const router = express_1.default.Router();
/**
* GET users listing.
*
* @since 0.0.0
*/
router.get('/', (_, res) => {
res.send('respond with a resource');
});
exports.default = router;

2
server/dist/types.js vendored Normal file
View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

37
server/package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "deemix-gui",
"version": "0.0.0",
"private": true,
"scripts": {
"start-old": "node bin/www",
"start": "nodemon src/app.ts",
"start-build": "node dist/app.js",
"lint": "eslint . --fix",
"prebuild": "yarn lint",
"build": "tsc"
},
"dependencies": {
"cookie-parser": "1.4.4",
"debug": "2.6.9",
"express": "4.16.1",
"morgan": "1.9.1"
},
"devDependencies": {
"@nuxtjs/eslint-config": "6.0.0",
"@types/cookie-parser": "1.4.2",
"@types/debug": "4.1.5",
"@types/express": "4.17.11",
"@types/morgan": "1.9.2",
"@types/node": "14.14.37",
"@typescript-eslint/eslint-plugin": "4.20.0",
"@typescript-eslint/parser": "4.20.0",
"eslint": "7.23.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "3.3.1",
"nodemon": "2.0.7",
"prettier": "2.2.1",
"ts-node": "9.1.1",
"ts-node-dev": "1.1.6",
"typescript": "4.2.3"
}
}

38
server/src/app.ts Normal file
View File

@@ -0,0 +1,38 @@
import http from 'http'
import express, { Application } from 'express'
import initDebug from 'debug'
import { registerMiddlewares } from './middlewares'
import indexRouter from './routes'
import usersRouter from './routes/users'
import { normalizePort } from './helpers/port'
import { getErrorCb, getListeningCb } from './helpers/server-callbacks'
import { registerApis } from './routes/api/register'
const PORT = normalizePort(process.env.PORT || '6595')
const debug = initDebug('deemix-gui:server')
const app: Application = express()
const server = http.createServer(app)
/* === Middlewares === */
registerMiddlewares(app)
/* === Routes === */
app.use('/', indexRouter)
app.use('/users', usersRouter)
/* === APIs === */
registerApis(app)
/* === Config === */
app.set('port', PORT)
/* === Server port === */
server.listen(PORT)
/* === Server callbacks === */
server.on('error', getErrorCb(PORT))
server.on('listening', getListeningCb(server, debug))

View File

@@ -0,0 +1,4 @@
import path from 'path'
export const ROOT_DIR = path.resolve('../')
export const WEBUI_DIR = path.join(ROOT_DIR, 'webui', 'public')

View File

@@ -0,0 +1,22 @@
import { Port } from '../types'
/**
* Normalize a port into a number, string, or false.
*
* @since 0.0.0
*/
export function normalizePort(portString: string): Port {
const port = parseInt(portString, 10)
if (isNaN(port)) {
// named pipe
return portString
}
if (port >= 0) {
// port number
return port
}
return false
}

View File

@@ -0,0 +1,47 @@
import http from 'http'
import type { Debugger } from 'debug'
/**
* Event listener for HTTP server "error" event.
*
* @since 0.0.0
*/
export function getErrorCb(port: number | string | boolean) {
return (error: any) => {
if (error.syscall !== 'listen') {
throw error
}
const 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)
}
case 'EADDRINUSE': {
console.error(bind + ' is already in use')
process.exit(1)
}
default:
throw error
}
}
}
/**
* Event listener for HTTP server "listening" event.
*
* @since 0.0.0
*/
export function getListeningCb(server: http.Server, debug: Debugger) {
return () => {
const addr = server.address()
if (addr) {
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
debug('Listening on ' + bind)
}
}
}

13
server/src/middlewares.ts Normal file
View File

@@ -0,0 +1,13 @@
import type { Application } from 'express'
import logger from 'morgan'
import express from 'express'
import cookieParser from 'cookie-parser'
import { WEBUI_DIR } from './helpers/paths'
export function registerMiddlewares(app: Application) {
app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(WEBUI_DIR))
}

View File

@@ -0,0 +1,3 @@
import { ApiHandler } from '../../../types'
export default [] as ApiHandler[]

View File

@@ -0,0 +1,3 @@
import sample from './sample'
export default [sample]

View File

@@ -0,0 +1,11 @@
import { ApiHandler } from '../../../types'
const path: ApiHandler['path'] = '/sample'
const handler: ApiHandler['handler'] = (_, res) => {
res.send('Mandi')
}
const apiHandler: ApiHandler = { path, handler }
export default apiHandler

View File

@@ -0,0 +1,3 @@
import { ApiHandler } from '../../../types'
export default [] as ApiHandler[]

View File

@@ -0,0 +1,3 @@
import { ApiHandler } from '../../../types'
export default [] as ApiHandler[]

View File

@@ -0,0 +1,42 @@
import type { Application } from 'express'
import type { ApiHandler } from '../../types'
import getEndpoints from './get'
import deleteEndpoints from './delete'
import postEndpoints from './post'
import patchEndpoints from './patch'
const prependApiPath = (path: string) => `/api${path}`
interface Method {
method: string
endpoints: ApiHandler[]
}
const methods: Method[] = [
{
method: 'get',
endpoints: getEndpoints
},
{
method: 'delete',
endpoints: deleteEndpoints
},
{
method: 'post',
endpoints: postEndpoints
},
{
method: 'patch',
endpoints: patchEndpoints
}
]
export function registerApis(app: Application) {
methods.forEach(({ method, endpoints }) => {
endpoints.forEach(endpoint => {
// @ts-ignore
app[method](prependApiPath(endpoint.path), endpoint.handler)
})
})
}

View File

@@ -0,0 +1,14 @@
import express from 'express'
const router = express.Router()
/**
* GET home page
*
* @since 0.0.0
*/
router.get('/', (_, res) => {
res.render('index', { title: 'Express' })
})
export default router

View File

@@ -0,0 +1,14 @@
import express from 'express'
const router = express.Router()
/**
* GET users listing.
*
* @since 0.0.0
*/
router.get('/', (_, res) => {
res.send('respond with a resource')
})
export default router

8
server/src/types.ts Normal file
View File

@@ -0,0 +1,8 @@
import { RequestHandler } from 'express'
export type Port = number | string | boolean
export interface ApiHandler {
path: string
handler: RequestHandler
}

71
server/tsconfig.json Normal file
View File

@@ -0,0 +1,71 @@
{
"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', 'react', 'react-jsx' or 'react-jsxdev'. */
// "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": "./src", /* 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. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* 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. */
}
}

3383
server/yarn.lock Normal file

File diff suppressed because it is too large Load Diff