├── .editorconfig ├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── ace ├── airbnb-api.json ├── app ├── Controllers │ └── Http │ │ ├── ImageController.js │ │ ├── PropertyController.js │ │ ├── SessionController.js │ │ └── UserController.js └── Models │ ├── Image.js │ ├── Property.js │ ├── Token.js │ └── User.js ├── config ├── app.js ├── auth.js ├── bodyParser.js ├── cors.js ├── database.js └── hash.js ├── database ├── factory.js └── migrations │ ├── 1503250034279_user.js │ ├── 1503250034280_token.js │ ├── 1530569796148_property_schema.js │ └── 1530572844377_image_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 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "globals": { 4 | "use": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adonis API application 2 | 3 | This is the boilerplate for creating an API server in AdonisJs, it comes pre-configured with. 4 | 5 | 1. Bodyparser 6 | 2. Authentication 7 | 3. CORS 8 | 4. Lucid ORM 9 | 5. Migrations and seeds 10 | 11 | ## Setup 12 | 13 | Use the adonis command to install the blueprint 14 | 15 | ```bash 16 | adonis new yardstick --api-only 17 | ``` 18 | 19 | or manually clone the repo and then run `npm install`. 20 | 21 | 22 | ### Migrations 23 | 24 | Run the following command to run startup migrations. 25 | 26 | ```js 27 | adonis migration:run 28 | ``` 29 | -------------------------------------------------------------------------------- /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 proxy 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 | -------------------------------------------------------------------------------- /airbnb-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "_type": "export", 3 | "__export_format": 3, 4 | "__export_date": "2018-07-03T02:22:17.227Z", 5 | "__export_source": "insomnia.desktop.app:v5.16.6", 6 | "resources": [ 7 | { 8 | "_id": "wrk_c980b1772c2e40049332105a12bcc800", 9 | "created": 1528822136220, 10 | "description": "", 11 | "modified": 1530578359677, 12 | "name": "AirBnB", 13 | "parentId": null, 14 | "_type": "workspace" 15 | }, 16 | { 17 | "_id": "env_39bdfd60daef4c5599ebf93493fad299", 18 | "color": null, 19 | "created": 1528822136288, 20 | "data": { 21 | "base_url": "http://localhost:3333", 22 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsImlhdCI6MTUzMDU3OTA0MH0.Q0GavD6_6oadYy0MmUDTD0z4HH-4chMflbhHLybKNio" 23 | }, 24 | "isPrivate": false, 25 | "modified": 1530579059773, 26 | "name": "New Environment", 27 | "parentId": "wrk_c980b1772c2e40049332105a12bcc800", 28 | "_type": "environment" 29 | }, 30 | { 31 | "_id": "jar_b68ff290c4b842cba16e2d6332203980", 32 | "cookies": [], 33 | "created": 1528822136396, 34 | "modified": 1528822136396, 35 | "name": "Default Jar", 36 | "parentId": "wrk_c980b1772c2e40049332105a12bcc800", 37 | "_type": "cookie_jar" 38 | }, 39 | { 40 | "_id": "fld_224592c38c644e749adb5ccb4bc463bb", 41 | "created": 1530578369664, 42 | "description": "", 43 | "environment": {}, 44 | "metaSortKey": -1530578369665, 45 | "modified": 1530578369664, 46 | "name": "Imóveis", 47 | "parentId": "wrk_c980b1772c2e40049332105a12bcc800", 48 | "_type": "request_group" 49 | }, 50 | { 51 | "_id": "fld_274e262dcb994a669dddf7536f95ec26", 52 | "created": 1530579069015, 53 | "description": "", 54 | "environment": {}, 55 | "metaSortKey": -1530579069015, 56 | "modified": 1530579069015, 57 | "name": "Auth", 58 | "parentId": "wrk_c980b1772c2e40049332105a12bcc800", 59 | "_type": "request_group" 60 | }, 61 | { 62 | "_id": "req_5e8d1548f17d4e029dc800baf95e3565", 63 | "authentication": { 64 | "token": "{{ token }}", 65 | "type": "bearer" 66 | }, 67 | "body": {}, 68 | "created": 1530578378869, 69 | "description": "", 70 | "headers": [], 71 | "isPrivate": false, 72 | "metaSortKey": -1530578378869, 73 | "method": "GET", 74 | "modified": 1530579109540, 75 | "name": "Index", 76 | "parameters": [], 77 | "parentId": "fld_224592c38c644e749adb5ccb4bc463bb", 78 | "settingDisableRenderRequestBody": false, 79 | "settingEncodeUrl": true, 80 | "settingRebuildPath": true, 81 | "settingSendCookies": true, 82 | "settingStoreCookies": true, 83 | "url": "{{ base_url }}/properties", 84 | "_type": "request" 85 | }, 86 | { 87 | "_id": "req_3b4c42b4df754c3fbd73862a004397c6", 88 | "authentication": { 89 | "token": "{{ token }}", 90 | "type": "bearer" 91 | }, 92 | "body": {}, 93 | "created": 1530583491069, 94 | "description": "", 95 | "headers": [], 96 | "isPrivate": false, 97 | "metaSortKey": -1530359554007, 98 | "method": "GET", 99 | "modified": 1530583866610, 100 | "name": "Show", 101 | "parameters": [], 102 | "parentId": "fld_224592c38c644e749adb5ccb4bc463bb", 103 | "settingDisableRenderRequestBody": false, 104 | "settingEncodeUrl": true, 105 | "settingRebuildPath": true, 106 | "settingSendCookies": true, 107 | "settingStoreCookies": true, 108 | "url": "{{ base_url }}/properties/1", 109 | "_type": "request" 110 | }, 111 | { 112 | "_id": "req_f587175dde1e421ab805ba0acbe04f31", 113 | "authentication": { 114 | "token": "{{ token }}", 115 | "type": "bearer" 116 | }, 117 | "body": {}, 118 | "created": 1530584039948, 119 | "description": "", 120 | "headers": [], 121 | "isPrivate": false, 122 | "metaSortKey": -1530250141576, 123 | "method": "DELETE", 124 | "modified": 1530584109626, 125 | "name": "Delete", 126 | "parameters": [], 127 | "parentId": "fld_224592c38c644e749adb5ccb4bc463bb", 128 | "settingDisableRenderRequestBody": false, 129 | "settingEncodeUrl": true, 130 | "settingRebuildPath": true, 131 | "settingSendCookies": true, 132 | "settingStoreCookies": true, 133 | "url": "\t{{ base_url }}/properties/1", 134 | "_type": "request" 135 | }, 136 | { 137 | "_id": "req_9d5a1d072a7445009e1af8bfecfae87b", 138 | "authentication": {}, 139 | "body": { 140 | "mimeType": "application/json", 141 | "text": "{\n\t\"username\": \"diego3g3\",\n\t\"email\": \"diego3@rocketseat.com.br\",\n\t\"password\": \"123456\"\n}" 142 | }, 143 | "created": 1530138692882, 144 | "description": "", 145 | "headers": [ 146 | { 147 | "id": "pair_38d56fbe9cac46a98c825f9b9a1cc5df", 148 | "name": "Content-Type", 149 | "value": "application/json" 150 | } 151 | ], 152 | "isPrivate": false, 153 | "metaSortKey": -1530140729145, 154 | "method": "POST", 155 | "modified": 1530584052484, 156 | "name": "Register", 157 | "parameters": [], 158 | "parentId": "fld_274e262dcb994a669dddf7536f95ec26", 159 | "settingDisableRenderRequestBody": false, 160 | "settingEncodeUrl": true, 161 | "settingRebuildPath": true, 162 | "settingSendCookies": true, 163 | "settingStoreCookies": true, 164 | "url": "{{ base_url }}/users", 165 | "_type": "request" 166 | }, 167 | { 168 | "_id": "req_7b0af4e36a114a6c9a01ca17a8ba36d1", 169 | "authentication": {}, 170 | "body": { 171 | "mimeType": "application/json", 172 | "text": "{\n\t\"email\": \"diego@rocketseat.com.br\",\n\t\"password\": \"123456\"\n}" 173 | }, 174 | "created": 1530140729095, 175 | "description": "", 176 | "headers": [ 177 | { 178 | "id": "pair_20ce7c95321e454192f3df9c391b672e", 179 | "name": "Content-Type", 180 | "value": "application/json" 181 | } 182 | ], 183 | "isPrivate": false, 184 | "metaSortKey": -1530140729095, 185 | "method": "POST", 186 | "modified": 1530579094463, 187 | "name": "Authentication", 188 | "parameters": [], 189 | "parentId": "fld_274e262dcb994a669dddf7536f95ec26", 190 | "settingDisableRenderRequestBody": false, 191 | "settingEncodeUrl": true, 192 | "settingRebuildPath": true, 193 | "settingSendCookies": true, 194 | "settingStoreCookies": true, 195 | "url": "{{ base_url }}/sessions", 196 | "_type": "request" 197 | } 198 | ] 199 | } -------------------------------------------------------------------------------- /app/Controllers/Http/ImageController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Helpers = use('Helpers') 4 | const Property = use('App/Models/Property') 5 | 6 | /** 7 | * Resourceful controller for interacting with images 8 | */ 9 | class ImageController { 10 | async show ({ params, response }) { 11 | return response.download(Helpers.tmpPath(`uploads/${params.path}`)) 12 | } 13 | 14 | /** 15 | * Create/save a new image. 16 | * POST images 17 | */ 18 | async store ({ params, request }) { 19 | const property = await Property.findOrFail(params.id) 20 | 21 | const images = request.file('image', { 22 | types: ['image'], 23 | size: '2mb' 24 | }) 25 | 26 | await images.moveAll(Helpers.tmpPath('uploads'), file => ({ 27 | name: `${Date.now()}-${file.clientName}` 28 | })) 29 | 30 | if (!images.movedAll()) { 31 | return images.errors() 32 | } 33 | 34 | await Promise.all( 35 | images 36 | .movedList() 37 | .map(image => property.images().create({ path: image.fileName })) 38 | ) 39 | } 40 | } 41 | 42 | module.exports = ImageController 43 | -------------------------------------------------------------------------------- /app/Controllers/Http/PropertyController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Helpers = use('Helpers') 4 | const Property = use('App/Models/Property') 5 | 6 | /** 7 | * Resourceful controller for interacting with properties 8 | */ 9 | class PropertyController { 10 | /** 11 | * Show a list of all properties. 12 | * GET properties 13 | */ 14 | async index ({ request }) { 15 | const { latitude, longitude } = request.all() 16 | 17 | const properties = Property.query() 18 | .with('images') 19 | .nearBy(latitude, longitude, 10) 20 | .fetch() 21 | 22 | return properties 23 | } 24 | 25 | /** 26 | * Create/save a new property. 27 | * POST properties 28 | */ 29 | async store ({ auth, request, response }) { 30 | const { id } = auth.user 31 | const data = request.only([ 32 | 'title', 33 | 'address', 34 | 'latitude', 35 | 'longitude', 36 | 'price' 37 | ]) 38 | 39 | const property = await Property.create({ ...data, user_id: id }) 40 | 41 | return property 42 | } 43 | 44 | /** 45 | * Display a single property. 46 | * GET properties/:id 47 | */ 48 | async show ({ params }) { 49 | const property = await Property.findOrFail(params.id) 50 | 51 | await property.load('images') 52 | 53 | return property 54 | } 55 | 56 | /** 57 | * Update property details. 58 | * PUT or PATCH properties/:id 59 | */ 60 | async update ({ params, request, response }) { 61 | const property = await Property.findOrFail(params.id) 62 | 63 | const data = request.only([ 64 | 'title', 65 | 'address', 66 | 'latitude', 67 | 'longitude', 68 | 'price' 69 | ]) 70 | 71 | property.merge(data) 72 | 73 | await property.save() 74 | 75 | return property 76 | } 77 | 78 | /** 79 | * Delete a property with id. 80 | * DELETE properties/:id 81 | */ 82 | async destroy ({ params, auth, response }) { 83 | const property = await Property.findOrFail(params.id) 84 | 85 | if (property.user_id !== auth.user.id) { 86 | return response.status(401).send({ error: 'Not authorized' }) 87 | } 88 | 89 | await property.delete() 90 | } 91 | } 92 | 93 | module.exports = PropertyController 94 | -------------------------------------------------------------------------------- /app/Controllers/Http/SessionController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class SessionController { 4 | async create ({ request, auth }) { 5 | const { email, password } = request.all() 6 | 7 | const token = await auth.attempt(email, password) 8 | 9 | return token 10 | } 11 | } 12 | 13 | module.exports = SessionController 14 | -------------------------------------------------------------------------------- /app/Controllers/Http/UserController.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const User = use("App/Models/User") 4 | 5 | class UserController { 6 | async create({ request }) { 7 | const data = request.only(["username", "email", "password"]) 8 | 9 | const user = await User.create(data) 10 | 11 | return user 12 | } 13 | } 14 | 15 | module.exports = UserController 16 | -------------------------------------------------------------------------------- /app/Models/Image.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Env = use('Env') 4 | const Model = use('Model') 5 | 6 | class Image extends Model { 7 | static get computed () { 8 | return ['url'] 9 | } 10 | 11 | getUrl ({ path }) { 12 | return `${Env.get('APP_URL')}/images/${path}` 13 | } 14 | } 15 | 16 | module.exports = Image 17 | -------------------------------------------------------------------------------- /app/Models/Property.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Model = use('Model') 4 | const Database = use('Database') 5 | 6 | class Property extends Model { 7 | static scopeNearBy (query, latitude, longitude, distance) { 8 | const haversine = `(6371 * acos(cos(radians(${latitude})) 9 | * cos(radians(latitude)) 10 | * cos(radians(longitude) 11 | - radians(${longitude})) 12 | + sin(radians(${latitude})) 13 | * sin(radians(latitude))))` 14 | 15 | return query 16 | .select('*', Database.raw(`round(${haversine}) as distance`)) 17 | .whereRaw(`${haversine} < ${distance}`) 18 | } 19 | 20 | user () { 21 | return this.belongsTo('App/Models/User') 22 | } 23 | 24 | images () { 25 | return this.hasMany('App/Models/Image') 26 | } 27 | } 28 | 29 | module.exports = Property 30 | -------------------------------------------------------------------------------- /app/Models/Token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Model = use('Model') 4 | 5 | class Token extends Model { 6 | } 7 | 8 | module.exports = Token 9 | -------------------------------------------------------------------------------- /app/Models/User.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Model = use('Model') 4 | const Hash = use('Hash') 5 | 6 | class User extends Model { 7 | static boot () { 8 | super.boot() 9 | 10 | this.addHook('beforeSave', async userInstance => { 11 | if (userInstance.dirty.password) { 12 | userInstance.password = await Hash.make(userInstance.password) 13 | } 14 | }) 15 | } 16 | 17 | tokens () { 18 | return this.hasMany('App/Models/Token') 19 | } 20 | 21 | properties () { 22 | return this.hasMany('App/Models/Property') 23 | } 24 | } 25 | 26 | module.exports = User 27 | -------------------------------------------------------------------------------- /config/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Env = use('Env') 4 | 5 | module.exports = { 6 | 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Application Name 10 | |-------------------------------------------------------------------------- 11 | | 12 | | This value is the name of your application and can used when you 13 | | need to place the application's name in a email, view or 14 | | other location. 15 | | 16 | */ 17 | 18 | name: Env.get('APP_NAME', 'AdonisJs'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | App Key 23 | |-------------------------------------------------------------------------- 24 | | 25 | | App key is a randomly generated 16 or 32 characters long string required 26 | | to encrypted cookies, sessions and other sensitive data. 27 | | 28 | */ 29 | appKey: Env.get('APP_KEY'), 30 | 31 | http: { 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Allow Method Spoofing 35 | |-------------------------------------------------------------------------- 36 | | 37 | | Method spoofing allows to make requests by spoofing the http verb. 38 | | Which means you can make a GET request but instruct the server to 39 | | treat as a POST or PUT request. If you want this feature, set the 40 | | below value to true. 41 | | 42 | */ 43 | allowMethodSpoofing: true, 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Trust Proxy 48 | |-------------------------------------------------------------------------- 49 | | 50 | | Trust proxy defines whether X-Forwaded-* headers should be trusted or not. 51 | | When your application is behind a proxy server like nginx, these values 52 | | are set automatically and should be trusted. Apart from setting it 53 | | to true or false Adonis supports handful or ways to allow proxy 54 | | values. Read documentation for that. 55 | | 56 | */ 57 | trustProxy: false, 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Subdomains 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Offset to be used for returning subdomains for a given request.For 65 | | majority of applications it will be 2, until you have nested 66 | | sudomains. 67 | | cheatsheet.adonisjs.com - offset - 2 68 | | virk.cheatsheet.adonisjs.com - offset - 3 69 | | 70 | */ 71 | subdomainOffset: 2, 72 | 73 | /* 74 | |-------------------------------------------------------------------------- 75 | | JSONP Callback 76 | |-------------------------------------------------------------------------- 77 | | 78 | | Default jsonp callback to be used when callback query string is missing 79 | | in request url. 80 | | 81 | */ 82 | jsonpCallback: 'callback', 83 | 84 | /* 85 | |-------------------------------------------------------------------------- 86 | | Etag 87 | |-------------------------------------------------------------------------- 88 | | 89 | | Set etag on all HTTP response. In order to disable for selected routes, 90 | | you can call the `response.send` with an options object as follows. 91 | | 92 | | response.send('Hello', { ignoreEtag: true }) 93 | | 94 | */ 95 | etag: false 96 | }, 97 | 98 | static: { 99 | /* 100 | |-------------------------------------------------------------------------- 101 | | Dot Files 102 | |-------------------------------------------------------------------------- 103 | | 104 | | Define how to treat dot files when trying to server static resources. 105 | | By default it is set to ignore, which will pretend that dotfiles 106 | | does not exists. 107 | | 108 | | Can be one of the following 109 | | ignore, deny, allow 110 | | 111 | */ 112 | dotfiles: 'ignore', 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | ETag 117 | |-------------------------------------------------------------------------- 118 | | 119 | | Enable or disable etag generation 120 | | 121 | */ 122 | etag: true, 123 | 124 | /* 125 | |-------------------------------------------------------------------------- 126 | | Extensions 127 | |-------------------------------------------------------------------------- 128 | | 129 | | Set file extension fallbacks. When set, if a file is not found, the given 130 | | extensions will be added to the file name and search for. The first 131 | | that exists will be served. Example: ['html', 'htm']. 132 | | 133 | */ 134 | extensions: false 135 | }, 136 | 137 | locales: { 138 | /* 139 | |-------------------------------------------------------------------------- 140 | | Loader 141 | |-------------------------------------------------------------------------- 142 | | 143 | | The loader to be used for fetching and updating locales. Below is the 144 | | list of available options. 145 | | 146 | | file, database 147 | | 148 | */ 149 | loader: 'file', 150 | 151 | /* 152 | |-------------------------------------------------------------------------- 153 | | Default Locale 154 | |-------------------------------------------------------------------------- 155 | | 156 | | Default locale to be used by Antl provider. You can always switch drivers 157 | | in runtime or use the official Antl middleware to detect the driver 158 | | based on HTTP headers/query string. 159 | | 160 | */ 161 | locale: 'en' 162 | }, 163 | 164 | logger: { 165 | /* 166 | |-------------------------------------------------------------------------- 167 | | Transport 168 | |-------------------------------------------------------------------------- 169 | | 170 | | Transport to be used for logging messages. You can have multiple 171 | | transports using same driver. 172 | | 173 | | Available drivers are: `file` and `console`. 174 | | 175 | */ 176 | transport: 'console', 177 | 178 | /* 179 | |-------------------------------------------------------------------------- 180 | | Console Transport 181 | |-------------------------------------------------------------------------- 182 | | 183 | | Using `console` driver for logging. This driver writes to `stdout` 184 | | and `stderr` 185 | | 186 | */ 187 | console: { 188 | driver: 'console', 189 | name: 'adonis-app', 190 | level: 'info' 191 | }, 192 | 193 | /* 194 | |-------------------------------------------------------------------------- 195 | | File Transport 196 | |-------------------------------------------------------------------------- 197 | | 198 | | File transport uses file driver and writes log messages for a given 199 | | file inside `tmp` directory for your app. 200 | | 201 | | For a different directory, set an absolute path for the filename. 202 | | 203 | */ 204 | file: { 205 | driver: 'file', 206 | name: 'adonis-app', 207 | filename: 'adonis.log', 208 | level: 'info' 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /config/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Env = use('Env') 4 | 5 | module.exports = { 6 | /* 7 | |-------------------------------------------------------------------------- 8 | | Authenticator 9 | |-------------------------------------------------------------------------- 10 | | 11 | | Authentication is a combination of serializer and scheme with extra 12 | | config to define on how to authenticate a user. 13 | | 14 | | Available Schemes - basic, session, jwt, api 15 | | Available Serializers - lucid, database 16 | | 17 | */ 18 | authenticator: 'jwt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Session 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Session authenticator makes use of sessions to authenticate a user. 26 | | Session authentication is always persistent. 27 | | 28 | */ 29 | session: { 30 | serializer: 'lucid', 31 | model: 'App/Models/User', 32 | scheme: 'session', 33 | uid: 'email', 34 | password: 'password' 35 | }, 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Basic Auth 40 | |-------------------------------------------------------------------------- 41 | | 42 | | The basic auth authenticator uses basic auth header to authenticate a 43 | | user. 44 | | 45 | | NOTE: 46 | | This scheme is not persistent and users are supposed to pass 47 | | login credentials on each request. 48 | | 49 | */ 50 | basic: { 51 | serializer: 'lucid', 52 | model: 'App/Models/User', 53 | scheme: 'basic', 54 | uid: 'email', 55 | password: 'password' 56 | }, 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Jwt 61 | |-------------------------------------------------------------------------- 62 | | 63 | | The jwt authenticator works by passing a jwt token on each HTTP request 64 | | via HTTP `Authorization` header. 65 | | 66 | */ 67 | jwt: { 68 | serializer: 'lucid', 69 | model: 'App/Models/User', 70 | scheme: 'jwt', 71 | uid: 'email', 72 | password: 'password', 73 | options: { 74 | secret: Env.get('APP_KEY') 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config/bodyParser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | JSON Parser 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Below settings are applied when request body contains JSON payload. If 10 | | you want body parser to ignore JSON payload, 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 `scrict` 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 seperated 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 seperated list of allowed methods 29 | | Array - An array of allowed methods 30 | | 31 | */ 32 | methods: ['GET', 'PUT', 'POST'], 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 on of the following. 41 | | 42 | | Boolean: true - Allow current request headers 43 | | Boolean: false - Disallow all 44 | | String - Comma seperated 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 on of the following. 59 | | 60 | | Boolean: false - Disallow all 61 | | String: Comma seperated 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 | const Env = use('Env') 4 | const Helpers = use('Helpers') 5 | 6 | module.exports = { 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Default Connection 10 | |-------------------------------------------------------------------------- 11 | | 12 | | Connection defines the default connection settings to be used while 13 | | interacting with SQL databases. 14 | | 15 | */ 16 | connection: Env.get('DB_CONNECTION', 'sqlite'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Sqlite 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Sqlite is a flat file database and can be good choice under development 24 | | environment. 25 | | 26 | | npm i --save sqlite3 27 | | 28 | */ 29 | sqlite: { 30 | client: 'sqlite3', 31 | connection: { 32 | filename: Helpers.databasePath(`${Env.get('DB_DATABASE', 'development')}.sqlite`) 33 | }, 34 | useNullAsDefault: true, 35 | debug: Env.get('DB_DEBUG', false) 36 | }, 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | MySQL 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Here we define connection settings for MySQL database. 44 | | 45 | | npm i --save mysql 46 | | 47 | */ 48 | mysql: { 49 | client: 'mysql', 50 | connection: { 51 | host: Env.get('DB_HOST', 'localhost'), 52 | port: Env.get('DB_PORT', ''), 53 | user: Env.get('DB_USER', 'root'), 54 | password: Env.get('DB_PASSWORD', ''), 55 | database: Env.get('DB_DATABASE', 'adonis') 56 | }, 57 | debug: Env.get('DB_DEBUG', false) 58 | }, 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | PostgreSQL 63 | |-------------------------------------------------------------------------- 64 | | 65 | | Here we define connection settings for PostgreSQL database. 66 | | 67 | | npm i --save pg 68 | | 69 | */ 70 | pg: { 71 | client: 'pg', 72 | connection: { 73 | host: Env.get('DB_HOST', 'localhost'), 74 | port: Env.get('DB_PORT', ''), 75 | user: Env.get('DB_USER', 'root'), 76 | password: Env.get('DB_PASSWORD', ''), 77 | database: Env.get('DB_DATABASE', 'adonis') 78 | }, 79 | debug: Env.get('DB_DEBUG', false) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /config/hash.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Env = use('Env') 4 | 5 | module.exports = { 6 | /* 7 | |-------------------------------------------------------------------------- 8 | | Driver 9 | |-------------------------------------------------------------------------- 10 | | 11 | | Driver to be used for hashing values. The same driver is used by the 12 | | auth module too. 13 | | 14 | */ 15 | driver: Env.get('HASH_DRIVER', 'bcrypt'), 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Bcrypt 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Config related to bcrypt hashing. https://www.npmjs.com/package/bcrypt 23 | | package is used internally. 24 | | 25 | */ 26 | bcrypt: { 27 | rounds: 10 28 | }, 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Argon 33 | |-------------------------------------------------------------------------- 34 | | 35 | | Config related to argon. https://www.npmjs.com/package/argon2 package is 36 | | used internally. 37 | | 38 | | Since argon is optional, you will have to install the dependency yourself 39 | | 40 | |============================================================================ 41 | | npm i argon2 42 | |============================================================================ 43 | | 44 | */ 45 | argon: { 46 | type: 1 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /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 | // const Factory = use('Factory') 15 | 16 | /** 17 | Factory.blueprint('App/Models/User', (faker) => { 18 | return { 19 | username: faker.username() 20 | } 21 | }) 22 | */ 23 | -------------------------------------------------------------------------------- /database/migrations/1503250034279_user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Schema = use('Schema') 4 | 5 | class UserSchema extends Schema { 6 | up () { 7 | this.create('users', (table) => { 8 | table.increments() 9 | table.string('username', 80).notNullable().unique() 10 | table.string('email', 254).notNullable().unique() 11 | table.string('password', 60).notNullable() 12 | table.timestamps() 13 | }) 14 | } 15 | 16 | down () { 17 | this.drop('users') 18 | } 19 | } 20 | 21 | module.exports = UserSchema 22 | -------------------------------------------------------------------------------- /database/migrations/1503250034280_token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Schema = use('Schema') 4 | 5 | class TokensSchema extends Schema { 6 | up () { 7 | this.create('tokens', (table) => { 8 | table.increments() 9 | table.integer('user_id').unsigned().references('id').inTable('users') 10 | table.string('token', 255).notNullable().unique().index() 11 | table.string('type', 80).notNullable() 12 | table.boolean('is_revoked').defaultTo(false) 13 | table.timestamps() 14 | }) 15 | } 16 | 17 | down () { 18 | this.drop('tokens') 19 | } 20 | } 21 | 22 | module.exports = TokensSchema 23 | -------------------------------------------------------------------------------- /database/migrations/1530569796148_property_schema.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Schema = use('Schema') 4 | 5 | class PropertySchema extends Schema { 6 | up () { 7 | this.create('properties', table => { 8 | table.increments() 9 | table 10 | .integer('user_id') 11 | .references('id') 12 | .inTable('users') 13 | .onUpdate('CASCADE') 14 | .onDelete('CASCADE') 15 | table.string('title').notNullable() 16 | table.string('address').notNullable() 17 | table.decimal('price').notNullable() 18 | table.decimal('latitude', 9, 6).notNullable() 19 | table.decimal('longitude', 9, 6).notNullable() 20 | table.timestamps() 21 | }) 22 | } 23 | 24 | down () { 25 | this.drop('properties') 26 | } 27 | } 28 | 29 | module.exports = PropertySchema 30 | -------------------------------------------------------------------------------- /database/migrations/1530572844377_image_schema.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Schema = use('Schema') 4 | 5 | class ImageSchema extends Schema { 6 | up () { 7 | this.create('images', table => { 8 | table.increments() 9 | table 10 | .integer('property_id') 11 | .references('id') 12 | .inTable('properties') 13 | .onUpdate('CASCADE') 14 | .onDelete('CASCADE') 15 | table.string('path').notNullable() 16 | table.timestamps() 17 | }) 18 | } 19 | 20 | down () { 21 | this.drop('images') 22 | } 23 | } 24 | 25 | module.exports = ImageSchema 26 | -------------------------------------------------------------------------------- /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.2", 20 | "@adonisjs/auth": "^3.0.5", 21 | "@adonisjs/bodyparser": "^2.0.3", 22 | "@adonisjs/cors": "^1.0.6", 23 | "@adonisjs/fold": "^4.0.8", 24 | "@adonisjs/framework": "^5.0.7", 25 | "@adonisjs/ignitor": "^2.0.6", 26 | "@adonisjs/lucid": "^5.0.4", 27 | "pg": "^7.4.3" 28 | }, 29 | "devDependencies": { 30 | "eslint": "^5.0.1", 31 | "eslint-config-standard": "^11.0.0", 32 | "eslint-plugin-import": "^2.13.0", 33 | "eslint-plugin-node": "^6.0.1", 34 | "eslint-plugin-promise": "^3.8.0", 35 | "eslint-plugin-standard": "^3.1.0" 36 | }, 37 | "autoload": { 38 | "App": "./app" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Http server 6 | |-------------------------------------------------------------------------- 7 | | 8 | | This file bootstrap 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 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 | ] 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Ace Providers 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Ace providers are required only when running ace commands. For example 27 | | Providers for migrations, tests etc. 28 | | 29 | */ 30 | const aceProviders = [ 31 | '@adonisjs/lucid/providers/MigrationsProvider' 32 | ] 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Aliases 37 | |-------------------------------------------------------------------------- 38 | | 39 | | Aliases are short unique names for IoC container bindings. You are free 40 | | to create your own aliases. 41 | | 42 | | For example: 43 | | { Route: 'Adonis/Src/Route' } 44 | | 45 | */ 46 | const aliases = {} 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Commands 51 | |-------------------------------------------------------------------------- 52 | | 53 | | Here you store ace commands for your package 54 | | 55 | */ 56 | const commands = [] 57 | 58 | module.exports = { providers, aceProviders, aliases, commands } 59 | -------------------------------------------------------------------------------- /start/kernel.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Server = use('Server') 4 | 5 | /* 6 | |-------------------------------------------------------------------------- 7 | | Global Middleware 8 | |-------------------------------------------------------------------------- 9 | | 10 | | Global middleware are executed on each http request only when the routes 11 | | match. 12 | | 13 | */ 14 | const globalMiddleware = [ 15 | 'Adonis/Middleware/BodyParser' 16 | ] 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Named Middleware 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Named middleware is key/value object to conditionally add middleware on 24 | | specific routes or group of routes. 25 | | 26 | | // define 27 | | { 28 | | auth: 'Adonis/Middleware/Auth' 29 | | } 30 | | 31 | | // use 32 | | Route.get().middleware('auth') 33 | | 34 | */ 35 | const namedMiddleware = { 36 | auth: 'Adonis/Middleware/Auth' 37 | } 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Server Middleware 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Server levl middleware are executed even when route for a given URL is 45 | | not registered. Features like `static assets` and `cors` needs better 46 | | control over request lifecycle. 47 | | 48 | */ 49 | const serverMiddleware = [ 50 | 'Adonis/Middleware/Static', 51 | 'Adonis/Middleware/Cors' 52 | ] 53 | 54 | Server 55 | .registerGlobal(globalMiddleware) 56 | .registerNamed(namedMiddleware) 57 | .use(serverMiddleware) 58 | -------------------------------------------------------------------------------- /start/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Route = use('Route') 4 | 5 | Route.post('/users', 'UserController.create') 6 | Route.post('/sessions', 'SessionController.create') 7 | 8 | Route.resource('properties', 'PropertyController') 9 | .apiOnly() 10 | .middleware('auth') 11 | 12 | Route.get('images/:path', 'ImageController.show').middleware('auth') 13 | Route.post('properties/:id/images', 'ImageController.store').middleware('auth') 14 | --------------------------------------------------------------------------------