├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── LICENSE ├── Readme.md ├── app ├── controllerMapper.js ├── controllerMapper.json ├── index.js └── user │ ├── controller.js │ ├── model.js │ └── routes.json ├── bin └── index.js ├── config.js ├── database ├── database.js ├── index.js ├── modelMapper.js ├── modelMapper.json └── relation.js ├── docs ├── .nojekyll ├── LICENSE ├── README.md ├── _sidebar.md ├── database.md ├── deploy.md ├── index.html ├── middleware.md ├── model.md └── module.md ├── env.example ├── haste ├── constant │ └── general.js ├── export │ └── script.js ├── middleware │ ├── middleware.js │ ├── removeMiddleware.js │ └── script.js ├── module │ ├── controller │ │ └── controller.js │ ├── database │ │ ├── index.js │ │ └── model.js │ ├── module.js │ ├── removeModule.js │ └── routes.json └── utils │ └── utils.js ├── middleware ├── isLoggedIn.js ├── middlewareMapper.js └── middlewareMapper.json ├── package.json ├── public └── roket.png ├── test ├── mapper.js └── user.js └── views ├── api-docs.ejs ├── homepage.ejs └── includes ├── header.ejs └── navbar.ejs /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: HasteJs CI 5 | 6 | on: 7 | push: 8 | branches: [ develop ] 9 | pull_request: 10 | branches: [ main, develop ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 14.6.0] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | db 4 | package-lock.json 5 | yarn-error.log 6 | yarn-lock.json 7 | .env 8 | cha-export -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MD SULTAN MAHAMUD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # **[HasteJS - A nodejs framework.](http://hastejs.com/)** 2 | [![NPM](https://img.shields.io/npm/v/hastejs-cli.svg?style=flat-square)](https://www.npmjs.com/package/hastejs-cli) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://github.com/sultanfendonus/HasteJS/blob/main/LICENSE) 4 | ![HasteJs CI](https://github.com/sultanfendonus/HasteJS/workflows/HasteJs%20CI/badge.svg) 5 | 6 | HasteJS is a NodeJs framework, built on top of ExpressJS, released as free and open-source software under the MIT Licence. It is particularly designed to develop a quick Restful API in record time. 7 | 8 | 9 | **Details Docs:** http://hastejs.com 10 | 11 | [![Netlify Status](https://api.netlify.com/api/v1/badges/96254405-9395-4994-9a7f-613ccfdfa563/deploy-status)](https://app.netlify.com/sites/hastejs/deploys) 12 | 13 | **Node.js** 14 | HasteJs only requires Node.js. The current recommended version to run Haste is Node v14. 15 | 16 | **Installation and run in development mode** 17 | `npx create-haste-app my-app` 18 | `cd my-app` 19 | `npm run develop` 20 | 21 | 22 | # Module 23 | 24 | ### What is Module in HasteJs? 25 | In **HasteJs**, Modules are some smaller part of your entire project. 26 | You may want to divide your entire project into some modules so that 27 | you can easily use these modules for your other hasteJs project easily. 28 | 29 | 30 | ### Create a new module 31 | It is very easy to create a new module in your hasteJs project. 32 | Just run below comand on your project root. 33 | 34 | `npx create-module moduleName` 35 | 36 | _It will generate a new folder in app directory with controllers, 37 | model and routes._ 38 | 39 | ```text 40 | . 41 | └── app 42 | └── moduleName 43 | ├── controller.js 44 | └── model.js 45 | └── routes.json 46 | ``` 47 | ### Remove an existing module 48 | If you want to remove a module, just run below command on your project 49 | root - 50 | 51 | `npx remove-module moduleName` 52 | 53 | Note: Don't delete a folder or module manually from app directory. 54 | That may cause unnecessary error on your code. 55 | 56 | ### Routes 57 | Routes refer to how Rest API's endpoints respond to client requests. 58 | When you create a new module, haste by default create some REST convention endpoint for you. 59 | Here, You can add new endpoints or update old endpoints as your need. 60 | 61 | ```json 62 | { 63 | "routes": [ 64 | { 65 | "method": "GET", 66 | "path": "/category", 67 | "controller": "category.find", 68 | "config": { 69 | "middleware": [] 70 | } 71 | }, 72 | { 73 | "method": "GET", 74 | "path": "/category/count", 75 | "controller": "category.count", 76 | "config": { 77 | "middleware": [] 78 | } 79 | }, 80 | { 81 | "method": "GET", 82 | "path": "/category/:id", 83 | "controller": "category.findOne", 84 | "config": { 85 | "middleware": [] 86 | } 87 | }, 88 | { 89 | "method": "POST", 90 | "path": "/category", 91 | "controller": "category.create", 92 | "config": { 93 | "middleware": [] 94 | } 95 | }, 96 | { 97 | "method": "PUT", 98 | "path": "/category/:id", 99 | "controller": "category.update", 100 | "config": { 101 | "middleware": [] 102 | } 103 | }, 104 | { 105 | "method": "DELETE", 106 | "path": "/category/:id", 107 | "controller": "category.delete", 108 | "config": { 109 | "middleware": [] 110 | } 111 | } 112 | ] 113 | } 114 | ``` 115 | ### Controller 116 | Every route passes the request to the defined controller. 117 | Controllers hold the business logic of your module. 118 | Every route must define a controller. Controllers can communicate with the model and return data to the client or Error handlers. 119 | 120 | ```javascript 121 | import {Model as Category} from '../../database/modelMapper.js' 122 | 123 | const controller = { 124 | async count(req, res, next){ 125 | try { 126 | const response = await Category.count({}); 127 | res.json({total: response}); 128 | } catch (err) { 129 | next(err); 130 | } 131 | }, 132 | } 133 | export default controller; 134 | ``` 135 | The above code is responsible for the return count of the Category. 136 | 137 | If you need to add a new function to your controller, you must add it to your routes.json file with the proper structure. 138 | 139 | ### Model 140 | HasteJs uses `sequelize` for managing database operations. For 141 | updating your model you need to update your `model.js` file to structure your table. 142 | For more about sequelize model visit here: https://sequelize.org/master/manual/model-basics.html 143 | 144 | Example model: 145 | ```javascript 146 | import {sequelize} from "../../database/index.js"; 147 | import DataTypes from 'sequelize'; 148 | 149 | export const Model = sequelize.define('Category', { 150 | // Model attributes are defined here 151 | // This are example attributes. please change as you want. 152 | // visit https://sequelize.org/master/manual/model-basics.html for details. 153 | 154 | title: { 155 | type: DataTypes.STRING, 156 | allowNull: false 157 | }, 158 | description: { 159 | type: DataTypes.STRING 160 | // allowNull defaults to true 161 | } 162 | }, { 163 | // Other model options go here 164 | }); 165 | ``` 166 | 167 | ### Relation/Association 168 | You can define all your relation/association here ```database/relation.js``` file. 169 | 170 | Example 171 | 172 | ```javascript 173 | import {Post, Category} from "./modelMapper.js"; 174 | 175 | const relation = ()=> { 176 | Post.belongsTo(Category, {foreignKey: 'category_id'}) 177 | Category.hasMany(Post, { 178 | foreignKey: 'category_id', 179 | sourceKey: 'id' 180 | }) 181 | } 182 | 183 | export default relation(); 184 | ``` 185 | 186 | ### Default Module 187 | When you create a HasteJs project by [create-haste-app](https://www.npmjs.com/package/create-haste-app), A default User module will automatically generate for you in your app directory, So that you can focus on your main modules to develop your app in haste mode. 188 | This default user module provides the following API 189 | 190 | - Login user 191 | - Register user 192 | - Count all users 193 | - Find Me 194 | - Find all users 195 | - Find Specific user. 196 | 197 | 198 | 199 | 200 | \ 201 | \ 202 | **License & copyright**\ 203 | © MD SULTAN MAHAMUD, Software Engineer\ 204 | Licensed under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /app/controllerMapper.js: -------------------------------------------------------------------------------- 1 | import user from './user/controller.js'; 2 | 3 | export const CONTROLLER_MAPPER = { 4 | user: user, 5 | } -------------------------------------------------------------------------------- /app/controllerMapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "import": [ 3 | "import user from './user/controller.js';" 4 | ], 5 | "mapper": { 6 | "user": "user" 7 | } 8 | } -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import bodyParser from 'body-parser'; 4 | import path from 'path' 5 | import {combinedRoutes} from "../haste/utils/utils.js"; 6 | import {CONTROLLER_MAPPER} from "./controllerMapper.js"; 7 | import {init} from '../database/index.js' 8 | import {MIDDLEWARE_MAPPER} from "../middleware/middlewareMapper.js"; 9 | import {port} from "../config.js"; 10 | 11 | import { dirname } from 'path'; 12 | import { fileURLToPath } from 'url'; 13 | import dotenv from 'dotenv' 14 | dotenv.config() 15 | 16 | const app = express() 17 | 18 | 19 | app.set("view engine", "ejs"); 20 | const __dirname = dirname(fileURLToPath(import.meta.url)); 21 | app.set("views", path.join(__dirname,"../views")); 22 | 23 | 24 | app.use(cors()); 25 | app.use(bodyParser.urlencoded({ extended: false })); 26 | app.use(bodyParser.json()); 27 | app.use( express.static( "public" ) ); 28 | 29 | //database init 30 | init() 31 | 32 | // console.log(routes); 33 | const routes = combinedRoutes() 34 | 35 | const appendMiddlewares = (middlewares)=> { 36 | let middlewareArray = [] 37 | middlewares.forEach((item)=> { 38 | middlewareArray.push(MIDDLEWARE_MAPPER[item]) 39 | }) 40 | return middlewareArray; 41 | } 42 | 43 | routes.map((item)=> { 44 | const [controller, method] = item.controller.split('.'); 45 | const middlewares = item.config['middleware'] 46 | app[item.method.toLowerCase()](item.path, middlewares.length > 0 ? appendMiddlewares(middlewares): [], 47 | CONTROLLER_MAPPER[controller][method]) 48 | }) 49 | 50 | let debugMode = process.env.DEBUG_MODE || true; 51 | 52 | if(debugMode === true || debugMode === 'true'){ 53 | app.get("/", (req, res) => { 54 | res.render("homepage",{ 55 | pageTitle: "HasteJs - Homepage", 56 | version: process.env.npm_package_version 57 | }); 58 | }); 59 | 60 | app.get("/api-docs", (req, res) => { 61 | res.render("api-docs",{ 62 | pageTitle: "HasteJs - API Documentation", 63 | version: process.env.npm_package_version, 64 | api: routes, 65 | port: process.env.PORT_NUMBER || port 66 | }); 67 | }); 68 | } 69 | 70 | 71 | app.use((err, req, res, next) => { 72 | if (!err.statusCode) { 73 | err.statusCode = 500; 74 | } 75 | res.status(err.statusCode).send({ code: err.statusCode, error: err.message }); 76 | }); 77 | 78 | 79 | export const server = app.listen(process.env.PORT_NUMBER || port, () => { 80 | console.log(`Haste app listening at http://localhost:${process.env.PORT_NUMBER || port}`) 81 | console.log(`API docs at http://localhost:${process.env.PORT_NUMBER || port}/api-docs`) 82 | console.log(`Full HasteJs documentation at http://hastejs.com`) 83 | }) -------------------------------------------------------------------------------- /app/user/controller.js: -------------------------------------------------------------------------------- 1 | import {Model as User} from './model.js'; 2 | import bcrypt from 'bcryptjs'; 3 | import jwt from 'jsonwebtoken'; 4 | import {jwtSecretKey} from "../../config.js"; 5 | 6 | const controller = { 7 | async find(req, res, next){ 8 | try { 9 | const response = await User.scope("withoutPassword").findAll({}); 10 | res.send(response); 11 | } catch (err) { 12 | next(err); 13 | } 14 | }, 15 | async count(req, res, next){ 16 | try { 17 | const response = await User.count({}); 18 | res.json({total: response}); 19 | } catch (err) { 20 | next(err); 21 | } 22 | }, 23 | async findOne(req, res, next){ 24 | try { 25 | const response = await User.scope("withoutPassword").findOne({ 26 | where: { 27 | id: req.params.id 28 | } 29 | }); 30 | if(response){ 31 | res.send(response); 32 | }else { 33 | res.status(404).json({message: 'No item found!'}) 34 | } 35 | } catch (err) { 36 | next(err); 37 | } 38 | }, 39 | async findMe(req, res, next){ 40 | try { 41 | const response = await User.scope("withoutPassword").findOne({ 42 | where: { 43 | id: req.user_id 44 | } 45 | }); 46 | if(response){ 47 | res.send(response); 48 | }else { 49 | res.status(404).json({message: 'No item found!'}) 50 | } 51 | } catch (err) { 52 | next(err); 53 | } 54 | }, 55 | async create(req, res, next){ 56 | try { 57 | bcrypt.genSalt(10, function (err, salt) { 58 | bcrypt.hash(req.body.password, salt, async function (err, hash) { 59 | //data insert 60 | let response; 61 | try { 62 | response = await User.create({ 63 | first_name: req.body.first_name, 64 | last_name: req.body.last_name, 65 | email: req.body.email, 66 | password: hash, 67 | }); 68 | } catch (e) { 69 | next(e); 70 | } 71 | 72 | const token = jwt.sign( 73 | { 74 | email: response.email, 75 | user_id: response.id 76 | }, 77 | process.env.JWT_SECRET || jwtSecretKey 78 | ); 79 | 80 | res.status(201).json({ 81 | email: req.body.email, 82 | first_name: req.body.first_name, 83 | last_name: req.body.last_name, 84 | token: token 85 | }); 86 | }); 87 | }); 88 | } catch (err) { 89 | next(err); 90 | } 91 | }, 92 | async login(req, res, next){ 93 | try{ 94 | const user = await User.findOne({ 95 | where: { email: req.body.email } 96 | }); 97 | if (user) { 98 | bcrypt.compare(req.body.password, user.password, async function (err, result) { 99 | // res === true 100 | if (result === true) { 101 | const token = jwt.sign( 102 | { 103 | email: user.email, 104 | user_id: user.id 105 | }, 106 | process.env.JWT_SECRET || jwtSecretKey 107 | ); 108 | 109 | const userData = await User.scope('withoutPassword').findOne({ 110 | where: { email: req.body.email } 111 | }); 112 | 113 | res.status(200).json({ 114 | user: userData, 115 | token: token 116 | }); 117 | } else { 118 | res 119 | .status(401) 120 | .json({ errors: [{ msg: 'Invalid email or password!' }] }); 121 | } 122 | }); 123 | } else { 124 | res.status(401).json({ errors: [{ msg: 'Invalid email or password!' }] }); 125 | } 126 | }catch (err){ 127 | next(err) 128 | } 129 | } 130 | } 131 | export default controller; -------------------------------------------------------------------------------- /app/user/model.js: -------------------------------------------------------------------------------- 1 | import {sequelize} from "../../database/index.js"; 2 | import DataTypes from 'sequelize'; 3 | 4 | export const Model = sequelize.define('User', { 5 | // Model attributes are defined here 6 | // This are example attributes. please change as you want. 7 | // visit https://sequelize.org/master/manual/model-basics.html for details. 8 | 9 | first_name: { 10 | type: DataTypes.STRING, 11 | allowNull: false 12 | }, 13 | last_name: { 14 | type: DataTypes.STRING 15 | }, 16 | email: { 17 | type: DataTypes.STRING, 18 | allowNull: false, 19 | unique: true 20 | }, 21 | password: { 22 | type: DataTypes.STRING 23 | }, 24 | isActive: { 25 | type: DataTypes.BOOLEAN, 26 | defaultValue: true 27 | }, 28 | isConfirmed: { 29 | type: DataTypes.BOOLEAN, 30 | defaultValue: false 31 | } 32 | }, { 33 | // Other model options go here 34 | defaultScope: { 35 | attributes: { exclude: [''] }, 36 | }, 37 | scopes: { 38 | withoutPassword: { 39 | attributes: {exclude: ['password']}, 40 | } 41 | } 42 | }); -------------------------------------------------------------------------------- /app/user/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/user", 6 | "controller": "user.find", 7 | "config": { 8 | "middleware": ["isLoggedIn"] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/user/count", 14 | "controller": "user.count", 15 | "config": { 16 | "middleware": ["isLoggedIn"] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/user/:id", 22 | "controller": "user.findOne", 23 | "config": { 24 | "middleware": ["isLoggedIn"] 25 | } 26 | }, 27 | { 28 | "method": "GET", 29 | "path": "/user/get/me", 30 | "controller": "user.findMe", 31 | "config": { 32 | "middleware": ["isLoggedIn"] 33 | } 34 | }, 35 | { 36 | "method": "POST", 37 | "path": "/user/register", 38 | "controller": "user.create", 39 | "config": { 40 | "middleware": [] 41 | } 42 | }, 43 | { 44 | "method": "POST", 45 | "path": "/user/login", 46 | "controller": "user.login", 47 | "config": { 48 | "middleware": [] 49 | } 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console.log("hello from hastejs-cli"); -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | export const port = 4999; 2 | export const autoMigration = true; //false to off auto migration/syncing. 3 | export const jwtSecretKey = 'secretssh'; 4 | export const db_log = true; -------------------------------------------------------------------------------- /database/database.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | dotenv.config() 3 | 4 | export const dbConfig = { 5 | settings: { 6 | client: process.env.DB_CLIENT || 'sqlite', /* one of 'sqlite' | 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ 7 | host: process.env.DB_HOSTNAME || '127.0.0.1', 8 | port: process.env.DB_PORT || '5432', 9 | database: process.env.DB_NAME || 'haste', 10 | username: process.env.DB_USER || 'haste', 11 | password: process.env.DB_PASSWORD || '', 12 | } 13 | } -------------------------------------------------------------------------------- /database/index.js: -------------------------------------------------------------------------------- 1 | import Sequelize from 'sequelize'; 2 | import {autoMigration} from '../config.js' 3 | import {dbConfig} from "./database.js"; 4 | import dotenv from 'dotenv' 5 | dotenv.config() 6 | 7 | export let sequelize; 8 | 9 | if(dbConfig.settings.client === 'sqlite'){ 10 | sequelize= new Sequelize({ 11 | dialect: 'sqlite', 12 | storage: 'db/database.sqlite', 13 | logging: (msg)=> { 14 | if(!process.env.DB_LOGS || process.env.DB_LOGS === 'false'){ 15 | return false; 16 | }else { 17 | return console.log(msg) 18 | } 19 | } 20 | }); 21 | }else { 22 | const {client, host, port, database, username, password} = dbConfig.settings; 23 | sequelize = new Sequelize(database, username, password, { 24 | host: `${host}`, 25 | dialect: client, 26 | logging: (msg)=> { 27 | if(!process.env.DB_LOGS || process.env.DB_LOGS === 'false'){ 28 | return false; 29 | }else { 30 | return console.log(msg) 31 | } 32 | } 33 | }); 34 | } 35 | 36 | export const syncAll = async ()=> { 37 | await sequelize.sync({ alter: true }); 38 | } 39 | 40 | export const init = async ()=> { 41 | try { 42 | await sequelize.authenticate(); 43 | console.log('Connection with database has been established successfully.'); 44 | autoMigration && await syncAll() 45 | } catch (error) { 46 | console.error('Unable to connect to the database:', error); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /database/modelMapper.js: -------------------------------------------------------------------------------- 1 | import relation from './relation.js'; 2 | 3 | export {} -------------------------------------------------------------------------------- /database/modelMapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "import": [ 3 | "import relation from './relation.js';" 4 | ], 5 | "export": [] 6 | } -------------------------------------------------------------------------------- /database/relation.js: -------------------------------------------------------------------------------- 1 | // import your models here.. 2 | 3 | const relation = ()=> { 4 | // Write your association/Relation logic here... 5 | } 6 | 7 | export default relation(); -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sultanfendonus/HasteJS/5e32d348c706696192506eea61536517bc8d8eec/docs/.nojekyll -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MD SULTAN MAHAMUD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # **HasteJS - A nodejs framework.** 2 | 3 | HasteJS is a NodeJs framework, built on top of ExpressJS, released as free and open-source software under the MIT Licence. It is particularly designed to develop a quick Restful API in record time. 4 | 5 | 6 | **Node.js**\ 7 | HasteJs only requires Node.js. The current recommended version to run Haste is Node v14. 8 | 9 | **Installation and run in development mode**\ 10 | `npx create-haste-app my-app`\ 11 | `cd my-app`\ 12 | `npm run develop` 13 | 14 | Now create a new [module](module.md). [Click here](module.md) 15 | 16 | 17 | 18 | \ 19 | \ 20 | **License & copyright**\ 21 | © MD SULTAN MAHAMUD, Software Engineer\ 22 | Licensed under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [Home](README.md "HasteJS - A nodejs framework") 2 | * [Module](module.md "HasteJs Module") 3 | * [Introduction](module.md?id=module) 4 | * [Create a new module](module.md?id=create-a-new-module) 5 | * [Remove an existing module](module.md?id=remove-an-existing-module) 6 | * [Routes](module.md?id=routes) 7 | * [Controllers](module.md?id=controller) 8 | * [Model](module.md?id=model) 9 | * [Relation/Association](module.md?id=relationassociation) 10 | * [Default Module](module.md?id=default-module) 11 | * [MiddleWare](middleware.md) 12 | * [What is middleware?](middleware.md?id=what-is-middleware) 13 | * [Create a new middleware](middleware.md?id=create-a-new-middleware) 14 | * [Remove an existing middleware](middleware.md?id=remove-an-existing-middleware) 15 | * [Database](database.md) 16 | * [Deployment](deploy.md) -------------------------------------------------------------------------------- /docs/database.md: -------------------------------------------------------------------------------- 1 | # Database 2 | HasteJs currently supports `MySQL`, `PostgreSQL`, `SQLite` and `MS SQL`. 3 | 4 | `MongoDB will be added soon.` 5 | 6 | HasteJs uses SQLite as a default database. But, you can 7 | use your appropriate one by changing 8 | your `.env` file. 9 | 10 | First, Create a `.env` file on your project root and copy the 11 | whole variable list from `env.example` 12 | file to your newly created `.env` file. 13 | 14 | Now change the following fields as your database reference value - 15 | 16 | ```dotenv 17 | DB_CLIENT = sqlite 18 | DB_HOSTNAME = 19 | DB_PORT = 20 | DB_NAME = 21 | DB_USER = 22 | DB_PASSWORD = 23 | ``` 24 | You need to restart the project. Stop the project and start again by 25 | `npm run develop` 26 | 27 | 28 | ### Auto Migration 29 | HasteJs and sequelize automatically manage your database migration 30 | when you changed your model files. So you don't have to worry 31 | about database migration. 32 | 33 | ### Manual Migration 34 | If you want to migrate your data manually then you have to follow 35 | 2 steps - 36 | 1. Disable auto migration from `Project Root -> config.js`. 37 | ``` 38 | export const autoMigration = false; 39 | ``` 40 | 2. Follow this documentation https://sequelize.org/master/manual/migrations.html -------------------------------------------------------------------------------- /docs/deploy.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | You can easily deploy a hasteJs project as you deploy a normal nodejs project. 3 | 4 | Here is a basic example of deployment - 5 | 6 | 1. Login to your server, and install [nodejs](https://nodejs.org/en/). 7 | 8 | 9 | 2. Install pm2 globally \ 10 | `npm install pm2 -g` 11 | 12 | 13 | 3. Clone your repository and go to project root. 14 | 15 | 16 | 4. Run `npm install` for node dependency. 17 | 18 | 19 | 5. Create a `.env` file and copy the `env.example` file content to the .env file. 20 | 21 | 22 | 6. Change all nessasary changes like port number to 80, set database credential etc. 23 | and save the file. 24 | 25 | 26 | 27 | 7. Run `npm run deploy` for deployment. 28 | 29 | 30 | 8. If you want to stop a hasteJs application just run `pm2 list` for a list of applications running 31 | and run `pm2 stop id` to stop an application. For more info about pm2 visit https://pm2.keymetrics.io/ 32 | 33 | 34 | 9. If you make any changes to the application code base then you must restart 35 | the process in pm2. Run `pm2 restart id` for the restart of application. 36 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasteJS - A nodejs framework 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/middleware.md: -------------------------------------------------------------------------------- 1 | # Middleware 2 | 3 | ### What is middleware? 4 | Middleware is a piece of code or functions that have access to the 5 | request object (req), the response object (res), and the next middleware 6 | function in the application’s request-response cycle. The next middleware 7 | function is commonly denoted by a variable named next. 8 | More about middleware visit [ExpressJS Middleware](https://expressjs.com/en/guide/using-middleware.html) 9 | 10 | In a general sense, you will need a middleware when 11 | you want to verify the request and do some stuff with the request 12 | before the controller function execute. 13 | You can define one or multiple middlewares in every route. 14 | 15 | `create-haste-app` automatically generates "isLoggedIn" 16 | middleware for you in `app -> middleware` folder. 17 | This middleware verifies the JW token and passes the request 18 | to the controller or error handlers. You can use this middleware 19 | on any of your routes to secure the route. 20 | 21 | Here user count route is secured by `isLoggedIn` middleware. You can add multiple 22 | middleware in middleware array. 23 | 24 | ```json 25 | { 26 | "method": "GET", 27 | "path": "/user/count", 28 | "controller": "user.count", 29 | "config": { 30 | "middleware": ["isLoggedIn"] 31 | } 32 | } 33 | ``` 34 | 35 | You can generate a new middleware for your need. Here is the shortcut way- 36 | 37 | ### Create a new middleware 38 | `npx create-middleware middlewareName` 39 | 40 | _It will generate a new middleware file for you._ 41 | 42 | ```text 43 | . 44 | └── middleware 45 | ├── isLoggedIn.js 46 | └── middlewareName.js 47 | ``` 48 | ```javascript 49 | const middlewareName = (req,res,next) => { 50 | try{ 51 | next(); 52 | }catch (e) { 53 | next(error); 54 | } 55 | } 56 | 57 | export default middlewareName; 58 | ``` 59 | 60 | ### Remove an existing middleware 61 | If you want to remove a middleware, just run below command on your project 62 | root - 63 | 64 | `npx remove-middleware middlewareName` 65 | 66 | Note: Don't delete middleware files manually from `middleware` directory. 67 | That may cause unnecessary error on your code. -------------------------------------------------------------------------------- /docs/model.md: -------------------------------------------------------------------------------- 1 | **Update the model**\ 2 | HasteJs uses `sequelize` for managing database operations. now you need to 3 | update your `model.js` file to structure your table. 4 | For more about sequelize model visit here: https://sequelize.org/master/manual/model-basics.html 5 | 6 | -------------------------------------------------------------------------------- /docs/module.md: -------------------------------------------------------------------------------- 1 | # Module 2 | 3 | ### What is Module in HasteJs? 4 | In **HasteJs**, Modules are some smaller part of your entire project. 5 | You may want to divide your entire project into some modules so that 6 | you can easily use these modules for your other hasteJs project easily. 7 | 8 | 9 | ### Create a new module 10 | It is very easy to create a new module in your hasteJs project. 11 | Just run below comand on your project root. 12 | 13 | `npx create-module moduleName` 14 | 15 | _It will generate a new folder in app directory with controllers, 16 | model and routes._ 17 | 18 | ```text 19 | . 20 | └── app 21 | └── moduleName 22 | ├── controller.js 23 | └── model.js 24 | └── routes.json 25 | ``` 26 | ### Remove an existing module 27 | If you want to remove a module, just run below command on your project 28 | root - 29 | 30 | `npx remove-module moduleName` 31 | 32 | Note: Don't delete a folder or module manually from app directory. 33 | That may cause unnecessary error on your code. 34 | 35 | ### Routes 36 | Routes refer to how Rest API's endpoints respond to client requests. 37 | When you create a new module, haste by default create some REST convention endpoint for you. 38 | Here, You can add new endpoints or update old endpoints as your need. 39 | 40 | ```json 41 | { 42 | "routes": [ 43 | { 44 | "method": "GET", 45 | "path": "/category", 46 | "controller": "category.find", 47 | "config": { 48 | "middleware": [] 49 | } 50 | }, 51 | { 52 | "method": "GET", 53 | "path": "/category/count", 54 | "controller": "category.count", 55 | "config": { 56 | "middleware": [] 57 | } 58 | }, 59 | { 60 | "method": "GET", 61 | "path": "/category/:id", 62 | "controller": "category.findOne", 63 | "config": { 64 | "middleware": [] 65 | } 66 | }, 67 | { 68 | "method": "POST", 69 | "path": "/category", 70 | "controller": "category.create", 71 | "config": { 72 | "middleware": [] 73 | } 74 | }, 75 | { 76 | "method": "PUT", 77 | "path": "/category/:id", 78 | "controller": "category.update", 79 | "config": { 80 | "middleware": [] 81 | } 82 | }, 83 | { 84 | "method": "DELETE", 85 | "path": "/category/:id", 86 | "controller": "category.delete", 87 | "config": { 88 | "middleware": [] 89 | } 90 | } 91 | ] 92 | } 93 | ``` 94 | ### Controller 95 | Every route passes the request to the defined controller. 96 | Controllers hold the business logic of your module. 97 | Every route must define a controller. Controllers can communicate with the model and return data to the client or Error handlers. 98 | 99 | ```javascript 100 | import {Model as Category} from '../../database/modelMapper.js' 101 | 102 | const controller = { 103 | async count(req, res, next){ 104 | try { 105 | const response = await Category.count({}); 106 | res.json({total: response}); 107 | } catch (err) { 108 | next(err); 109 | } 110 | }, 111 | } 112 | export default controller; 113 | ``` 114 | The above code is responsible for the return count of the Category. 115 | 116 | If you need to add a new function to your controller, you must add it to your routes.json file with the proper structure. 117 | 118 | ### Model 119 | HasteJs uses `sequelize` for managing database operations. For 120 | updating your model you need to update your `model.js` file to structure your table. 121 | For more about sequelize model visit here: https://sequelize.org/master/manual/model-basics.html 122 | 123 | Example model: 124 | ```javascript 125 | import {sequelize} from "../../database/index.js"; 126 | import DataTypes from 'sequelize'; 127 | 128 | export const Model = sequelize.define('Category', { 129 | // Model attributes are defined here 130 | // This are example attributes. please change as you want. 131 | // visit https://sequelize.org/master/manual/model-basics.html for details. 132 | 133 | title: { 134 | type: DataTypes.STRING, 135 | allowNull: false 136 | }, 137 | description: { 138 | type: DataTypes.STRING 139 | // allowNull defaults to true 140 | } 141 | }, { 142 | // Other model options go here 143 | }); 144 | ``` 145 | 146 | ### Relation/Association 147 | You can define all your relation/association here ```database/relation.js``` file. 148 | 149 | Example 150 | 151 | ```javascript 152 | import {Post, Category} from "./modelMapper.js"; 153 | 154 | const relation = ()=> { 155 | Post.belongsTo(Category, {foreignKey: 'category_id'}) 156 | Category.hasMany(Post, { 157 | foreignKey: 'category_id', 158 | sourceKey: 'id' 159 | }) 160 | } 161 | 162 | export default relation(); 163 | ``` 164 | 165 | ### Default Module 166 | When you create a HasteJs project by [create-haste-app](https://www.npmjs.com/package/create-haste-app), A default User module will automatically generate for you in your app directory, So that you can focus on your main modules to develop your app in haste mode. 167 | This default user module provides the following API 168 | 169 | - Login user 170 | - Register user 171 | - Count all users 172 | - Find Me 173 | - Find all users 174 | - Find Specific user. 175 | 176 | 177 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | PORT_NUMBER = 4999 2 | JWT_SECRET = secretssh 3 | DEBUG_MODE = true 4 | 5 | #Database 'sqlite' | 'mysql' | 'mariadb' | 'postgres' | 'mssql' 6 | DB_LOGS = false 7 | DB_CLIENT = sqlite 8 | DB_HOSTNAME = 9 | DB_PORT = 10 | DB_NAME = 11 | DB_USER = 12 | DB_PASSWORD = -------------------------------------------------------------------------------- /haste/constant/general.js: -------------------------------------------------------------------------------- 1 | export const frameworkName = "haste" -------------------------------------------------------------------------------- /haste/export/script.js: -------------------------------------------------------------------------------- 1 | import fse from 'fs-extra' 2 | import path from "path"; 3 | import fs from "fs"; 4 | 5 | const exportedFolder = [ 6 | "app", 7 | "database", 8 | "haste", 9 | "middleware", 10 | "public", 11 | "test", 12 | "views", 13 | ".gitignore", 14 | "config.js", 15 | "env.example", 16 | "package.json", 17 | "Readme.md" 18 | ] 19 | 20 | const currentDir = process.cwd(); 21 | 22 | exportedFolder.forEach((item, index)=> { 23 | const sourceDir = path.join(currentDir, item); 24 | const destinationDir = path.join(currentDir, `cha-export/${item}`); 25 | 26 | fse.copy(sourceDir, destinationDir, (err)=> { 27 | if (err){ 28 | console.log(err) 29 | }else { 30 | if(index === exportedFolder.length - 1){ 31 | const packageJonFilePath = path.join(currentDir, 'cha-export/package.json'); 32 | const packageJsonContent = fs.readFileSync(packageJonFilePath); 33 | let packageJson = JSON.parse(packageJsonContent.toString()); 34 | packageJson.dependencies['hastejs-cli'] = packageJson.version; 35 | packageJson.name = "packageName"; 36 | delete packageJson.description; 37 | delete packageJson.repository; 38 | delete packageJson.bin; 39 | delete packageJson.author; 40 | delete packageJson.license; 41 | 42 | fs.writeFileSync(packageJonFilePath, JSON.stringify(packageJson, null, 2)); 43 | } 44 | 45 | } 46 | }) 47 | }) 48 | 49 | -------------------------------------------------------------------------------- /haste/middleware/middleware.js: -------------------------------------------------------------------------------- 1 | const MIDDLEWARE_NAME = (req,res,next) => { 2 | // Your middleware code write here... 3 | 4 | next() 5 | } 6 | 7 | export default MIDDLEWARE_NAME; -------------------------------------------------------------------------------- /haste/middleware/removeMiddleware.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {frameworkName} from "../constant/general.js"; 4 | import shell from "shelljs"; 5 | import fs from "fs"; 6 | import chalk from "chalk"; 7 | import clear from "clear"; 8 | import figlet from "figlet"; 9 | 10 | //Little style 11 | clear(); 12 | console.log( 13 | chalk.yellow( 14 | figlet.textSync(frameworkName, { horizontalLayout: 'full' }) 15 | ) 16 | ); 17 | 18 | // Get middleware name 19 | let middlewareName = process.argv[2]; 20 | middlewareName = middlewareName.toLowerCase(); 21 | 22 | // isLoggedIn Middleware can not be deleted 23 | if(middlewareName === 'isLoggedIn'){ 24 | console.log(chalk.red('isLoggedIn Middleware can not be removed!')); 25 | process.exit(1); 26 | } 27 | 28 | //Check a Module Exists or Not 29 | let dir = `./middleware/${middlewareName}.js`; 30 | if(!fs.existsSync(dir)){ 31 | console.log(chalk.red('Middleware Not Found!')) 32 | process.exit(1) 33 | } 34 | 35 | const removeMiddleWareFile = ()=> { 36 | shell.rm('-rf', dir) 37 | } 38 | removeMiddleWareFile(); 39 | 40 | 41 | const removeMiddlewareMapper = ()=> { 42 | const file_content = fs.readFileSync('./middleware/middlewareMapper.json'); 43 | 44 | let middlewareMapper = JSON.parse(file_content.toString()); 45 | 46 | // middlewareMapper.import.push(`import ${middlewareName} from `+"'"+`./${middlewareName}.js` +"';") 47 | // middlewareMapper.mapper[middlewareName] = middlewareName 48 | 49 | const index = middlewareMapper.import.indexOf(`import ${middlewareName} from `+"'"+`./${middlewareName}.js` +"';"); 50 | if (index > -1) { 51 | middlewareMapper.import.splice(index, 1); 52 | } 53 | 54 | delete middlewareMapper.mapper[middlewareName] 55 | 56 | let middlewareMapperText = ""; 57 | 58 | middlewareMapper.import.forEach((item)=> { 59 | middlewareMapperText = middlewareMapperText.concat(`${item} \n`) 60 | }) 61 | middlewareMapperText = middlewareMapperText.concat(`\n`) 62 | 63 | let mapperObject = "" 64 | Object.keys(middlewareMapper.mapper).forEach((item)=> { 65 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`) 66 | }) 67 | 68 | let mapperObjectText = "export const MIDDLEWARE_MAPPER = {\n" + 69 | mapperObject + 70 | "}" 71 | middlewareMapperText = middlewareMapperText.concat(mapperObjectText) 72 | 73 | fs.writeFileSync('./middleware/middlewareMapper.js', middlewareMapperText) 74 | fs.writeFileSync('./middleware/middlewareMapper.json', JSON.stringify(middlewareMapper, null, 4)) 75 | console.log(chalk.green("Middleware Removed Successfully!")); 76 | } 77 | 78 | removeMiddlewareMapper(); -------------------------------------------------------------------------------- /haste/middleware/script.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {frameworkName} from "../constant/general.js"; 4 | import {capitalizeFirstLetter, copyFile, isDuplicate} from "../utils/utils.js"; 5 | import shell from "shelljs"; 6 | import fs from "fs"; 7 | import clear from "clear"; 8 | import chalk from "chalk"; 9 | import figlet from "figlet"; 10 | 11 | 12 | //Little style 13 | clear(); 14 | console.log( 15 | chalk.yellow( 16 | figlet.textSync(frameworkName, { horizontalLayout: 'full' }) 17 | ) 18 | ); 19 | 20 | // Get middleware name 21 | let middlewareName = process.argv[2]; 22 | middlewareName = middlewareName.toLowerCase(); 23 | 24 | const copyAndReplaceMiddlewareFile = ()=> { 25 | //copy controller.js file 26 | const sourceDir = `./${frameworkName}/middleware/middleware.js`; 27 | const destinationDir = `./middleware/${middlewareName}.js`; 28 | copyFile(sourceDir, destinationDir, 'Middleware File'); 29 | 30 | //replace with module name 31 | shell.sed('-i', 'MIDDLEWARE_NAME', middlewareName, destinationDir); 32 | } 33 | 34 | const generateMiddlewareMapper = ()=> { 35 | const file_content = fs.readFileSync('./middleware/middlewareMapper.json'); 36 | 37 | let middlewareMapper = JSON.parse(file_content.toString()); 38 | 39 | middlewareMapper.import.push(`import ${middlewareName} from `+"'"+`./${middlewareName}.js` +"';") 40 | middlewareMapper.mapper[middlewareName] = middlewareName 41 | 42 | let middlewareMapperText = ""; 43 | 44 | middlewareMapper.import.forEach((item)=> { 45 | middlewareMapperText = middlewareMapperText.concat(`${item} \n`) 46 | }) 47 | middlewareMapperText = middlewareMapperText.concat(`\n`) 48 | 49 | let mapperObject = "" 50 | Object.keys(middlewareMapper.mapper).forEach((item)=> { 51 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`) 52 | }) 53 | 54 | let mapperObjectText = "export const MIDDLEWARE_MAPPER = {\n" + 55 | mapperObject + 56 | "}" 57 | middlewareMapperText = middlewareMapperText.concat(mapperObjectText) 58 | 59 | fs.writeFileSync('./middleware/middlewareMapper.js', middlewareMapperText) 60 | fs.writeFileSync('./middleware/middlewareMapper.json', JSON.stringify(middlewareMapper, null, 4)) 61 | } 62 | 63 | copyAndReplaceMiddlewareFile(); 64 | generateMiddlewareMapper(); -------------------------------------------------------------------------------- /haste/module/controller/controller.js: -------------------------------------------------------------------------------- 1 | import {UPPER} from '../../database/modelMapper.js' 2 | 3 | const controller = { 4 | async find(req, res, next){ 5 | try { 6 | let { limit, page } = req.query; 7 | if (!page || page < 0) { 8 | page = 0; 9 | }else { 10 | page = page - 1; 11 | } 12 | if (!limit || limit < 0) { 13 | limit = 10; 14 | } 15 | const response = await UPPER.findAll({ 16 | limit: limit, 17 | offset: page * limit 18 | }); 19 | 20 | const total = await UPPER.count(); 21 | let totalPages = total / limit; 22 | totalPages = Math.ceil(totalPages); 23 | res.status(200).json({ totalItems: total, totalPages: totalPages, contents: response }); 24 | } catch (err) { 25 | next(err); 26 | } 27 | }, 28 | async count(req, res, next){ 29 | try { 30 | const response = await UPPER.count({}); 31 | res.json({total: response}); 32 | } catch (err) { 33 | next(err); 34 | } 35 | }, 36 | async findOne(req, res, next){ 37 | try { 38 | const response = await UPPER.findOne({ 39 | where: { 40 | id: req.params.id 41 | } 42 | }); 43 | if(response){ 44 | res.send(response); 45 | }else { 46 | res.status(404).json({message: 'No item found!'}) 47 | } 48 | } catch (err) { 49 | next(err); 50 | } 51 | }, 52 | async create(req, res, next){ 53 | try { 54 | const response = await UPPER.create(req.body); 55 | res.status(201).json(response); 56 | } catch (err) { 57 | next(err); 58 | } 59 | }, 60 | async update(req, res, next){ 61 | try { 62 | const response = await UPPER.update(req.body, { 63 | where: req.params 64 | }); 65 | if(response[0] === 1){ 66 | res.status(200).json({status: 'success', message: 'Item Updated!'}); 67 | }else { 68 | res.status(400).json({message: 'Something went wrong when update the data!'}) 69 | } 70 | 71 | } catch (err) { 72 | next(err); 73 | } 74 | }, 75 | async delete(req, res, next){ 76 | try { 77 | const response = await UPPER.destroy({ 78 | where: req.params 79 | }) 80 | if(response === 1){ 81 | res.status(200).json({status: 'success', message: 'Item Deleted Successfully!'}) 82 | }else { 83 | res.status(404).json({message: 'Item not found!'}) 84 | } 85 | 86 | } catch (err) { 87 | next(err); 88 | } 89 | } 90 | } 91 | export default controller; -------------------------------------------------------------------------------- /haste/module/database/index.js: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | import {copyFile} from "../../utils/utils.js"; 3 | import {frameworkName} from "../../constant/general.js"; 4 | 5 | const initSequelize = ()=> { 6 | } 7 | 8 | initSequelize(); -------------------------------------------------------------------------------- /haste/module/database/model.js: -------------------------------------------------------------------------------- 1 | import {sequelize} from "../../database/index.js"; 2 | import DataTypes from 'sequelize'; 3 | 4 | export const Model = sequelize.define('REPLACE_ME', { 5 | // Model attributes are defined here 6 | // This are example attributes. please change as you want. 7 | // visit https://sequelize.org/master/manual/model-basics.html for details. 8 | 9 | title: { 10 | type: DataTypes.STRING, 11 | allowNull: false 12 | }, 13 | description: { 14 | type: DataTypes.STRING 15 | // allowNull defaults to true 16 | } 17 | }, { 18 | // Other model options go here 19 | }); 20 | -------------------------------------------------------------------------------- /haste/module/module.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'fs'; 4 | import shell from 'shelljs' 5 | import clear from 'clear' 6 | import chalk from "chalk" 7 | import figlet from 'figlet' 8 | import {frameworkName} from "../constant/general.js"; 9 | import {copyFile, isDuplicate} from "../utils/utils.js"; 10 | import {capitalizeFirstLetter} from "../utils/utils.js"; 11 | 12 | //Little style 13 | clear(); 14 | console.log( 15 | chalk.yellow( 16 | figlet.textSync(frameworkName, { horizontalLayout: 'full' }) 17 | ) 18 | ); 19 | 20 | // Get module name 21 | let moduleName = process.argv[2]; 22 | moduleName = moduleName.toLowerCase(); 23 | 24 | //CREATE A NEW DIRECTORY DEFINE FROM ARGS. 25 | let dir = `./app/${moduleName}`; 26 | if(!fs.existsSync(dir)){ 27 | fs.mkdirSync(dir); 28 | }else { 29 | console.log(chalk.red('Module already exist! Please create a new module or update existing one from app directory.')) 30 | process.exit(1) 31 | } 32 | 33 | const copyAndReplaceRouteFile = ()=> { 34 | //copy routes.json file 35 | const sourceDir = `./${frameworkName}/module/routes.json`; 36 | const destinationDir = `./app/${moduleName}/routes.json`; 37 | copyFile(sourceDir, destinationDir, 'Routes File'); 38 | 39 | //replace with module name 40 | shell.sed('-i', 'REPLACE_ME', moduleName, destinationDir); 41 | } 42 | 43 | copyAndReplaceRouteFile(); 44 | 45 | const copyAndReplaceControllerFile = ()=> { 46 | //copy controller.js file 47 | const controllerSourceDir = `./${frameworkName}/module/controller/controller.js`; 48 | const controllerDestinationDir = `./app/${moduleName}/controller.js`; 49 | copyFile(controllerSourceDir, controllerDestinationDir, 'Controllers File'); 50 | 51 | //replace with module name 52 | shell.sed('-i', 'REPLACE_ME', moduleName, controllerDestinationDir); 53 | shell.sed('-i', 'UPPER', capitalizeFirstLetter(moduleName), controllerDestinationDir); 54 | } 55 | 56 | copyAndReplaceControllerFile(); 57 | 58 | 59 | const generateControllerMapper = ()=> { 60 | const file_content = fs.readFileSync('./app/controllerMapper.json'); 61 | 62 | let controllerMapper = JSON.parse(file_content.toString()); 63 | 64 | controllerMapper.import.push(`import ${moduleName} from `+"'"+`./${moduleName}/controller.js` +"';") 65 | controllerMapper.mapper[moduleName] = moduleName 66 | 67 | let controllerMapperText = ""; 68 | 69 | controllerMapper.import.forEach((item)=> { 70 | controllerMapperText = controllerMapperText.concat(`${item} \n`) 71 | }) 72 | controllerMapperText = controllerMapperText.concat(`\n`) 73 | 74 | let mapperObject = "" 75 | Object.keys(controllerMapper.mapper).forEach((item)=> { 76 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`) 77 | }) 78 | 79 | let mapperObjectText = "export const CONTROLLER_MAPPER = {\n" + 80 | mapperObject + 81 | "}" 82 | controllerMapperText = controllerMapperText.concat(mapperObjectText) 83 | 84 | fs.writeFileSync('./app/controllerMapper.js', controllerMapperText) 85 | fs.writeFileSync('./app/controllerMapper.json', JSON.stringify(controllerMapper, null, 4)) 86 | } 87 | 88 | generateControllerMapper(); 89 | 90 | 91 | const copyAndReplaceModelFile = ()=> { 92 | //copy model.js file 93 | const modelSourceDir = `./${frameworkName}/module/database/model.js`; 94 | const modelDestinationDir = `./app/${moduleName}/model.js`; 95 | copyFile(modelSourceDir, modelDestinationDir, 'Model Files'); 96 | 97 | //replace model.js with module name 98 | shell.sed('-i', 'REPLACE_ME', capitalizeFirstLetter(moduleName), modelDestinationDir); 99 | } 100 | 101 | copyAndReplaceModelFile(); 102 | 103 | 104 | const generateModelMapper = ()=> { 105 | const file_content = fs.readFileSync('./database/modelMapper.json'); 106 | 107 | let modelMapper = JSON.parse(file_content.toString()); 108 | 109 | modelMapper.import.push(`import {Model as ${capitalizeFirstLetter(moduleName)}} from `+"'"+`../app/${moduleName}/model.js` +"';"); 110 | modelMapper.export.push(capitalizeFirstLetter(moduleName)); 111 | 112 | // move the relation import to the last position. 113 | modelMapper.import.push(modelMapper.import.splice(modelMapper.import.indexOf(`import relation from './relation.js';`), 1)[0]); 114 | 115 | let modelMapperText = ""; 116 | 117 | modelMapper.import.forEach((item)=> { 118 | modelMapperText = modelMapperText.concat(`${item} \n`) 119 | }) 120 | modelMapperText = modelMapperText.concat(`\n`) 121 | 122 | let exportObject = "" 123 | modelMapper.export.forEach((item)=> { 124 | exportObject = exportObject.concat(`${item},`) 125 | }) 126 | 127 | let exportObjectText = "export {" + exportObject +"}" 128 | modelMapperText = modelMapperText.concat(exportObjectText) 129 | 130 | fs.writeFileSync('./database/modelMapper.js', modelMapperText) 131 | fs.writeFileSync('./database/modelMapper.json', JSON.stringify(modelMapper, null, 4)) 132 | } 133 | 134 | 135 | generateModelMapper() 136 | 137 | -------------------------------------------------------------------------------- /haste/module/removeModule.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'fs'; 4 | import shell from 'shelljs' 5 | import clear from 'clear' 6 | import chalk from "chalk" 7 | import figlet from 'figlet' 8 | import {frameworkName} from "../constant/general.js"; 9 | import {capitalizeFirstLetter} from "../utils/utils.js"; 10 | 11 | //Little style 12 | clear(); 13 | console.log( 14 | chalk.yellow( 15 | figlet.textSync(frameworkName, { horizontalLayout: 'full' }) 16 | ) 17 | ); 18 | 19 | // Get module name 20 | let moduleName = process.argv[2]; 21 | moduleName = moduleName.toLowerCase(); 22 | 23 | // User module can not be deleted 24 | if(moduleName === 'user'){ 25 | console.log(chalk.red('User Module can not be removed!')); 26 | process.exit(1); 27 | } 28 | 29 | //Check a Module Exists or Not 30 | let dir = `./app/${moduleName}`; 31 | if(!fs.existsSync(dir)){ 32 | console.log(chalk.red('Module Not Found!')) 33 | process.exit(1) 34 | } 35 | 36 | const removeDirectory = ()=> { 37 | shell.rm('-rf', dir) 38 | } 39 | removeDirectory(); 40 | 41 | const removeControllerMapper = ()=> { 42 | const file_content = fs.readFileSync('./app/controllerMapper.json'); 43 | 44 | let controllerMapper = JSON.parse(file_content.toString()); 45 | 46 | const index = controllerMapper.import.indexOf(`import ${moduleName} from `+"'"+`./${moduleName}/controller.js` +"';"); 47 | if (index > -1) { 48 | controllerMapper.import.splice(index, 1); 49 | } 50 | 51 | delete controllerMapper.mapper[moduleName] 52 | 53 | let controllerMapperText = ""; 54 | 55 | controllerMapper.import.forEach((item)=> { 56 | controllerMapperText = controllerMapperText.concat(`${item} \n`) 57 | }) 58 | controllerMapperText = controllerMapperText.concat(`\n`) 59 | 60 | let mapperObject = "" 61 | Object.keys(controllerMapper.mapper).forEach((item)=> { 62 | mapperObject = mapperObject.concat(` ${item}: ${item}, \n`) 63 | }) 64 | 65 | let mapperObjectText = "export const CONTROLLER_MAPPER = {\n" + 66 | mapperObject + 67 | "}" 68 | controllerMapperText = controllerMapperText.concat(mapperObjectText) 69 | 70 | fs.writeFileSync('./app/controllerMapper.js', controllerMapperText) 71 | fs.writeFileSync('./app/controllerMapper.json', JSON.stringify(controllerMapper, null, 4)) 72 | } 73 | 74 | removeControllerMapper(); 75 | 76 | const removeModelMapper = ()=> { 77 | const file_content = fs.readFileSync('./database/modelMapper.json'); 78 | 79 | let modelMapper = JSON.parse(file_content.toString()); 80 | 81 | const index = modelMapper.import.indexOf(`import {Model as ${capitalizeFirstLetter(moduleName)}} from `+"'"+`../app/${moduleName}/model.js` +"';"); 82 | if (index > -1) { 83 | modelMapper.import.splice(index, 1); 84 | } 85 | 86 | const objectIndex = modelMapper.export.indexOf(capitalizeFirstLetter(moduleName)); 87 | if (objectIndex > -1) { 88 | modelMapper.export.splice(objectIndex, 1); 89 | } 90 | 91 | // move the relation import to the last position. 92 | modelMapper.import.push(modelMapper.import.splice(modelMapper.import.indexOf(`import relation from './relation.js';`), 1)[0]); 93 | 94 | let modelMapperText = ""; 95 | 96 | modelMapper.import.forEach((item)=> { 97 | modelMapperText = modelMapperText.concat(`${item} \n`) 98 | }) 99 | modelMapperText = modelMapperText.concat(`\n`) 100 | 101 | let exportObject = "" 102 | modelMapper.export.forEach((item)=> { 103 | exportObject = exportObject.concat(`${item},`) 104 | }) 105 | 106 | let exportObjectText = "export {" + exportObject +"}" 107 | modelMapperText = modelMapperText.concat(exportObjectText) 108 | 109 | fs.writeFileSync('./database/modelMapper.js', modelMapperText) 110 | fs.writeFileSync('./database/modelMapper.json', JSON.stringify(modelMapper, null, 4)) 111 | console.log(chalk.green("Module Removed Successfully!")); 112 | } 113 | 114 | removeModelMapper(); 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /haste/module/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/REPLACE_ME", 6 | "controller": "REPLACE_ME.find", 7 | "config": { 8 | "middleware": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/REPLACE_ME/count", 14 | "controller": "REPLACE_ME.count", 15 | "config": { 16 | "middleware": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/REPLACE_ME/:id", 22 | "controller": "REPLACE_ME.findOne", 23 | "config": { 24 | "middleware": [] 25 | } 26 | }, 27 | { 28 | "method": "POST", 29 | "path": "/REPLACE_ME", 30 | "controller": "REPLACE_ME.create", 31 | "config": { 32 | "middleware": [] 33 | } 34 | }, 35 | { 36 | "method": "PUT", 37 | "path": "/REPLACE_ME/:id", 38 | "controller": "REPLACE_ME.update", 39 | "config": { 40 | "middleware": [] 41 | } 42 | }, 43 | { 44 | "method": "DELETE", 45 | "path": "/REPLACE_ME/:id", 46 | "controller": "REPLACE_ME.delete", 47 | "config": { 48 | "middleware": [] 49 | } 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /haste/utils/utils.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import shell from "shelljs"; 3 | import chalk from "chalk"; 4 | 5 | export const copyFile = (sourceDir, desDir, FileName = 'Files')=> { 6 | fs.copyFileSync(sourceDir, desDir); 7 | console.log(chalk.green(`${FileName} generated successfully!`)); 8 | } 9 | 10 | export const readContent = (file) => { 11 | return fs.readFileSync(file); 12 | } 13 | 14 | export const combinedRoutes = ()=> { 15 | let result=[]; 16 | shell.cd('app'); 17 | shell.ls('./*/routes.json').forEach((file)=> { 18 | let data = readContent(file); 19 | result.push(...JSON.parse(data.toString()).routes) 20 | }); 21 | shell.cd('..'); 22 | return result; 23 | } 24 | 25 | export const capitalizeFirstLetter = (string) => { 26 | return string.charAt(0).toUpperCase() + string.slice(1); 27 | } 28 | 29 | export const isDuplicate = (file, string) => { 30 | const data = fs.readFileSync(file); 31 | return data.includes(string); 32 | 33 | } 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /middleware/isLoggedIn.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import {jwtSecretKey} from "../config.js"; 3 | 4 | const isLoggedIn = (req,res,next) => { 5 | if(!req.get('Authorization')){ 6 | const error = new Error("Unauthorized User!"); 7 | error.statusCode = 401; 8 | throw error; 9 | } 10 | let token = req.get('Authorization').split(" ")[1]; 11 | let decodedToken; 12 | try{ 13 | decodedToken = jwt.verify(token, process.env.JWT_SECRET || jwtSecretKey); 14 | if(!decodedToken){ 15 | const error = new Error("Unauthorized User!") 16 | error.statusCode = 403; 17 | throw error; 18 | } 19 | req.user_id = decodedToken.user_id 20 | req.user_email = decodedToken.email 21 | next() 22 | }catch (e) { 23 | const error = new Error("Unauthorized User!") 24 | error.statusCode = 403; 25 | next(error) 26 | } 27 | } 28 | 29 | export default isLoggedIn; -------------------------------------------------------------------------------- /middleware/middlewareMapper.js: -------------------------------------------------------------------------------- 1 | import isLoggedIn from './isLoggedIn.js'; 2 | 3 | export const MIDDLEWARE_MAPPER = { 4 | isLoggedIn: isLoggedIn, 5 | } -------------------------------------------------------------------------------- /middleware/middlewareMapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "import": [ 3 | "import isLoggedIn from './isLoggedIn.js';" 4 | ], 5 | "mapper": { 6 | "isLoggedIn": "isLoggedIn" 7 | } 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hastejs-cli", 3 | "version": "0.0.9", 4 | "description": "A nodejs Framework for develop API in record time.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/sultanfendonus/HasteJS" 8 | }, 9 | "main": "app/index.js", 10 | "type": "module", 11 | "scripts": { 12 | "start": "nodemon app/index.js", 13 | "develop": "nodemon app/index.js", 14 | "deploy": "pm2 start app/index.js", 15 | "test": "mocha ./test", 16 | "sequelize": "sequelize", 17 | "db:sync": "node database/sync.js", 18 | "export": "node haste/export/script.js", 19 | "create-module": "node haste/module/module.js", 20 | "remove-module": "node haste/module/removeModule.js", 21 | "create-middleware": "node haste/middleware/script.js", 22 | "remove-middleware": "node haste/middleware/removeMiddleware.js" 23 | }, 24 | "bin": { 25 | "haste": "./bin/index.js", 26 | "create-module": "./haste/module/module.js", 27 | "remove-module": "./haste/module/removeModule.js", 28 | "create-middleware": "./haste/middleware/script.js", 29 | "remove-middleware": "./haste/middleware/removeMiddleware.js" 30 | }, 31 | "author": "SULTAN MAHAMUD", 32 | "license": "MIT", 33 | "dependencies": { 34 | "bcryptjs": "^2.4.3", 35 | "body-parser": "1.19.0", 36 | "chai": "^4.2.0", 37 | "chai-http": "^4.3.0", 38 | "chalk": "^4.1.0", 39 | "clear": "^0.1.0", 40 | "cors": "2.8.5", 41 | "dotenv": "^8.2.0", 42 | "ejs": "^3.1.5", 43 | "express": "^4.17.1", 44 | "faker": "^5.1.0", 45 | "figlet": "^1.5.0", 46 | "fs-extra": "^9.0.1", 47 | "hastejs-cli": "^0.0.6", 48 | "jsonwebtoken": "^8.5.1", 49 | "mariadb": "^2.5.2", 50 | "mocha": "^8.2.1", 51 | "mysql2": "^2.2.5", 52 | "nodemon": "^2.0.6", 53 | "pg": "^8.5.1", 54 | "pg-hstore": "^2.3.3", 55 | "sequelize": "^6.3.5", 56 | "shelljs": "^0.8.4", 57 | "sqlite3": "^5.0.0", 58 | "yargs": "^16.2.0" 59 | }, 60 | "devDependencies": { 61 | "cross-env": "^7.0.2", 62 | "sequelize-cli": "^6.2.0" 63 | }, 64 | "keywords": [ 65 | "hasteJs", 66 | "framework", 67 | "web", 68 | "rest", 69 | "api", 70 | "restful", 71 | "express", 72 | "hastejs-cli" 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /public/roket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sultanfendonus/HasteJS/5e32d348c706696192506eea61536517bc8d8eec/public/roket.png -------------------------------------------------------------------------------- /test/mapper.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import chaiHttp from 'chai-http'; 3 | import fs from "fs"; 4 | 5 | chai.should(); 6 | chai.use(chaiHttp) 7 | 8 | let expect = chai.expect; 9 | let assert = chai.assert; 10 | 11 | describe("Check Existing of Mapper Files", ()=> { 12 | describe("Check existing of ControllerMapper",()=> { 13 | it('should check existing of controllerMapper.js and controllerMapper.json', function (done) { 14 | const controllerMapperJs = fs.existsSync('./app/controllerMapper.js'); 15 | const controllerMapperJson = fs.existsSync('./app/controllerMapper.json'); 16 | assert.equal(controllerMapperJs && controllerMapperJson, true) 17 | done() 18 | }); 19 | }) 20 | 21 | describe("Check existing of MiddlewareMapper",()=> { 22 | it('should check existing of middlewareMapper.js and middlewareMapper.json', function (done) { 23 | const middlewareMapperJs = fs.existsSync('./middleware/middlewareMapper.js'); 24 | const middlewareMapperJson = fs.existsSync('./middleware/middlewareMapper.json'); 25 | assert.equal(middlewareMapperJs && middlewareMapperJson, true) 26 | done() 27 | }); 28 | }) 29 | }) -------------------------------------------------------------------------------- /test/user.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import chaiHttp from 'chai-http'; 3 | import {server} from '../app/index.js' 4 | import fs from "fs"; 5 | import faker from 'faker'; 6 | 7 | chai.should(); 8 | chai.use(chaiHttp) 9 | 10 | let expect = chai.expect; 11 | let assert = chai.assert; 12 | 13 | describe('User API', ()=> { 14 | describe("User Module found", ()=> { 15 | it("should find the User module and the child files", (done)=> { 16 | const route = fs.existsSync('./app/user/routes.json'); 17 | const controller = fs.existsSync('./app/user/controller.js'); 18 | const model = fs.existsSync('./app/user/model.js'); 19 | assert.equal(route && controller && model, true) 20 | done() 21 | }); 22 | }) 23 | describe("User API", ()=> { 24 | describe("GET /user/", ()=> { 25 | it("should return 401 if I do not put Authorization token", (done)=>{ 26 | chai.request(server) 27 | .get("/user/") 28 | .end((err, response)=> { 29 | response.should.have.status(401) 30 | done(); 31 | }) 32 | }) 33 | 34 | it("should return 403 if I put invalid token", (done)=>{ 35 | chai.request(server) 36 | .get("/user/") 37 | .set("Authorization", 'dummy token+dd') 38 | .end((err, response)=> { 39 | response.should.have.status(403) 40 | done(); 41 | }) 42 | }) 43 | 44 | it("should return array list of users", (done)=> { 45 | chai.request(server) 46 | .get("/user/") 47 | .set('Authorization', 'token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InN1bHRhbjE2NTJAZ21haWwuY29tIiwidXNlcl9pZCI6MTMsImlhdCI6MTYwNjc5NzU3Nn0.jgI54Nvo0se7v40QqAPFT-fe8FSnkUnUErfW0u6kmXs') 48 | .end((err, response)=> { 49 | response.should.have.status(200); 50 | response.body.should.be.a('array'); 51 | done(); 52 | }) 53 | }) 54 | }) 55 | 56 | // describe("User Authentication, POST /user/register", ()=> { 57 | // let fakeEmail = faker.internet.email(); 58 | // it("should able to register a random generated user", (done)=>{ 59 | // chai.request(server) 60 | // .post("/user/register/") 61 | // .send({ 62 | // first_name: faker.name.firstName(), 63 | // last_name: faker.name.lastName(), 64 | // email: fakeEmail, 65 | // password: "123456" 66 | // }) 67 | // .end((err, response)=> { 68 | // response.should.have.status(201); 69 | // response.body.should.have.property('token'); 70 | // done(); 71 | // }) 72 | // }) 73 | // 74 | // it("It should able to be login", (done)=>{ 75 | // chai.request(server) 76 | // .post('/user/login/') 77 | // .send({ 78 | // email: fakeEmail, 79 | // password: '123456' 80 | // }) 81 | // .end((err, response)=> { 82 | // response.should.have.status(200) 83 | // done(); 84 | // }) 85 | // }) 86 | // }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /views/api-docs.ejs: -------------------------------------------------------------------------------- 1 | <%- include('includes/header.ejs')%> 2 | 3 | 4 | 5 |
6 |
7 |
8 | <%- include('includes/navbar.ejs')%> 9 |
10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <% for(var i=0; i < api.length; i++) { %> 25 | 26 | 27 | <% if(api[i].method === 'DELETE') { %> 28 | 29 | <% }else if(api[i].method === 'POST') {%> 30 | 31 | <% }else if(api[i].method === 'PUT') {%> 32 | 33 | <% }else {%> 34 | 35 | <% } %> 36 | 37 | 38 | 39 | 40 | 41 | <% } %> 42 | 43 |
#MethodEndpointUrlMiddleware
<%= i+1%><%= api[i].path %>http://127.0.0.1:<%= port %><%= api[i].path %><%= api[i].config.middleware %>
44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /views/homepage.ejs: -------------------------------------------------------------------------------- 1 | <%- include('includes/header.ejs')%> 2 | 3 | 4 | 5 |
6 |
7 |
8 | <%- include('includes/navbar.ejs')%> 9 |
10 |
11 |
12 |
13 | API Documentation 14 | 15 |

HasteJs Install worked successfully! Congratulations!!

16 |

Now create some module and secure them by middleware.

17 |
18 |
19 |
20 |
21 | HasteJs Documentation 22 |
23 |
24 |
HasteJS - A nodejs framework.
25 |

HasteJS is a NodeJs framework, built on top of ExpressJS, 26 | released as free and open-source software under the MIT Licence. 27 | It is particularly designed to develop a quick Restful API in record time.

28 | Visit Documentation 29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /views/includes/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= pageTitle%> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /views/includes/navbar.ejs: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------