Browse Source

complete container

kungtinglin 6 years ago
commit
2e6449bb1b

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+node_modules/
+build/
+.idea/
+yarn.lock
+yarn-error.log
+src/env.ts
+.env

+ 8 - 0
.sequelizerc

@@ -0,0 +1,8 @@
+const {resolve} = require('path')
+
+module.exports = {
+  'migrations-path': resolve('build', 'database', 'migrations'),
+  'seeders-path': resolve('build', 'database', 'seeders'),
+  'models-path': resolve('build', 'database', 'models'),
+  'config': resolve('build', 'database', 'config', 'config.js'),
+}

+ 6 - 0
README.md

@@ -0,0 +1,6 @@
+# Start dev server
+
+```shell script
+npm run dev
+# or yarn dev 
+```

+ 90 - 0
bin/www

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

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "name": "maabackend",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "start": "node ./bin/www",
+    "tsc": "tsc",
+    "dev": "tsc -w & nodemon ./bin/www"
+  },
+  "dependencies": {
+    "@types/bluebird": "^3.5.27",
+    "@types/dotenv": "^6.1.1",
+    "@types/express": "^4.17.1",
+    "@types/express-validator": "^3.0.0",
+    "@types/jsonwebtoken": "^8.3.5",
+    "@types/morgan": "^1.7.37",
+    "@types/node": "^12.7.5",
+    "@types/validator": "^10.11.3",
+    "cookie-parser": "~1.4.4",
+    "debug": "~2.6.9",
+    "dotenv": "^8.1.0",
+    "express": "~4.16.1",
+    "express-validator": "^6.2.0",
+    "jsonwebtoken": "^8.5.1",
+    "mariadb": "^2.1.1",
+    "morgan": "~1.9.1",
+    "nodemon": "^1.19.2",
+    "reflect-metadata": "^0.1.13",
+    "sequelize": "^5.18.4",
+    "sequelize-cli": "^5.5.1",
+    "sequelize-typescript": "^1.0.0",
+    "typescript": "^3.6.3"
+  }
+}

+ 23 - 0
src/app.ts

@@ -0,0 +1,23 @@
+import express from 'express'
+import morgan from 'morgan'
+import './env'
+import { sequelize } from './database'
+import { loadRoute } from './container'
+
+const app: express.Application = express()
+
+app.use(morgan('dev'))
+app.use(express.json())
+app.use(express.urlencoded({ extended: false }))
+app.use('/', loadRoute())
+
+sequelize
+  .authenticate()
+  .then(() => {
+    console.log('connection succeed')
+  })
+  .catch(err => {
+    console.log(err)
+  })
+
+module.exports = app

+ 86 - 0
src/container.ts

@@ -0,0 +1,86 @@
+import * as controllers from './controllers/'
+import * as middlewares from './middleware/'
+import { routes } from './routes'
+import express from 'express'
+
+interface ControllerCollection {
+  [key: string]: any
+}
+
+const controllerInstances: ControllerCollection = {}
+const router = express.Router()
+
+export function loadRoute(): express.Router {
+  for (const route of routes) {
+    const [controllerName, controllerAction] = route.action.split('@')
+    const middlewareList = []
+
+    if (!isControllerInitialized(controllerName)) {
+      if (isControllerExisted(controllerName)) {
+        controllerInstances[controllerName] = new controllers[controllerName]()
+      } else {
+        throw new Error(`Controller ${controllerName} not exist.`)
+      }
+    }
+
+    if (route.middlewares) {
+      for (const middleware of route.middlewares) {
+        if (!isMiddlewareExists(middleware)) {
+          throw new Error(`Middleware ${middleware} not exist.`)
+        }
+
+        middlewareList.push(middlewares[middleware])
+      }
+    }
+
+    switch (route.method) {
+      case 'get':
+        router.get(route.url, middlewareList, controllerInstances[controllerName][controllerAction])
+        break
+
+      case 'post':
+        router.post(route.url, middlewareList, controllerInstances[controllerName][controllerAction])
+        break
+
+      case 'delete':
+        router.delete(route.url, middlewareList, controllerInstances[controllerName][controllerAction])
+        break
+
+      case 'patch':
+        router.patch(route.url, middlewareList, controllerInstances[controllerName][controllerAction])
+        break
+
+      case 'put':
+        router.put(route.url, middlewareList, controllerInstances[controllerName][controllerAction])
+        break
+    }
+  }
+
+  return router
+}
+
+function isControllerInitialized(controller: string): boolean {
+  return controller in controllerInstances
+}
+
+function isControllerExisted(controller: string): controller is keyof typeof controllers {
+  return controller in controllers
+}
+
+function isActionExisted(controller: object, action: string) {
+  return action in controller
+}
+
+function isMiddlewareExists(middlewareName: string): middlewareName is keyof typeof middlewares {
+  return middlewareName in middlewares
+}
+
+function isMethodExisted(method: string): method is keyof typeof router {
+  const httpMethods = ['get', 'post', 'delete', 'put', 'patch']
+
+  if (!httpMethods.includes(method)) {
+    return false
+  }
+
+  return method in router
+}

+ 12 - 0
src/controllers/AuthController.ts

@@ -0,0 +1,12 @@
+import { Request, Response } from 'express'
+import { User } from '../database/models/User'
+
+class AuthController {
+  async echo(req: Request, res: Response) {
+    var users = await User.findByPk(1)
+
+    res.json(users)
+  }
+}
+
+export default AuthController

+ 1 - 0
src/controllers/index.ts

@@ -0,0 +1 @@
+export { default as AuthController } from './AuthController'

+ 11 - 0
src/database.ts

@@ -0,0 +1,11 @@
+import {Sequelize} from "sequelize-typescript";
+
+export const sequelize = new Sequelize({
+    port: Number(process.env.DB_PORT),
+    database: process.env.DB_DATABASE,
+    username: process.env.DB_USERNAME,
+    password: process.env.DB_PASSWORD,
+    host: process.env.DB_HOST,
+    dialect: 'mariadb',
+    models: [__dirname + '/database/models'],
+});

+ 12 - 0
src/database/config/config.ts

@@ -0,0 +1,12 @@
+require('dotenv').config();
+
+module.exports = {
+    development: {
+        port: Number(process.env.DB_PORT),
+        database: process.env.DB_DATABASE,
+        username: process.env.DB_USERNAME,
+        password: process.env.DB_PASSWORD,
+        host: process.env.DB_HOST,
+        dialect: 'mariadb',
+    }
+};

+ 21 - 0
src/database/models/User.ts

@@ -0,0 +1,21 @@
+import { Model, Table, Column, Unique, Default, CreatedAt, UpdatedAt } from 'sequelize-typescript'
+
+@Table({ tableName: 'users' })
+export class User extends Model<User> {
+  @Unique
+  @Column
+  name: string
+
+  @Column
+  password: string
+
+  @Default(0)
+  @Column
+  permission: Number
+
+  @CreatedAt
+  created_at: Date
+
+  @UpdatedAt
+  updated_at: Date
+}

+ 11 - 0
src/middleware/AuthMiddleware.ts

@@ -0,0 +1,11 @@
+import { Request, Response, NextFunction } from 'express'
+
+function AuthMiddleware(req: Request, res: Response, next: NextFunction) {
+  if (!req.header('Authorization')) {
+    return res.status(401).send('unauthorized')
+  }
+
+  next()
+}
+
+export default AuthMiddleware

+ 19 - 0
src/middleware/dummyMiddleware.ts

@@ -0,0 +1,19 @@
+import { Request, Response, NextFunction } from 'express'
+import { validationResult } from 'express-validator'
+
+export function AuthMiddleware(req: Request, res: Response, next: NextFunction) {
+  if (!req.header('Authorization')) {
+    return res.status(401).send('unauthorized')
+  }
+
+  next()
+}
+
+export function showApiError(req: Request, res: Response, next: NextFunction) {
+  const errors = validationResult(req)
+  if (!errors.isEmpty()) {
+    return res.status(400).json({ errors: errors.array() })
+  }
+
+  next()
+}

+ 1 - 0
src/middleware/index.ts

@@ -0,0 +1 @@
+export { default as AuthMiddleware } from './AuthMiddleware'

+ 8 - 0
src/requests/AuthRequest.ts

@@ -0,0 +1,8 @@
+import {check} from "express-validator";
+import {showApiError} from "../middleware/dummyMiddleware";
+
+export const loginRequest = [
+    check('username').exists().isLength({min: 4}),
+    check('password').exists().isLength({min: 4}),
+    showApiError
+];

+ 18 - 0
src/routes(deprecated)/auth.route.ts

@@ -0,0 +1,18 @@
+import AuthController from '../controllers/AuthController'
+import Route from './route'
+import { loginRequest } from '../requests/AuthRequest'
+
+class AuthRoute extends Route {
+  private authController = new AuthController()
+
+  constructor() {
+    super()
+    this.setRoutes()
+  }
+
+  protected setRoutes() {
+    this.router.post('/login', loginRequest, this.authController.echo)
+  }
+}
+
+export default AuthRoute

+ 17 - 0
src/routes(deprecated)/route.ts

@@ -0,0 +1,17 @@
+import { Router } from 'express'
+
+abstract class Route {
+  protected router = Router()
+  protected abstract setRoutes(): void
+  protected prefix: string = '/'
+
+  public getPrefix() {
+    return this.prefix
+  }
+
+  public getRouter() {
+    return this.router
+  }
+}
+
+export default Route

+ 7 - 0
src/routes.ts

@@ -0,0 +1,7 @@
+export const routes = [
+  {
+    url: '/login',
+    method: 'get',
+    action: 'AuthController@echo'
+  }
+]

+ 63 - 0
tsconfig.json

@@ -0,0 +1,63 @@
+{
+  "compilerOptions": {
+    /* Basic Options */
+    // "incremental": true,                   /* Enable incremental compilation */
+    "target": "es6",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 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": "./build/",                        /* 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": false,  /* 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. */
+  }
+}