├── .editorconfig ├── .env.example ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── Untitled-1 ├── ace ├── app ├── Controllers │ ├── Http │ │ ├── AuthController.js │ │ ├── BaseController.js │ │ ├── PostController.js │ │ └── UserController.js │ └── Ws │ │ └── BaseWController.js ├── Exceptions │ ├── Handler.js │ ├── InvalidFilterJsonException.js │ └── InvalidSearchQueryException.js ├── Helpers │ └── FilterParser.js ├── Middleware │ └── ConvertEmptyStringsToNull.js ├── Models │ ├── Hooks │ │ └── User.js │ ├── Post.js │ ├── Token.js │ ├── Traits │ │ └── NoTimestamp.js │ └── User.js └── Validators │ ├── AuthLogin.js │ ├── AuthRegister.js │ └── User.js ├── config ├── app.js ├── auth.js ├── bodyParser.js ├── cors.js ├── database.js └── hash.js ├── database ├── adonis.sqlite ├── factory.js └── migrations │ ├── 1503250034279_user.js │ ├── 1503250034280_token.js │ └── 1565517995551_post_schema.js ├── package-lock.json ├── package.json ├── server.js └── start ├── app.js ├── kernel.js └── routes.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | HOST=127.0.0.1 2 | PORT=3333 3 | NODE_ENV=development 4 | 5 | APP_NAME=AdonisJs 6 | APP_URL=http://${HOST}:${PORT} 7 | 8 | CACHE_VIEWS=false 9 | 10 | APP_KEY= 11 | 12 | DB_CONNECTION=sqlite 13 | DB_HOST=127.0.0.1 14 | DB_PORT=3306 15 | DB_USER=root 16 | DB_PASSWORD= 17 | DB_DATABASE=adonis 18 | 19 | HASH_DRIVER=bcrypt 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules 3 | 4 | # Adonis directory for storing tmp files 5 | tmp 6 | 7 | # Environment variables, never commit this file 8 | .env 9 | 10 | # The development sqlite file 11 | database/development.sqlite 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hypermine.in 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basestack for Adonis framework. 2 | 3 | A basestack where lot of features will be handelled outof the box. 4 | So even if you don't know adonis and quickly want resource endpoints, you can use this template. 5 | 6 | # Adonis API application 7 | 8 | This is the boilerplate for creating an API server in AdonisJs, it comes pre-configured with. 9 | 10 | 1. Bodyparser 11 | 2. Authentication 12 | 3. CORS 13 | 4. Lucid ORM 14 | 5. Migrations and seeds 15 | 5. Pagination 16 | 5. Resource 17 | 18 | 19 | ## Setup 20 | Install `Adonis` 21 | 22 | ```bash 23 | npm i -g @adonisjs/cli 24 | ``` 25 | 26 | Use the adonis command to install the blueprint 27 | 28 | ```bash 29 | git clone https://github.com/hypermine-bc/adonis-rest-template.git 30 | ``` 31 | 32 | clone the repo and then run `npm install`. 33 | 34 | ```bash 35 | adonis serve --dev 36 | ``` 37 | 38 | 39 | ### Migrations 40 | 41 | Run the following command to run startup migrations. 42 | 43 | ```js 44 | adonis migration:run 45 | ``` 46 | 47 | ### Examples (for lazy developers) 48 | 49 | #### Example User Controller 50 | 51 | We are lazy developers Right! :stuck_out_tongue_winking_eye: 52 | 53 | So lets make the controller as lean as possible :wink: 54 | 55 | Pay attention to these variables, that are defined 56 | 57 | allowedWiths 58 | updatable, 59 | searchable 60 | 61 | ``` 62 | 'use strict' 63 | const BaseController = use('App/Controllers/Http/BaseController') 64 | const UserValidations = use('App/Validators/AuthRegister') 65 | const User = use('App/Models/User'); 66 | 67 | class UserController extends BaseController { 68 | 69 | constructor() { 70 | 71 | super(); 72 | 73 | this.model = User 74 | 75 | this.allowedWiths = [ 76 | 'posts', 77 | 'tokens' 78 | ] 79 | 80 | this.updatable = [ 81 | 'username', 82 | 'email', 83 | 'password' 84 | ] 85 | 86 | this.searchable = [ 87 | 'username', 88 | 'email', 89 | 'id' 90 | ] 91 | 92 | this.getValidators = function () { 93 | return new UserValidations() 94 | // return validations.rules 95 | } 96 | } 97 | } 98 | 99 | module.exports = UserController 100 | 101 | ``` 102 | 103 | #### Search 104 | 105 | Yes! you got it right search is out of the box for every resource that extends baseController. 106 | 107 | Ex 1 : Search for user contains vik 108 | 109 | ``` 110 | {{HYPERMINE_API_HOST}}/api/v1/users/?query=vik 111 | ``` 112 | Ex 2 : Search for user contains Vik with Posts and token 113 | ``` 114 | {{HYPERMINE_API_HOST}}/api/v1/users/?query=vik&attributes=posts,tokens 115 | ``` 116 | Look how easy it was right :wink: 117 | 118 | 119 | #### Attributes 120 | Lets say you want to get User with Posts and tokens 121 | 122 | You can do request API like 123 | 124 | ``` 125 | {{HYPERMINE_API_HOST}}/api/v1/users/?attributes=posts,tokens 126 | ``` 127 | 128 | ### Filters 129 | Since we are very lazy and generally fitrers are complicated so. 130 | This template add's it out-of-the-box. 131 | 132 | Lets say you want to get users where username=vikram 133 | 134 | ``` 135 | {{HYPERMINE_API_HOST}}/api/v1/users/?filters=[{"username": "vikram"}] 136 | ``` 137 | 138 | Or perhaps you want to get vikram whoes id = 1 139 | 140 | ``` 141 | {{HYPERMINE_API_HOST}}/api/v1/users/?filters=[{"username":"vikram"},{"id": 1}] 142 | ``` 143 | 144 | ### Attribute + Filters 145 | Lets get one step further, lets say you want to get 146 | Get user starts with `vi` and id=1 with posts and token 147 | 148 | ``` 149 | {{HYPERMINE_API_HOST}}/api/v1/users/?attributes=posts,tokens&filters=[{"username": [{"startsWith": "vi"}]},{"id": 1}] 150 | ``` 151 | Some more ;) 152 | 153 | ``` 154 | {{HYPERMINE_API_HOST}}/api/v1/users/?attributes=posts,tokens&filters=[{"username": [{"endsWith": "ide"}]}] 155 | ``` 156 | #### Logging 157 | 158 | Finally we have logging Out-Of-The_box 159 | 160 | As explained in Adonis framework the access log you can fine in your 161 | tmp/adonis.log file. 162 | 163 | Ex: Loggin an update method 164 | 165 | ``` 166 | {"userid":2,"controller":"UserController","method":"update()","data":{},"time":"Mon Aug 19 2019 01:14:09 GMT+0530 (IST)","level":"info","message":"Resource-id : 5"} 167 | ``` -------------------------------------------------------------------------------- /Untitled-1: -------------------------------------------------------------------------------- 1 | this.addWheres = function ($query){ 2 | let filters = this.request.input('filters', '').split(",") 3 | 4 | $relations.forEach((relation, index)=>{ $query.with(relation)}) 5 | 6 | filters.forEach(($value, $key)=>{ 7 | //If non equality operator 8 | if ( Array.isArray($value)) { 9 | $value.foreach($valueToOperate, $operator) { 10 | switch ($operator) { 11 | case 'lte': 12 | $query = $query -> where($key, '<=', $valueToOperate); 13 | break; 14 | case 'lt': 15 | $query = $query -> where($key, '<', $valueToOperate); 16 | break; 17 | case 'gte': 18 | $query = $query -> where($key, '>=', $valueToOperate); 19 | break; 20 | case 'gt': 21 | $query = $query -> where($key, '>', $valueToOperate); 22 | break; 23 | case 'startsWith': 24 | $query = $query -> where($key, 'like', "{$valueToOperate}%"); 25 | break; 26 | case 'endsWith': 27 | $query = $query -> where($key, 'like', "%{$valueToOperate}"); 28 | break; 29 | case 'between': 30 | if (is_array($valueToOperate)) 31 | $query = $query -> whereBetween($key, $valueToOperate); 32 | break; 33 | case 'in': 34 | if (is_array($valueToOperate)) 35 | $query = $query -> whereIn($key, $valueToOperate); 36 | break; 37 | } 38 | } 39 | } else { 40 | $query = $query -> where($key, $value); 41 | } 42 | }) 43 | 44 | return $query 45 | } -------------------------------------------------------------------------------- /ace: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Ace Commands 6 | |-------------------------------------------------------------------------- 7 | | 8 | | The ace file is just a regular Javascript file but with no extension. You 9 | | can call `node ace` followed by the command name and it just works. 10 | | 11 | | Also you can use `adonis` followed by the command name, since the adonis 12 | | global proxies all the ace commands. 13 | | 14 | */ 15 | 16 | const { Ignitor } = require('@adonisjs/ignitor') 17 | 18 | new Ignitor(require('@adonisjs/fold')) 19 | .appRoot(__dirname) 20 | .fireAce() 21 | .catch(console.error) 22 | -------------------------------------------------------------------------------- /app/Controllers/Http/AuthController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const User = use('App/Models/User'); 3 | 4 | const cloneAndPluck = function (sourceObject, keys) { 5 | const newObject = {}; 6 | keys.forEach((obj, key) => { newObject[obj] = sourceObject[obj]; }); 7 | return newObject; 8 | }; 9 | 10 | class AuthController { 11 | 12 | constructor(){ 13 | /** 14 | * Allowed properties from user obejct, 15 | * to see the outside world 16 | */ 17 | this.allowed = ['username', 'id','token','email'] 18 | } 19 | /** 20 | * Register a user 21 | * @param {request} param0 22 | */ 23 | async register({ request, auth, response }) { 24 | 25 | let user = await User.create(request.all()) 26 | 27 | //generate token for user; 28 | let token = await auth.generate(user) 29 | 30 | Object.assign(user, token) 31 | 32 | let res = cloneAndPluck(user, this.allowed); 33 | 34 | return response.json(res) 35 | } 36 | 37 | /** 38 | * Login by passing 39 | * Useremail and Password 40 | * @param {request} param0 41 | */ 42 | async login({ request, auth, response }) { 43 | 44 | let { email, password } = request.all(); 45 | 46 | try { 47 | if (await auth.attempt(email, password)) { 48 | let user = await User.findBy('email', email) 49 | let token = await auth.generate(user) 50 | 51 | Object.assign(user, token) 52 | // console.log(this.allowed);die; 53 | let res = cloneAndPluck(user, this.allowed); 54 | return response.json(res) 55 | } 56 | 57 | 58 | } 59 | catch (e) { 60 | console.log(e) 61 | return response.json({ message: 'You are not registered!' }) 62 | } 63 | } 64 | } 65 | 66 | module.exports = AuthController 67 | -------------------------------------------------------------------------------- /app/Controllers/Http/BaseController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @typedef {import('@adonisjs/framework/src/Request')} Request */ 4 | /** @typedef {import('@adonisjs/framework/src/Response')} Response */ 5 | /** @typedef {import('@adonisjs/framework/src/View')} View */ 6 | 7 | const InvalidFilterJsonException = use('App/Exceptions/InvalidFilterJsonException') 8 | const InvalidSearchQueryException = use('App/Exceptions/InvalidSearchQueryException') 9 | const FilterParser = use('App/Helpers/FilterParser') 10 | const { validate } = use('Validator') 11 | const Logger = use('Logger') 12 | 13 | /** 14 | * Resourceful controller for interacting with basehttps 15 | */ 16 | class BaseController { 17 | 18 | constructor(){ 19 | 20 | this.model 21 | this.allowedWiths 22 | this.request 23 | this.response 24 | this.updatable 25 | this.searchable 26 | this.logData = { 27 | userid:"", 28 | controller:"", 29 | method:"", 30 | data:{} 31 | } 32 | 33 | this.accessLog = function (auth, message, method, ControllerName, data={}) { 34 | var d = Date(Date.now()); 35 | this.logData.userid = auth.user.id 36 | this.logData.method = method 37 | this.logData.controller = ControllerName 38 | this.logData.data = data 39 | this.logData.time = d.toString() 40 | 41 | Logger 42 | .transport('file') 43 | .info(message,this.logData) 44 | } 45 | 46 | this.addWheres = function ($query) { 47 | let req = this.request.input("filters", "[]") 48 | let filters = []; 49 | try{ 50 | 51 | filters = JSON.parse(req) 52 | 53 | } catch (error) { 54 | 55 | throw new InvalidFilterJsonException() 56 | 57 | } 58 | 59 | let filter = new FilterParser() 60 | 61 | return filter.parse($query, filters) 62 | } 63 | 64 | this.addSearchables = function ($query) { 65 | let req = this.request.input("query", "") 66 | 67 | try{ 68 | if(req) 69 | { 70 | this.searchable.forEach(columnName => { 71 | $query.orWhere(columnName, 'like', '%' + req + '%') 72 | }) 73 | } 74 | } catch (error) { 75 | 76 | throw new InvalidSearchQueryException() 77 | 78 | } 79 | 80 | return $query 81 | } 82 | 83 | this.addWiths = function ($query,$relations){ 84 | $relations.forEach(relation=>{ $query.with(relation)}) 85 | return $query 86 | } 87 | 88 | this.getwiths = function () { 89 | let req = this.request.input('attributes', '').split(",") 90 | let res = req.filter(value => -1 !== this.allowedWiths.indexOf(value)) 91 | return res 92 | } 93 | 94 | this.getValidator = function (){ 95 | } 96 | 97 | this.preCreate = function (data) { 98 | 99 | return data 100 | 101 | } 102 | this.postCreate = function (data) {} 103 | 104 | this.preUpdate = function (data) { 105 | 106 | return data 107 | 108 | } 109 | this.postUpdate = function (data) {} 110 | } 111 | 112 | 113 | /** 114 | * Show a list of all basehttps. 115 | * GET basehttps 116 | * 117 | * @param {object} ctx 118 | * @param {Request} ctx.request 119 | * @param {Response} ctx.response 120 | * @param {View} ctx.view 121 | */ 122 | async index({ request, response, auth }) { 123 | this.request = request 124 | this.response = response 125 | 126 | const page = this.request.input("page", 1) 127 | 128 | let query = this.model.query() 129 | query = this.addWiths(query, this.getwiths()) 130 | query = this.addWheres(query) 131 | query = this.addSearchables(query) 132 | 133 | let res = await query.paginate(page) 134 | 135 | this.accessLog(auth, "", "index()", this.constructor.name) 136 | 137 | return response.json(res) 138 | 139 | } 140 | 141 | /** 142 | * Create/save a new basehttp. 143 | * POST basehttps 144 | * 145 | * @param {object} ctx 146 | * @param {Request} ctx.request 147 | * @param {Response} ctx.response 148 | */ 149 | async store({ request, response, auth }) { 150 | 151 | let rules = this.getValidators().rules; 152 | let messages = this.getValidators().messages; 153 | 154 | const validation = await validate(request.all(), rules, messages) 155 | 156 | if (validation.fails()) { 157 | return response.status(422).json(validation.messages()) 158 | } 159 | 160 | let dataToCreate = request.only(Object.keys(rules)); 161 | 162 | dataToCreate = this.preCreate(dataToCreate); 163 | 164 | let data = await this.model.create(dataToCreate); 165 | 166 | this.postCreate(data) 167 | 168 | this.accessLog(auth, "", "store()", this.constructor.name) 169 | 170 | return response.status(201).json(data) 171 | 172 | } 173 | 174 | /** 175 | * Display a single basehttp. 176 | * GET basehttps/:id 177 | * 178 | * @param {object} ctx 179 | * @param {Request} ctx.request 180 | * @param {Response} ctx.response 181 | * @param {View} ctx.view 182 | */ 183 | async show({ params, request, response, auth }) { 184 | 185 | this.request = request 186 | this.response = response 187 | 188 | let query = this.model.query() 189 | 190 | query = this.addWiths(query, this.getwiths()) 191 | 192 | query = query.where({ id: params.id}) 193 | 194 | let res = await query.fetch() 195 | 196 | this.accessLog(auth, "", "show()", this.constructor.name) 197 | 198 | return response.json(res) 199 | 200 | } 201 | 202 | /** 203 | * Update basehttp details. 204 | * PUT or PATCH basehttps/:id 205 | * 206 | * @param {object} ctx 207 | * @param {Request} ctx.request 208 | * @param {Response} ctx.response 209 | */ 210 | async update({ params, request, response, auth }) { 211 | const entity = await this.model.find(params.id) 212 | 213 | if (!entity) { 214 | return response.status(404).json({ data: 'Resource not found' }) 215 | } 216 | let rules = this.getValidators().rules; 217 | let messages = this.getValidators().messages; 218 | 219 | const validation = await validate(request.all(), rules, messages) 220 | 221 | if (validation.fails()) { 222 | return response.status(422).json(validation.messages()) 223 | } 224 | 225 | let dataToUpdate = request.only(Object.keys(rules)) 226 | 227 | entity.merge(dataToUpdate) 228 | 229 | let data = await entity.save() 230 | 231 | this.postUpdate(data) 232 | 233 | this.accessLog(auth, "Resource-id : " + params.id, "update()", this.constructor.name) 234 | 235 | return response.status(201).json(data) 236 | 237 | } 238 | 239 | /** 240 | * Delete a basehttp with id. 241 | * DELETE basehttps/:id 242 | * 243 | * @param {object} ctx 244 | * @param {Request} ctx.request 245 | * @param {Response} ctx.response 246 | */ 247 | async destroy ({ params, request, response }) { 248 | } 249 | 250 | async delete({ params, response, auth }) { 251 | const entity = await this.model.find(params.id) 252 | 253 | if (!entity) { 254 | return response.status(404).json({ data: 'Resource not found' }) 255 | } 256 | 257 | await entity.delete() 258 | 259 | this.accessLog(auth, "Resource-id : " + params.id, "delete()", this.constructor.name) 260 | 261 | return response.status(204).json(null) 262 | } 263 | } 264 | 265 | module.exports = BaseController 266 | -------------------------------------------------------------------------------- /app/Controllers/Http/PostController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const BaseController = use('App/Controllers/Http/BaseController') 3 | const Post = use('App/Models/Post'); 4 | 5 | class PostController extends BaseController{ 6 | 7 | constructor(){ 8 | this.model = Post 9 | } 10 | } 11 | 12 | module.exports = PostController 13 | -------------------------------------------------------------------------------- /app/Controllers/Http/UserController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const BaseController = use('App/Controllers/Http/BaseController') 3 | const UserValidations = use('App/Validators/AuthRegister') 4 | const User = use('App/Models/User'); 5 | 6 | class UserController extends BaseController { 7 | 8 | constructor() { 9 | 10 | super(); 11 | 12 | this.model = User 13 | 14 | this.allowedWiths = [ 15 | 'posts', 16 | 'tokens' 17 | ] 18 | 19 | this.updatable = [ 20 | 'username', 21 | 'email', 22 | 'password' 23 | ] 24 | 25 | this.searchable = [ 26 | 'username', 27 | 'email', 28 | 'id' 29 | ] 30 | 31 | this.getValidators = function () { 32 | return new UserValidations() 33 | // return validations.rules 34 | } 35 | } 36 | } 37 | 38 | module.exports = UserController 39 | -------------------------------------------------------------------------------- /app/Controllers/Ws/BaseWController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class BaseWController { 4 | constructor ({ socket, request }) { 5 | this.socket = socket 6 | this.request = request 7 | } 8 | } 9 | 10 | module.exports = BaseWController 11 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const BaseExceptionHandler = use('BaseExceptionHandler') 4 | 5 | /** 6 | * This class handles all exceptions thrown during 7 | * the HTTP request lifecycle. 8 | * 9 | * @class ExceptionHandler 10 | */ 11 | class ExceptionHandler extends BaseExceptionHandler { 12 | /** 13 | * Handle exception thrown during the HTTP lifecycle 14 | * 15 | * @method handle 16 | * 17 | * @param {Object} error 18 | * @param {Object} options.request 19 | * @param {Object} options.response 20 | * 21 | * @return {void} 22 | */ 23 | async handle (error, { request, response }) { 24 | response.status(error.status).send(error.message) 25 | } 26 | 27 | /** 28 | * Report exception for logging or debugging. 29 | * 30 | * @method report 31 | * 32 | * @param {Object} error 33 | * @param {Object} options.request 34 | * 35 | * @return {void} 36 | */ 37 | async report (error, { request }) { 38 | } 39 | } 40 | 41 | module.exports = ExceptionHandler 42 | -------------------------------------------------------------------------------- /app/Exceptions/InvalidFilterJsonException.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { LogicalException } = require('@adonisjs/generic-exceptions') 4 | 5 | const message = 'Invalid json in filters' 6 | const status = 403 7 | const code = 'E_NOT_JSON' 8 | 9 | class InvalidFilterJsonException extends LogicalException { 10 | /** 11 | * Handle this exception by itself 12 | */ 13 | // handle () {} 14 | 15 | constructor() { 16 | super(message, status, code) 17 | } 18 | } 19 | 20 | module.exports = InvalidFilterJsonException 21 | -------------------------------------------------------------------------------- /app/Exceptions/InvalidSearchQueryException.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { LogicalException } = require('@adonisjs/generic-exceptions') 4 | 5 | const message = 'Invalid Search Query' 6 | const status = 403 7 | const code = 'E_NOT_STRING' 8 | 9 | class InvalidSearchQueryException extends LogicalException { 10 | /** 11 | * Handle this exception by itself 12 | */ 13 | // handle () {} 14 | 15 | constructor() { 16 | super(message, status, code) 17 | } 18 | 19 | // async handle(error, { response }) { 20 | // response.status(status).send(message) 21 | // } 22 | } 23 | 24 | module.exports = InvalidSearchQueryException 25 | -------------------------------------------------------------------------------- /app/Helpers/FilterParser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class FilterParser { 4 | 5 | parse($query,filters){ 6 | 7 | filters.forEach(values => { 8 | let $key = Object.keys(values)[0] 9 | let $value = values[$key] 10 | 11 | //If non equality operator 12 | if (Array.isArray($value)) { 13 | $value.forEach((operators) => { 14 | let $operator = Object.keys(operators)[0] 15 | let $valueToOperate = operators[$operator] 16 | 17 | switch ($operator) { 18 | case 'lte': 19 | $query = $query.where($key, '<=', $valueToOperate); 20 | break; 21 | case 'lt': 22 | $query = $query.where($key, '<', $valueToOperate); 23 | break; 24 | case 'gte': 25 | $query = $query.where($key, '>=', $valueToOperate); 26 | break; 27 | case 'gt': 28 | $query = $query.where($key, '>', $valueToOperate); 29 | break; 30 | case 'startsWith': 31 | $query = $query.where($key, 'LIKE',$valueToOperate + '%') 32 | break; 33 | case 'endsWith': 34 | $query = $query.where($key, 'LIKE', '%' + $valueToOperate) 35 | break; 36 | // case 'between': 37 | // if (is_array($valueToOperate)) 38 | // $query = $query.whereBetween($key, $valueToOperate); 39 | // break; 40 | // case 'in': 41 | // if (is_array($valueToOperate)) 42 | // $query = $query.whereIn($key, $valueToOperate); 43 | // break; 44 | } 45 | }) 46 | } else { 47 | $query = $query.where(values); 48 | } 49 | 50 | }) 51 | 52 | return $query 53 | } 54 | } 55 | 56 | module.exports = FilterParser 57 | -------------------------------------------------------------------------------- /app/Middleware/ConvertEmptyStringsToNull.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class ConvertEmptyStringsToNull { 4 | async handle ({ request }, next) { 5 | if (Object.keys(request.body).length) { 6 | request.body = Object.assign( 7 | ...Object.keys(request.body).map(key => ({ 8 | [key]: request.body[key] !== '' ? request.body[key] : null 9 | })) 10 | ) 11 | } 12 | 13 | await next() 14 | } 15 | } 16 | 17 | module.exports = ConvertEmptyStringsToNull 18 | -------------------------------------------------------------------------------- /app/Models/Hooks/User.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Hash = use('Hash') 4 | 5 | const UserHook = module.exports = {} 6 | 7 | /** 8 | * Hash using password as a hook. 9 | * 10 | * @method 11 | * 12 | * @param {Object} userInstance 13 | * 14 | * @return {void} 15 | */ 16 | UserHook.hashPassword = async (userInstance) => { 17 | if (userInstance.dirty.password) { 18 | userInstance.password = await Hash.make(userInstance.password) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Models/Post.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ 4 | const Model = use('Model') 5 | 6 | class Post extends Model { 7 | 8 | user() { 9 | return this.belongsTo('App/Models/User'); 10 | } 11 | } 12 | 13 | module.exports = Post 14 | -------------------------------------------------------------------------------- /app/Models/Token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ 4 | const Model = use('Model') 5 | 6 | class Token extends Model { 7 | } 8 | 9 | module.exports = Token 10 | -------------------------------------------------------------------------------- /app/Models/Traits/NoTimestamp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class NoTimestamp { 4 | register (Model) { 5 | Object.defineProperties(Model, { 6 | createdAtColumn: { 7 | get: () => null, 8 | }, 9 | updatedAtColumn: { 10 | get: () => null, 11 | } 12 | }) 13 | } 14 | } 15 | 16 | module.exports = NoTimestamp 17 | -------------------------------------------------------------------------------- /app/Models/User.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ 4 | const Model = use('Model') 5 | 6 | /** @type {import('@adonisjs/framework/src/Hash')} */ 7 | const Hash = use('Hash') 8 | 9 | class User extends Model { 10 | 11 | /** 12 | * Dont show password field 13 | */ 14 | static get hidden() { 15 | return ['password',''] 16 | } 17 | 18 | static boot () { 19 | super.boot() 20 | 21 | /** 22 | * A hook to hash the user password before saving 23 | * it to the database. 24 | */ 25 | this.addHook('beforeSave', async (userInstance) => { 26 | if (userInstance.dirty.password) { 27 | userInstance.password = await Hash.make(userInstance.password) 28 | } 29 | }) 30 | } 31 | 32 | /** 33 | * A relationship on tokens is required for auth to 34 | * work. Since features like `refreshTokens` or 35 | * `rememberToken` will be saved inside the 36 | * tokens table. 37 | * 38 | * @method tokens 39 | * 40 | * @return {Object} 41 | */ 42 | tokens () { 43 | return this.hasMany('App/Models/Token') 44 | } 45 | 46 | /** 47 | * A relationship on posts models 48 | * It will return all the post for a user 49 | * 50 | */ 51 | posts() { 52 | return this.hasMany('App/Models/Post') 53 | } 54 | } 55 | 56 | module.exports = User 57 | -------------------------------------------------------------------------------- /app/Validators/AuthLogin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class AuthLogin { 4 | 5 | /** 6 | * Rules for validating the inputs 7 | */ 8 | get rules() { 9 | return { 10 | email: 'required|email', 11 | password: 'required' 12 | } 13 | } 14 | 15 | /** 16 | * Request sanitisation goes here 17 | */ 18 | get sanitizationRules() { 19 | // sanitize data before validation 20 | } 21 | } 22 | 23 | module.exports = AuthLogin 24 | -------------------------------------------------------------------------------- /app/Validators/AuthRegister.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class AuthRegister { 4 | 5 | /** 6 | * Rules for validating the inputs 7 | */ 8 | get rules () { 9 | return { 10 | email: 'required|email|unique:users,email', 11 | username: 'required|unique:users,username', 12 | password: 'required' 13 | } 14 | } 15 | 16 | /** 17 | * Request sanitisation goes here 18 | */ 19 | get sanitizationRules() { 20 | // sanitize data before validation 21 | } 22 | 23 | get messages() { 24 | return { 25 | 'username.unique': 'You must provide unique username', 26 | 'username.required': 'You must provide username', 27 | 'email.required': 'You must provide a email address.', 28 | 'email.email': 'You must provide a valid email address.', 29 | 'email.unique': 'This email is already registered.', 30 | 'password.required': 'You must provide a password' 31 | } 32 | } 33 | 34 | } 35 | 36 | module.exports = AuthRegister 37 | -------------------------------------------------------------------------------- /app/Validators/User.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class User { 4 | get rules () { 5 | return { 6 | // validation rules 7 | } 8 | } 9 | } 10 | 11 | module.exports = User 12 | -------------------------------------------------------------------------------- /config/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/framework/src/Env')} */ 4 | const Env = use('Env') 5 | 6 | module.exports = { 7 | 8 | /* 9 | |-------------------------------------------------------------------------- 10 | | Application Name 11 | |-------------------------------------------------------------------------- 12 | | 13 | | This value is the name of your application and can used when you 14 | | need to place the application's name in a email, view or 15 | | other location. 16 | | 17 | */ 18 | 19 | name: Env.get('APP_NAME', 'AdonisJs'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | App Key 24 | |-------------------------------------------------------------------------- 25 | | 26 | | App key is a randomly generated 16 or 32 characters long string required 27 | | to encrypt cookies, sessions and other sensitive data. 28 | | 29 | */ 30 | appKey: Env.getOrFail('APP_KEY'), 31 | 32 | http: { 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Allow Method Spoofing 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Method spoofing allows to make requests by spoofing the http verb. 39 | | Which means you can make a GET request but instruct the server to 40 | | treat as a POST or PUT request. If you want this feature, set the 41 | | below value to true. 42 | | 43 | */ 44 | allowMethodSpoofing: true, 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Trust Proxy 49 | |-------------------------------------------------------------------------- 50 | | 51 | | Trust proxy defines whether X-Forwarded-* headers should be trusted or not. 52 | | When your application is behind a proxy server like nginx, these values 53 | | are set automatically and should be trusted. Apart from setting it 54 | | to true or false Adonis supports handful or ways to allow proxy 55 | | values. Read documentation for that. 56 | | 57 | */ 58 | trustProxy: false, 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | Subdomains 63 | |-------------------------------------------------------------------------- 64 | | 65 | | Offset to be used for returning subdomains for a given request.For 66 | | majority of applications it will be 2, until you have nested 67 | | sudomains. 68 | | cheatsheet.adonisjs.com - offset - 2 69 | | virk.cheatsheet.adonisjs.com - offset - 3 70 | | 71 | */ 72 | subdomainOffset: 2, 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | JSONP Callback 77 | |-------------------------------------------------------------------------- 78 | | 79 | | Default jsonp callback to be used when callback query string is missing 80 | | in request url. 81 | | 82 | */ 83 | jsonpCallback: 'callback', 84 | 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Etag 89 | |-------------------------------------------------------------------------- 90 | | 91 | | Set etag on all HTTP response. In order to disable for selected routes, 92 | | you can call the `response.send` with an options object as follows. 93 | | 94 | | response.send('Hello', { ignoreEtag: true }) 95 | | 96 | */ 97 | etag: false 98 | }, 99 | 100 | views: { 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Cache Views 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Define whether or not to cache the compiled view. Set it to true in 107 | | production to optimize view loading time. 108 | | 109 | */ 110 | cache: Env.get('CACHE_VIEWS', true) 111 | }, 112 | 113 | static: { 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Dot Files 117 | |-------------------------------------------------------------------------- 118 | | 119 | | Define how to treat dot files when trying to server static resources. 120 | | By default it is set to ignore, which will pretend that dotfiles 121 | | does not exists. 122 | | 123 | | Can be one of the following 124 | | ignore, deny, allow 125 | | 126 | */ 127 | dotfiles: 'ignore', 128 | 129 | /* 130 | |-------------------------------------------------------------------------- 131 | | ETag 132 | |-------------------------------------------------------------------------- 133 | | 134 | | Enable or disable etag generation 135 | | 136 | */ 137 | etag: true, 138 | 139 | /* 140 | |-------------------------------------------------------------------------- 141 | | Extensions 142 | |-------------------------------------------------------------------------- 143 | | 144 | | Set file extension fallbacks. When set, if a file is not found, the given 145 | | extensions will be added to the file name and search for. The first 146 | | that exists will be served. Example: ['html', 'htm']. 147 | | 148 | */ 149 | extensions: false 150 | }, 151 | 152 | locales: { 153 | /* 154 | |-------------------------------------------------------------------------- 155 | | Loader 156 | |-------------------------------------------------------------------------- 157 | | 158 | | The loader to be used for fetching and updating locales. Below is the 159 | | list of available options. 160 | | 161 | | file, database 162 | | 163 | */ 164 | loader: 'file', 165 | 166 | /* 167 | |-------------------------------------------------------------------------- 168 | | Default Locale 169 | |-------------------------------------------------------------------------- 170 | | 171 | | Default locale to be used by Antl provider. You can always switch drivers 172 | | in runtime or use the official Antl middleware to detect the driver 173 | | based on HTTP headers/query string. 174 | | 175 | */ 176 | locale: 'en' 177 | }, 178 | 179 | logger: { 180 | /* 181 | |-------------------------------------------------------------------------- 182 | | Transport 183 | |-------------------------------------------------------------------------- 184 | | 185 | | Transport to be used for logging messages. You can have multiple 186 | | transports using same driver. 187 | | 188 | | Available drivers are: `file` and `console`. 189 | | 190 | */ 191 | transport: 'console', 192 | 193 | /* 194 | |-------------------------------------------------------------------------- 195 | | Console Transport 196 | |-------------------------------------------------------------------------- 197 | | 198 | | Using `console` driver for logging. This driver writes to `stdout` 199 | | and `stderr` 200 | | 201 | */ 202 | console: { 203 | driver: 'console', 204 | name: 'adonis-app', 205 | level: 'info' 206 | }, 207 | 208 | /* 209 | |-------------------------------------------------------------------------- 210 | | File Transport 211 | |-------------------------------------------------------------------------- 212 | | 213 | | File transport uses file driver and writes log messages for a given 214 | | file inside `tmp` directory for your app. 215 | | 216 | | For a different directory, set an absolute path for the filename. 217 | | 218 | */ 219 | file: { 220 | driver: 'file', 221 | name: 'adonis-app', 222 | filename: 'adonis.log', 223 | level: 'info' 224 | } 225 | }, 226 | 227 | /* 228 | |-------------------------------------------------------------------------- 229 | | Generic Cookie Options 230 | |-------------------------------------------------------------------------- 231 | | 232 | | The following cookie options are generic settings used by AdonisJs to create 233 | | cookies. However, some parts of the application like `sessions` can have 234 | | separate settings for cookies inside `config/session.js`. 235 | | 236 | */ 237 | cookie: { 238 | httpOnly: true, 239 | sameSite: false, 240 | path: '/', 241 | maxAge: 7200 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /config/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/framework/src/Env')} */ 4 | const Env = use('Env') 5 | 6 | module.exports = { 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Authenticator 10 | |-------------------------------------------------------------------------- 11 | | 12 | | Authentication is a combination of serializer and scheme with extra 13 | | config to define on how to authenticate a user. 14 | | 15 | | Available Schemes - basic, session, jwt, api 16 | | Available Serializers - lucid, database 17 | | 18 | */ 19 | authenticator: 'jwt', 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Session 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Session authenticator makes use of sessions to authenticate a user. 27 | | Session authentication is always persistent. 28 | | 29 | */ 30 | session: { 31 | serializer: 'lucid', 32 | model: 'App/Models/User', 33 | scheme: 'session', 34 | uid: 'email', 35 | password: 'password' 36 | }, 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Basic Auth 41 | |-------------------------------------------------------------------------- 42 | | 43 | | The basic auth authenticator uses basic auth header to authenticate a 44 | | user. 45 | | 46 | | NOTE: 47 | | This scheme is not persistent and users are supposed to pass 48 | | login credentials on each request. 49 | | 50 | */ 51 | basic: { 52 | serializer: 'lucid', 53 | model: 'App/Models/User', 54 | scheme: 'basic', 55 | uid: 'email', 56 | password: 'password' 57 | }, 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Jwt 62 | |-------------------------------------------------------------------------- 63 | | 64 | | The jwt authenticator works by passing a jwt token on each HTTP request 65 | | via HTTP `Authorization` header. 66 | | 67 | */ 68 | jwt: { 69 | serializer: 'lucid', 70 | model: 'App/Models/User', 71 | scheme: 'jwt', 72 | uid: 'email', 73 | password: 'password', 74 | options: { 75 | secret: Env.get('APP_KEY') 76 | } 77 | }, 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Api 82 | |-------------------------------------------------------------------------- 83 | | 84 | | The Api scheme makes use of API personal tokens to authenticate a user. 85 | | 86 | */ 87 | api: { 88 | serializer: 'lucid', 89 | model: 'App/Models/User', 90 | scheme: 'api', 91 | uid: 'email', 92 | password: 'password' 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /config/bodyParser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | JSON Parser 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Below settings are applied when the request body contains a JSON payload. 10 | | If you want body parser to ignore JSON payloads, then simply set `types` 11 | | to an empty array. 12 | */ 13 | json: { 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | limit 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Defines the limit of JSON that can be sent by the client. If payload 20 | | is over 1mb it will not be processed. 21 | | 22 | */ 23 | limit: '1mb', 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | strict 28 | |-------------------------------------------------------------------------- 29 | | 30 | | When `strict` is set to true, body parser will only parse Arrays and 31 | | Object. Otherwise everything parseable by `JSON.parse` is parsed. 32 | | 33 | */ 34 | strict: true, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | types 39 | |-------------------------------------------------------------------------- 40 | | 41 | | Which content types are processed as JSON payloads. You are free to 42 | | add your own types here, but the request body should be parseable 43 | | by `JSON.parse` method. 44 | | 45 | */ 46 | types: [ 47 | 'application/json', 48 | 'application/json-patch+json', 49 | 'application/vnd.api+json', 50 | 'application/csp-report' 51 | ] 52 | }, 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Raw Parser 57 | |-------------------------------------------------------------------------- 58 | | 59 | | 60 | | 61 | */ 62 | raw: { 63 | types: [ 64 | 'text/*' 65 | ] 66 | }, 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Form Parser 71 | |-------------------------------------------------------------------------- 72 | | 73 | | 74 | | 75 | */ 76 | form: { 77 | types: [ 78 | 'application/x-www-form-urlencoded' 79 | ] 80 | }, 81 | 82 | /* 83 | |-------------------------------------------------------------------------- 84 | | Files Parser 85 | |-------------------------------------------------------------------------- 86 | | 87 | | 88 | | 89 | */ 90 | files: { 91 | types: [ 92 | 'multipart/form-data' 93 | ], 94 | 95 | /* 96 | |-------------------------------------------------------------------------- 97 | | Max Size 98 | |-------------------------------------------------------------------------- 99 | | 100 | | Below value is the max size of all the files uploaded to the server. It 101 | | is validated even before files have been processed and hard exception 102 | | is thrown. 103 | | 104 | | Consider setting a reasonable value here, otherwise people may upload GB's 105 | | of files which will keep your server busy. 106 | | 107 | | Also this value is considered when `autoProcess` is set to true. 108 | | 109 | */ 110 | maxSize: '20mb', 111 | 112 | /* 113 | |-------------------------------------------------------------------------- 114 | | Auto Process 115 | |-------------------------------------------------------------------------- 116 | | 117 | | Whether or not to auto-process files. Since HTTP servers handle files via 118 | | couple of specific endpoints. It is better to set this value off and 119 | | manually process the files when required. 120 | | 121 | | This value can contain a boolean or an array of route patterns 122 | | to be autoprocessed. 123 | */ 124 | autoProcess: true, 125 | 126 | /* 127 | |-------------------------------------------------------------------------- 128 | | Process Manually 129 | |-------------------------------------------------------------------------- 130 | | 131 | | The list of routes that should not process files and instead rely on 132 | | manual process. This list should only contain routes when autoProcess 133 | | is to true. Otherwise everything is processed manually. 134 | | 135 | */ 136 | processManually: [] 137 | 138 | /* 139 | |-------------------------------------------------------------------------- 140 | | Temporary file name 141 | |-------------------------------------------------------------------------- 142 | | 143 | | Define a function, which should return a string to be used as the 144 | | tmp file name. 145 | | 146 | | If not defined, Bodyparser will use `uuid` as the tmp file name. 147 | | 148 | | To be defined as. If you are defining the function, then do make sure 149 | | to return a value from it. 150 | | 151 | | tmpFileName () { 152 | | return 'some-unique-value' 153 | | } 154 | | 155 | */ 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /config/cors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Origin 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Set a list of origins to be allowed. The value can be one of the following 10 | | 11 | | Boolean: true - Allow current request origin 12 | | Boolean: false - Disallow all 13 | | String - Comma separated list of allowed origins 14 | | Array - An array of allowed origins 15 | | String: * - A wildcard to allow current request origin 16 | | Function - Receives the current origin and should return one of the above values. 17 | | 18 | */ 19 | origin: false, 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Methods 24 | |-------------------------------------------------------------------------- 25 | | 26 | | HTTP methods to be allowed. The value can be one of the following 27 | | 28 | | String - Comma separated list of allowed methods 29 | | Array - An array of allowed methods 30 | | 31 | */ 32 | methods: ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'], 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Headers 37 | |-------------------------------------------------------------------------- 38 | | 39 | | List of headers to be allowed via Access-Control-Request-Headers header. 40 | | The value can be one of the following. 41 | | 42 | | Boolean: true - Allow current request headers 43 | | Boolean: false - Disallow all 44 | | String - Comma separated list of allowed headers 45 | | Array - An array of allowed headers 46 | | String: * - A wildcard to allow current request headers 47 | | Function - Receives the current header and should return one of the above values. 48 | | 49 | */ 50 | headers: true, 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Expose Headers 55 | |-------------------------------------------------------------------------- 56 | | 57 | | A list of headers to be exposed via `Access-Control-Expose-Headers` 58 | | header. The value can be one of the following. 59 | | 60 | | Boolean: false - Disallow all 61 | | String: Comma separated list of allowed headers 62 | | Array - An array of allowed headers 63 | | 64 | */ 65 | exposeHeaders: false, 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Credentials 70 | |-------------------------------------------------------------------------- 71 | | 72 | | Define Access-Control-Allow-Credentials header. It should always be a 73 | | boolean. 74 | | 75 | */ 76 | credentials: false, 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | MaxAge 81 | |-------------------------------------------------------------------------- 82 | | 83 | | Define Access-Control-Allow-Max-Age 84 | | 85 | */ 86 | maxAge: 90 87 | } 88 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/framework/src/Env')} */ 4 | const Env = use('Env') 5 | 6 | /** @type {import('@adonisjs/ignitor/src/Helpers')} */ 7 | const Helpers = use('Helpers') 8 | 9 | module.exports = { 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Default Connection 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Connection defines the default connection settings to be used while 16 | | interacting with SQL databases. 17 | | 18 | */ 19 | connection: Env.get('DB_CONNECTION', 'sqlite'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Sqlite 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Sqlite is a flat file database and can be a good choice for a development 27 | | environment. 28 | | 29 | | npm i --save sqlite3 30 | | 31 | */ 32 | sqlite: { 33 | client: 'sqlite3', 34 | connection: { 35 | filename: Helpers.databasePath(`${Env.get('DB_DATABASE', 'development')}.sqlite`) 36 | }, 37 | useNullAsDefault: true, 38 | debug: Env.get('DB_DEBUG', false) 39 | }, 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | MySQL 44 | |-------------------------------------------------------------------------- 45 | | 46 | | Here we define connection settings for MySQL database. 47 | | 48 | | npm i --save mysql 49 | | 50 | */ 51 | mysql: { 52 | client: 'mysql', 53 | connection: { 54 | host: Env.get('DB_HOST', 'localhost'), 55 | port: Env.get('DB_PORT', ''), 56 | user: Env.get('DB_USER', 'root'), 57 | password: Env.get('DB_PASSWORD', ''), 58 | database: Env.get('DB_DATABASE', 'adonis') 59 | }, 60 | debug: Env.get('DB_DEBUG', false) 61 | }, 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | PostgreSQL 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Here we define connection settings for PostgreSQL database. 69 | | 70 | | npm i --save pg 71 | | 72 | */ 73 | pg: { 74 | client: 'pg', 75 | connection: { 76 | host: Env.get('DB_HOST', 'localhost'), 77 | port: Env.get('DB_PORT', ''), 78 | user: Env.get('DB_USER', 'root'), 79 | password: Env.get('DB_PASSWORD', ''), 80 | database: Env.get('DB_DATABASE', 'adonis') 81 | }, 82 | debug: Env.get('DB_DEBUG', false) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /config/hash.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/framework/src/Env')} */ 4 | const Env = use('Env') 5 | 6 | module.exports = { 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Driver 10 | |-------------------------------------------------------------------------- 11 | | 12 | | Driver to be used for hashing values. The same driver is used by the 13 | | auth module too. 14 | | 15 | */ 16 | driver: Env.get('HASH_DRIVER', 'bcrypt'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Bcrypt 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Config related to bcrypt hashing. https://www.npmjs.com/package/bcrypt 24 | | package is used internally. 25 | | 26 | */ 27 | bcrypt: { 28 | rounds: 10 29 | }, 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Argon 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Config related to argon. https://www.npmjs.com/package/argon2 package is 37 | | used internally. 38 | | 39 | | Since argon is optional, you will have to install the dependency yourself 40 | | 41 | |============================================================================ 42 | | npm i argon2 43 | |============================================================================ 44 | | 45 | */ 46 | argon: { 47 | type: 1 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /database/adonis.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermine-bc/adonis-rest-template/aed2c22d0eb54d2b3bd0a066f6792da1bbccc5ae/database/adonis.sqlite -------------------------------------------------------------------------------- /database/factory.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Factory 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Factories are used to define blueprints for database tables or Lucid 9 | | models. Later you can use these blueprints to seed your database 10 | | with dummy data. 11 | | 12 | */ 13 | 14 | /** @type {import('@adonisjs/lucid/src/Factory')} */ 15 | // const Factory = use('Factory') 16 | 17 | // Factory.blueprint('App/Models/User', (faker) => { 18 | // return { 19 | // username: faker.username() 20 | // } 21 | // }) 22 | -------------------------------------------------------------------------------- /database/migrations/1503250034279_user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/lucid/src/Schema')} */ 4 | const Schema = use('Schema') 5 | 6 | class UserSchema extends Schema { 7 | up () { 8 | this.create('users', (table) => { 9 | table.increments() 10 | table.string('username', 80).notNullable().unique() 11 | table.string('email', 254).notNullable().unique() 12 | table.string('password', 60).notNullable() 13 | table.timestamps() 14 | }) 15 | } 16 | 17 | down () { 18 | this.drop('users') 19 | } 20 | } 21 | 22 | module.exports = UserSchema 23 | -------------------------------------------------------------------------------- /database/migrations/1503250034280_token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/lucid/src/Schema')} */ 4 | const Schema = use('Schema') 5 | 6 | class TokensSchema extends Schema { 7 | up () { 8 | this.create('tokens', (table) => { 9 | table.increments() 10 | table.integer('user_id').unsigned().references('id').inTable('users') 11 | table.string('token', 255).notNullable().unique().index() 12 | table.string('type', 80).notNullable() 13 | table.boolean('is_revoked').defaultTo(false) 14 | table.timestamps() 15 | }) 16 | } 17 | 18 | down () { 19 | this.drop('tokens') 20 | } 21 | } 22 | 23 | module.exports = TokensSchema 24 | -------------------------------------------------------------------------------- /database/migrations/1565517995551_post_schema.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/lucid/src/Schema')} */ 4 | const Schema = use('Schema') 5 | 6 | class PostSchema extends Schema { 7 | up () { 8 | this.create('posts', (table) => { 9 | table.increments() 10 | table.string('title') 11 | table.string('description') 12 | table.integer('user_id').unsigned(); 13 | table.foreign('user_id').references('Users.id').onDelete('cascade'); 14 | table.timestamps() 15 | }) 16 | } 17 | 18 | down () { 19 | this.drop('posts') 20 | } 21 | } 22 | 23 | module.exports = PostSchema 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adonis-api-app", 3 | "version": "4.1.0", 4 | "adonis-version": "4.1.0", 5 | "description": "Adonisjs boilerplate for API server with pre-configured JWT", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "node server.js", 9 | "test": "node ace test" 10 | }, 11 | "keywords": [ 12 | "adonisjs", 13 | "adonis-app" 14 | ], 15 | "author": "", 16 | "license": "UNLICENSED", 17 | "private": true, 18 | "dependencies": { 19 | "@adonisjs/ace": "^5.0.8", 20 | "@adonisjs/auth": "^3.0.7", 21 | "@adonisjs/bodyparser": "^2.0.5", 22 | "@adonisjs/cors": "^1.0.7", 23 | "@adonisjs/fold": "^4.0.9", 24 | "@adonisjs/framework": "^5.0.9", 25 | "@adonisjs/ignitor": "^2.0.8", 26 | "@adonisjs/lucid": "^6.1.3", 27 | "@adonisjs/validator": "^5.0.6", 28 | "sqlite3": "^4.0.9" 29 | }, 30 | "devDependencies": {}, 31 | "autoload": { 32 | "App": "./app" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Http server 6 | |-------------------------------------------------------------------------- 7 | | 8 | | This file bootstraps Adonisjs to start the HTTP server. You are free to 9 | | customize the process of booting the http server. 10 | | 11 | | """ Loading ace commands """ 12 | | At times you may want to load ace commands when starting the HTTP server. 13 | | Same can be done by chaining `loadCommands()` method after 14 | | 15 | | """ Preloading files """ 16 | | Also you can preload files by calling `preLoad('path/to/file')` method. 17 | | Make sure to pass a relative path from the project root. 18 | */ 19 | 20 | const { Ignitor } = require('@adonisjs/ignitor') 21 | 22 | new Ignitor(require('@adonisjs/fold')) 23 | .appRoot(__dirname) 24 | .fireHttpServer() 25 | .catch(console.error) 26 | -------------------------------------------------------------------------------- /start/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Providers 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Providers are building blocks for your Adonis app. Anytime you install 9 | | a new Adonis specific package, chances are you will register the 10 | | provider here. 11 | | 12 | */ 13 | const providers = [ 14 | '@adonisjs/framework/providers/AppProvider', 15 | '@adonisjs/auth/providers/AuthProvider', 16 | '@adonisjs/bodyparser/providers/BodyParserProvider', 17 | '@adonisjs/cors/providers/CorsProvider', 18 | '@adonisjs/lucid/providers/LucidProvider', 19 | '@adonisjs/validator/providers/ValidatorProvider' 20 | ] 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Ace Providers 25 | |-------------------------------------------------------------------------- 26 | | 27 | | Ace providers are required only when running ace commands. For example 28 | | Providers for migrations, tests etc. 29 | | 30 | */ 31 | const aceProviders = [ 32 | '@adonisjs/lucid/providers/MigrationsProvider' 33 | ] 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Aliases 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Aliases are short unique names for IoC container bindings. You are free 41 | | to create your own aliases. 42 | | 43 | | For example: 44 | | { Route: 'Adonis/Src/Route' } 45 | | 46 | */ 47 | const aliases = {} 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Commands 52 | |-------------------------------------------------------------------------- 53 | | 54 | | Here you store ace commands for your package 55 | | 56 | */ 57 | const commands = [] 58 | 59 | module.exports = { providers, aceProviders, aliases, commands } 60 | -------------------------------------------------------------------------------- /start/kernel.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** @type {import('@adonisjs/framework/src/Server')} */ 4 | const Server = use('Server') 5 | 6 | /* 7 | |-------------------------------------------------------------------------- 8 | | Global Middleware 9 | |-------------------------------------------------------------------------- 10 | | 11 | | Global middleware are executed on each http request only when the routes 12 | | match. 13 | | 14 | */ 15 | const globalMiddleware = [ 16 | 'Adonis/Middleware/BodyParser', 17 | 'Adonis/Middleware/AuthInit', 18 | 'App/Middleware/ConvertEmptyStringsToNull' 19 | ] 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Named Middleware 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Named middleware is key/value object to conditionally add middleware on 27 | | specific routes or group of routes. 28 | | 29 | | // define 30 | | { 31 | | auth: 'Adonis/Middleware/Auth' 32 | | } 33 | | 34 | | // use 35 | | Route.get().middleware('auth') 36 | | 37 | */ 38 | const namedMiddleware = { 39 | auth: 'Adonis/Middleware/Auth', 40 | guest: 'Adonis/Middleware/AllowGuestOnly' 41 | } 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Server Middleware 46 | |-------------------------------------------------------------------------- 47 | | 48 | | Server level middleware are executed even when route for a given URL is 49 | | not registered. Features like `static assets` and `cors` needs better 50 | | control over request lifecycle. 51 | | 52 | */ 53 | const serverMiddleware = [ 54 | // 'Adonis/Middleware/Static', 55 | 'Adonis/Middleware/Cors' 56 | ] 57 | 58 | Server 59 | .registerGlobal(globalMiddleware) 60 | .registerNamed(namedMiddleware) 61 | .use(serverMiddleware) 62 | -------------------------------------------------------------------------------- /start/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Routes 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Http routes are entry points to your web application. You can create 9 | | routes for different URLs and bind Controller actions to them. 10 | | 11 | | A complete guide on routing is available here. 12 | | http://adonisjs.com/docs/4.1/routing 13 | | 14 | */ 15 | 16 | /** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */ 17 | const Route = use('Route') 18 | 19 | Route.get('/', () => { 20 | return { greeting: 'Welcome to SuperBaseStack' } 21 | }) 22 | 23 | Route 24 | .post('authenticate', 'AuthController.login') 25 | .validator('AuthLogin') 26 | 27 | Route 28 | .post('register', 'AuthController.register') 29 | .validator('AuthRegister') 30 | 31 | /** 32 | * Routes grouped as API 33 | */ 34 | Route 35 | .group(() => { 36 | 37 | Route.resource('posts', 'PostController') 38 | 39 | Route.resource('users', 'UserController') 40 | 41 | }) 42 | .middleware(['auth']) 43 | .prefix('api/v1') 44 | 45 | --------------------------------------------------------------------------------