├── .babelrc
├── .env.example
├── .gitignore
├── .npmrc
├── README.md
├── knexfile.js
├── package.json
├── src
├── app
│ ├── controllers
│ │ ├── auth
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── users
│ │ │ └── index.js
│ ├── exceptions
│ │ ├── NotFoundException.js
│ │ ├── UnAuthorizedException.js
│ │ ├── ValidationException.js
│ │ └── index.js
│ ├── middlewares
│ │ ├── Auth.js
│ │ ├── FormValidations.js
│ │ └── index.js
│ ├── models
│ │ ├── index.js
│ │ └── user
│ │ │ ├── Accessors.js
│ │ │ └── index.js
│ ├── repositories
│ │ ├── index.js
│ │ └── user
│ │ │ └── index.js
│ ├── transformers
│ │ ├── index.js
│ │ └── user
│ │ │ └── index.js
│ └── validations
│ │ ├── auth.validations.js
│ │ └── users.validations.js
├── bootstrap
│ └── app.js
├── config
│ ├── app.config.js
│ ├── auth.config.js
│ ├── database.config.js
│ └── index.js
├── constants
│ ├── DBTables.js
│ ├── HTTPCode.js
│ ├── Pagination.js
│ └── UserRoles.js
├── database
│ ├── factories
│ │ └── UserFactory.js
│ ├── migrations
│ │ └── 20190707103825_create_users_table.js
│ └── seeds
│ │ └── UserTableSeeder.js
├── helpers
│ └── core.helper.js
├── libraries
│ ├── FakerFactories
│ │ ├── FakerFactory.js
│ │ └── index.js
│ ├── Repository
│ │ ├── RepositoryMixin.js
│ │ ├── exceptions
│ │ │ ├── ModelNotFoundException.js
│ │ │ └── index.js
│ │ ├── helpers
│ │ │ └── Pagination.js
│ │ ├── index.js
│ │ ├── model
│ │ │ ├── index.js
│ │ │ └── plugins
│ │ │ │ ├── Authenticatable.js
│ │ │ │ └── Password.js
│ │ └── transformer
│ │ │ └── index.js
│ └── controller
│ │ ├── Response.js
│ │ ├── index.js
│ │ └── router
│ │ └── index.js
├── routes
│ ├── auth.routes.js
│ ├── index.js
│ └── users.routes.js
└── server.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ],
5 | "plugins": [
6 | [
7 | "@babel/plugin-proposal-decorators",
8 | {
9 | "legacy": true
10 | }
11 | ]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME="e-Sifaris"
2 | APP_PORT=5000
3 | APP_DEBUG=true
4 | JWT_SECRET=
5 |
6 | DB_HOST=127.0.0.1
7 | DB_PORT=5432
8 | DB_NAME=
9 | DB_USER=
10 | DB_PASS=
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # testing
5 | /coverage
6 |
7 | # production
8 | /build
9 |
10 | # misc
11 | .DS_Store
12 | .env.local
13 | .env.development.local
14 | .env.test.local
15 | .env.production.local
16 |
17 | npm-debug.log*
18 | yarn-debug.log*
19 | yarn-error.log*
20 |
21 | .idea
22 | .env
23 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Node.js MVC
2 |
3 | Simple Node.js MVC architecture with class object implementation using babel.
4 |
5 | ## Design Pattern
6 | - Repository Design Pattern
7 |
8 | ## Requirement
9 | This boilerplate uses latest version of Node/NPM. i.e. node v12 and npm v6.9.
10 |
11 | ## Tech
12 | - [Node.js] - JavaScript Runtime.
13 | - [Express] - Web Application Framework.
14 | - [Knex.js] - A query builder for database
15 | - [ObjectionJs] - ORM for interacting with database on top of [Knex.js].
16 |
17 | [Knex.js]:
18 | [Node.js]:
19 | [Express]:
20 | [ObjectionJs]:
21 |
22 | ## Installation
23 |
24 | 1. Clone the repository
25 | 2. Copy `.env.example` to `.env`
26 | ```
27 | $ cp .env.example .env
28 | ```
29 | 3. Update the information in `.env` file with your information such as database credentials etc.
30 | 4. Install npm packages. I recommend to use `yarn`.
31 | ```
32 | $ yarn
33 | ```
34 | or,
35 | ```
36 | $ npm install
37 | ```
38 | 5. Now, start server with following commands.
39 | ```
40 | $ yarn start
41 | ```
42 |
43 | ## Other important commands
44 | 1. Create database migration file
45 | ```
46 | $ yarn make:migration create_users_table
47 | ```
48 | 2. Run Migrations
49 | ```
50 | $ yarn migrate
51 | ```
52 | 3. Rollback migration
53 | ```
54 | $ yarn migrate:rollback
55 | ```
56 | 4. Create database seeder file
57 | ```
58 | $ yarn make:seed UsersTableSeeder
59 | ```
60 | 5. Run seeders
61 | ```
62 | $ yarn db:seed
63 | ```
64 |
65 | ## Ready for production
66 |
67 | To deploy in production, first build app with following command
68 | ```
69 | $ yarn build
70 | ```
71 | After building app, deploy `build` directory and run like any other node app.
72 |
73 | ## Support
74 |
75 | If you faced any issue with this boilerplate create an issue. Or if you manage to improve it, send pull request.
--------------------------------------------------------------------------------
/knexfile.js:
--------------------------------------------------------------------------------
1 | import { DBConfig } from "./src/config"
2 |
3 | module.exports = { ...DBConfig }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-sifaris-server",
3 | "version": "1.0.0",
4 | "description": "e-sifaris express server",
5 | "main": "src/server.js",
6 | "engines": {
7 | "node": "~v12",
8 | "npm": "~6.9"
9 | },
10 | "scripts": {
11 | "test": "echo \"Error: no test specified\" && exit 1",
12 | "start": "nodemon --exec babel-node src/server.js",
13 | "build": "babel src --out-dir build",
14 | "knex": "babel-node node_modules/.bin/knex --knexfile knexfile.js --env knexDB",
15 | "make:migration": "yarn knex migrate:make",
16 | "migrate": "yarn knex migrate:latest",
17 | "migrate:rollback": "yarn knex migrate:rollback",
18 | "make:seed": "yarn knex seed:make",
19 | "db:seed": "yarn knex seed:run"
20 | },
21 | "author": "puncoz ",
22 | "license": "ISC",
23 | "dependencies": {
24 | "bcrypt": "3.0.6",
25 | "body-parser": "1.19.0",
26 | "compression": "1.7.4",
27 | "cors": "2.8.5",
28 | "express": "4.17.1",
29 | "express-validator": "6.2.0",
30 | "faker": "4.1.0",
31 | "helmet": "3.21.2",
32 | "jsonwebtoken": "8.5.1",
33 | "knex": "0.20.0",
34 | "lodash": "4.17.15",
35 | "method-override": "3.0.0",
36 | "morgan": "1.9.1",
37 | "objection": "1.6.11",
38 | "passport": "0.4.0",
39 | "passport-jwt": "4.0.0",
40 | "passport-local": "1.0.0",
41 | "pg": "7.12.1"
42 | },
43 | "devDependencies": {
44 | "@babel/cli": "7.6.4",
45 | "@babel/core": "7.6.4",
46 | "@babel/node": "7.6.3",
47 | "@babel/plugin-proposal-decorators": "7.6.0",
48 | "@babel/preset-env": "7.6.3",
49 | "dotenv": "8.2.0",
50 | "nodemon": "1.19.4"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/app/controllers/auth/index.js:
--------------------------------------------------------------------------------
1 | import passport from "passport"
2 | import Controller from "../../../libraries/controller"
3 | import { UnAuthorizedException } from "../../exceptions"
4 | import { UserTransformer } from "../../transformers"
5 |
6 | export default class AuthController extends Controller {
7 |
8 | async login(req, res, next) {
9 | passport.authenticate("local", {
10 | session: false,
11 | }, (err, user, message) => {
12 | if (!user) {
13 | return next(new UnAuthorizedException(message))
14 | }
15 |
16 | const token = user.generateToken()
17 | user = UserTransformer.transform(user)
18 | user.token = token
19 |
20 | this.sendResponse(res, user, message)
21 | })(req, res)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/controllers/index.js:
--------------------------------------------------------------------------------
1 | export { default as UsersController } from "./users"
2 | export { default as AuthController } from "./auth"
3 |
--------------------------------------------------------------------------------
/src/app/controllers/users/index.js:
--------------------------------------------------------------------------------
1 | import { matchedData } from "express-validator"
2 | import { PAGINATE_MD } from "../../../constants/Pagination"
3 | import Controller from "../../../libraries/controller"
4 | import { UserRepository } from "../../repositories"
5 | import { UserTransformer } from "../../transformers"
6 |
7 | export default class UserController extends Controller {
8 |
9 | async index(req, res) {
10 | const users = await UserRepository.setTransformer(UserTransformer).paginate(PAGINATE_MD, req.query.page)
11 |
12 | return this.sendResponse(res, users)
13 | }
14 |
15 | async show(req, res, next) {
16 | try {
17 | const user = await UserRepository.setTransformer(UserTransformer).find(req.params.userId)
18 |
19 | return this.sendResponse(res, user)
20 | } catch (e) {
21 | next(e)
22 | }
23 | }
24 |
25 | async create(req, res) {
26 | const user = await UserRepository.setTransformer(UserTransformer).create(matchedData(req))
27 |
28 | this.sendResponse(res, user)
29 | }
30 |
31 | async update(req, res, next) {
32 | try {
33 | const user = await UserRepository.setTransformer(UserTransformer).update(matchedData(req), req.params.userId)
34 |
35 | return this.sendResponse(res, user)
36 | } catch (e) {
37 | next(e)
38 | }
39 | }
40 |
41 | async delete(req, res, next) {
42 | try {
43 | await UserRepository.delete(req.params.userId)
44 |
45 | return this.sendResponse(res, null, "User deleted successfully.")
46 | } catch (e) {
47 | next(e)
48 | }
49 | }
50 |
51 | async changePassword(req, res, next) {
52 | const { password } = matchedData(req)
53 |
54 | try {
55 | await UserRepository.update({ password }, req.params.userId)
56 |
57 | return this.sendResponse(res, null, "Password changed successfully.")
58 | } catch (e) {
59 | next(e)
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/app/exceptions/NotFoundException.js:
--------------------------------------------------------------------------------
1 | import { HTTP_NOT_FOUND } from "../../constants/HTTPCode"
2 |
3 | export default class NotFoundException extends Error {
4 | constructor(message) {
5 | super(message || "Not found.")
6 |
7 | this.status = HTTP_NOT_FOUND
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/exceptions/UnAuthorizedException.js:
--------------------------------------------------------------------------------
1 | import { HTTP_UNAUTHORIZED } from "../../constants/HTTPCode"
2 |
3 | export default class UnAuthorizedException extends Error {
4 | constructor(message) {
5 | super(message || "Unautorized")
6 |
7 | this.status = HTTP_UNAUTHORIZED
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/exceptions/ValidationException.js:
--------------------------------------------------------------------------------
1 | import { HTTP_UNPROCESSABLE_ENTITY } from "../../constants/HTTPCode"
2 |
3 | export default class ValidationException extends Error {
4 | constructor(errors) {
5 | super("Validation errors.")
6 |
7 | this.status = HTTP_UNPROCESSABLE_ENTITY
8 | this.errors = errors
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/exceptions/index.js:
--------------------------------------------------------------------------------
1 | export { default as NotFoundException } from "./NotFoundException"
2 | export { default as ValidationException } from "./ValidationException"
3 | export { default as UnAuthorizedException } from "./UnAuthorizedException"
4 |
--------------------------------------------------------------------------------
/src/app/middlewares/Auth.js:
--------------------------------------------------------------------------------
1 | import passport from "passport"
2 | import { UnAuthorizedException } from "../exceptions"
3 |
4 | class Auth {
5 | handle(req, res, next) {
6 | passport.authenticate("jwt", {
7 | session: false,
8 | }, (err, user, info) => {
9 | if (!user) {
10 | return next(new UnAuthorizedException(info.message))
11 | }
12 |
13 | req.user = user
14 | next()
15 | })(req, res, next)
16 | }
17 | }
18 |
19 | export default (new Auth()).handle
20 |
--------------------------------------------------------------------------------
/src/app/middlewares/FormValidations.js:
--------------------------------------------------------------------------------
1 | import { validationResult } from "express-validator"
2 | import { ValidationException } from "../exceptions"
3 |
4 | export default (req, res, next) => {
5 | const errorFormatter = ({ location, msg, param, value, nestedErrors }) => msg
6 |
7 | const errors = validationResult(req).formatWith(errorFormatter)
8 |
9 | if (!errors.isEmpty()) {
10 | throw new ValidationException(errors.mapped())
11 | }
12 |
13 | next()
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/middlewares/index.js:
--------------------------------------------------------------------------------
1 | export { default as FormValidations }from "./FormValidations"
2 | export { default as auth } from "./Auth"
3 |
--------------------------------------------------------------------------------
/src/app/models/index.js:
--------------------------------------------------------------------------------
1 | export { default as UserModel } from "./user"
2 |
--------------------------------------------------------------------------------
/src/app/models/user/Accessors.js:
--------------------------------------------------------------------------------
1 | export default base => class extends base {
2 | fullName() {
3 | if (!this.full_name) {
4 | return ""
5 | }
6 |
7 | const fullName = []
8 |
9 | if (this.full_name.hasOwnProperty("first_name")) {
10 | fullName.push(this.full_name.first_name)
11 | }
12 |
13 | if (this.full_name.hasOwnProperty("last_name")) {
14 | fullName.push(this.full_name.last_name)
15 | }
16 |
17 | return fullName.join(" ")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/models/user/index.js:
--------------------------------------------------------------------------------
1 | import { USERS_TABLE } from "../../../constants/DBTables"
2 | import BaseModel from "../../../libraries/Repository/model"
3 | import Authenticatable from "../../../libraries/Repository/model/plugins/Authenticatable"
4 | import Password from "../../../libraries/Repository/model/plugins/Password"
5 | import UserAccessors from "./Accessors"
6 |
7 | @UserAccessors
8 | @Password
9 | @Authenticatable
10 | export default class User extends BaseModel {
11 | static get tableName() {
12 | return USERS_TABLE
13 | }
14 |
15 | static get idColumn() {
16 | return "id"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/repositories/index.js:
--------------------------------------------------------------------------------
1 | export { default as UserRepository } from "./user"
2 |
--------------------------------------------------------------------------------
/src/app/repositories/user/index.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from "../../models"
2 | import BaseRepository from "./../../../libraries/Repository"
3 |
4 | class UserRepository extends BaseRepository {
5 | constructor(props) {
6 | super(props)
7 | }
8 |
9 | model() {
10 | return UserModel
11 | }
12 | }
13 |
14 | export default new UserRepository()
15 |
--------------------------------------------------------------------------------
/src/app/transformers/index.js:
--------------------------------------------------------------------------------
1 | export { default as UserTransformer } from "./user"
2 |
--------------------------------------------------------------------------------
/src/app/transformers/user/index.js:
--------------------------------------------------------------------------------
1 | import BaseTransformer from "../../../libraries/Repository/transformer"
2 |
3 | class UserTransformer extends BaseTransformer {
4 | transform(user) {
5 | return {
6 | id: user.id,
7 | email: user.email,
8 | username: user.username,
9 | full_name: user.fullName(),
10 | role: user.role,
11 | }
12 | }
13 | }
14 |
15 | export default new UserTransformer()
16 |
--------------------------------------------------------------------------------
/src/app/validations/auth.validations.js:
--------------------------------------------------------------------------------
1 | import { check } from "express-validator"
2 |
3 | export default {
4 | login: [
5 | check("username").
6 | isString().withMessage("Username is required."),
7 |
8 | check("password").
9 | isString().withMessage("Password is required."),
10 | ],
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/validations/users.validations.js:
--------------------------------------------------------------------------------
1 | import { check } from "express-validator"
2 | import { UserRepository } from "../repositories"
3 |
4 | export default {
5 | create: [
6 | check("email").
7 | isString().withMessage("Email is required.").
8 | normalizeEmail().isEmail().withMessage("Invalid email format").
9 | custom(async email => {
10 | try {
11 | await UserRepository.findByColumn("email", email)
12 | } catch (e) {
13 | return true
14 | }
15 |
16 | throw new Error("Email already exists.")
17 | }),
18 |
19 | check("username").
20 | isString().withMessage("Username is required").
21 | custom(async username => {
22 | try {
23 | await UserRepository.findByColumn("username", username)
24 | } catch (e) {
25 | return true
26 | }
27 |
28 | throw new Error("Username already exists.")
29 | }),
30 |
31 | check("password").
32 | isString().withMessage("Password is required").
33 | isLength({ min: 6 }).withMessage("Password should be greater than 6 characters long."),
34 | ],
35 |
36 | update: [
37 | check("email").
38 | isString().withMessage("Email is required.").
39 | normalizeEmail().isEmail().withMessage("Invalid email format").
40 | custom(async (email, { req }) => {
41 | const userId = req.params.userId
42 |
43 | const user = await UserRepository.query().where("email", email).where("id", "<>", userId)
44 |
45 | if (user.length === 0) {
46 | return true
47 | }
48 |
49 | throw new Error("Email already exists.")
50 | }),
51 |
52 | check("username").
53 | isString().withMessage("Username is required").
54 | custom(async (username, { req }) => {
55 | const userId = req.params.userId
56 |
57 | const user = await UserRepository.query().where("username", username).where("id", "<>", userId)
58 |
59 | if (user.length === 0) {
60 | return true
61 | }
62 |
63 | throw new Error("Username already exists.")
64 | }),
65 | ],
66 |
67 | passwordChange: [
68 | check("password").
69 | isString().withMessage("Password is required").
70 | isLength({ min: 6 }).withMessage("Password should be greater than 6 characters long."),
71 |
72 | check("confirm-password").
73 | isString().withMessage("Password is required").
74 | custom(async (password, { req }) => {
75 | if (password === req.body.password) {
76 | return true
77 | }
78 |
79 | throw new Error("Password confirmation does not match password")
80 | }),
81 | ],
82 | }
83 |
--------------------------------------------------------------------------------
/src/bootstrap/app.js:
--------------------------------------------------------------------------------
1 | import {
2 | json,
3 | urlencoded,
4 | } from "body-parser"
5 | import compression from "compression"
6 | import cors from "cors"
7 | import express from "express"
8 | import helmet from "helmet"
9 | import Knex from "knex"
10 | import methodOverride from "method-override"
11 | import logger from "morgan"
12 | import { Model } from "objection"
13 | import passport from "passport"
14 | import {
15 | ExtractJwt,
16 | Strategy as JWTStrategy,
17 | } from "passport-jwt"
18 | import LocalStrategy from "passport-local"
19 | import {
20 | NotFoundException,
21 | UnAuthorizedException,
22 | ValidationException,
23 | } from "../app/exceptions"
24 | import {
25 | HTTP_INTERNAL_SERVER_ERROR,
26 | HTTP_NOT_FOUND,
27 | HTTP_UNAUTHORIZED,
28 | HTTP_UNPROCESSABLE_ENTITY,
29 | } from "../constants/HTTPCode"
30 | import { ModelNotFoundException } from "../libraries/Repository/exceptions"
31 | import {
32 | AppConfig,
33 | AuthConfig,
34 | DBConfig,
35 | } from "./../config"
36 | import routes from "./../routes"
37 |
38 | class App {
39 | constructor() {
40 | this.app = express()
41 | this.appDebug = AppConfig.debug === "true"
42 |
43 | this.setup()
44 | this.database()
45 | this.authentication()
46 | this.routers()
47 | }
48 |
49 | setup() {
50 | this.app.use(helmet())
51 | this.app.use(compression())
52 | this.app.use(json())
53 | this.app.use(urlencoded({ extended: true }))
54 | this.app.use(logger("dev"))
55 | this.app.use(cors())
56 | this.app.use(methodOverride("_method"))
57 | }
58 |
59 | database() {
60 | const knex = Knex(DBConfig)
61 | Model.knex(knex)
62 | }
63 |
64 | routers() {
65 | routes(this.app)
66 |
67 | this.app.use((req, res, next) => {
68 | throw new NotFoundException()
69 | })
70 |
71 | this.app.use((error, req, res, next) => {
72 | if (error instanceof NotFoundException || error instanceof ModelNotFoundException) {
73 | res.status(error.status || HTTP_NOT_FOUND).json({
74 | message: error.message,
75 | })
76 |
77 | return
78 | }
79 |
80 | if (error instanceof ValidationException) {
81 | res.status(error.status || HTTP_UNPROCESSABLE_ENTITY).json({
82 | errors: error.errors,
83 | message: error.message,
84 | })
85 |
86 | return
87 | }
88 |
89 | if (error instanceof UnAuthorizedException) {
90 | res.status(error.status || HTTP_UNAUTHORIZED).json({
91 | message: error.message,
92 | })
93 |
94 | return
95 | }
96 |
97 | console.error(error)
98 | res.status(error.status || HTTP_INTERNAL_SERVER_ERROR).json({
99 | message: this.appDebug ? error.message : "Server error.",
100 | errors: this.appDebug ? error : null,
101 | })
102 | })
103 | }
104 |
105 | authentication() {
106 | this.app.use(passport.initialize({}))
107 |
108 | const authModel = new AuthConfig.authModel()
109 |
110 | passport.use("local", new LocalStrategy({
111 | usernameField: AuthConfig.request.usernameField,
112 | passwordField: AuthConfig.request.passwordField,
113 | }, authModel.authenticate))
114 |
115 | passport.use("jwt", new JWTStrategy({
116 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
117 | secretOrKey: AuthConfig.jwtSecret,
118 | }, authModel.authenticateJwt))
119 | }
120 | }
121 |
122 | export default new App().app
123 |
--------------------------------------------------------------------------------
/src/config/app.config.js:
--------------------------------------------------------------------------------
1 | import { env } from "./../helpers/core.helper"
2 |
3 | export default {
4 | name: env("APP_NAME", "NodeJs App"),
5 | port: env("APP_PORT", 3000),
6 | debug: env("APP_DEBUG", false),
7 | }
8 |
--------------------------------------------------------------------------------
/src/config/auth.config.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from "../app/models"
2 | import { env } from "../helpers/core.helper"
3 |
4 | export default {
5 | passwordField: "password",
6 |
7 | hashRounds: 12,
8 |
9 | tokenExpiration: 60 * 60, // in seconds
10 |
11 | jwtSecret: env("JWT_SECRET"),
12 |
13 | request: {
14 | usernameField: "username",
15 | passwordField: "password",
16 | },
17 |
18 | authModel: UserModel,
19 | }
20 |
--------------------------------------------------------------------------------
/src/config/database.config.js:
--------------------------------------------------------------------------------
1 | import {
2 | env,
3 | src_path,
4 | } from "../helpers/core.helper"
5 | import { MIGRATIONS_TABLE } from "./../constants/DBTables"
6 |
7 | export default {
8 | client: "postgresql",
9 | connection: {
10 | host: env("DB_HOST"),
11 | port: env("DB_PORT"),
12 | database: env("DB_NAME"),
13 | user: env("DB_USER"),
14 | password: env("DB_PASS"),
15 | },
16 | pool: {
17 | min: 2,
18 | max: 10,
19 | },
20 | migrations: {
21 | tableName: MIGRATIONS_TABLE,
22 | directory: src_path("database/migrations"),
23 | },
24 | seeds: {
25 | directory: src_path("database/seeds"),
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | export { default as AppConfig } from "./app.config"
2 | export { default as DBConfig } from "./database.config"
3 | export { default as AuthConfig } from "./auth.config"
4 |
--------------------------------------------------------------------------------
/src/constants/DBTables.js:
--------------------------------------------------------------------------------
1 | export const MIGRATIONS_TABLE = "sys_migrations"
2 |
3 | export const USERS_TABLE = "auth_users"
4 |
--------------------------------------------------------------------------------
/src/constants/HTTPCode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Informational
3 | */
4 | export const HTTP_CONTINUE = 100
5 | export const HTTP_SWITCHING_PROTOCOLS = 101
6 | export const HTTP_PROCESSING = 102 // RFC2518
7 |
8 | /**
9 | * Success
10 | */
11 | export const HTTP_OK = 200
12 | export const HTTP_CREATED = 201
13 | export const HTTP_ACCEPTED = 202
14 | export const HTTP_NON_AUTHORITATIVE_INFORMATION = 203
15 | export const HTTP_NO_CONTENT = 204
16 | export const HTTP_RESET_CONTENT = 205
17 | export const HTTP_PARTIAL_CONTENT = 206
18 | export const HTTP_MULTI_STATUS = 207 // RFC4918
19 | export const HTTP_ALREADY_REPORTED = 208 // RFC5842
20 | export const HTTP_IM_USED = 226 // RFC3229
21 |
22 | /**
23 | * Redirection
24 | */
25 | export const HTTP_MULTIPLE_CHOICES = 300
26 | export const HTTP_MOVED_PERMANENTLY = 301
27 | export const HTTP_FOUND = 302
28 | export const HTTP_SEE_OTHER = 303
29 | export const HTTP_NOT_MODIFIED = 304
30 | export const HTTP_USE_PROXY = 305
31 | export const HTTP_RESERVED = 306
32 | export const HTTP_TEMPORARY_REDIRECT = 307
33 | export const HTTP_PERMANENTLY_REDIRECT = 308 // RFC7238
34 |
35 | /**
36 | * Client Error
37 | */
38 | export const HTTP_BAD_REQUEST = 400
39 | export const HTTP_UNAUTHORIZED = 401
40 | export const HTTP_PAYMENT_REQUIRED = 402
41 | export const HTTP_FORBIDDEN = 403
42 | /**
43 | * The requested resource could not be found
44 | *
45 | * Note: This is sometimes used to mask if there was an UNAUTHORIZED (401) or
46 | * FORBIDDEN (403) error, for security reasons
47 | */
48 | export const HTTP_NOT_FOUND = 404
49 | export const HTTP_METHOD_NOT_ALLOWED = 405
50 | export const HTTP_NOT_ACCEPTABLE = 406
51 | export const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407
52 | export const HTTP_REQUEST_TIMEOUT = 408
53 | export const HTTP_CONFLICT = 409
54 | export const HTTP_GONE = 410
55 | export const HTTP_LENGTH_REQUIRED = 411
56 | export const HTTP_PRECONDITION_FAILED = 412
57 | export const HTTP_REQUEST_ENTITY_TOO_LARGE = 413
58 | export const HTTP_REQUEST_URI_TOO_LONG = 414
59 | export const HTTP_UNSUPPORTED_MEDIA_TYPE = 415
60 | export const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416
61 | export const HTTP_EXPECTATION_FAILED = 417
62 | export const HTTP_I_AM_A_TEAPOT = 418 // RFC2324
63 | export const HTTP_UNPROCESSABLE_ENTITY = 422 // RFC4918
64 | export const HTTP_LOCKED = 423 // RFC4918
65 | export const HTTP_FAILED_DEPENDENCY = 424 // RFC4918
66 | export const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425 // RFC2817
67 | export const HTTP_UPGRADE_REQUIRED = 426 // RFC2817
68 | export const HTTP_PRECONDITION_REQUIRED = 428 // RFC6585
69 | export const HTTP_TOO_MANY_REQUESTS = 429 // RFC6585
70 | export const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 // RFC6585
71 |
72 | /**
73 | * The server encountered an unexpected error
74 | */
75 | export const HTTP_INTERNAL_SERVER_ERROR = 500
76 | export const HTTP_NOT_IMPLEMENTED = 501
77 | export const HTTP_BAD_GATEWAY = 502
78 | export const HTTP_SERVICE_UNAVAILABLE = 503
79 | export const HTTP_GATEWAY_TIMEOUT = 504
80 | export const HTTP_VERSION_NOT_SUPPORTED = 505
81 | export const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506 // RFC2295
82 | export const HTTP_INSUFFICIENT_STORAGE = 507 // RFC4918
83 | export const HTTP_LOOP_DETECTED = 508 // RFC5842
84 | export const HTTP_NOT_EXTENDED = 510 // RFC2774
85 | export const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511
86 |
--------------------------------------------------------------------------------
/src/constants/Pagination.js:
--------------------------------------------------------------------------------
1 | export const PAGINATE_XS = 5
2 | export const PAGINATE_SM = 10
3 | export const PAGINATE_MD = 15
4 | export const PAGINATE_LG = 20
5 | export const PAGINATE_XL = 25
6 |
--------------------------------------------------------------------------------
/src/constants/UserRoles.js:
--------------------------------------------------------------------------------
1 | export const SUPER_ADMIN = "superadmin"
2 | export const ADMIN = "admin"
3 |
4 | export const ALL = [SUPER_ADMIN, ADMIN]
5 |
--------------------------------------------------------------------------------
/src/database/factories/UserFactory.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from "../../app/models"
2 | import { ALL as ROLES } from "../../constants/UserRoles"
3 | import factory from "../../libraries/FakerFactories"
4 |
5 | factory.define("users", UserModel, faker => ({
6 | email: faker.internet.email(),
7 | username: faker.internet.userName(),
8 | password: "secret",
9 | full_name: {
10 | first_name: faker.name.firstName(),
11 | last_name: faker.name.lastName(),
12 | },
13 | role: faker.random.arrayElement(ROLES),
14 | }))
15 |
--------------------------------------------------------------------------------
/src/database/migrations/20190707103825_create_users_table.js:
--------------------------------------------------------------------------------
1 | import { USERS_TABLE } from "./../../constants/DBTables"
2 |
3 | export const up = knex => knex.schema.createTable(USERS_TABLE, table => {
4 | table.increments()
5 | table.string("email").unique().notNullable()
6 | table.string("username").unique().notNullable()
7 | table.string("password").notNullable()
8 | table.jsonb("full_name").nullable()
9 | table.jsonb("metadata").nullable()
10 | table.string("profile_picture").nullable()
11 | table.string("role")
12 |
13 | table.integer("created_by").unsigned().nullable().index().references("id").inTable(USERS_TABLE)
14 | table.integer("updated_by").unsigned().nullable().index().references("id").inTable(USERS_TABLE)
15 | table.integer("deleted_by").unsigned().nullable().index().references("id").inTable(USERS_TABLE)
16 |
17 | table.timestamps(true, true)
18 | table.timestamp("deleted_at")
19 | })
20 |
21 | export const down = knex => knex.schema.dropTable(USERS_TABLE)
22 |
--------------------------------------------------------------------------------
/src/database/seeds/UserTableSeeder.js:
--------------------------------------------------------------------------------
1 | import { USERS_TABLE } from "../../constants/DBTables"
2 | import { SUPER_ADMIN } from "../../constants/UserRoles"
3 | import { random } from "../../helpers/core.helper"
4 | import factory from "../../libraries/FakerFactories"
5 | import "./../factories/UserFactory"
6 |
7 | export const seed = async knex => {
8 | const superAdmin = await knex(USERS_TABLE).where("email", "admin@admin.com")
9 |
10 | if (superAdmin.length === 0) {
11 | factory("users", 1).create({
12 | email: "admin@admin.com",
13 | username: "admin",
14 | full_name: {
15 | first_name: "Administrator",
16 | last_name: "",
17 | },
18 | role: SUPER_ADMIN,
19 | })
20 | }
21 |
22 | await factory("users", random(15, 25)).create()
23 | }
24 |
--------------------------------------------------------------------------------
/src/helpers/core.helper.js:
--------------------------------------------------------------------------------
1 | import { config as dotEnvConfig } from "dotenv"
2 | import path from "path"
3 |
4 | export const root_path = (directory = undefined) => path.resolve(__dirname, "../../..", directory || "")
5 | export const client_path = (directory) => path.resolve(root_path(), "client", directory || "")
6 | export const server_path = (directory) => path.resolve(root_path(), "server", directory || "")
7 | export const src_path = (directory) => path.resolve(server_path(), "src", directory || "")
8 | export const app_path = (directory) => path.resolve(server_path(), "src/app", directory || "")
9 | dotEnvConfig({ path: root_path(".env") })
10 |
11 | export const env = (key, defaultValue) => {
12 | const value = process.env[key] || defaultValue
13 | if (typeof value === "undefined") {
14 | throw new Error(`Environment variable ${key} not set.`)
15 | }
16 |
17 | return value
18 | }
19 |
20 | export const normalizePort = (port) => {
21 | port = parseInt(port, 10)
22 |
23 | if (isNaN(port)) {
24 | throw new Error("Invalid port.")
25 | }
26 |
27 | if (port <= 0) {
28 | throw new Error("Invalid port.")
29 | }
30 |
31 | return port
32 | }
33 |
34 | export const onServerListening = (server) => {
35 | const addr = server.address()
36 | const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr.port}`
37 | console.info(`Server listening on ${bind}`)
38 | }
39 |
40 | export const onServerError = (error) => {
41 | if (error.syscall !== "listen") {
42 | throw error
43 | }
44 |
45 | const bind = typeof port === "string" ? "Pipe " + port : "Port " + port
46 | switch (error.code) {
47 | case "EACCES":
48 | console.error(`${bind} requires elevated privileges`)
49 | process.exit(1)
50 | break
51 | case "EADDRINUSE":
52 | console.error(`${bind} is already in use`)
53 | process.exit(1)
54 | break
55 | default:
56 | throw error
57 | }
58 | }
59 |
60 | export function * range(begin, end, interval = 1) {
61 | for (let i = begin; i < end; i += interval) {
62 | yield i
63 | }
64 | }
65 |
66 | export const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min)
67 |
68 | export const isArray = data => Array.isArray(data)
69 | export const isObject = data => typeof data === "object" && !isArray(data)
70 | export const isBycryptedHash = (string) => /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/.test(string)
71 |
--------------------------------------------------------------------------------
/src/libraries/FakerFactories/FakerFactory.js:
--------------------------------------------------------------------------------
1 | import faker from "faker"
2 | import Knex from "knex"
3 | import { Model } from "objection"
4 | import { DBConfig } from "../../config"
5 | import { range } from "../../helpers/core.helper"
6 |
7 | export default class FakerFactory {
8 | /**
9 | * @param {BaseModel} model
10 | * @param {function} callback
11 | * @return {FakerFactory}
12 | */
13 | constructor(model, callback) {
14 | this.initDatabase()
15 |
16 | this.model = model
17 | this.callback = callback
18 | this.number = 1
19 | }
20 |
21 | initDatabase() {
22 | const knex = Knex(DBConfig)
23 | Model.knex(knex)
24 | }
25 |
26 | /**
27 | *
28 | * @param attributes
29 | * @return {object|Array}
30 | */
31 | async create(attributes = {}) {
32 | let data = this.make(attributes)
33 |
34 | return await this.model.query().insert(data)
35 | }
36 |
37 | /**
38 | * @param {object} attributes
39 | * @return {Array|object}
40 | */
41 | make(attributes = {}) {
42 | if (this.number === 1) {
43 | return this.prepareData(attributes)
44 | }
45 |
46 | const data = []
47 | for (let i of range(0, this.number)) {
48 | data.push(this.prepareData(attributes))
49 | }
50 |
51 | return data
52 | }
53 |
54 | /**
55 | * @param {object} attributes
56 | * @return {object}
57 | */
58 | prepareData(attributes) {
59 | return { ...this.callback(faker), ...attributes }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/libraries/FakerFactories/index.js:
--------------------------------------------------------------------------------
1 | import FakerFactory from "./FakerFactory"
2 |
3 | const registeredFaker = {}
4 |
5 | /**
6 | * @param {string} label
7 | * @param {Number} number
8 | * @returns {FakerFactory}
9 | */
10 | const factory = (label, number = 1) => {
11 | const faker = registeredFaker[label]
12 |
13 | if (!(faker instanceof FakerFactory)) {
14 | throw new Error(`Faker '${label}' not defined.`)
15 | }
16 |
17 | faker.number = number
18 |
19 | return faker
20 | }
21 |
22 | /**
23 | *
24 | * @param {string} label
25 | * @param {BaseModel} model
26 | * @param {function} callback
27 | */
28 | factory.define = (label, model, callback) => {
29 | registeredFaker[label] = new FakerFactory(model, callback)
30 | }
31 |
32 | export default factory
33 |
--------------------------------------------------------------------------------
/src/libraries/Repository/RepositoryMixin.js:
--------------------------------------------------------------------------------
1 | import { ModelNotFoundException } from "./exceptions"
2 | import Pagination from "./helpers/Pagination"
3 |
4 | export default base => class extends base {
5 | async create(data) {
6 | const result = await this.model.query().insert(data)
7 |
8 | return this.parserResult(result)
9 | }
10 |
11 | async update(data, id) {
12 | await this.find(id)
13 |
14 | const result = await this.model.query().patchAndFetchById(id, data)
15 |
16 | return this.parserResult(result)
17 | }
18 |
19 | async delete(id) {
20 | await this.find(id)
21 |
22 | return await this.model.query().deleteById(id)
23 | }
24 |
25 | async find(id) {
26 | const result = await this.model.query().findById(id)
27 |
28 | if (!result) {
29 | throw new ModelNotFoundException(null, id)
30 | }
31 |
32 | return this.parserResult(result)
33 | }
34 |
35 | async findByColumn(column, value) {
36 | const result = await this.model.query().where(column, value)
37 |
38 | if (result.length === 0) {
39 | throw new ModelNotFoundException(null, value, column)
40 | }
41 |
42 | return this.parserResult(result[0])
43 | }
44 |
45 | async all() {
46 | const results = await this.model.query()
47 |
48 | return this.parserResult(results)
49 | }
50 |
51 | async paginate(perPage = 10, page = 1) {
52 | const results = await this.model.query().page(page - 1, perPage)
53 |
54 | return this.parserResult(new Pagination(results, perPage, page))
55 | }
56 |
57 | query() {
58 | return this.model.query()
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/libraries/Repository/exceptions/ModelNotFoundException.js:
--------------------------------------------------------------------------------
1 | export default class ModelNotFoundException extends Error {
2 | constructor(modelName, id, idColumn = "id") {
3 | super()
4 | this.modelName = modelName
5 | this.id = id
6 | this.message = `No record found for ${this.modelName} with ${idColumn}: ${this.id}`
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/libraries/Repository/exceptions/index.js:
--------------------------------------------------------------------------------
1 | export { default as ModelNotFoundException } from "./ModelNotFoundException"
2 |
--------------------------------------------------------------------------------
/src/libraries/Repository/helpers/Pagination.js:
--------------------------------------------------------------------------------
1 | export default class Pagination {
2 | constructor(data, perPage, page) {
3 | this.data = data
4 | this.perPage = perPage
5 | this.page = parseInt(page)
6 |
7 | this.setup()
8 | }
9 |
10 | setup() {
11 | const totalPages = Math.ceil(this.data.total / this.perPage)
12 |
13 | this.pagination = {
14 | total: this.data.total,
15 | count: this.data.results.length,
16 | per_page: this.perPage,
17 | current_page: this.page,
18 | total_pages: totalPages,
19 | }
20 | }
21 |
22 | get() {
23 | return {
24 | data: this.data.results,
25 | pagination: this.pagination,
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/libraries/Repository/index.js:
--------------------------------------------------------------------------------
1 | import { isArray } from "../../helpers/core.helper"
2 | import Pagination from "./helpers/Pagination"
3 | import RepositoryMixin from "./RepositoryMixin"
4 | import BaseTransformer from "./transformer"
5 |
6 | @RepositoryMixin
7 | export default class BaseRepository {
8 | constructor() {
9 | this._init()
10 | }
11 |
12 | _init() {
13 | if (this.model === undefined) {
14 | throw new TypeError("Repository should have 'model' method defined.")
15 | }
16 |
17 | this.model = this.model()
18 | this.transformer = null
19 | this.transformerSkipped = false
20 | }
21 |
22 | setTransformer(transformer) {
23 | this.transformer = transformer
24 |
25 | return this
26 | }
27 |
28 | skipTransformer(skip = true) {
29 | this.transformerSkipped = skip
30 | }
31 |
32 | parserResult(data) {
33 | if (this.transformerSkipped || !(this.transformer instanceof BaseTransformer)) {
34 | return data instanceof Pagination ? data.get() : data
35 | }
36 |
37 | if (data instanceof Pagination) {
38 | const paginatedResults = data.get()
39 | const results = paginatedResults.data.map(datum => this.transformer.transform(datum))
40 | return { paginatedData: results, meta: { pagination: paginatedResults.pagination } }
41 | }
42 |
43 | return isArray(data) ? data.map(datum => this.transformer.transform(datum)) : this.transformer.transform(data)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/libraries/Repository/model/index.js:
--------------------------------------------------------------------------------
1 | import { Model } from "objection"
2 |
3 | export default class BaseModel extends Model {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/src/libraries/Repository/model/plugins/Authenticatable.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken"
2 | import { AuthConfig } from "../../../../config"
3 |
4 | export default base => class extends base {
5 | generateToken() {
6 | const today = new Date()
7 | const expirationDate = new Date(today)
8 | expirationDate.setTime(today.getTime() + AuthConfig.tokenExpiration * 1000)
9 |
10 | return jwt.sign({
11 | id: this.id,
12 | email: this.email,
13 | exp: parseInt((expirationDate.getTime() / 1000).toString(), 10),
14 | }, AuthConfig.jwtSecret)
15 | }
16 |
17 | async authenticate(username, password, done) {
18 | const user = await AuthConfig.authModel.query().findOne({ "username": username })
19 |
20 | if (!user || !user.matchPassword(password)) {
21 | return done(null, null, "Invalid login.")
22 | }
23 |
24 | return done(null, user, "Login success.")
25 | }
26 |
27 | async authenticateJwt(jwtPayload, done) {
28 | const user = await AuthConfig.authModel.query().findById(jwtPayload.id)
29 |
30 | if (!user) {
31 | return done("UnAuthenticated")
32 | }
33 |
34 | return done(null, user)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/libraries/Repository/model/plugins/Password.js:
--------------------------------------------------------------------------------
1 | import Bcrypt from "bcrypt"
2 | import { AuthConfig } from "../../../../config"
3 | import { isBycryptedHash } from "../../../../helpers/core.helper"
4 |
5 | export default base => class extends base {
6 | async $beforeInsert(context) {
7 | await super.$beforeInsert(context)
8 |
9 | const password = this[AuthConfig.passwordField]
10 | this[AuthConfig.passwordField] = await this.generateHash(password)
11 | }
12 |
13 | async $beforeUpdate(queryOptions, context) {
14 | await super.$beforeUpdate(queryOptions, context)
15 |
16 | const password = this[AuthConfig.passwordField]
17 | if (!password) {
18 | return
19 | }
20 |
21 | this[AuthConfig.passwordField] = await this.generateHash(password)
22 | }
23 |
24 | generateHash(password) {
25 | if (!password) {
26 | return password
27 | }
28 |
29 | if (isBycryptedHash(password)) {
30 | return password
31 | }
32 |
33 | return Bcrypt.hash(password, AuthConfig.hashRounds)
34 | }
35 |
36 | matchPassword(password) {
37 | return Bcrypt.compare(password, this[AuthConfig.passwordField])
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/libraries/Repository/transformer/index.js:
--------------------------------------------------------------------------------
1 | export default class BaseTransformer {
2 | constructor() {
3 | if (this.transform === undefined) {
4 | throw new TypeError("Transformer should have 'transform' method defined.")
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/libraries/controller/Response.js:
--------------------------------------------------------------------------------
1 | import {
2 | HTTP_NOT_FOUND,
3 | HTTP_OK,
4 | } from "../../constants/HTTPCode"
5 | import { isObject } from "../../helpers/core.helper"
6 |
7 | export default base => class extends base {
8 | constructor(props) {
9 | super(props)
10 | this.errors = null
11 | this.metadata = null
12 | }
13 |
14 | setErrors(errors) {
15 | this.errors = errors
16 |
17 | return this
18 | }
19 |
20 | setMetadata(metadata) {
21 | this.metadata = metadata
22 |
23 | return this
24 | }
25 |
26 | sendResponse(res, data = null, message = "", code = HTTP_OK) {
27 | if (data && isObject(data) && data.hasOwnProperty("meta")) {
28 | this.metadata = { ...this.metadata, ...data.meta }
29 | }
30 |
31 | res.status(code)
32 | res.json(this._prepareResponse(message, data))
33 | }
34 |
35 | sendError(res, error = "Error", code = HTTP_NOT_FOUND) {
36 | res.status(code)
37 | res.json(this._prepareErrorResponse(error))
38 | }
39 |
40 | _prepareResponse(message, data) {
41 | const response = {
42 | status: true,
43 | }
44 |
45 | if (message) {
46 | response["message"] = message
47 | }
48 |
49 | if (data) {
50 | response["data"] = this._extractData(data)
51 | }
52 |
53 | if (this.metadata) {
54 | response["metadata"] = this.metadata
55 | }
56 |
57 | return response
58 | }
59 |
60 | _prepareErrorResponse(errorMessage) {
61 | const response = {
62 | status: false,
63 | }
64 |
65 | if (errorMessage) {
66 | response["message"] = errorMessage
67 | }
68 |
69 | if (this.errors) {
70 | response["data"] = this.errors
71 | }
72 |
73 | if (this.metadata) {
74 | response["metadata"] = this.metadata
75 | }
76 |
77 | return response
78 | }
79 |
80 | _extractData(data) {
81 | if (isObject(data) && data.hasOwnProperty("paginatedData")) {
82 | return data.paginatedData
83 | }
84 |
85 | return data
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/libraries/controller/index.js:
--------------------------------------------------------------------------------
1 | import Response from "./Response"
2 |
3 | @Response
4 | export default class Controller {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/src/libraries/controller/router/index.js:
--------------------------------------------------------------------------------
1 | import { Router as ExpressRouter } from "express"
2 | import * as Controllers from "./../../../app/controllers"
3 |
4 | class Router {
5 | constructor() {
6 | this.router = ExpressRouter()
7 | }
8 |
9 | get(path, ...actions) {
10 | this.router.get(path, ...this._resolveController(actions))
11 | }
12 |
13 | post(path, ...actions) {
14 | this.router.post(path, ...this._resolveController(actions))
15 | }
16 |
17 | patch(path, ...actions) {
18 | this.router.patch(path, ...this._resolveController(actions))
19 | }
20 |
21 | delete(path, ...actions) {
22 | this.router.delete(path, ...this._resolveController(actions))
23 | }
24 |
25 | export() {
26 | return this.router
27 | }
28 |
29 | _resolveController(actions) {
30 | const lastIndex = actions.length - 1
31 | const action = actions[lastIndex]
32 |
33 | const [controllerName, methodName] = action.split("@")
34 | const controller = new Controllers[controllerName]
35 |
36 | actions[lastIndex] = typeof action === "string" ? controller[methodName].bind(controller) : action
37 |
38 | return actions
39 | }
40 | }
41 |
42 | export default new Router()
43 |
--------------------------------------------------------------------------------
/src/routes/auth.routes.js:
--------------------------------------------------------------------------------
1 | import { FormValidations } from "../app/middlewares"
2 | import AuthValidations from "../app/validations/auth.validations"
3 | import Router from "../libraries/controller/router"
4 |
5 | Router.post("/login", [
6 | AuthValidations.login, FormValidations,
7 | ], "AuthController@login")
8 |
9 | export default Router.export()
10 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import AuthRoutes from "./auth.routes"
2 | import UserRoutes from "./users.routes"
3 |
4 | export default app => {
5 | app.use("/", AuthRoutes)
6 | app.use("/users", UserRoutes)
7 | }
8 |
--------------------------------------------------------------------------------
/src/routes/users.routes.js:
--------------------------------------------------------------------------------
1 | import {
2 | auth,
3 | FormValidations,
4 | } from "../app/middlewares"
5 | import UsersValidations from "./../app/validations/users.validations"
6 | import Router from "./../libraries/controller/router"
7 |
8 | Router.get("/", "UsersController@index")
9 | Router.get("/:userId", "UsersController@show")
10 | Router.post("/", [auth, UsersValidations.create, FormValidations], "UsersController@create")
11 | Router.patch("/:userId", [auth, UsersValidations.update, FormValidations], "UsersController@update")
12 | Router.post("/:userId/change-password", [auth, UsersValidations.passwordChange, FormValidations], "UsersController@changePassword")
13 | Router.delete("/:userId", auth, "UsersController@delete")
14 |
15 | export default Router.export()
16 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import { createServer } from "http"
2 | import app from "./bootstrap/app"
3 | import { AppConfig } from "./config"
4 | import {
5 | normalizePort,
6 | onServerError,
7 | onServerListening,
8 | } from "./helpers/core.helper"
9 |
10 | (async () => {
11 | const port = normalizePort(AppConfig.port)
12 | app.set("port", port)
13 |
14 | const server = createServer(app)
15 | server.listen(port)
16 | server.on("listening", onServerListening.bind(this, server))
17 | server.on("error", onServerError)
18 |
19 | process.on("unhandledRejection", (reason, p) => {
20 | console.error("Unhandled Rejection at:", p, "reason:", reason)
21 | })
22 | })()
23 |
--------------------------------------------------------------------------------