├── .adonisrc.json ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── ace ├── ace-manifest.json ├── app ├── Controllers │ └── Http │ │ ├── AuthController.ts │ │ ├── PagesController.ts │ │ └── TasksController.ts ├── Exceptions │ └── Handler.ts ├── Middleware │ ├── Auth.ts │ ├── Guest.ts │ └── SilentAuth.ts └── Models │ ├── Task.ts │ └── User.ts ├── commands └── index.ts ├── config ├── app.ts ├── auth.ts ├── bodyparser.ts ├── cors.ts ├── database.ts ├── hash.ts ├── session.ts ├── shield.ts └── static.ts ├── contracts ├── auth.ts ├── events.ts └── hash.ts ├── database └── migrations │ ├── 1587988332388_users.ts │ └── 1591657469878_tasks.ts ├── env.ts ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── providers └── AppProvider.ts ├── public └── favicon.ico ├── resources └── views │ ├── about.edge │ ├── auth │ ├── login.edge │ └── register.edge │ ├── contact.edge │ ├── errors │ ├── not-found.edge │ ├── server-error.edge │ └── unauthorized.edge │ ├── layouts │ ├── app.edge │ └── partials │ │ └── _navbar.edge │ ├── tasks │ └── index.edge │ └── welcome.edge ├── server.ts ├── start ├── kernel.ts └── routes.ts └── tsconfig.json /.adonisrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript": true, 3 | "commands": [ 4 | "./commands", 5 | "@adonisjs/core/build/commands", 6 | "@adonisjs/lucid/build/commands" 7 | ], 8 | "exceptionHandlerNamespace": "App/Exceptions/Handler", 9 | "aliases": { 10 | "App": "app", 11 | "Contracts": "contracts", 12 | "Config": "config" 13 | }, 14 | "preloads": [ 15 | "./start/routes", 16 | "./start/kernel" 17 | ], 18 | "providers": [ 19 | "./providers/AppProvider", 20 | "@adonisjs/core", 21 | "@adonisjs/session", 22 | "@adonisjs/view", 23 | "@adonisjs/lucid", 24 | "@adonisjs/shield", 25 | "@adonisjs/auth" 26 | ], 27 | "metaFiles": [ 28 | ".env", 29 | ".adonisrc.json", 30 | "resources/views/**/*.edge", 31 | "public/**" 32 | ] 33 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*] 3 | indent_style = space 4 | indent_size = 2 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.json] 11 | insert_final_newline = ignore 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PORT=3333 2 | HOST=127.0.0.1 3 | NODE_ENV=development 4 | APP_KEY= 5 | SESSION_DRIVER=cookie 6 | CACHE_VIEWS=false 7 | DB_CONNECTION=mysql 8 | MYSQL_HOST=localhost 9 | MYSQL_PORT=3306 10 | MYSQL_USER=root 11 | MYSQL_PASSWORD= 12 | MYSQL_DB_NAME=taskist 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:adonis/typescriptApp", 4 | "prettier" 5 | ], 6 | "plugins": [ 7 | "prettier" 8 | ], 9 | "rules": { 10 | "prettier/prettier": [ 11 | "error" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | .vscode 5 | .DS_STORE 6 | .env 7 | tmp -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": false, 4 | "singleQuote": true, 5 | "useTabs": false, 6 | "quoteProps": "consistent", 7 | "bracketSpacing": true, 8 | "arrowParens": "always", 9 | "printWidth": 100 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdonisJS 5 from scratch 2 | 3 | Source code for the [AdonisJS 5 from scracth](https://adonismastery.com/courses/adonisjs-5-from-scratch) course project. 4 | 5 | ## Getting started 6 | 7 | Clone the project repository by running the command below: 8 | 9 | ```bash 10 | # using SSH 11 | git clone git@github.com:mezielabs/taskist.git 12 | # using HTTPS 13 | git clone https://github.com/mezielabs/taskist.git 14 | ``` 15 | 16 | After cloning, run: 17 | 18 | ```bash 19 | npm install 20 | ``` 21 | 22 | Duplicate `.env.example`: 23 | 24 | ```bash 25 | cp .env.example .env 26 | ``` 27 | 28 | Generate `APP_KEY`: 29 | 30 | ```bash 31 | node ace generate:key 32 | ``` 33 | 34 | > This will output a random string, which you will need to add inside `.env`. 35 | 36 | Update environment variables: 37 | 38 | ```txt 39 | // .env 40 | 41 | APP_KEY=YOUR_GENERATED_KEY_COPIED_FROM_ABOVE 42 | DB_CONNECTION=mysql 43 | MYSQL_HOST=localhost 44 | MYSQL_PORT=3306 45 | MYSQL_USER=YOUR_DATABASE_USERNAME 46 | MYSQL_PASSWORD=YOUR_DATABASE_PASSWORD 47 | MYSQL_DB_NAME=taskist 48 | ``` 49 | 50 | > Remember to update `YOUR_DATABASE_USERNAME` and `YOUR_DATABASE_PASSWORD` with your database details. 51 | 52 | Run the migrations: 53 | 54 | ```bash 55 | node ace migration:run 56 | ``` 57 | 58 | Finally, start the application: 59 | 60 | ```bash 61 | node ace serve 62 | ``` 63 | 64 | and visit [http://0.0.0.0:3333](http://0.0.0.0:3333) to see the application in action. 65 | 66 | ## Learn AdonisJS 67 | 68 | Want to learn how to build projects like this with AdonisJS? Check out [Adonis Mastery](https://adonismastery.com), where you get to learn AdonisJS through practical screencasts. 69 | -------------------------------------------------------------------------------- /ace: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Ace Commands 4 | |-------------------------------------------------------------------------- 5 | | 6 | | This file is the entry point for running ace commands. For typescript 7 | | projects, the ace commands will fallback to the compiled code and 8 | | hence this file has to be executable by node directly. 9 | | 10 | */ 11 | 12 | require('reflect-metadata') 13 | require('source-map-support').install({ handleUncaughtExceptions: false }) 14 | 15 | const { Ignitor } = require('@adonisjs/core/build/src/Ignitor') 16 | new Ignitor(__dirname) 17 | .ace() 18 | .handle(process.argv.slice(2)) 19 | .catch(console.error) 20 | -------------------------------------------------------------------------------- /ace-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "dump:rcfile": { 4 | "settings": {}, 5 | "commandPath": "@adonisjs/core/build/commands/DumpRc", 6 | "commandName": "dump:rcfile", 7 | "description": "Dump contents of .adonisrc.json file along with defaults", 8 | "args": [], 9 | "aliases": [], 10 | "flags": [] 11 | }, 12 | "list:routes": { 13 | "settings": { 14 | "loadApp": true 15 | }, 16 | "commandPath": "@adonisjs/core/build/commands/ListRoutes", 17 | "commandName": "list:routes", 18 | "description": "List application routes", 19 | "args": [], 20 | "aliases": [], 21 | "flags": [ 22 | { 23 | "name": "json", 24 | "propertyName": "json", 25 | "type": "boolean", 26 | "description": "Output as JSON" 27 | } 28 | ] 29 | }, 30 | "generate:key": { 31 | "settings": {}, 32 | "commandPath": "@adonisjs/core/build/commands/GenerateKey", 33 | "commandName": "generate:key", 34 | "description": "Generate a new APP_KEY secret", 35 | "args": [], 36 | "aliases": [], 37 | "flags": [] 38 | }, 39 | "db:seed": { 40 | "settings": { 41 | "loadApp": true 42 | }, 43 | "commandPath": "@adonisjs/lucid/build/commands/DbSeed", 44 | "commandName": "db:seed", 45 | "description": "Execute database seeder files", 46 | "args": [], 47 | "aliases": [], 48 | "flags": [ 49 | { 50 | "name": "connection", 51 | "propertyName": "connection", 52 | "type": "string", 53 | "description": "Define a custom database connection for the seeders", 54 | "alias": "c" 55 | }, 56 | { 57 | "name": "interactive", 58 | "propertyName": "interactive", 59 | "type": "boolean", 60 | "description": "Run seeders in interactive mode", 61 | "alias": "i" 62 | }, 63 | { 64 | "name": "files", 65 | "propertyName": "files", 66 | "type": "array", 67 | "description": "Define a custom set of seeders files names to run", 68 | "alias": "f" 69 | } 70 | ] 71 | }, 72 | "make:model": { 73 | "settings": { 74 | "loadApp": true 75 | }, 76 | "commandPath": "@adonisjs/lucid/build/commands/MakeModel", 77 | "commandName": "make:model", 78 | "description": "Make a new Lucid model", 79 | "args": [ 80 | { 81 | "type": "string", 82 | "propertyName": "name", 83 | "name": "name", 84 | "required": true, 85 | "description": "Name of the model class" 86 | } 87 | ], 88 | "aliases": [], 89 | "flags": [ 90 | { 91 | "name": "migration", 92 | "propertyName": "migration", 93 | "type": "boolean", 94 | "alias": "m", 95 | "description": "Generate the migration for the model" 96 | }, 97 | { 98 | "name": "controller", 99 | "propertyName": "controller", 100 | "type": "boolean", 101 | "alias": "c", 102 | "description": "Generate the controller for the model" 103 | } 104 | ] 105 | }, 106 | "make:migration": { 107 | "settings": { 108 | "loadApp": true 109 | }, 110 | "commandPath": "@adonisjs/lucid/build/commands/MakeMigration", 111 | "commandName": "make:migration", 112 | "description": "Make a new migration file", 113 | "args": [ 114 | { 115 | "type": "string", 116 | "propertyName": "name", 117 | "name": "name", 118 | "required": true, 119 | "description": "Name of the migration file" 120 | } 121 | ], 122 | "aliases": [], 123 | "flags": [ 124 | { 125 | "name": "connection", 126 | "propertyName": "connection", 127 | "type": "string", 128 | "description": "The connection flag is used to lookup the directory for the migration file" 129 | }, 130 | { 131 | "name": "folder", 132 | "propertyName": "folder", 133 | "type": "string", 134 | "description": "Pre-select a migration directory" 135 | }, 136 | { 137 | "name": "create", 138 | "propertyName": "create", 139 | "type": "string", 140 | "description": "Define the table name for creating a new table" 141 | }, 142 | { 143 | "name": "table", 144 | "propertyName": "table", 145 | "type": "string", 146 | "description": "Define the table name for altering an existing table" 147 | } 148 | ] 149 | }, 150 | "make:seeder": { 151 | "settings": {}, 152 | "commandPath": "@adonisjs/lucid/build/commands/MakeSeeder", 153 | "commandName": "make:seeder", 154 | "description": "Make a new Seeder file", 155 | "args": [ 156 | { 157 | "type": "string", 158 | "propertyName": "name", 159 | "name": "name", 160 | "required": true, 161 | "description": "Name of the seeder class" 162 | } 163 | ], 164 | "aliases": [], 165 | "flags": [] 166 | }, 167 | "migration:run": { 168 | "settings": { 169 | "loadApp": true 170 | }, 171 | "commandPath": "@adonisjs/lucid/build/commands/Migration/Run", 172 | "commandName": "migration:run", 173 | "description": "Run pending migrations", 174 | "args": [], 175 | "aliases": [], 176 | "flags": [ 177 | { 178 | "name": "connection", 179 | "propertyName": "connection", 180 | "type": "string", 181 | "description": "Define a custom database connection", 182 | "alias": "c" 183 | }, 184 | { 185 | "name": "force", 186 | "propertyName": "force", 187 | "type": "boolean", 188 | "description": "Explicitly force to run migrations in production" 189 | }, 190 | { 191 | "name": "dry-run", 192 | "propertyName": "dryRun", 193 | "type": "boolean", 194 | "description": "Print SQL queries, instead of running the migrations" 195 | } 196 | ] 197 | }, 198 | "migration:rollback": { 199 | "settings": { 200 | "loadApp": true 201 | }, 202 | "commandPath": "@adonisjs/lucid/build/commands/Migration/Rollback", 203 | "commandName": "migration:rollback", 204 | "description": "Rollback migrations to a given batch number", 205 | "args": [], 206 | "aliases": [], 207 | "flags": [ 208 | { 209 | "name": "connection", 210 | "propertyName": "connection", 211 | "type": "string", 212 | "description": "Define a custom database connection", 213 | "alias": "c" 214 | }, 215 | { 216 | "name": "force", 217 | "propertyName": "force", 218 | "type": "boolean", 219 | "description": "Explictly force to run migrations in production" 220 | }, 221 | { 222 | "name": "dry-run", 223 | "propertyName": "dryRun", 224 | "type": "boolean", 225 | "description": "Print SQL queries, instead of running the migrations" 226 | }, 227 | { 228 | "name": "batch", 229 | "propertyName": "batch", 230 | "type": "number", 231 | "description": "Define custom batch number for rollback. Use 0 to rollback to initial state" 232 | } 233 | ] 234 | }, 235 | "migration:status": { 236 | "settings": { 237 | "loadApp": true 238 | }, 239 | "commandPath": "@adonisjs/lucid/build/commands/Migration/Status", 240 | "commandName": "migration:status", 241 | "description": "Check migrations current status.", 242 | "args": [], 243 | "aliases": [], 244 | "flags": [ 245 | { 246 | "name": "connection", 247 | "propertyName": "connection", 248 | "type": "string", 249 | "description": "Define a custom database connection", 250 | "alias": "c" 251 | } 252 | ] 253 | } 254 | }, 255 | "aliases": {} 256 | } 257 | -------------------------------------------------------------------------------- /app/Controllers/Http/AuthController.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | import { rules, schema } from '@ioc:Adonis/Core/Validator' 3 | import User from 'App/Models/User' 4 | 5 | export default class AuthController { 6 | public showRegister({ view }: HttpContextContract) { 7 | return view.render('auth/register') 8 | } 9 | 10 | public async register({ request, auth, response }: HttpContextContract) { 11 | const validationSchema = schema.create({ 12 | name: schema.string({ trim: true }), 13 | email: schema.string({ trim: true }, [ 14 | rules.email(), 15 | rules.maxLength(255), 16 | rules.unique({ table: 'users', column: 'email' }), 17 | ]), 18 | password: schema.string({ trim: true }, [rules.confirmed()]), 19 | }) 20 | 21 | const validatedData = await request.validate({ 22 | schema: validationSchema, 23 | }) 24 | 25 | const user = await User.create(validatedData) 26 | 27 | await auth.login(user) 28 | 29 | return response.redirect('/') 30 | } 31 | 32 | public async logout({ auth, response }: HttpContextContract) { 33 | await auth.logout() 34 | 35 | return response.redirect('/') 36 | } 37 | 38 | public showLogin({ view }: HttpContextContract) { 39 | return view.render('auth/login') 40 | } 41 | 42 | public async login({ request, auth, session, response }: HttpContextContract) { 43 | const { email, password } = request.all() 44 | 45 | try { 46 | await auth.attempt(email, password) 47 | 48 | return response.redirect('/') 49 | } catch (error) { 50 | session.flash('notification', "We couldn't verify your credentials.") 51 | 52 | return response.redirect('back') 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Controllers/Http/PagesController.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | 3 | export default class PagesController { 4 | public home({ view }: HttpContextContract) { 5 | return view.render('tasks/index') 6 | } 7 | 8 | public about({ view, params }: HttpContextContract) { 9 | const name = params.name 10 | 11 | return view.render('about', { name }) 12 | } 13 | 14 | public contact({ view }: HttpContextContract) { 15 | return view.render('contact') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Controllers/Http/TasksController.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | import { rules, schema } from '@ioc:Adonis/Core/Validator' 3 | import Task from 'App/Models/Task' 4 | 5 | export default class TasksController { 6 | public async index({ view, auth }: HttpContextContract) { 7 | const user = auth.user 8 | await user?.load('tasks') 9 | 10 | return view.render('tasks/index', { tasks: user?.tasks }) 11 | } 12 | 13 | public async store({ request, response, session, auth }: HttpContextContract) { 14 | const validationSchema = schema.create({ 15 | title: schema.string({ trim: true }, [rules.maxLength(255)]), 16 | }) 17 | 18 | const validatedData = await request.validate({ 19 | schema: validationSchema, 20 | messages: { 21 | 'title.required': 'Enter task title', 22 | 'title.maxLength': 'Task title can not exceed 255 character', 23 | }, 24 | }) 25 | 26 | await auth.user?.related('tasks').create({ 27 | title: validatedData.title, 28 | }) 29 | 30 | session.flash('notification', 'Task added!') 31 | 32 | return response.redirect('back') 33 | } 34 | 35 | public async update({ request, response, session, params }: HttpContextContract) { 36 | const task = await Task.findOrFail(params.id) 37 | 38 | task.isCompleted = !!request.input('completed') 39 | await task.save() 40 | 41 | session.flash('notification', 'Task updated!') 42 | 43 | return response.redirect('back') 44 | } 45 | 46 | public async destroy({ params, session, response }: HttpContextContract) { 47 | const task = await Task.findOrFail(params.id) 48 | 49 | await task.delete() 50 | 51 | session.flash('notification', 'Task deleted!') 52 | 53 | return response.redirect('back') 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Http Exception Handler 4 | |-------------------------------------------------------------------------- 5 | | 6 | | AdonisJs will forward all exceptions occurred during an HTTP request to 7 | | the following class. You can learn more about exception handling by 8 | | reading docs. 9 | | 10 | | The exception handler extends a base `HttpExceptionHandler` which is not 11 | | mandatory, however it can do lot of heavy lifting to handle the errors 12 | | properly. 13 | | 14 | */ 15 | 16 | import Logger from '@ioc:Adonis/Core/Logger' 17 | import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler' 18 | 19 | export default class ExceptionHandler extends HttpExceptionHandler { 20 | protected statusPages = { 21 | '403': 'errors/unauthorized', 22 | '404': 'errors/not-found', 23 | '500..599': 'errors/server-error', 24 | } 25 | 26 | constructor() { 27 | super(Logger) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Middleware/Auth.ts: -------------------------------------------------------------------------------- 1 | import { GuardsList } from '@ioc:Adonis/Addons/Auth' 2 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 3 | import { AuthenticationException } from '@adonisjs/auth/build/standalone' 4 | 5 | /** 6 | * Auth middleware is meant to restrict un-authenticated access to a given route 7 | * or a group of routes. 8 | * 9 | * You must register this middleware inside `start/kernel.ts` file under the list 10 | * of named middleware. 11 | */ 12 | export default class AuthMiddleware { 13 | /** 14 | * The URL to redirect to when request is Unauthorized 15 | */ 16 | protected redirectTo = '/login' 17 | 18 | /** 19 | * Authenticates the current HTTP request against a custom set of defined 20 | * guards. 21 | * 22 | * The authentication loop stops as soon as the user is authenticated using any 23 | * of the mentioned guards and that guard will be used by the rest of the code 24 | * during the current request. 25 | */ 26 | protected async authenticate(auth: HttpContextContract['auth'], guards: (keyof GuardsList)[]) { 27 | /** 28 | * Hold reference to the guard last attempted within the for loop. We pass 29 | * the reference of the guard to the "AuthenticationException", so that 30 | * it can decide the correct response behavior based upon the guard 31 | * driver 32 | */ 33 | let guardLastAttempted: string | undefined 34 | 35 | for (let guard of guards) { 36 | guardLastAttempted = guard 37 | 38 | if (await auth.use(guard).check()) { 39 | /** 40 | * Instruct auth to use the given guard as the default guard for 41 | * the rest of the request, since the user authenticated 42 | * succeeded here 43 | */ 44 | auth.defaultGuard = guard 45 | return true 46 | } 47 | } 48 | 49 | /** 50 | * Unable to authenticate using any guard 51 | */ 52 | throw new AuthenticationException( 53 | 'Unauthorized access', 54 | 'E_UNAUTHORIZED_ACCESS', 55 | guardLastAttempted, 56 | this.redirectTo 57 | ) 58 | } 59 | 60 | /** 61 | * Handle request 62 | */ 63 | public async handle( 64 | { auth }: HttpContextContract, 65 | next: () => Promise, 66 | customGuards: (keyof GuardsList)[] 67 | ) { 68 | /** 69 | * Uses the user defined guards or the default guard mentioned in 70 | * the config file 71 | */ 72 | const guards = customGuards.length ? customGuards : [auth.name] 73 | await this.authenticate(auth, guards) 74 | await next() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/Middleware/Guest.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | 3 | export default class Guest { 4 | public async handle({ auth, response }: HttpContextContract, next: () => Promise) { 5 | if (auth.isLoggedIn) { 6 | return response.redirect('/') 7 | } 8 | 9 | await next() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/Middleware/SilentAuth.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | 3 | /** 4 | * Silent auth middleware can be used as a global middleware to silent check 5 | * if the user is logged-in or not. 6 | * 7 | * The request continues as usual, even when the user is not logged-in. 8 | */ 9 | export default class SilentAuthMiddleware { 10 | /** 11 | * Handle request 12 | */ 13 | public async handle({ auth }: HttpContextContract, next: () => Promise) { 14 | /** 15 | * Check if user is logged-in or not. If yes, then `ctx.auth.user` will be 16 | * set to the instance of the currently logged in user. 17 | */ 18 | await auth.check() 19 | await next() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Models/Task.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, column, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm' 2 | import { DateTime } from 'luxon' 3 | import User from './User' 4 | 5 | export default class Task extends BaseModel { 6 | @column({ isPrimary: true }) 7 | public id: number 8 | 9 | @column() 10 | public userId: number 11 | 12 | @column() 13 | public title: string 14 | 15 | @column() 16 | public isCompleted: boolean 17 | 18 | @column.dateTime({ autoCreate: true }) 19 | public createdAt: DateTime 20 | 21 | @column.dateTime({ autoCreate: true, autoUpdate: true }) 22 | public updatedAt: DateTime 23 | 24 | @belongsTo(() => User) 25 | public user: BelongsTo 26 | } 27 | -------------------------------------------------------------------------------- /app/Models/User.ts: -------------------------------------------------------------------------------- 1 | import Hash from '@ioc:Adonis/Core/Hash' 2 | import { BaseModel, beforeSave, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm' 3 | import { DateTime } from 'luxon' 4 | import Task from './Task' 5 | 6 | export default class User extends BaseModel { 7 | @column({ isPrimary: true }) 8 | public id: number 9 | 10 | @column() 11 | public name: string 12 | 13 | @column() 14 | public email: string 15 | 16 | @column() 17 | public password: string 18 | 19 | @column() 20 | public rememberMeToken?: string 21 | 22 | @column.dateTime({ autoCreate: true }) 23 | public createdAt: DateTime 24 | 25 | @column.dateTime({ autoCreate: true, autoUpdate: true }) 26 | public updatedAt: DateTime 27 | 28 | @beforeSave() 29 | public static async hashPassword(user: User) { 30 | if (user.$dirty.password) { 31 | user.password = await Hash.make(user.password) 32 | } 33 | } 34 | 35 | @hasMany(() => Task) 36 | public tasks: HasMany 37 | } 38 | -------------------------------------------------------------------------------- /commands/index.ts: -------------------------------------------------------------------------------- 1 | import { listDirectoryFiles } from '@adonisjs/core/build/standalone' 2 | import Application from '@ioc:Adonis/Core/Application' 3 | 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Exporting an array of commands 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Instead of manually exporting each file from this directory, we use the 10 | | helper `listDirectoryFiles` to recursively collect and export an array 11 | | of filenames. 12 | | 13 | | Couple of things to note: 14 | | 15 | | 1. The file path must be relative from the project root and not this directory. 16 | | 2. We must ignore this file to avoid getting into an infinite loop 17 | | 18 | */ 19 | export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index']) 20 | -------------------------------------------------------------------------------- /config/app.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JfefZ 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import Env from '@ioc:Adonis/Core/Env' 9 | import { LoggerConfig } from '@ioc:Adonis/Core/Logger' 10 | import { ProfilerConfig } from '@ioc:Adonis/Core/Profiler' 11 | import { RequestConfig } from '@ioc:Adonis/Core/Request' 12 | import { ResponseConfig } from '@ioc:Adonis/Core/Response' 13 | import proxyAddr from 'proxy-addr' 14 | 15 | type HttpConfig = RequestConfig & ResponseConfig 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Application secret key 20 | |-------------------------------------------------------------------------- 21 | | 22 | | The secret to encrypt and sign different values in your application. 23 | | Make sure to keep the `APP_KEY` as an environment variable and secure. 24 | | 25 | | Note: Changing the application key for an existing app will make all 26 | | the cookies invalid and also the existing encrypted data will not 27 | | be decrypted. 28 | | 29 | */ 30 | export const appKey: string = Env.get('APP_KEY') as string 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Http server configuration 35 | |-------------------------------------------------------------------------- 36 | | 37 | | The configuration for the HTTP(s) server. Make sure to go through all 38 | | the config properties to make keep server secure. 39 | | 40 | */ 41 | export const http: HttpConfig = { 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Allow method spoofing 45 | |-------------------------------------------------------------------------- 46 | | 47 | | Method spoofing enables defining custom HTTP methods using a query string 48 | | `_method`. This is usually required when you are making traditional 49 | | form requests and wants to use HTTP verbs like `PUT`, `DELETE` and 50 | | so on. 51 | | 52 | */ 53 | allowMethodSpoofing: true, 54 | 55 | /* 56 | |-------------------------------------------------------------------------- 57 | | Subdomain offset 58 | |-------------------------------------------------------------------------- 59 | */ 60 | subdomainOffset: 2, 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Request Ids 65 | |-------------------------------------------------------------------------- 66 | | 67 | | Setting this value to `true` will generate a unique request id for each 68 | | HTTP request and set it as `x-request-id` header. 69 | | 70 | */ 71 | generateRequestId: false, 72 | 73 | /* 74 | |-------------------------------------------------------------------------- 75 | | Trusting proxy servers 76 | |-------------------------------------------------------------------------- 77 | | 78 | | Define the proxy servers that AdonisJs must trust for reading `X-Forwarded` 79 | | headers. 80 | | 81 | */ 82 | trustProxy: proxyAddr.compile('loopback'), 83 | 84 | /* 85 | |-------------------------------------------------------------------------- 86 | | Generating Etag 87 | |-------------------------------------------------------------------------- 88 | | 89 | | Whether or not to generate an etag for every response. 90 | | 91 | */ 92 | etag: false, 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | JSONP Callback 97 | |-------------------------------------------------------------------------- 98 | */ 99 | jsonpCallbackName: 'callback', 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Cookie settings 104 | |-------------------------------------------------------------------------- 105 | */ 106 | cookie: { 107 | domain: '', 108 | path: '/', 109 | maxAge: '2h', 110 | httpOnly: true, 111 | secure: false, 112 | sameSite: false, 113 | }, 114 | } 115 | 116 | /* 117 | |-------------------------------------------------------------------------- 118 | | Logger 119 | |-------------------------------------------------------------------------- 120 | */ 121 | export const logger: LoggerConfig = { 122 | /* 123 | |-------------------------------------------------------------------------- 124 | | Application name 125 | |-------------------------------------------------------------------------- 126 | | 127 | | The name of the application you want to add to the log. It is recommended 128 | | to always have app name in every log line. 129 | | 130 | | The `APP_NAME` environment variable is automatically set by AdonisJS by 131 | | reading the `name` property from the `package.json` file. 132 | | 133 | */ 134 | name: Env.get('APP_NAME') as string, 135 | 136 | /* 137 | |-------------------------------------------------------------------------- 138 | | Toggle logger 139 | |-------------------------------------------------------------------------- 140 | | 141 | | Enable or disable logger application wide 142 | | 143 | */ 144 | enabled: true, 145 | 146 | /* 147 | |-------------------------------------------------------------------------- 148 | | Logging level 149 | |-------------------------------------------------------------------------- 150 | | 151 | | The level from which you want the logger to flush logs. It is recommended 152 | | to make use of the environment variable, so that you can define log levels 153 | | at deployment level and not code level. 154 | | 155 | */ 156 | level: Env.get('LOG_LEVEL', 'info') as string, 157 | 158 | /* 159 | |-------------------------------------------------------------------------- 160 | | Pretty print 161 | |-------------------------------------------------------------------------- 162 | | 163 | | It is highly advised NOT to use `prettyPrint` in production, since it 164 | | can have huge impact on performance. 165 | | 166 | */ 167 | prettyPrint: Env.get('NODE_ENV') === 'development', 168 | } 169 | 170 | /* 171 | |-------------------------------------------------------------------------- 172 | | Profiler 173 | |-------------------------------------------------------------------------- 174 | */ 175 | export const profiler: ProfilerConfig = { 176 | /* 177 | |-------------------------------------------------------------------------- 178 | | Toggle profiler 179 | |-------------------------------------------------------------------------- 180 | | 181 | | Enable or disable profiler 182 | | 183 | */ 184 | enabled: true, 185 | 186 | /* 187 | |-------------------------------------------------------------------------- 188 | | Blacklist actions/row labels 189 | |-------------------------------------------------------------------------- 190 | | 191 | | Define an array of actions or row labels that you want to disable from 192 | | getting profiled. 193 | | 194 | */ 195 | blacklist: [], 196 | 197 | /* 198 | |-------------------------------------------------------------------------- 199 | | Whitelist actions/row labels 200 | |-------------------------------------------------------------------------- 201 | | 202 | | Define an array of actions or row labels that you want to whitelist for 203 | | the profiler. When whitelist is defined, then `blacklist` is ignored. 204 | | 205 | */ 206 | whitelist: [], 207 | } 208 | -------------------------------------------------------------------------------- /config/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JvyKy 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import { AuthConfig } from '@ioc:Adonis/Addons/Auth' 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Authentication Mapping 13 | |-------------------------------------------------------------------------- 14 | | 15 | | List of available authentication mapping. You must first define them 16 | | inside the `contracts/auth.ts` file before mentioning them here. 17 | | 18 | */ 19 | const authConfig: AuthConfig = { 20 | guard: 'web', 21 | guards: { 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Web Guard 25 | |-------------------------------------------------------------------------- 26 | | 27 | | Web guard uses classic old school sessions for authenticating users. 28 | | If you are building a standard web application, it is recommended to 29 | | use web guard with session driver 30 | | 31 | */ 32 | web: { 33 | driver: 'session', 34 | 35 | provider: { 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Driver 39 | |-------------------------------------------------------------------------- 40 | | 41 | | Name of the driver 42 | | 43 | */ 44 | driver: 'lucid', 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Identifier key 49 | |-------------------------------------------------------------------------- 50 | | 51 | | The identifier key is the unique key on the model. In most cases specifying 52 | | the primary key is the right choice. 53 | | 54 | */ 55 | identifierKey: 'id', 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Uids 60 | |-------------------------------------------------------------------------- 61 | | 62 | | Uids are used to search a user against one of the mentioned columns. During 63 | | login, the auth module will search the user mentioned value against one 64 | | of the mentioned columns to find their user record. 65 | | 66 | */ 67 | uids: ['email'], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Model 72 | |-------------------------------------------------------------------------- 73 | | 74 | | The model to use for fetching or finding users 75 | | 76 | */ 77 | model: () => import('App/Models/User'), 78 | }, 79 | }, 80 | }, 81 | } 82 | 83 | export default authConfig 84 | -------------------------------------------------------------------------------- /config/bodyparser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/Jfefn 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import { BodyParserConfig } from '@ioc:Adonis/Core/BodyParser' 9 | 10 | const bodyParserConfig: BodyParserConfig = { 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | White listed methods 14 | |-------------------------------------------------------------------------- 15 | | 16 | | HTTP methods for which body parsing must be performed. It is a good practice 17 | | to avoid body parsing for `GET` requests. 18 | | 19 | */ 20 | whitelistedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'], 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | JSON parser settings 25 | |-------------------------------------------------------------------------- 26 | | 27 | | The settings for the JSON parser. The types defines the request content 28 | | types which gets processed by the JSON parser. 29 | | 30 | */ 31 | json: { 32 | encoding: 'utf-8', 33 | limit: '1mb', 34 | strict: true, 35 | types: [ 36 | 'application/json', 37 | 'application/json-patch+json', 38 | 'application/vnd.api+json', 39 | 'application/csp-report', 40 | ], 41 | }, 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Form parser settings 46 | |-------------------------------------------------------------------------- 47 | | 48 | | The settings for the `application/x-www-form-urlencoded` parser. The types 49 | | defines the request content types which gets processed by the form parser. 50 | | 51 | */ 52 | form: { 53 | encoding: 'utf-8', 54 | limit: '1mb', 55 | queryString: {}, 56 | types: ['application/x-www-form-urlencoded'], 57 | convertEmptyStringsToNull: true, 58 | }, 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | Raw body parser settings 63 | |-------------------------------------------------------------------------- 64 | | 65 | | Raw body just reads the request body stream as a plain text, which you 66 | | can process by hand. This must be used when request body type is not 67 | | supported by the body parser. 68 | | 69 | */ 70 | raw: { 71 | encoding: 'utf-8', 72 | limit: '1mb', 73 | queryString: {}, 74 | types: ['text/*'], 75 | }, 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Multipart parser settings 80 | |-------------------------------------------------------------------------- 81 | | 82 | | The settings for the `multipart/form-data` parser. The types defines the 83 | | request content types which gets processed by the form parser. 84 | | 85 | */ 86 | multipart: { 87 | /* 88 | |-------------------------------------------------------------------------- 89 | | Auto process 90 | |-------------------------------------------------------------------------- 91 | | 92 | | The auto process option will process uploaded files and writes them to 93 | | the `tmp` folder. You can turn it off and then manually use the stream 94 | | to pipe stream to a different destination. 95 | | 96 | | It is recommended to keep `autoProcess=true`. Unless you are processing bigger 97 | | file sizes. 98 | | 99 | */ 100 | autoProcess: true, 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Files to be processed manually 105 | |-------------------------------------------------------------------------- 106 | | 107 | | You can turn off `autoProcess` for certain routes by defining 108 | | routes inside the following array. 109 | | 110 | | NOTE: Make sure the route pattern starts with a leading slash. 111 | | 112 | | Correct 113 | | ```js 114 | | /projects/:id/file 115 | | ``` 116 | | 117 | | Incorrect 118 | | ```js 119 | | projects/:id/file 120 | | ``` 121 | */ 122 | processManually: [], 123 | 124 | /* 125 | |-------------------------------------------------------------------------- 126 | | Temporary file name 127 | |-------------------------------------------------------------------------- 128 | | 129 | | When auto processing is on. We will use this method to compute the temporary 130 | | file name. AdonisJs will compute a unique `tmpPath` for you automatically, 131 | | However, you can also define your own custom method. 132 | | 133 | */ 134 | // tmpFileName () { 135 | // }, 136 | 137 | /* 138 | |-------------------------------------------------------------------------- 139 | | Encoding 140 | |-------------------------------------------------------------------------- 141 | | 142 | | Request body encoding 143 | | 144 | */ 145 | encoding: 'utf-8', 146 | 147 | /* 148 | |-------------------------------------------------------------------------- 149 | | Max Fields 150 | |-------------------------------------------------------------------------- 151 | | 152 | | The maximum number of fields allowed in the request body. The field includes 153 | | text inputs and files both. 154 | | 155 | */ 156 | maxFields: 1000, 157 | 158 | /* 159 | |-------------------------------------------------------------------------- 160 | | Request body limit 161 | |-------------------------------------------------------------------------- 162 | | 163 | | The total limit to the multipart body. This includes all request files 164 | | and fields data. 165 | | 166 | */ 167 | limit: '20mb', 168 | 169 | /* 170 | |-------------------------------------------------------------------------- 171 | | Types 172 | |-------------------------------------------------------------------------- 173 | | 174 | | The types that will be considered and parsed as multipart body. 175 | | 176 | */ 177 | types: ['multipart/form-data'], 178 | convertEmptyStringsToNull: true, 179 | }, 180 | } 181 | 182 | export default bodyParserConfig 183 | -------------------------------------------------------------------------------- /config/cors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JfefC 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import { CorsConfig } from '@ioc:Adonis/Core/Cors' 9 | 10 | const corsConfig: CorsConfig = { 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | Enabled 14 | |-------------------------------------------------------------------------- 15 | | 16 | | A boolean to enable or disable CORS integration from your AdonisJs 17 | | application. 18 | | 19 | | Setting the value to `true` will enable the CORS for all HTTP request. However, 20 | | you can define a function to enable/disable it on per request basis as well. 21 | | 22 | */ 23 | enabled: false, 24 | 25 | // enabled: (request) => request.url().startsWith('/api') 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Origin 30 | |-------------------------------------------------------------------------- 31 | | 32 | | Set a list of origins to be allowed for `Access-Control-Allow-Origin`. 33 | | The value can be one of the following: 34 | | 35 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin 36 | | 37 | | Boolean (true) - Allow current request origin. 38 | | Boolean (false) - Disallow all. 39 | | String - Comma seperated list of allowed origins. 40 | | Array - An array of allowed origins. 41 | | String (*) - A wildcard (*) to allow all request origins. 42 | | Function - Receives the current origin stirng and should return 43 | | one of the above values. 44 | | 45 | */ 46 | origin: true, 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Methods 51 | |-------------------------------------------------------------------------- 52 | | 53 | | An array of allowed HTTP methods for CORS. The `Access-Control-Request-Method` 54 | | is checked against the following list. 55 | | 56 | | Following is the list of default methods. Feel free to add more. 57 | */ 58 | methods: ['GET', 'HEAD', 'POST'], 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | Headers 63 | |-------------------------------------------------------------------------- 64 | | 65 | | List of headers to be allowed for `Access-Control-Allow-Headers` header. 66 | | The value can be one of the following: 67 | | 68 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers 69 | | 70 | | Boolean(true) - Allow all headers mentioned in `Access-Control-Request-Headers`. 71 | | Boolean(false) - Disallow all headers. 72 | | String - Comma seperated list of allowed headers. 73 | | Array - An array of allowed headers. 74 | | Function - Receives the current header and should return one of the above values. 75 | | 76 | */ 77 | headers: true, 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Expose Headers 82 | |-------------------------------------------------------------------------- 83 | | 84 | | A list of headers to be exposed by setting `Access-Control-Expose-Headers`. 85 | | header. By default following 6 simple response headers are exposed. 86 | | 87 | | Cache-Control 88 | | Content-Language 89 | | Content-Type 90 | | Expires 91 | | Last-Modified 92 | | Pragma 93 | | 94 | | In order to add more headers, simply define them inside the following array. 95 | | 96 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers 97 | | 98 | */ 99 | exposeHeaders: [ 100 | 'cache-control', 101 | 'content-language', 102 | 'content-type', 103 | 'expires', 104 | 'last-modified', 105 | 'pragma', 106 | ], 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Credentials 111 | |-------------------------------------------------------------------------- 112 | | 113 | | Toggle `Access-Control-Allow-Credentials` header. If value is set to `true`, 114 | | then header will be set, otherwise not. 115 | | 116 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials 117 | | 118 | */ 119 | credentials: true, 120 | 121 | /* 122 | |-------------------------------------------------------------------------- 123 | | MaxAge 124 | |-------------------------------------------------------------------------- 125 | | 126 | | Define `Access-Control-Max-Age` header in seconds. 127 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age 128 | | 129 | */ 130 | maxAge: 90, 131 | } 132 | 133 | export default corsConfig 134 | -------------------------------------------------------------------------------- /config/database.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JesV9 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import Env from '@ioc:Adonis/Core/Env' 9 | import Application from '@ioc:Adonis/Core/Application' 10 | import { DatabaseConfig } from '@ioc:Adonis/Lucid/Database' 11 | 12 | const databaseConfig: DatabaseConfig = { 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | Connection 16 | |-------------------------------------------------------------------------- 17 | | 18 | | The primary connection for making database queries across the application 19 | | You can use any key from the `connections` object defined in this same 20 | | file. 21 | | 22 | */ 23 | connection: Env.get('DB_CONNECTION', 'sqlite') as string, 24 | 25 | connections: { 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Sqlite 29 | |-------------------------------------------------------------------------- 30 | | 31 | | Configuration for the Sqlite database. Make sure to install the driver 32 | | from npm when using this connection 33 | | 34 | | npm i sqlite3 35 | | 36 | */ 37 | sqlite: { 38 | client: 'sqlite', 39 | connection: { 40 | filename: Application.tmpPath('db.sqlite3'), 41 | }, 42 | useNullAsDefault: true, 43 | healthCheck: false, 44 | }, 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Mysql config 49 | |-------------------------------------------------------------------------- 50 | | 51 | | Configuration for Mysql database. Make sure to install the driver 52 | | from npm when using this connection 53 | | 54 | | npm i mysql 55 | | 56 | */ 57 | mysql: { 58 | client: 'mysql', 59 | connection: { 60 | host: Env.get('MYSQL_HOST'), 61 | port: Env.get('MYSQL_PORT'), 62 | user: Env.get('MYSQL_USER'), 63 | password: Env.get('MYSQL_PASSWORD'), 64 | database: Env.get('MYSQL_DB_NAME'), 65 | }, 66 | healthCheck: false, 67 | debug: false, 68 | }, 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | PostgreSQL config 73 | |-------------------------------------------------------------------------- 74 | | 75 | | Configuration for PostgreSQL database. Make sure to install the driver 76 | | from npm when using this connection 77 | | 78 | | npm i pg 79 | | 80 | */ 81 | pg: { 82 | client: 'pg', 83 | connection: { 84 | host: Env.get('DB_HOST', '127.0.0.1') as string, 85 | port: Number(Env.get('DB_PORT', 5432)), 86 | user: Env.get('DB_USER', 'lucid') as string, 87 | password: Env.get('DB_PASSWORD', 'lucid') as string, 88 | database: Env.get('DB_NAME', 'lucid') as string, 89 | }, 90 | healthCheck: false, 91 | }, 92 | }, 93 | } 94 | 95 | export default databaseConfig 96 | -------------------------------------------------------------------------------- /config/hash.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JfefW 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import Env from '@ioc:Adonis/Core/Env' 9 | import { HashConfig } from '@ioc:Adonis/Core/Hash' 10 | 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | Hash Config 14 | |-------------------------------------------------------------------------- 15 | | 16 | | The `HashConfig` relies on the `HashList` interface which is 17 | | defined inside `contracts` directory. 18 | | 19 | */ 20 | const hashConfig: HashConfig = { 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Default hasher 24 | |-------------------------------------------------------------------------- 25 | | 26 | | By default we make use of the argon hasher to hash values. However, feel 27 | | free to change the default value 28 | | 29 | */ 30 | default: Env.get('HASH_DRIVER', 'argon'), 31 | 32 | list: { 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Argon 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Argon mapping uses the `argon2` driver to hash values. 39 | | 40 | | Make sure you install the underlying dependency for this driver to work. 41 | | https://www.npmjs.com/package/phc-argon2. 42 | | 43 | | npm install phc-argon2 44 | | 45 | */ 46 | argon: { 47 | driver: 'argon2', 48 | variant: 'id', 49 | iterations: 3, 50 | memory: 4096, 51 | parallelism: 1, 52 | saltSize: 16, 53 | }, 54 | 55 | /* 56 | |-------------------------------------------------------------------------- 57 | | Bcrypt 58 | |-------------------------------------------------------------------------- 59 | | 60 | | Bcrypt mapping uses the `bcrypt` driver to hash values. 61 | | 62 | | Make sure you install the underlying dependency for this driver to work. 63 | | https://www.npmjs.com/package/phc-bcrypt. 64 | | 65 | | npm install phc-bcrypt 66 | | 67 | */ 68 | bcrypt: { 69 | driver: 'bcrypt', 70 | rounds: 10, 71 | }, 72 | }, 73 | } 74 | 75 | export default hashConfig 76 | -------------------------------------------------------------------------------- /config/session.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JeYHp 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import Env from '@ioc:Adonis/Core/Env' 9 | import { SessionConfig } from '@ioc:Adonis/Addons/Session' 10 | 11 | const sessionConfig: SessionConfig = { 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Enable/Disable sessions 15 | |-------------------------------------------------------------------------- 16 | | 17 | | Setting the following property to "false" will disable the session for the 18 | | entire application 19 | | 20 | */ 21 | enabled: true, 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Driver 26 | |-------------------------------------------------------------------------- 27 | | 28 | | The session driver to use. You can choose between one of the following 29 | | drivers. 30 | | 31 | | - cookie (Uses signed cookies to store session values) 32 | | - file (Uses filesystem to store session values) 33 | | - redis (Uses redis. Make sure to install "@adonisjs/redis" as well) 34 | | 35 | | Note: Switching drivers will make existing sessions invalid. 36 | | 37 | */ 38 | driver: Env.get('SESSION_DRIVER', 'cookie') as string, 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Cookie name 43 | |-------------------------------------------------------------------------- 44 | | 45 | | The name of the cookie that will hold the session id. 46 | | 47 | */ 48 | cookieName: 'adonis-session', 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Clear session when browser closes 53 | |-------------------------------------------------------------------------- 54 | | 55 | | Whether or not you want to destroy the session when browser closes. Setting 56 | | this value to `true` will ignore the `age`. 57 | | 58 | */ 59 | clearWithBrowser: false, 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Session age 64 | |-------------------------------------------------------------------------- 65 | | 66 | | The duration for which session stays active after no activity. A new HTTP 67 | | request to the server is considered as activity. 68 | | 69 | | The value can be a number in milliseconds or a string that must be valid 70 | | as per https://npmjs.org/package/ms package. 71 | | 72 | | Example: `2 days`, `2.5 hrs`, `1y`, `5s` and so on. 73 | | 74 | */ 75 | age: '2h', 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Cookie values 80 | |-------------------------------------------------------------------------- 81 | | 82 | | The cookie settings are used to setup the session id cookie and also the 83 | | driver will use the same values. 84 | | 85 | */ 86 | cookie: { 87 | path: '/', 88 | httpOnly: true, 89 | sameSite: false, 90 | }, 91 | 92 | /* 93 | |-------------------------------------------------------------------------- 94 | | Configuration for file driver 95 | |-------------------------------------------------------------------------- 96 | | 97 | | The file driver needs absolute path to the directory in which sessions 98 | | must be stored. 99 | | 100 | */ 101 | file: { 102 | location: '', 103 | }, 104 | 105 | /* 106 | |-------------------------------------------------------------------------- 107 | | Redis driver 108 | |-------------------------------------------------------------------------- 109 | | 110 | | The redis connection you want session driver to use. The same connection 111 | | must be defined inside `config/redis.ts` file 112 | | 113 | */ 114 | redisConnection: 'session', 115 | } 116 | 117 | export default sessionConfig 118 | -------------------------------------------------------------------------------- /config/shield.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/Jvwvt 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import { ShieldConfig } from '@ioc:Adonis/Addons/Shield' 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Content Security Policy 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Content security policy filters out the origins not allowed to execute 16 | | and load resources like scripts, styles and fonts. There are wide 17 | | variety of options to choose from. 18 | */ 19 | export const csp: ShieldConfig['csp'] = { 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Enable/disable CSP 23 | |-------------------------------------------------------------------------- 24 | | 25 | | The CSP rules are disabled by default for seamless onboarding. 26 | | 27 | */ 28 | enabled: false, 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Directives 33 | |-------------------------------------------------------------------------- 34 | | 35 | | All directives are defined in camelCase and here is the list of 36 | | available directives and their possible values. 37 | | 38 | | https://content-security-policy.com 39 | | 40 | | @example 41 | | directives: { 42 | | defaultSrc: ['self', '@nonce', 'cdnjs.cloudflare.com'] 43 | | } 44 | | 45 | */ 46 | directives: {}, 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Report only 51 | |-------------------------------------------------------------------------- 52 | | 53 | | Setting `reportOnly=true` will not block the scripts from running and 54 | | instead report them to a URL. 55 | | 56 | */ 57 | reportOnly: false, 58 | } 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | CSRF Protection 63 | |-------------------------------------------------------------------------- 64 | | 65 | | CSRF Protection adds another layer of security by making sure, actionable 66 | | routes does have a valid token to execute an action. 67 | | 68 | */ 69 | export const csrf: ShieldConfig['csrf'] = { 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Enable/Disable CSRF 73 | |-------------------------------------------------------------------------- 74 | */ 75 | enabled: true, 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Routes to Ignore 80 | |-------------------------------------------------------------------------- 81 | | 82 | | Define an array of route patterns that you want to ignore from CSRF 83 | | validation. Make sure the route patterns are started with a leading 84 | | slash. Example: 85 | | 86 | | `/foo/bar` 87 | | 88 | */ 89 | exceptRoutes: [], 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Enable Sharing Token Via Cookie 94 | |-------------------------------------------------------------------------- 95 | | 96 | | When the following flag is enabled, AdonisJS will drop `XSRF-TOKEN` 97 | | cookie that frontend frameworks can read and return back as a 98 | | `X-XSRF-TOKEN` header. 99 | | 100 | | The cookie has `httpOnly` flag set to false, so it is little insecure and 101 | | can be turned off when you are not using a frontend framework making 102 | | AJAX requests. 103 | | 104 | */ 105 | enableXsrfCookie: true, 106 | 107 | /* 108 | |-------------------------------------------------------------------------- 109 | | Methods to Validate 110 | |-------------------------------------------------------------------------- 111 | | 112 | | Define an array of HTTP methods to be validated for a valid CSRF token. 113 | | 114 | */ 115 | methods: ['POST', 'PUT', 'PATCH', 'DELETE'], 116 | } 117 | 118 | /* 119 | |-------------------------------------------------------------------------- 120 | | DNS Prefetching 121 | |-------------------------------------------------------------------------- 122 | | 123 | | DNS prefetching allows browsers to proactively perform domain name 124 | | resolution in background. 125 | | 126 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control 127 | | 128 | */ 129 | export const dnsPrefetch: ShieldConfig['dnsPrefetch'] = { 130 | /* 131 | |-------------------------------------------------------------------------- 132 | | Enable/disable this feature 133 | |-------------------------------------------------------------------------- 134 | */ 135 | enabled: true, 136 | 137 | /* 138 | |-------------------------------------------------------------------------- 139 | | Allow or Dis-Allow Explicitly 140 | |-------------------------------------------------------------------------- 141 | | 142 | | The `enabled` boolean does not set `X-DNS-Prefetch-Control` header. However 143 | | the `allow` boolean controls the value of `X-DNS-Prefetch-Control` header. 144 | | 145 | | - When `allow = true`, then `X-DNS-Prefetch-Control = 'on'` 146 | | - When `allow = false`, then `X-DNS-Prefetch-Control = 'off'` 147 | | 148 | */ 149 | allow: true, 150 | } 151 | 152 | /* 153 | |-------------------------------------------------------------------------- 154 | | Iframe Options 155 | |-------------------------------------------------------------------------- 156 | | 157 | | xFrame defines whether or not your website can be embedded inside an 158 | | iframe. Choose from one of the following options. 159 | | 160 | | - DENY 161 | | - SAMEORIGIN 162 | | - ALLOW-FROM http://example.com 163 | | 164 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 165 | */ 166 | export const xFrame: ShieldConfig['xFrame'] = { 167 | enabled: true, 168 | action: 'DENY', 169 | } 170 | 171 | /* 172 | |-------------------------------------------------------------------------- 173 | | Http Strict Transport Security 174 | |-------------------------------------------------------------------------- 175 | | 176 | | A security to ensure that a browser always makes a connection over 177 | | HTTPS. 178 | | 179 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security 180 | | 181 | */ 182 | export const hsts: ShieldConfig['hsts'] = { 183 | enabled: true, 184 | /* 185 | |-------------------------------------------------------------------------- 186 | | Max Age 187 | |-------------------------------------------------------------------------- 188 | | 189 | | Control, how long the browser should remember that a site is only to be 190 | | accessed using HTTPS. 191 | | 192 | */ 193 | maxAge: '180 days', 194 | 195 | /* 196 | |-------------------------------------------------------------------------- 197 | | Include Subdomains 198 | |-------------------------------------------------------------------------- 199 | | 200 | | Apply rules on the subdomains as well. 201 | | 202 | */ 203 | includeSubDomains: true, 204 | 205 | /* 206 | |-------------------------------------------------------------------------- 207 | | Preloading 208 | |-------------------------------------------------------------------------- 209 | | 210 | | Google maintains a service to register your domain and it will preload 211 | | the HSTS policy. Learn more https://hstspreload.org/ 212 | | 213 | */ 214 | preload: false, 215 | } 216 | 217 | /* 218 | |-------------------------------------------------------------------------- 219 | | No Sniff 220 | |-------------------------------------------------------------------------- 221 | | 222 | | Browsers have a habit of sniffing content-type of a response. Which means 223 | | files with .txt extension containing Javascript code will be executed as 224 | | Javascript. You can disable this behavior by setting nosniff to false. 225 | | 226 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 227 | | 228 | */ 229 | export const contentTypeSniffing: ShieldConfig['contentTypeSniffing'] = { 230 | enabled: true, 231 | } 232 | -------------------------------------------------------------------------------- /config/static.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/Jfefl 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this config 5 | * file. 6 | */ 7 | 8 | import { AssetsConfig } from '@ioc:Adonis/Core/Static' 9 | 10 | const staticConfig: AssetsConfig = { 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | Enabled 14 | |-------------------------------------------------------------------------- 15 | | 16 | | A boolean to enable or disable serving static files. The static files 17 | | are served from the `public` directory inside the application root. 18 | | However, you can override the default path inside `.adonisrc.json` 19 | | file. 20 | | 21 | | 22 | */ 23 | enabled: true, 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Handling Dot Files 28 | |-------------------------------------------------------------------------- 29 | | 30 | | Decide how you want the static assets server to handle the `dotfiles`. 31 | | By default, we ignore them as if they don't exists. However, you 32 | | can choose between one of the following options. 33 | | 34 | | - ignore: Behave as if the file doesn't exists. Results in 404. 35 | | - deny: Deny access to the file. Results in 403. 36 | | - allow: Serve the file contents 37 | | 38 | */ 39 | dotFiles: 'ignore', 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Generating Etag 44 | |-------------------------------------------------------------------------- 45 | | 46 | | Handle whether or not to generate etags for the files. Etag allows browser 47 | | to utilize the cache when file hasn't been changed. 48 | | 49 | */ 50 | etag: true, 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Set Last Modified 55 | |-------------------------------------------------------------------------- 56 | | 57 | | Whether or not to set the `Last-Modified` header in the response. Uses 58 | | the file system's last modified value. 59 | | 60 | */ 61 | lastModified: true, 62 | } 63 | 64 | export default staticConfig 65 | -------------------------------------------------------------------------------- /contracts/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contract source: https://git.io/JvyKD 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this 5 | * file. 6 | */ 7 | 8 | import User from 'App/Models/User' 9 | 10 | declare module '@ioc:Adonis/Addons/Auth' { 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | Providers 14 | |-------------------------------------------------------------------------- 15 | | 16 | | The providers are used to fetch users. The Auth module comes pre-bundled 17 | | with two providers that are `Lucid` and `Database`. Both uses database 18 | | to fetch user details. 19 | | 20 | | You can also create and register your own custom providers. 21 | | 22 | */ 23 | interface ProvidersList { 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | User Provider 27 | |-------------------------------------------------------------------------- 28 | | 29 | | The following provider uses Lucid models as a driver for fetching user 30 | | details from the database for authentication. 31 | | 32 | | You can create multiple providers using the same underlying driver with 33 | | different Lucid models. 34 | | 35 | */ 36 | user: { 37 | implementation: LucidProviderContract 38 | config: LucidProviderConfig 39 | } 40 | } 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Guards 45 | |-------------------------------------------------------------------------- 46 | | 47 | | The guards are used for authenticating users using different drivers. 48 | | The auth module comes with 4 different guards. 49 | | 50 | | - SessionGuardContract 51 | | - BasicAuthGuardContract 52 | | - JwtGuardContract 53 | | - OATGuardContract ( Opaque access token ) 54 | | 55 | | Every guard needs a provider for looking up users from the database. 56 | | 57 | */ 58 | interface GuardsList { 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Web Guard 62 | |-------------------------------------------------------------------------- 63 | | 64 | | The web guard uses sessions for maintaining user login state. It uses 65 | | the `user` provider for fetching user details. 66 | | 67 | */ 68 | web: { 69 | implementation: SessionGuardContract<'user', 'web'> 70 | config: SessionGuardConfig<'user'> 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/events.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contract source: https://git.io/JfefG 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this contract 5 | * file. 6 | */ 7 | 8 | declare module '@ioc:Adonis/Core/Event' { 9 | /* 10 | |-------------------------------------------------------------------------- 11 | | Define typed events 12 | |-------------------------------------------------------------------------- 13 | | 14 | | You can define types for events inside the following interface and 15 | | AdonisJS will make sure that all listeners and emit calls adheres 16 | | to the defined types. 17 | | 18 | | For example: 19 | | 20 | | interface EventsList { 21 | | 'new:user': UserModel 22 | | } 23 | | 24 | | Now calling `Event.emit('new:user')` will statically ensure that passed value is 25 | | an instance of the the UserModel only. 26 | | 27 | */ 28 | interface EventsList {} 29 | } 30 | -------------------------------------------------------------------------------- /contracts/hash.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contract source: https://git.io/Jfefs 3 | * 4 | * Feel free to let us know via PR, if you find something broken in this contract 5 | * file. 6 | */ 7 | 8 | declare module '@ioc:Adonis/Core/Hash' { 9 | import { HashDrivers } from '@ioc:Adonis/Core/Hash' 10 | 11 | interface HashersList { 12 | bcrypt: { 13 | config: BcryptConfig 14 | implementation: BcryptContract 15 | } 16 | argon: { 17 | config: ArgonConfig 18 | implementation: ArgonContract 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /database/migrations/1587988332388_users.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema from '@ioc:Adonis/Lucid/Schema' 2 | 3 | export default class UsersSchema extends BaseSchema { 4 | protected tableName = 'users' 5 | 6 | public async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id').primary() 9 | table.string('name').notNullable() 10 | table.string('email', 255).notNullable() 11 | table.string('password', 180).notNullable() 12 | table.string('remember_me_token').nullable() 13 | table.timestamps(true) 14 | }) 15 | } 16 | 17 | public async down() { 18 | this.schema.dropTable(this.tableName) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /database/migrations/1591657469878_tasks.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema from '@ioc:Adonis/Lucid/Schema' 2 | 3 | export default class Tasks extends BaseSchema { 4 | protected tableName = 'tasks' 5 | 6 | public async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id').primary() 9 | table.integer('user_id').unsigned().notNullable() 10 | table.string('title').notNullable() 11 | table.boolean('is_completed').defaultTo(0) 12 | table.timestamps(true) 13 | }) 14 | } 15 | 16 | public async down() { 17 | this.schema.dropTable(this.tableName) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /env.ts: -------------------------------------------------------------------------------- 1 | import Env from '@ioc:Adonis/Core/Env' 2 | 3 | export default Env.rules({ 4 | HOST: Env.schema.string({ format: 'host' }), 5 | PORT: Env.schema.number(), 6 | APP_KEY: Env.schema.string(), 7 | NODE_ENV: Env.schema.enum(['development', 'production', 'testing'] as const), 8 | CACHE_VIEWS: Env.schema.boolean(), 9 | SESSION_DRIVER: Env.schema.string(), 10 | MYSQL_HOST: Env.schema.string({ format: 'host' }), 11 | MYSQL_PORT: Env.schema.number(), 12 | MYSQL_USER: Env.schema.string(), 13 | MYSQL_PASSWORD: Env.schema.string.optional(), 14 | MYSQL_DB_NAME: Env.schema.string(), 15 | }) 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taskist", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "node ace serve --watch", 7 | "build": "node ace build --production", 8 | "start": "node server.js", 9 | "lint": "eslint . --ext=.ts", 10 | "format": "prettier --write ." 11 | }, 12 | "devDependencies": { 13 | "@adonisjs/assembler": "^5.3.8", 14 | "adonis-preset-ts": "^2.1.0", 15 | "eslint": "^8.2.0", 16 | "eslint-config-prettier": "^8.3.0", 17 | "eslint-plugin-adonis": "^2.0.0", 18 | "eslint-plugin-prettier": "^4.0.0", 19 | "pino-pretty": "^7.2.0", 20 | "prettier": "^2.4.1", 21 | "typescript": "^4.2.4", 22 | "youch": "^2.2.2", 23 | "youch-terminal": "^1.1.1" 24 | }, 25 | "dependencies": { 26 | "@adonisjs/auth": "^8.0.10", 27 | "@adonisjs/core": "^5.4.0", 28 | "@adonisjs/lucid": "^16.3.2", 29 | "@adonisjs/repl": "^3.1.7", 30 | "@adonisjs/session": "^6.1.2", 31 | "@adonisjs/shield": "^7.0.7", 32 | "@adonisjs/view": "^6.1.1", 33 | "luxon": "^2.1.1", 34 | "mysql": "^2.18.1", 35 | "phc-argon2": "^1.1.2", 36 | "proxy-addr": "^2.0.7", 37 | "reflect-metadata": "^0.1.13", 38 | "source-map-support": "^0.5.20" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /providers/AppProvider.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationContract } from '@ioc:Adonis/Core/Application' 2 | 3 | export default class AppProvider { 4 | constructor(protected app: ApplicationContract) {} 5 | 6 | public register() { 7 | // Register your own bindings 8 | } 9 | 10 | public boot() { 11 | // IoC container is ready 12 | } 13 | 14 | public shutdown() { 15 | // Cleanup, since app is going down 16 | } 17 | 18 | public ready() { 19 | // App is ready 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mezielabs/taskist/a590905843fd1845b03b409997e8959e61f830a2/public/favicon.ico -------------------------------------------------------------------------------- /resources/views/about.edge: -------------------------------------------------------------------------------- 1 | {{-- @if(name) 2 |

This is {{ name }}'s about page

3 | @else 4 |

This is the about page

5 | @endif --}} 6 | 7 |

8 | {{ name ? `This is ${name}'s about page` : 'This is the about page' }} 9 |

10 | -------------------------------------------------------------------------------- /resources/views/auth/login.edge: -------------------------------------------------------------------------------- 1 | @layout('layouts/app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |

Login

8 | 9 | @if (flashMessages.has('notification')) 10 |
11 | {{ flashMessages.get('notification') }} 12 |
13 | @endif 14 | 15 |
16 | {{ csrfField() }} 17 | 18 |
19 | 20 | 21 |
22 | 29 |
30 |
31 | 32 |
33 | 34 | 35 |
36 | 42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 | @endsection 53 | -------------------------------------------------------------------------------- /resources/views/auth/register.edge: -------------------------------------------------------------------------------- 1 | @layout('layouts/app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |

Register

8 | 9 |
10 | {{ csrfField() }} 11 | 12 |
13 | 14 | 15 |
16 | 22 |
23 | 24 | @if (flashMessages.has('errors.name')) 25 |

26 | {{ flashMessages.get('errors.name') }} 27 |

28 | @endif 29 |
30 | 31 |
32 | 33 | 34 |
35 | 41 |
42 | 43 | @if (flashMessages.has('errors.email')) 44 |

45 | {{ flashMessages.get('errors.email') }} 46 |

47 | @endif 48 |
49 | 50 |
51 | 52 | 53 |
54 | 59 |
60 | 61 | @if (flashMessages.has('errors.password')) 62 |

63 | {{ flashMessages.get('errors.password') }} 64 |

65 | @endif 66 |
67 | 68 |
69 | 70 | 71 |
72 | 77 |
78 | 79 | @if (flashMessages.has('errors.password_confirmation')) 80 |

81 | {{ flashMessages.get('errors.password_confirmation') }} 82 |

83 | @endif 84 |
85 | 86 |
87 | 88 |
89 |
90 |
91 |
92 |
93 | @endsection 94 | -------------------------------------------------------------------------------- /resources/views/contact.edge: -------------------------------------------------------------------------------- 1 |

This is the contact page

2 | -------------------------------------------------------------------------------- /resources/views/errors/not-found.edge: -------------------------------------------------------------------------------- 1 |

It's a 404

2 | -------------------------------------------------------------------------------- /resources/views/errors/server-error.edge: -------------------------------------------------------------------------------- 1 |

It's a 500

2 | -------------------------------------------------------------------------------- /resources/views/errors/unauthorized.edge: -------------------------------------------------------------------------------- 1 |

It's a 403

2 | -------------------------------------------------------------------------------- /resources/views/layouts/app.edge: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Taskist 7 | 8 | 9 | 10 | 15 | 16 | 17 | @include('layouts/partials/_navbar') 18 | 19 |
20 |
21 | @!section('content') 22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /resources/views/layouts/partials/_navbar.edge: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /resources/views/tasks/index.edge: -------------------------------------------------------------------------------- 1 | @layout('layouts/app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 | {{ csrfField() }} 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 | @if(flashMessages.has('errors.title')) 20 |

21 | {{ flashMessages.get('errors.title') }} 22 |

23 | @endif 24 |
25 |
26 | 27 | @if(flashMessages.has('notification')) 28 |
29 | {{ flashMessages.get('notification') }} 30 |
31 | @endif 32 | 33 |
34 | 35 | 36 | @each(task in tasks) 37 | 38 | 47 | 52 | 59 | 60 | @else 61 | 62 | 63 | 64 | @endeach 65 | 66 |
39 |
40 | {{ csrfField() }} 41 | 42 | 45 |
46 |
48 |
49 | {{ task.title }} 50 |
51 |
53 |
54 | {{ csrfField() }} 55 | 56 | 57 |
58 |
No tasks!
67 |
68 |
69 |
70 | @endsection 71 | -------------------------------------------------------------------------------- /resources/views/welcome.edge: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdonisJS - A Modern Web Framework For Node.js 7 | 8 | 9 | 10 | About 11 | Contact 12 | 13 | @set('name', 'Chimezie Enyinnaya') 14 | 15 |

{{ name }}

16 | 17 |

@{{ name }}

18 | 19 | {{ '

This will be escaped

' }} 20 | 21 | {{{ '

This will not be escaped

' }}} 22 | 23 | 24 | -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | AdonisJs Server 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The contents in this file is meant to bootstrap the AdonisJs application 7 | | and start the HTTP server to accept incoming connections. You must avoid 8 | | making this file dirty and instead make use of `lifecycle hooks` provided 9 | | by AdonisJs service providers for custom code. 10 | | 11 | */ 12 | 13 | import 'reflect-metadata' 14 | import sourceMapSupport from 'source-map-support' 15 | import { Ignitor } from '@adonisjs/core/build/standalone' 16 | 17 | sourceMapSupport.install({ handleUncaughtExceptions: false }) 18 | 19 | new Ignitor(__dirname).httpServer().start() 20 | -------------------------------------------------------------------------------- /start/kernel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Application middleware 4 | |-------------------------------------------------------------------------- 5 | | 6 | | This file is used to define middleware for HTTP requests. You can register 7 | | middleware as a `closure` or an IoC container binding. The bindings are 8 | | preferred, since they keep this file clean. 9 | | 10 | */ 11 | 12 | import Server from '@ioc:Adonis/Core/Server' 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Global middleware 17 | |-------------------------------------------------------------------------- 18 | | 19 | | An array of global middleware, that will be executed in the order they 20 | | are defined for all HTTP requests. 21 | | 22 | */ 23 | Server.middleware.register([ 24 | () => import('@ioc:Adonis/Core/BodyParser'), 25 | () => import('@ioc:Adonis/Addons/Shield'), 26 | () => import('App/Middleware/SilentAuth'), 27 | ]) 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Named middleware 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Named middleware are defined a key-value pair. The value is the namespace 35 | | or middleware function and key is the alias. Later you can use these 36 | | alias on individual routes. For example: 37 | | 38 | | { auth: 'Adonis/Auth/Middleware' } 39 | | 40 | | and then use it as follows 41 | | 42 | | Route.get('dashboard', 'UserController.dashboard').middleware('auth') 43 | | 44 | */ 45 | Server.middleware.registerNamed({ 46 | auth: () => import('App/Middleware/Auth'), 47 | guest: () => import('App/Middleware/Guest'), 48 | }) 49 | -------------------------------------------------------------------------------- /start/routes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Routes 4 | |-------------------------------------------------------------------------- 5 | | 6 | | This file is dedicated for defining HTTP routes. A single file is enough 7 | | for majority of projects, however you can define routes in different 8 | | files and just make sure to import them inside this file. For example 9 | | 10 | | Define routes in following two files 11 | | ├── start/routes/cart.ts 12 | | ├── start/routes/customer.ts 13 | | 14 | | and then import them inside `start/routes/index.ts` as follows 15 | | 16 | | import './cart' 17 | | import './customer' 18 | | 19 | */ 20 | 21 | import Route from '@ioc:Adonis/Core/Route' 22 | 23 | // Route.on('/').render('welcome') 24 | 25 | Route.group(() => { 26 | Route.get('/', 'TasksController.index') 27 | Route.post('/tasks', 'TasksController.store') 28 | Route.patch('/tasks/:id', 'TasksController.update') 29 | Route.delete('/tasks/:id', 'TasksController.destroy') 30 | }).middleware('auth') 31 | 32 | Route.get('/register', 'AuthController.showRegister').middleware('guest') 33 | Route.post('/register', 'AuthController.register') 34 | Route.post('/logout', 'AuthController.logout') 35 | Route.get('/login', 'AuthController.showLogin').middleware('guest') 36 | Route.post('/login', 'AuthController.login') 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*" 4 | ], 5 | "exclude": [ 6 | "node_modules", 7 | "build" 8 | ], 9 | "extends": "./node_modules/adonis-preset-ts/tsconfig", 10 | "compilerOptions": { 11 | "outDir": "build", 12 | "rootDir": "./", 13 | "sourceMap": true, 14 | "paths": { 15 | "App/*": [ 16 | "./app/*" 17 | ], 18 | "Contracts/*": [ 19 | "./contracts/*" 20 | ] 21 | }, 22 | "types": [ 23 | "@adonisjs/core", 24 | "@adonisjs/session", 25 | "@adonisjs/view", 26 | "@adonisjs/lucid", 27 | "@adonisjs/shield", 28 | "@adonisjs/auth" 29 | ] 30 | } 31 | } 32 | --------------------------------------------------------------------------------