├── .adonisrc.json ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── cleavr_51sea0ntw0.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── ace ├── ace-manifest.json ├── app ├── Controllers │ └── Http │ │ ├── JokesController.ts │ │ ├── SessionController.ts │ │ └── SignupController.ts ├── Exceptions │ └── Handler.ts ├── Middleware │ ├── Auth.ts │ └── SilentAuth.ts ├── Models │ ├── Joke.ts │ └── User.ts └── Validators │ ├── CreateJokeValidator.ts │ └── SignupValidator.ts ├── commands ├── PublishAssets.ts └── index.ts ├── config ├── app.ts ├── auth.ts ├── bodyparser.ts ├── cors.ts ├── database.ts ├── drive.ts ├── hash.ts ├── session.ts ├── shield.ts └── static.ts ├── contracts ├── auth.ts ├── drive.ts ├── env.ts ├── events.ts └── hash.ts ├── database ├── factories │ └── index.ts └── migrations │ ├── 1638164583286_users.ts │ └── 1738114600160_jokes.ts ├── env.ts ├── lh-score.png ├── package-lock.json ├── package.json ├── postcss.config.js ├── providers ├── AppProvider.ts └── Up │ ├── Provider.ts │ ├── context.ts │ └── index.ts ├── public ├── assets │ ├── entrypoints.json │ └── manifest.json └── favicon.ico ├── resources ├── css │ └── app.css ├── fonts │ └── baloo.woff ├── js │ └── app.js └── views │ ├── components │ └── layouts │ │ ├── app.edge │ │ ├── auth.edge │ │ └── root.edge │ ├── errors │ ├── not-found.edge │ ├── server-error.edge │ └── unauthorized.edge │ ├── home.edge │ ├── pages │ ├── jokes │ │ ├── create.edge │ │ ├── index.edge │ │ └── show.edge │ ├── login.edge │ └── signup.edge │ └── partials │ ├── header.edge │ └── sidebar.edge ├── server.ts ├── start ├── kernel.ts ├── routes.ts └── view.ts ├── tailwind.config.js ├── tsconfig.json └── webpack.config.js /.adonisrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript": true, 3 | "commands": [ 4 | "./commands", 5 | "@adonisjs/core/build/commands/index.js", 6 | "@adonisjs/repl/build/commands", 7 | "@adonisjs/lucid/build/commands" 8 | ], 9 | "exceptionHandlerNamespace": "App/Exceptions/Handler", 10 | "aliases": { 11 | "App": "app", 12 | "Config": "config", 13 | "Database": "database", 14 | "Contracts": "contracts" 15 | }, 16 | "preloads": [ 17 | "./start/routes", 18 | "./start/kernel", 19 | { 20 | "file": "./start/view", 21 | "environment": [ 22 | "web" 23 | ] 24 | } 25 | ], 26 | "providers": [ 27 | "./providers/AppProvider", 28 | "@adonisjs/core", 29 | "./providers/Up/Provider", 30 | "@adonisjs/session", 31 | "@adonisjs/view", 32 | "@adonisjs/shield", 33 | "@adonisjs/lucid", 34 | "@adonisjs/auth" 35 | ], 36 | "metaFiles": [ 37 | { 38 | "pattern": "public/**", 39 | "reloadServer": false 40 | }, 41 | { 42 | "pattern": "resources/views/**/*.edge", 43 | "reloadServer": false 44 | } 45 | ], 46 | "aceProviders": [ 47 | "@adonisjs/repl" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.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=0.0.0.0 3 | NODE_ENV=development 4 | APP_KEY=0hnE1jQTNjulEy50oTwEi-pkkCTKd9ar 5 | DRIVE_DISK=local 6 | SESSION_DRIVER=cookie 7 | CACHE_VIEWS=false 8 | DB_CONNECTION=pg 9 | PG_HOST=localhost 10 | PG_PORT=5432 11 | PG_USER=virk 12 | PG_PASSWORD= 13 | PG_DB_NAME=jokes-app 14 | S3_KEY= 15 | S3_SECRET= 16 | S3_BUCKET= 17 | S3_REGION= 18 | S3_ENDPOINT= 19 | S3_CDN_URL= 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:adonis/typescriptApp", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": ["error"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/cleavr_51sea0ntw0.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | # This GitHub Actions workflow is generated and deployed by https://cleavr.io 4 | # The filename, cleavr_51sea0ntw0, of this workflow is tracked from within Cleavr, 5 | # please don't change the name. Though, feel free to add your own steps or tweak 6 | # the existing ones except anything listed below the warning message. 7 | # Read more about Cleavr's GitHub Actions integration feature at: https://bit.ly/397WKh6 8 | 9 | name: Deploy remix-jokes.adonisjs.dev App with Cleavr 10 | on: 11 | workflow_dispatch: 12 | push: 13 | branches: 14 | # This is the branch that gets deployed when a push is made to this repository. 15 | # If you change the default branch in Cleavr under the Webapp's settings, make sure to change it here as well. 16 | - main 17 | jobs: 18 | build: 19 | runs-on: ubuntu-20.04 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: bahmutov/npm-install@v1 23 | 24 | - name: Build app 25 | run: npm run build --production 26 | 27 | ### --- PLEASE DO NOT MODIFY ANYTHING BELOW THIS LINE --- ### 28 | 29 | - name: Generate artifact name 30 | id: artifact 31 | uses: nanzm/get-time-action@v1.1 32 | with: 33 | timeZone: 8 34 | format: 'YYYYMMDDHHmmss' 35 | 36 | - name: Upload artifact 37 | uses: actions/upload-artifact@v2 38 | with: 39 | name: '${{ steps.artifact.outputs.time }}' 40 | path: build/ 41 | 42 | - name: Start Cleavr Deployment 43 | uses: fjogeleit/http-request-action@master 44 | with: 45 | url: '${{ secrets.DEPLOY_TRIGGER_HOOK_cleavr_51sea0ntw0 }}' 46 | method: 'POST' 47 | timeout: 60000 48 | customHeaders: '{"artifact": "${{ steps.artifact.outputs.time }}"}' 49 | 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | .vscode 5 | .DS_STORE 6 | .env 7 | tmp 8 | -------------------------------------------------------------------------------- /.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 | # Remix Jokes App 2 | 3 | > A copy of the Remix framework [Jokes app](https://remix-jokes.lol/) built using AdonisJS and Unpoly 4 | 5 | The repo contains the source code for a demo application built using AdonisJS and [Unpoly](https://unpoly.com/tutorial). The application's primary goal is to showcase that developers can ship applications with a great user experience without opting for a front-end framework. 6 | 7 | ## Goal of the application 8 | The goal of the application is not to showcase AdonisJS or its features. Instead, the goal is to showcase [Unpoly](https://unpoly.com/tutorial), a front-end library that uses HTML attributes for performing **partial updates**, **restoring user scroll position**, and a lot more. 9 | 10 | Unpoly works as a progressive enhancement on top of your existing Server-side applications. This is how I see Unpoly. 11 | 12 | - You begin by using a backend framework. It can be AdonisJS, Rails, Laravel, or any other framework you love. 13 | - You create a standard web application and serve HTML from the server. 14 | - Every time a user clicks a link, it results in the full page reload. 15 | - So far, you have created a working application in the simplest possible way. This app is not inferior at all, but we can improve it. 16 | - Then, you set up Unpoly and instruct it to hijack the navigation and perform partial updates to the DOM vs. reloading the entire page. 17 | - Unpoly takes instructions from the HTML attributes like `up-target`, `up-follow`, `up-submit`, and so on. 18 | - The great thing about this approach is, you can ship the first version of your application without Unpoly and then gradually make navigation between pages smoother. 19 | 20 | ## What will I learn from this app? 21 | 22 | - You will learn how to create a CRUD application using AdonisJS 23 | - Next, use the Unpoly HTML attributes to **perform partial page updates**, **restore user scroll position**, **manage focus** as the user navigates. 24 | 25 | ## Demo 26 | You can view the application demo at https://remix-jokes.adonisjs.dev. 27 | 28 | ## Share and love 29 | If you like this application, then please share it with your co-workers and friends or maybe tweet it to the universe :) 30 | 31 | ## Deployment 32 | 33 | The application is deployed on Digital Ocean using [Cleavr](https://cleavr.io/). It runs on a `$5` droplet and the PostgreSQL database running on the same server. 34 | 35 | The static assets are published on Digital ocean spaces and served using a CDN. Take a look at the `commands/PublishAssets.ts` file responsible for publishing the assets on DO spaces. The command is executed during the deployment phase using Cleavr hooks. 36 | 37 | ## Lighthouse score 38 | 39 | ![](./lh-score.png) 40 | -------------------------------------------------------------------------------- /ace: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Ace Commands 4 | |-------------------------------------------------------------------------- 5 | | 6 | | This file is the entry point for running ace commands. 7 | | 8 | */ 9 | 10 | require('reflect-metadata') 11 | require('source-map-support').install({ handleUncaughtExceptions: false }) 12 | 13 | const { Ignitor } = require('@adonisjs/core/build/standalone') 14 | new Ignitor(__dirname) 15 | .ace() 16 | .handle(process.argv.slice(2)) 17 | -------------------------------------------------------------------------------- /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 | "repl": { 40 | "settings": { 41 | "loadApp": true, 42 | "environment": "repl", 43 | "stayAlive": true 44 | }, 45 | "commandPath": "@adonisjs/repl/build/commands/AdonisRepl", 46 | "commandName": "repl", 47 | "description": "Start a new REPL session", 48 | "args": [], 49 | "aliases": [], 50 | "flags": [] 51 | }, 52 | "db:seed": { 53 | "settings": { 54 | "loadApp": true 55 | }, 56 | "commandPath": "@adonisjs/lucid/build/commands/DbSeed", 57 | "commandName": "db:seed", 58 | "description": "Execute database seeder files", 59 | "args": [], 60 | "aliases": [], 61 | "flags": [ 62 | { 63 | "name": "connection", 64 | "propertyName": "connection", 65 | "type": "string", 66 | "description": "Define a custom database connection for the seeders", 67 | "alias": "c" 68 | }, 69 | { 70 | "name": "interactive", 71 | "propertyName": "interactive", 72 | "type": "boolean", 73 | "description": "Run seeders in interactive mode", 74 | "alias": "i" 75 | }, 76 | { 77 | "name": "files", 78 | "propertyName": "files", 79 | "type": "array", 80 | "description": "Define a custom set of seeders files names to run", 81 | "alias": "f" 82 | } 83 | ] 84 | }, 85 | "make:model": { 86 | "settings": { 87 | "loadApp": true 88 | }, 89 | "commandPath": "@adonisjs/lucid/build/commands/MakeModel", 90 | "commandName": "make:model", 91 | "description": "Make a new Lucid model", 92 | "args": [ 93 | { 94 | "type": "string", 95 | "propertyName": "name", 96 | "name": "name", 97 | "required": true, 98 | "description": "Name of the model class" 99 | } 100 | ], 101 | "aliases": [], 102 | "flags": [ 103 | { 104 | "name": "migration", 105 | "propertyName": "migration", 106 | "type": "boolean", 107 | "alias": "m", 108 | "description": "Generate the migration for the model" 109 | }, 110 | { 111 | "name": "controller", 112 | "propertyName": "controller", 113 | "type": "boolean", 114 | "alias": "c", 115 | "description": "Generate the controller for the model" 116 | } 117 | ] 118 | }, 119 | "make:migration": { 120 | "settings": { 121 | "loadApp": true 122 | }, 123 | "commandPath": "@adonisjs/lucid/build/commands/MakeMigration", 124 | "commandName": "make:migration", 125 | "description": "Make a new migration file", 126 | "args": [ 127 | { 128 | "type": "string", 129 | "propertyName": "name", 130 | "name": "name", 131 | "required": true, 132 | "description": "Name of the migration file" 133 | } 134 | ], 135 | "aliases": [], 136 | "flags": [ 137 | { 138 | "name": "connection", 139 | "propertyName": "connection", 140 | "type": "string", 141 | "description": "The connection flag is used to lookup the directory for the migration file" 142 | }, 143 | { 144 | "name": "folder", 145 | "propertyName": "folder", 146 | "type": "string", 147 | "description": "Pre-select a migration directory" 148 | }, 149 | { 150 | "name": "create", 151 | "propertyName": "create", 152 | "type": "string", 153 | "description": "Define the table name for creating a new table" 154 | }, 155 | { 156 | "name": "table", 157 | "propertyName": "table", 158 | "type": "string", 159 | "description": "Define the table name for altering an existing table" 160 | } 161 | ] 162 | }, 163 | "make:seeder": { 164 | "settings": {}, 165 | "commandPath": "@adonisjs/lucid/build/commands/MakeSeeder", 166 | "commandName": "make:seeder", 167 | "description": "Make a new Seeder file", 168 | "args": [ 169 | { 170 | "type": "string", 171 | "propertyName": "name", 172 | "name": "name", 173 | "required": true, 174 | "description": "Name of the seeder class" 175 | } 176 | ], 177 | "aliases": [], 178 | "flags": [] 179 | }, 180 | "migration:run": { 181 | "settings": { 182 | "loadApp": true 183 | }, 184 | "commandPath": "@adonisjs/lucid/build/commands/Migration/Run", 185 | "commandName": "migration:run", 186 | "description": "Run pending migrations", 187 | "args": [], 188 | "aliases": [], 189 | "flags": [ 190 | { 191 | "name": "connection", 192 | "propertyName": "connection", 193 | "type": "string", 194 | "description": "Define a custom database connection", 195 | "alias": "c" 196 | }, 197 | { 198 | "name": "force", 199 | "propertyName": "force", 200 | "type": "boolean", 201 | "description": "Explicitly force to run migrations in production" 202 | }, 203 | { 204 | "name": "dry-run", 205 | "propertyName": "dryRun", 206 | "type": "boolean", 207 | "description": "Print SQL queries, instead of running the migrations" 208 | } 209 | ] 210 | }, 211 | "migration:rollback": { 212 | "settings": { 213 | "loadApp": true 214 | }, 215 | "commandPath": "@adonisjs/lucid/build/commands/Migration/Rollback", 216 | "commandName": "migration:rollback", 217 | "description": "Rollback migrations to a given batch number", 218 | "args": [], 219 | "aliases": [], 220 | "flags": [ 221 | { 222 | "name": "connection", 223 | "propertyName": "connection", 224 | "type": "string", 225 | "description": "Define a custom database connection", 226 | "alias": "c" 227 | }, 228 | { 229 | "name": "force", 230 | "propertyName": "force", 231 | "type": "boolean", 232 | "description": "Explictly force to run migrations in production" 233 | }, 234 | { 235 | "name": "dry-run", 236 | "propertyName": "dryRun", 237 | "type": "boolean", 238 | "description": "Print SQL queries, instead of running the migrations" 239 | }, 240 | { 241 | "name": "batch", 242 | "propertyName": "batch", 243 | "type": "number", 244 | "description": "Define custom batch number for rollback. Use 0 to rollback to initial state" 245 | } 246 | ] 247 | }, 248 | "migration:status": { 249 | "settings": { 250 | "loadApp": true 251 | }, 252 | "commandPath": "@adonisjs/lucid/build/commands/Migration/Status", 253 | "commandName": "migration:status", 254 | "description": "Check migrations current status.", 255 | "args": [], 256 | "aliases": [], 257 | "flags": [ 258 | { 259 | "name": "connection", 260 | "propertyName": "connection", 261 | "type": "string", 262 | "description": "Define a custom database connection", 263 | "alias": "c" 264 | } 265 | ] 266 | } 267 | }, 268 | "aliases": {} 269 | } 270 | -------------------------------------------------------------------------------- /app/Controllers/Http/JokesController.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | import Joke from 'App/Models/Joke' 3 | import CreateJokeValidator from 'App/Validators/CreateJokeValidator' 4 | 5 | export default class JokesController { 6 | public async index({ view, auth }: HttpContextContract) { 7 | let randomJoke: Joke | null = null 8 | if (auth.isLoggedIn) { 9 | randomJoke = await auth.user!.related('jokes').query().orderByRaw('RANDOM()').limit(1).first() 10 | } 11 | 12 | return view.render('pages/jokes/index', { randomJoke }) 13 | } 14 | 15 | public async show({ view, params, auth }: HttpContextContract) { 16 | const joke = await auth.user!.related('jokes').query().where('id', params.id).firstOrFail() 17 | return view.render('pages/jokes/show', { joke }) 18 | } 19 | 20 | public async create({ view }: HttpContextContract) { 21 | return view.render('pages/jokes/create') 22 | } 23 | 24 | public async store({ request, response, auth }: HttpContextContract) { 25 | const { name, content } = await request.validate(CreateJokeValidator) 26 | 27 | const joke = await auth.user!.related('jokes').create({ name, content }) 28 | response.redirect().toRoute('JokesController.show', [joke.id]) 29 | } 30 | 31 | public async destroy({ params, auth, response }: HttpContextContract) { 32 | const joke = await auth.user!.related('jokes').query().where('id', params.id).first() 33 | if (joke) { 34 | await joke.delete() 35 | } 36 | 37 | response.redirect().toRoute('JokesController.index') 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Controllers/Http/SessionController.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | 3 | export default class SessionController { 4 | public async create({ view }: HttpContextContract) { 5 | return view.render('pages/login') 6 | } 7 | 8 | public async store({ request, auth, response }: HttpContextContract) { 9 | await auth.attempt(request.input('username'), request.input('password')) 10 | response.redirect().toRoute('JokesController.index') 11 | } 12 | 13 | public async destroy({ auth, response }: HttpContextContract) { 14 | await auth.logout() 15 | response.redirect().toPath('/') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Controllers/Http/SignupController.ts: -------------------------------------------------------------------------------- 1 | import User from 'App/Models/User' 2 | import SignupValidator from 'App/Validators/SignupValidator' 3 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 4 | 5 | export default class SignupController { 6 | public async create({ view }: HttpContextContract) { 7 | return view.render('pages/signup') 8 | } 9 | 10 | public async store({ request, auth, response }: HttpContextContract) { 11 | const { username, password } = await request.validate(SignupValidator) 12 | const user = await User.create({ username, password }) 13 | 14 | await auth.login(user) 15 | response.redirect().toRoute('JokesController.index') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 18 | import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler' 19 | 20 | export default class ExceptionHandler extends HttpExceptionHandler { 21 | protected statusPages = { 22 | '403': 'errors/unauthorized', 23 | '404': 'errors/not-found', 24 | '500..599': 'errors/server-error', 25 | } 26 | 27 | constructor() { 28 | super(Logger) 29 | } 30 | 31 | public async handle(error: any, ctx: HttpContextContract) { 32 | if (error.code === 'E_VALIDATION_FAILURE') { 33 | ctx.up.setTarget(ctx.up.getFailTarget()) 34 | } 35 | 36 | if (!error.status || this.expandedStatusPages[error.status]) { 37 | ctx.up.fullReload() 38 | } 39 | 40 | return super.handle(error, ctx) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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/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/Joke.ts: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon' 2 | import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm' 3 | 4 | export default class Joke extends BaseModel { 5 | @column({ isPrimary: true }) 6 | public id: number 7 | 8 | @column() 9 | public userId: number 10 | 11 | @column() 12 | public name: string 13 | 14 | @column() 15 | public content: string 16 | 17 | @column.dateTime({ autoCreate: true }) 18 | public createdAt: DateTime 19 | 20 | @column.dateTime({ autoCreate: true, autoUpdate: true }) 21 | public updatedAt: DateTime 22 | } 23 | -------------------------------------------------------------------------------- /app/Models/User.ts: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon' 2 | import Joke from 'App/Models/Joke' 3 | import Hash from '@ioc:Adonis/Core/Hash' 4 | import { column, beforeSave, BaseModel, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm' 5 | 6 | export default class User extends BaseModel { 7 | @column({ isPrimary: true }) 8 | public id: number 9 | 10 | @column() 11 | public username: string 12 | 13 | @column({ serializeAs: null }) 14 | public password: string 15 | 16 | @column() 17 | public rememberMeToken?: string 18 | 19 | @column.dateTime({ autoCreate: true }) 20 | public createdAt: DateTime 21 | 22 | @column.dateTime({ autoCreate: true, autoUpdate: true }) 23 | public updatedAt: DateTime 24 | 25 | @beforeSave() 26 | public static async hashPassword(user: User) { 27 | if (user.$dirty.password) { 28 | user.password = await Hash.make(user.password) 29 | } 30 | } 31 | 32 | @hasMany(() => Joke) 33 | public jokes: HasMany 34 | } 35 | -------------------------------------------------------------------------------- /app/Validators/CreateJokeValidator.ts: -------------------------------------------------------------------------------- 1 | import { schema } from '@ioc:Adonis/Core/Validator' 2 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 3 | 4 | export default class CreateJokeValidator { 5 | constructor(protected ctx: HttpContextContract) {} 6 | 7 | /* 8 | * Define schema to validate the "shape", "type", "formatting" and "integrity" of data. 9 | * 10 | * For example: 11 | * 1. The username must be of data type string. But then also, it should 12 | * not contain special characters or numbers. 13 | * ``` 14 | * schema.string({}, [ rules.alpha() ]) 15 | * ``` 16 | * 17 | * 2. The email must be of data type string, formatted as a valid 18 | * email. But also, not used by any other user. 19 | * ``` 20 | * schema.string({}, [ 21 | * rules.email(), 22 | * rules.unique({ table: 'users', column: 'email' }), 23 | * ]) 24 | * ``` 25 | */ 26 | public schema = schema.create({ 27 | name: schema.string(), 28 | content: schema.string(), 29 | }) 30 | 31 | /** 32 | * Custom messages for validation failures. You can make use of dot notation `(.)` 33 | * for targeting nested fields and array expressions `(*)` for targeting all 34 | * children of an array. For example: 35 | * 36 | * { 37 | * 'profile.username.required': 'Username is required', 38 | * 'scores.*.number': 'Define scores as valid numbers' 39 | * } 40 | * 41 | */ 42 | public messages = { 43 | 'name.required': 'A joke must have a name', 44 | 'content.required': 'A joke without content is not a joke', 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Validators/SignupValidator.ts: -------------------------------------------------------------------------------- 1 | import { schema, rules } from '@ioc:Adonis/Core/Validator' 2 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 3 | 4 | export default class SignupValidator { 5 | constructor(protected ctx: HttpContextContract) {} 6 | 7 | /* 8 | * Define schema to validate the "shape", "type", "formatting" and "integrity" of data. 9 | * 10 | * For example: 11 | * 1. The username must be of data type string. But then also, it should 12 | * not contain special characters or numbers. 13 | * ``` 14 | * schema.string({}, [ rules.alpha() ]) 15 | * ``` 16 | * 17 | * 2. The email must be of data type string, formatted as a valid 18 | * email. But also, not used by any other user. 19 | * ``` 20 | * schema.string({}, [ 21 | * rules.email(), 22 | * rules.unique({ table: 'users', column: 'email' }), 23 | * ]) 24 | * ``` 25 | */ 26 | public schema = schema.create({ 27 | username: schema.string({}, [rules.unique({ table: 'users', column: 'username' })]), 28 | password: schema.string({}, [rules.minLength(4)]), 29 | }) 30 | 31 | /** 32 | * Custom messages for validation failures. You can make use of dot notation `(.)` 33 | * for targeting nested fields and array expressions `(*)` for targeting all 34 | * children of an array. For example: 35 | * 36 | * { 37 | * 'profile.username.required': 'Username is required', 38 | * 'scores.*.number': 'Define scores as valid numbers' 39 | * } 40 | * 41 | */ 42 | public messages = { 43 | 'username.required': 'Choose a username for your account', 44 | 'username.unique': 'The username is already in use', 45 | 'password.required': 'Enter account password', 46 | 'password.minLength': 'Password must be 4 characters or long', 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /commands/PublishAssets.ts: -------------------------------------------------------------------------------- 1 | import mime from 'mime-types' 2 | import S3SyncClient from 's3-sync-client' 3 | import { BaseCommand } from '@adonisjs/core/build/standalone' 4 | 5 | export default class PublishAssets extends BaseCommand { 6 | /** 7 | * Command name is used to run the command 8 | */ 9 | public static commandName = 'publish:assets' 10 | 11 | /** 12 | * Command description is displayed in the "help" output 13 | */ 14 | public static description = 'Publish assets to a remote s3 or digital ocean bucket' 15 | 16 | public static settings = { 17 | /** 18 | * Set the following value to true, if you want to load the application 19 | * before running the command 20 | */ 21 | loadApp: false, 22 | 23 | /** 24 | * Set the following value to true, if you want this command to keep running until 25 | * you manually decide to exit the process 26 | */ 27 | stayAlive: false, 28 | } 29 | 30 | /** 31 | * Returns an instance of the sync client 32 | */ 33 | private getSyncClient() { 34 | return new S3SyncClient({ 35 | credentials: { 36 | accessKeyId: this.application.env.get('S3_KEY'), 37 | secretAccessKey: this.application.env.get('S3_SECRET'), 38 | }, 39 | region: this.application.env.get('S3_REGION'), 40 | endpoint: this.application.env.get('S3_ENDPOINT'), 41 | }) 42 | } 43 | 44 | /** 45 | * Monitor transfer of assets 46 | */ 47 | private monitorTransfer() { 48 | const monitor = new S3SyncClient.TransferMonitor() 49 | monitor.on('progress', ({ count }) => { 50 | this.logger.logUpdate( 51 | `Uploading ${this.colors.yellow(count.current)} of ${this.colors.yellow(count.total)}` 52 | ) 53 | }) 54 | 55 | return monitor 56 | } 57 | 58 | public async run() { 59 | /** 60 | * The setup method loads the environment variables 61 | */ 62 | await this.application.setup() 63 | 64 | const client = this.getSyncClient() 65 | const monitor = this.monitorTransfer() 66 | 67 | this.logger.info(`Syncing assets to ${this.application.env.get('S3_ENDPOINT')}`) 68 | 69 | /** 70 | * Sync the local assets directory with the bucket assets 71 | * directory. Also cleanup files missing on the local 72 | */ 73 | await client.sync( 74 | this.application.publicPath('assets'), 75 | `s3://${this.application.env.get('S3_BUCKET')}/jokes-app/assets`, 76 | { 77 | monitor, 78 | del: true, 79 | commandInput: { 80 | ACL: 'public-read', 81 | ContentType: (syncCommandInput) => mime.lookup(syncCommandInput.Key), 82 | }, 83 | } 84 | ) 85 | 86 | this.logger.success('Sync completed') 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /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 proxyAddr from 'proxy-addr' 9 | import Env from '@ioc:Adonis/Core/Env' 10 | import Application from '@ioc:Adonis/Core/Application' 11 | import { ServerConfig } from '@ioc:Adonis/Core/Server' 12 | import { LoggerConfig } from '@ioc:Adonis/Core/Logger' 13 | import { ProfilerConfig } from '@ioc:Adonis/Core/Profiler' 14 | import { ValidatorConfig } from '@ioc:Adonis/Core/Validator' 15 | import { AssetsManagerConfig } from '@ioc:Adonis/Core/AssetsManager' 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') 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: ServerConfig = { 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: 'strict', 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'), 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'), 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 | 209 | /* 210 | |-------------------------------------------------------------------------- 211 | | Validator 212 | |-------------------------------------------------------------------------- 213 | | 214 | | Configure the global configuration for the validator. Here's the reference 215 | | to the default config https://git.io/JT0WE 216 | | 217 | */ 218 | export const validator: ValidatorConfig = {} 219 | 220 | /* 221 | |-------------------------------------------------------------------------- 222 | | Assets 223 | |-------------------------------------------------------------------------- 224 | | 225 | | Configure the asset manager you are using to compile the frontend assets 226 | | 227 | */ 228 | export const assets: AssetsManagerConfig = { 229 | /* 230 | |-------------------------------------------------------------------------- 231 | | Driver 232 | |-------------------------------------------------------------------------- 233 | | 234 | | Currently we only support webpack encore and may introduce more drivers 235 | | in the future 236 | | 237 | */ 238 | driver: 'encore', 239 | 240 | /* 241 | |-------------------------------------------------------------------------- 242 | | Public path 243 | |-------------------------------------------------------------------------- 244 | | 245 | | Directory to search for the "manifest.json" and the "entrypoints.json" 246 | | files 247 | | 248 | */ 249 | publicPath: Application.publicPath('assets'), 250 | 251 | /* 252 | |-------------------------------------------------------------------------- 253 | | Script tag 254 | |-------------------------------------------------------------------------- 255 | | 256 | | Define attributes for the entryPointScripts tags 257 | | 258 | */ 259 | script: { 260 | attributes: { 261 | defer: true, 262 | }, 263 | }, 264 | 265 | /* 266 | |-------------------------------------------------------------------------- 267 | | Style tag 268 | |-------------------------------------------------------------------------- 269 | | 270 | | Define attributes for the entryPointStyles tags 271 | | 272 | */ 273 | style: { 274 | attributes: {}, 275 | }, 276 | } 277 | -------------------------------------------------------------------------------- /config/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JY0mp 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: ['username'], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Model 72 | |-------------------------------------------------------------------------- 73 | | 74 | | The model to use for fetching or finding users. The model is imported 75 | | lazily since the config files are read way earlier in the lifecycle 76 | | of booting the app and the models may not be in a usable state at 77 | | that time. 78 | | 79 | */ 80 | model: () => import('App/Models/User'), 81 | }, 82 | }, 83 | }, 84 | } 85 | 86 | export default authConfig 87 | -------------------------------------------------------------------------------- /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 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Convert empty strings to null 60 | |-------------------------------------------------------------------------- 61 | | 62 | | Convert empty form fields to null. HTML forms results in field string 63 | | value when the field is left blank. This option normalizes all the blank 64 | | field values to "null" 65 | | 66 | */ 67 | convertEmptyStringsToNull: true, 68 | 69 | types: ['application/x-www-form-urlencoded'], 70 | }, 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | Raw body parser settings 75 | |-------------------------------------------------------------------------- 76 | | 77 | | Raw body just reads the request body stream as a plain text, which you 78 | | can process by hand. This must be used when request body type is not 79 | | supported by the body parser. 80 | | 81 | */ 82 | raw: { 83 | encoding: 'utf-8', 84 | limit: '1mb', 85 | queryString: {}, 86 | types: ['text/*'], 87 | }, 88 | 89 | /* 90 | |-------------------------------------------------------------------------- 91 | | Multipart parser settings 92 | |-------------------------------------------------------------------------- 93 | | 94 | | The settings for the `multipart/form-data` parser. The types defines the 95 | | request content types which gets processed by the form parser. 96 | | 97 | */ 98 | multipart: { 99 | /* 100 | |-------------------------------------------------------------------------- 101 | | Auto process 102 | |-------------------------------------------------------------------------- 103 | | 104 | | The auto process option will process uploaded files and writes them to 105 | | the `tmp` folder. You can turn it off and then manually use the stream 106 | | to pipe stream to a different destination. 107 | | 108 | | It is recommended to keep `autoProcess=true`. Unless you are processing bigger 109 | | file sizes. 110 | | 111 | */ 112 | autoProcess: true, 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Files to be processed manually 117 | |-------------------------------------------------------------------------- 118 | | 119 | | You can turn off `autoProcess` for certain routes by defining 120 | | routes inside the following array. 121 | | 122 | | NOTE: Make sure the route pattern starts with a leading slash. 123 | | 124 | | Correct 125 | | ```js 126 | | /projects/:id/file 127 | | ``` 128 | | 129 | | Incorrect 130 | | ```js 131 | | projects/:id/file 132 | | ``` 133 | */ 134 | processManually: [], 135 | 136 | /* 137 | |-------------------------------------------------------------------------- 138 | | Temporary file name 139 | |-------------------------------------------------------------------------- 140 | | 141 | | When auto processing is on. We will use this method to compute the temporary 142 | | file name. AdonisJs will compute a unique `tmpPath` for you automatically, 143 | | However, you can also define your own custom method. 144 | | 145 | */ 146 | // tmpFileName () { 147 | // }, 148 | 149 | /* 150 | |-------------------------------------------------------------------------- 151 | | Encoding 152 | |-------------------------------------------------------------------------- 153 | | 154 | | Request body encoding 155 | | 156 | */ 157 | encoding: 'utf-8', 158 | 159 | /* 160 | |-------------------------------------------------------------------------- 161 | | Convert empty strings to null 162 | |-------------------------------------------------------------------------- 163 | | 164 | | Convert empty form fields to null. HTML forms results in field string 165 | | value when the field is left blank. This option normalizes all the blank 166 | | field values to "null" 167 | | 168 | */ 169 | convertEmptyStringsToNull: true, 170 | 171 | /* 172 | |-------------------------------------------------------------------------- 173 | | Max Fields 174 | |-------------------------------------------------------------------------- 175 | | 176 | | The maximum number of fields allowed in the request body. The field includes 177 | | text inputs and files both. 178 | | 179 | */ 180 | maxFields: 1000, 181 | 182 | /* 183 | |-------------------------------------------------------------------------- 184 | | Request body limit 185 | |-------------------------------------------------------------------------- 186 | | 187 | | The total limit to the multipart body. This includes all request files 188 | | and fields data. 189 | | 190 | */ 191 | limit: '20mb', 192 | 193 | /* 194 | |-------------------------------------------------------------------------- 195 | | Types 196 | |-------------------------------------------------------------------------- 197 | | 198 | | The types that will be considered and parsed as multipart body. 199 | | 200 | */ 201 | types: ['multipart/form-data'], 202 | }, 203 | } 204 | 205 | export default bodyParserConfig 206 | -------------------------------------------------------------------------------- /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 | // You can also use a function that return true or false. 26 | // enabled: (request) => request.url().startsWith('/api') 27 | 28 | /* 29 | |-------------------------------------------------------------------------- 30 | | Origin 31 | |-------------------------------------------------------------------------- 32 | | 33 | | Set a list of origins to be allowed for `Access-Control-Allow-Origin`. 34 | | The value can be one of the following: 35 | | 36 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin 37 | | 38 | | Boolean (true) - Allow current request origin. 39 | | Boolean (false) - Disallow all. 40 | | String - Comma separated list of allowed origins. 41 | | Array - An array of allowed origins. 42 | | String (*) - A wildcard (*) to allow all request origins. 43 | | Function - Receives the current origin string and should return 44 | | one of the above values. 45 | | 46 | */ 47 | origin: true, 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Methods 52 | |-------------------------------------------------------------------------- 53 | | 54 | | An array of allowed HTTP methods for CORS. The `Access-Control-Request-Method` 55 | | is checked against the following list. 56 | | 57 | | Following is the list of default methods. Feel free to add more. 58 | */ 59 | methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Headers 64 | |-------------------------------------------------------------------------- 65 | | 66 | | List of headers to be allowed for `Access-Control-Allow-Headers` header. 67 | | The value can be one of the following: 68 | | 69 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers 70 | | 71 | | Boolean(true) - Allow all headers mentioned in `Access-Control-Request-Headers`. 72 | | Boolean(false) - Disallow all headers. 73 | | String - Comma separated list of allowed headers. 74 | | Array - An array of allowed headers. 75 | | Function - Receives the current header and should return one of the above values. 76 | | 77 | */ 78 | headers: true, 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Expose Headers 83 | |-------------------------------------------------------------------------- 84 | | 85 | | A list of headers to be exposed by setting `Access-Control-Expose-Headers`. 86 | | header. By default following 6 simple response headers are exposed. 87 | | 88 | | Cache-Control 89 | | Content-Language 90 | | Content-Type 91 | | Expires 92 | | Last-Modified 93 | | Pragma 94 | | 95 | | In order to add more headers, simply define them inside the following array. 96 | | 97 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers 98 | | 99 | */ 100 | exposeHeaders: [ 101 | 'cache-control', 102 | 'content-language', 103 | 'content-type', 104 | 'expires', 105 | 'last-modified', 106 | 'pragma', 107 | ], 108 | 109 | /* 110 | |-------------------------------------------------------------------------- 111 | | Credentials 112 | |-------------------------------------------------------------------------- 113 | | 114 | | Toggle `Access-Control-Allow-Credentials` header. If value is set to `true`, 115 | | then header will be set, otherwise not. 116 | | 117 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials 118 | | 119 | */ 120 | credentials: true, 121 | 122 | /* 123 | |-------------------------------------------------------------------------- 124 | | MaxAge 125 | |-------------------------------------------------------------------------- 126 | | 127 | | Define `Access-Control-Max-Age` header in seconds. 128 | | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age 129 | | 130 | */ 131 | maxAge: 90, 132 | } 133 | 134 | export default corsConfig 135 | -------------------------------------------------------------------------------- /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 { DatabaseConfig } from '@ioc:Adonis/Lucid/Database' 10 | 11 | const databaseConfig: DatabaseConfig = { 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Connection 15 | |-------------------------------------------------------------------------- 16 | | 17 | | The primary connection for making database queries across the application 18 | | You can use any key from the `connections` object defined in this same 19 | | file. 20 | | 21 | */ 22 | connection: Env.get('DB_CONNECTION'), 23 | 24 | connections: { 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | PostgreSQL config 28 | |-------------------------------------------------------------------------- 29 | | 30 | | Configuration for PostgreSQL database. Make sure to install the driver 31 | | from npm when using this connection 32 | | 33 | | npm i pg 34 | | 35 | */ 36 | pg: { 37 | client: 'pg', 38 | connection: { 39 | host: Env.get('PG_HOST'), 40 | port: Env.get('PG_PORT'), 41 | user: Env.get('PG_USER'), 42 | password: Env.get('PG_PASSWORD', ''), 43 | database: Env.get('PG_DB_NAME'), 44 | }, 45 | migrations: { 46 | naturalSort: true, 47 | }, 48 | healthCheck: false, 49 | debug: false, 50 | }, 51 | 52 | } 53 | } 54 | 55 | export default databaseConfig 56 | -------------------------------------------------------------------------------- /config/drive.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Config source: https://git.io/JBt3o 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 { DriveConfig } from '@ioc:Adonis/Core/Drive' 10 | import Application from '@ioc:Adonis/Core/Application' 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Drive Config 15 | |-------------------------------------------------------------------------- 16 | | 17 | | The `DriveConfig` relies on the `DisksList` interface which is 18 | | defined inside the `contracts` directory. 19 | | 20 | */ 21 | const driveConfig: DriveConfig = { 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Default disk 25 | |-------------------------------------------------------------------------- 26 | | 27 | | The default disk to use for managing file uploads. The value is driven by 28 | | the `DRIVE_DISK` environment variable. 29 | | 30 | */ 31 | disk: Env.get('DRIVE_DISK'), 32 | 33 | disks: { 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Local 37 | |-------------------------------------------------------------------------- 38 | | 39 | | Uses the local file system to manage files. Make sure to turn off serving 40 | | files when not using this disk. 41 | | 42 | */ 43 | local: { 44 | driver: 'local', 45 | visibility: 'public', 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Storage root - Local driver only 50 | |-------------------------------------------------------------------------- 51 | | 52 | | Define an absolute path to the storage directory from where to read the 53 | | files. 54 | | 55 | */ 56 | root: Application.tmpPath('uploads'), 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Serve files - Local driver only 61 | |-------------------------------------------------------------------------- 62 | | 63 | | When this is set to true, AdonisJS will configure a files server to serve 64 | | files from the disk root. This is done to mimic the behavior of cloud 65 | | storage services that has inbuilt capabilities to serve files. 66 | | 67 | */ 68 | serveFiles: true, 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Base path - Local driver only 73 | |-------------------------------------------------------------------------- 74 | | 75 | | Base path is always required when "serveFiles = true". Also make sure 76 | | the `basePath` is unique across all the disks using "local" driver and 77 | | you are not registering routes with this prefix. 78 | | 79 | */ 80 | basePath: '/uploads', 81 | }, 82 | 83 | /* 84 | |-------------------------------------------------------------------------- 85 | | S3 Driver 86 | |-------------------------------------------------------------------------- 87 | | 88 | | Uses the S3 cloud storage to manage files. Make sure to install the s3 89 | | drive separately when using it. 90 | | 91 | |************************************************************************** 92 | | npm i @adonisjs/drive-s3 93 | |************************************************************************** 94 | | 95 | */ 96 | // s3: { 97 | // driver: 's3', 98 | // visibility: 'public', 99 | // key: Env.get('S3_KEY'), 100 | // secret: Env.get('S3_SECRET'), 101 | // region: Env.get('S3_REGION'), 102 | // bucket: Env.get('S3_BUCKET'), 103 | // endpoint: Env.get('S3_ENDPOINT'), 104 | // }, 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | GCS Driver 109 | |-------------------------------------------------------------------------- 110 | | 111 | | Uses the Google cloud storage to manage files. Make sure to install the GCS 112 | | drive separately when using it. 113 | | 114 | |************************************************************************** 115 | | npm i @adonisjs/drive-gcs 116 | |************************************************************************** 117 | | 118 | */ 119 | // gcs: { 120 | // driver: 'gcs', 121 | // visibility: 'public', 122 | // keyFilename: Env.get('GCS_KEY_FILENAME'), 123 | // bucket: Env.get('GCS_BUCKET'), 124 | 125 | /* 126 | |-------------------------------------------------------------------------- 127 | | Uniform ACL - Google cloud storage only 128 | |-------------------------------------------------------------------------- 129 | | 130 | | When using the Uniform ACL on the bucket, the "visibility" option is 131 | | ignored. Since, the files ACL is managed by the google bucket policies 132 | | directly. 133 | | 134 | |************************************************************************** 135 | | Learn more: https://cloud.google.com/storage/docs/uniform-bucket-level-access 136 | |************************************************************************** 137 | | 138 | | The following option just informs drive whether your bucket is using uniform 139 | | ACL or not. The actual setting needs to be toggled within the Google cloud 140 | | console. 141 | | 142 | */ 143 | // usingUniformAcl: false 144 | // }, 145 | }, 146 | } 147 | 148 | export default driveConfig 149 | -------------------------------------------------------------------------------- /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 Application from '@ioc:Adonis/Core/Application' 10 | import { SessionConfig } from '@ioc:Adonis/Addons/Session' 11 | 12 | const sessionConfig: SessionConfig = { 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | Enable/Disable sessions 16 | |-------------------------------------------------------------------------- 17 | | 18 | | Setting the following property to "false" will disable the session for the 19 | | entire application 20 | | 21 | */ 22 | enabled: true, 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Driver 27 | |-------------------------------------------------------------------------- 28 | | 29 | | The session driver to use. You can choose between one of the following 30 | | drivers. 31 | | 32 | | - cookie (Uses signed cookies to store session values) 33 | | - file (Uses filesystem to store session values) 34 | | - redis (Uses redis. Make sure to install "@adonisjs/redis" as well) 35 | | 36 | | Note: Switching drivers will make existing sessions invalid. 37 | | 38 | */ 39 | driver: Env.get('SESSION_DRIVER'), 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Cookie name 44 | |-------------------------------------------------------------------------- 45 | | 46 | | The name of the cookie that will hold the session id. 47 | | 48 | */ 49 | cookieName: 'adonis-session', 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Clear session when browser closes 54 | |-------------------------------------------------------------------------- 55 | | 56 | | Whether or not you want to destroy the session when browser closes. Setting 57 | | this value to `true` will ignore the `age`. 58 | | 59 | */ 60 | clearWithBrowser: false, 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Session age 65 | |-------------------------------------------------------------------------- 66 | | 67 | | The duration for which session stays active after no activity. A new HTTP 68 | | request to the server is considered as activity. 69 | | 70 | | The value can be a number in milliseconds or a string that must be valid 71 | | as per https://npmjs.org/package/ms package. 72 | | 73 | | Example: `2 days`, `2.5 hrs`, `1y`, `5s` and so on. 74 | | 75 | */ 76 | age: '2h', 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Cookie values 81 | |-------------------------------------------------------------------------- 82 | | 83 | | The cookie settings are used to setup the session id cookie and also the 84 | | driver will use the same values. 85 | | 86 | */ 87 | cookie: { 88 | path: '/', 89 | httpOnly: true, 90 | sameSite: 'strict', 91 | }, 92 | 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Configuration for the file driver 96 | |-------------------------------------------------------------------------- 97 | | 98 | | The file driver needs absolute path to the directory in which sessions 99 | | must be stored. 100 | | 101 | */ 102 | file: { 103 | location: Application.tmpPath('sessions'), 104 | }, 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Redis driver 109 | |-------------------------------------------------------------------------- 110 | | 111 | | The redis connection you want session driver to use. The same connection 112 | | must be defined inside `config/redis.ts` file as well. 113 | | 114 | */ 115 | redisConnection: 'local', 116 | } 117 | 118 | export default sessionConfig 119 | -------------------------------------------------------------------------------- /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 Env from '@ioc:Adonis/Core/Env' 9 | import { ShieldConfig } from '@ioc:Adonis/Addons/Shield' 10 | 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | Content Security Policy 14 | |-------------------------------------------------------------------------- 15 | | 16 | | Content security policy filters out the origins not allowed to execute 17 | | and load resources like scripts, styles and fonts. There are wide 18 | | variety of options to choose from. 19 | */ 20 | export const csp: ShieldConfig['csp'] = { 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Enable/disable CSP 24 | |-------------------------------------------------------------------------- 25 | | 26 | | The CSP rules are disabled by default for seamless onboarding. 27 | | 28 | */ 29 | enabled: false, 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Directives 34 | |-------------------------------------------------------------------------- 35 | | 36 | | All directives are defined in camelCase and here is the list of 37 | | available directives and their possible values. 38 | | 39 | | https://content-security-policy.com 40 | | 41 | | @example 42 | | directives: { 43 | | defaultSrc: ["'self'", '@nonce', 'cdnjs.cloudflare.com'] 44 | | } 45 | | 46 | */ 47 | directives: {}, 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Report only 52 | |-------------------------------------------------------------------------- 53 | | 54 | | Setting `reportOnly=true` will not block the scripts from running and 55 | | instead report them to a URL. 56 | | 57 | */ 58 | reportOnly: false, 59 | } 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | CSRF Protection 64 | |-------------------------------------------------------------------------- 65 | | 66 | | CSRF Protection adds another layer of security by making sure, actionable 67 | | routes does have a valid token to execute an action. 68 | | 69 | */ 70 | export const csrf: ShieldConfig['csrf'] = { 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Enable/Disable CSRF 74 | |-------------------------------------------------------------------------- 75 | */ 76 | enabled: Env.get('NODE_ENV') !== 'testing', 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Routes to Ignore 81 | |-------------------------------------------------------------------------- 82 | | 83 | | Define an array of route patterns that you want to ignore from CSRF 84 | | validation. Make sure the route patterns are started with a leading 85 | | slash. Example: 86 | | 87 | | `/foo/bar` 88 | | 89 | | Also you can define a function that is evaluated on every HTTP Request. 90 | | ``` 91 | | exceptRoutes: ({ request }) => request.url().includes('/api') 92 | | ``` 93 | | 94 | */ 95 | exceptRoutes: [], 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Enable Sharing Token Via Cookie 100 | |-------------------------------------------------------------------------- 101 | | 102 | | When the following flag is enabled, AdonisJS will drop `XSRF-TOKEN` 103 | | cookie that frontend frameworks can read and return back as a 104 | | `X-XSRF-TOKEN` header. 105 | | 106 | | The cookie has `httpOnly` flag set to false, so it is little insecure and 107 | | can be turned off when you are not using a frontend framework making 108 | | AJAX requests. 109 | | 110 | */ 111 | enableXsrfCookie: true, 112 | 113 | /* 114 | |-------------------------------------------------------------------------- 115 | | Methods to Validate 116 | |-------------------------------------------------------------------------- 117 | | 118 | | Define an array of HTTP methods to be validated for a valid CSRF token. 119 | | 120 | */ 121 | methods: ['POST', 'PUT', 'PATCH', 'DELETE'], 122 | } 123 | 124 | /* 125 | |-------------------------------------------------------------------------- 126 | | DNS Prefetching 127 | |-------------------------------------------------------------------------- 128 | | 129 | | DNS prefetching allows browsers to proactively perform domain name 130 | | resolution in background. 131 | | 132 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control 133 | | 134 | */ 135 | export const dnsPrefetch: ShieldConfig['dnsPrefetch'] = { 136 | /* 137 | |-------------------------------------------------------------------------- 138 | | Enable/disable this feature 139 | |-------------------------------------------------------------------------- 140 | */ 141 | enabled: true, 142 | 143 | /* 144 | |-------------------------------------------------------------------------- 145 | | Allow or Dis-Allow Explicitly 146 | |-------------------------------------------------------------------------- 147 | | 148 | | The `enabled` boolean does not set `X-DNS-Prefetch-Control` header. However 149 | | the `allow` boolean controls the value of `X-DNS-Prefetch-Control` header. 150 | | 151 | | - When `allow = true`, then `X-DNS-Prefetch-Control = 'on'` 152 | | - When `allow = false`, then `X-DNS-Prefetch-Control = 'off'` 153 | | 154 | */ 155 | allow: true, 156 | } 157 | 158 | /* 159 | |-------------------------------------------------------------------------- 160 | | Iframe Options 161 | |-------------------------------------------------------------------------- 162 | | 163 | | xFrame defines whether or not your website can be embedded inside an 164 | | iframe. Choose from one of the following options. 165 | | 166 | | - DENY 167 | | - SAMEORIGIN 168 | | - ALLOW-FROM http://example.com 169 | | 170 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 171 | */ 172 | export const xFrame: ShieldConfig['xFrame'] = { 173 | enabled: true, 174 | action: 'DENY', 175 | } 176 | 177 | /* 178 | |-------------------------------------------------------------------------- 179 | | Http Strict Transport Security 180 | |-------------------------------------------------------------------------- 181 | | 182 | | A security to ensure that a browser always makes a connection over 183 | | HTTPS. 184 | | 185 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security 186 | | 187 | */ 188 | export const hsts: ShieldConfig['hsts'] = { 189 | enabled: true, 190 | /* 191 | |-------------------------------------------------------------------------- 192 | | Max Age 193 | |-------------------------------------------------------------------------- 194 | | 195 | | Control, how long the browser should remember that a site is only to be 196 | | accessed using HTTPS. 197 | | 198 | */ 199 | maxAge: '180 days', 200 | 201 | /* 202 | |-------------------------------------------------------------------------- 203 | | Include Subdomains 204 | |-------------------------------------------------------------------------- 205 | | 206 | | Apply rules on the subdomains as well. 207 | | 208 | */ 209 | includeSubDomains: true, 210 | 211 | /* 212 | |-------------------------------------------------------------------------- 213 | | Preloading 214 | |-------------------------------------------------------------------------- 215 | | 216 | | Google maintains a service to register your domain and it will preload 217 | | the HSTS policy. Learn more https://hstspreload.org/ 218 | | 219 | */ 220 | preload: false, 221 | } 222 | 223 | /* 224 | |-------------------------------------------------------------------------- 225 | | No Sniff 226 | |-------------------------------------------------------------------------- 227 | | 228 | | Browsers have a habit of sniffing content-type of a response. Which means 229 | | files with .txt extension containing Javascript code will be executed as 230 | | Javascript. You can disable this behavior by setting nosniff to false. 231 | | 232 | | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 233 | | 234 | */ 235 | export const contentTypeSniffing: ShieldConfig['contentTypeSniffing'] = { 236 | enabled: true, 237 | } 238 | -------------------------------------------------------------------------------- /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 | |-------------------------------------------------------------------------- 65 | | Max age 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Set the value for the max-age directive. Set a higher value in production 69 | | if you fingerprint your assets. 70 | | 71 | | Learn more: https://docs.adonisjs.com/guides/deployment#serving-static-assets 72 | | 73 | */ 74 | maxAge: 0, 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Immutable 79 | |-------------------------------------------------------------------------- 80 | | 81 | | Set the immutable directive. Set it to `true` if the assets are generated 82 | | with a fingerprint. In others words the file name changes when the file 83 | | contents change. 84 | | 85 | */ 86 | immutable: false, 87 | } 88 | 89 | export default staticConfig 90 | -------------------------------------------------------------------------------- /contracts/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contract source: https://git.io/JOdz5 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 3 different guards. 49 | | 50 | | - SessionGuardContract 51 | | - BasicAuthGuardContract 52 | | - OATGuardContract ( Opaque access token ) 53 | | 54 | | Every guard needs a provider for looking up users from the database. 55 | | 56 | */ 57 | interface GuardsList { 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Web Guard 61 | |-------------------------------------------------------------------------- 62 | | 63 | | The web guard uses sessions for maintaining user login state. It uses 64 | | the `user` provider for fetching user details. 65 | | 66 | */ 67 | web: { 68 | implementation: SessionGuardContract<'user', 'web'> 69 | config: SessionGuardConfig<'user'> 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/drive.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contract source: https://git.io/JBt3I 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/Drive' { 9 | interface DisksList { 10 | local: { 11 | config: LocalDriverConfig 12 | implementation: LocalDriverContract 13 | } 14 | // s3: { 15 | // config: S3DriverConfig 16 | // implementation: S3DriverContract 17 | // } 18 | // gcs: { 19 | // config: GcsDriverConfig 20 | // implementation: GcsDriverContract 21 | // } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/env.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contract source: https://git.io/JTm6U 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/Env' { 9 | /* 10 | |-------------------------------------------------------------------------- 11 | | Getting types for validated environment variables 12 | |-------------------------------------------------------------------------- 13 | | 14 | | The `default` export from the "../env.ts" file exports types for the 15 | | validated environment variables. Here we merge them with the `EnvTypes` 16 | | interface so that you can enjoy intellisense when using the "Env" 17 | | module. 18 | | 19 | */ 20 | 21 | type CustomTypes = typeof import('../env').default 22 | interface EnvTypes extends CustomTypes {} 23 | } 24 | -------------------------------------------------------------------------------- /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 | interface HashersList { 10 | bcrypt: { 11 | config: BcryptConfig 12 | implementation: BcryptContract 13 | } 14 | argon: { 15 | config: ArgonConfig 16 | implementation: ArgonContract 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /database/factories/index.ts: -------------------------------------------------------------------------------- 1 | // import Factory from '@ioc:Adonis/Lucid/Factory' 2 | -------------------------------------------------------------------------------- /database/migrations/1638164583286_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('username', 255).notNullable() 10 | table.string('password', 180).notNullable() 11 | table.string('remember_me_token').nullable() 12 | 13 | /** 14 | * Uses timestampz for PostgreSQL and DATETIME2 for MSSQL 15 | */ 16 | table.timestamp('created_at', { useTz: true }).notNullable() 17 | table.timestamp('updated_at', { useTz: true }).notNullable() 18 | }) 19 | } 20 | 21 | public async down() { 22 | this.schema.dropTable(this.tableName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/migrations/1738114600160_jokes.ts: -------------------------------------------------------------------------------- 1 | import BaseSchema from '@ioc:Adonis/Lucid/Schema' 2 | 3 | export default class Jokes extends BaseSchema { 4 | protected tableName = 'jokes' 5 | 6 | public async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id') 9 | table.integer('user_id').unsigned().notNullable().references('users.id') 10 | table.string('name').notNullable() 11 | table.text('content').notNullable() 12 | 13 | /** 14 | * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL 15 | */ 16 | table.timestamp('created_at', { useTz: true }) 17 | table.timestamp('updated_at', { useTz: true }) 18 | }) 19 | } 20 | 21 | public async down() { 22 | this.schema.dropTable(this.tableName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /env.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Validating Environment Variables 4 | |-------------------------------------------------------------------------- 5 | | 6 | | In this file we define the rules for validating environment variables. 7 | | By performing validation we ensure that your application is running in 8 | | a stable environment with correct configuration values. 9 | | 10 | | This file is read automatically by the framework during the boot lifecycle 11 | | and hence do not rename or move this file to a different location. 12 | | 13 | */ 14 | 15 | import Env from '@ioc:Adonis/Core/Env' 16 | 17 | export default Env.rules({ 18 | HOST: Env.schema.string({ format: 'host' }), 19 | PORT: Env.schema.number(), 20 | APP_KEY: Env.schema.string(), 21 | APP_NAME: Env.schema.string(), 22 | CACHE_VIEWS: Env.schema.boolean(), 23 | SESSION_DRIVER: Env.schema.string(), 24 | DRIVE_DISK: Env.schema.enum(['local'] as const), 25 | NODE_ENV: Env.schema.enum(['development', 'production', 'testing'] as const), 26 | DB_CONNECTION: Env.schema.string(), 27 | PG_HOST: Env.schema.string({ format: 'host' }), 28 | PG_PORT: Env.schema.number(), 29 | PG_USER: Env.schema.string(), 30 | PG_PASSWORD: Env.schema.string.optional(), 31 | PG_DB_NAME: Env.schema.string(), 32 | }) 33 | -------------------------------------------------------------------------------- /lh-score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adonisjs-community/remix-jokes/dd55551f19287a3ce3c24e0451d59bb2082a802a/lh-score.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remix-jokes-lol", 3 | "version": "1.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 | "@symfony/webpack-encore": "^1.6.1", 15 | "adonis-preset-ts": "^2.1.0", 16 | "autoprefixer": "^10.4.0", 17 | "eslint": "^8.3.0", 18 | "eslint-config-prettier": "^8.3.0", 19 | "eslint-plugin-adonis": "^2.0.0", 20 | "eslint-plugin-prettier": "^4.0.0", 21 | "pino-pretty": "^7.2.0", 22 | "postcss": "^8.4.4", 23 | "postcss-loader": "^6.2.1", 24 | "prettier": "^2.5.0", 25 | "s3-sync-client": "^2.1.0", 26 | "tailwindcss": "^2.2.19", 27 | "typescript": "~4.5", 28 | "unpoly": "^2.4.1", 29 | "youch": "^2.2.2", 30 | "youch-terminal": "^1.1.1" 31 | }, 32 | "dependencies": { 33 | "@adonisjs/auth": "^8.0.10", 34 | "@adonisjs/core": "^5.4.1", 35 | "@adonisjs/lucid": "^16.3.2", 36 | "@adonisjs/repl": "^3.1.7", 37 | "@adonisjs/session": "^6.1.2", 38 | "@adonisjs/shield": "^7.0.7", 39 | "@adonisjs/view": "^6.1.1", 40 | "luxon": "^2.1.1", 41 | "pg": "^8.7.1", 42 | "phc-argon2": "^1.1.2", 43 | "proxy-addr": "^2.0.7", 44 | "reflect-metadata": "^0.1.13", 45 | "source-map-support": "^0.5.21" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /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 async boot() { 11 | // IoC container is ready 12 | } 13 | 14 | public async ready() { 15 | // App is ready 16 | } 17 | 18 | public async shutdown() { 19 | // Cleanup, since app is going down 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /providers/Up/Provider.ts: -------------------------------------------------------------------------------- 1 | import Up from './index' 2 | import { ApplicationContract } from '@ioc:Adonis/Core/Application' 3 | 4 | export default class AppProvider { 5 | constructor(protected app: ApplicationContract) {} 6 | 7 | public async boot() { 8 | const HttpContext = this.app.container.resolveBinding('Adonis/Core/HttpContext') 9 | const Server = this.app.container.resolveBinding('Adonis/Core/Server') 10 | 11 | HttpContext.getter( 12 | 'up', 13 | function () { 14 | return new Up(this) 15 | }, 16 | true 17 | ) 18 | 19 | Server.hooks.before(async (ctx) => { 20 | ctx.view.share({ up: ctx.up }) 21 | }) 22 | 23 | Server.hooks.after(async (ctx) => { 24 | ctx.up.commit() 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /providers/Up/context.ts: -------------------------------------------------------------------------------- 1 | import Up from './index' 2 | 3 | declare module '@ioc:Adonis/Core/HttpContext' { 4 | interface HttpContextContract { 5 | up: Up 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /providers/Up/index.ts: -------------------------------------------------------------------------------- 1 | import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 2 | 3 | /** 4 | * A list of supported unpoly headers 5 | */ 6 | const UNPOLY_HEADERS = [ 7 | 'X-Up-Accept-Layer', 8 | 'X-Up-Clear-Cache', 9 | 'X-Up-Context', 10 | 'X-Up-Dismiss-Layer', 11 | 'X-Up-Events', 12 | 'X-Up-Fail-Context', 13 | 'X-Up-Fail-Mode', 14 | 'X-Up-Fail-Target', 15 | 'X-Up-Location', 16 | 'X-Up-Method', 17 | 'X-Up-Mode', 18 | 'X-Up-Reload-From-Time', 19 | 'X-Up-Target', 20 | 'X-Full-Reload', 21 | ] 22 | 23 | export default class Up { 24 | /** 25 | * The headers to set on the response or flash messages in case of redirect 26 | */ 27 | private headers: Record = {} 28 | 29 | /** 30 | * Converts the unpoly header name to flash messages key 31 | */ 32 | private headerToFlashKey(header: string) { 33 | return header.replace('X-', '').toLowerCase() 34 | } 35 | 36 | /** 37 | * Returns the value of an unpoly header. Giving priority to the 38 | * flash messages 39 | */ 40 | private getProperty(header: string): string { 41 | return this.ctx.session.flashMessages.get( 42 | this.headerToFlashKey(header), 43 | this.ctx.request.header(header) 44 | ) 45 | } 46 | 47 | /** 48 | * Set the unpoly response header 49 | */ 50 | private setProperty(header: string, value: string): void { 51 | this.headers[header] = value 52 | } 53 | 54 | /** 55 | * Set unpoly headers as flash messages 56 | */ 57 | private setHeadersAsFlashMessages(headers: Record) { 58 | Object.keys(headers).forEach((header) => { 59 | this.ctx.session.flash(this.headerToFlashKey(header), headers[header]) 60 | }) 61 | } 62 | 63 | /** 64 | * Set unpoly headers as response headers 65 | */ 66 | private setHeadersAsResponse(headers: Record) { 67 | Object.keys(headers).forEach((header) => { 68 | this.ctx.response.header(header, headers[header]) 69 | }) 70 | } 71 | 72 | constructor(private ctx: HttpContextContract) {} 73 | 74 | /** 75 | * Commit response 76 | */ 77 | public commit() { 78 | const headers = Object.assign( 79 | UNPOLY_HEADERS.reduce((result, header) => { 80 | const value = this.ctx.session.flashMessages.get(this.headerToFlashKey(header)) 81 | if (value) { 82 | result[header] = value 83 | } 84 | return result 85 | }, {}), 86 | this.headers 87 | ) 88 | 89 | if (this.ctx.response.getHeader('Location')) { 90 | this.setHeadersAsFlashMessages(headers) 91 | } else { 92 | this.setHeadersAsResponse(headers) 93 | } 94 | } 95 | 96 | public getLayer() { 97 | return this.getProperty('X-Up-Accept-Layer') 98 | } 99 | 100 | public getCache() { 101 | return this.getProperty('X-Up-Clear-Cache') 102 | } 103 | 104 | public getContext() { 105 | return this.getProperty('X-Up-Context') 106 | } 107 | 108 | public getDismissLayer() { 109 | return this.getProperty('X-Up-Dismiss-Layer') 110 | } 111 | 112 | public getEvents() { 113 | return this.getProperty('X-Up-Events') 114 | } 115 | 116 | public getFailContext() { 117 | return this.getProperty('X-Up-Fail-Context') 118 | } 119 | 120 | public getFailMode() { 121 | return this.getProperty('X-Up-Fail-Mode') 122 | } 123 | 124 | public getFailTarget() { 125 | return this.getProperty('X-Up-Fail-Target') 126 | } 127 | 128 | public getLocation() { 129 | return this.getProperty('X-Up-Location') 130 | } 131 | 132 | public getMethod() { 133 | return this.getProperty('X-Up-Method') 134 | } 135 | 136 | public getMode() { 137 | return this.getProperty('X-Up-Mode') 138 | } 139 | 140 | public getReloadFromTime() { 141 | return this.getProperty('X-Up-Reload-From-Time') 142 | } 143 | 144 | public getTarget() { 145 | return this.getProperty('X-Up-Target') || 'body' 146 | } 147 | 148 | public targetIncludes(selector: string): boolean { 149 | const target = this.getTarget() 150 | .split(',') 151 | .map((value) => value.trim()) 152 | return target.includes('body') ? true : target.includes(selector) 153 | } 154 | 155 | public getTitle() { 156 | return this.getProperty('X-Up-Title') 157 | } 158 | 159 | public getValidate() { 160 | return this.getProperty('X-Up-Validate') 161 | } 162 | 163 | public getVersion() { 164 | return this.getProperty('X-Up-Version') 165 | } 166 | 167 | public setLayer(value: string) { 168 | return this.setProperty('X-Up-Accept-Layer', value) 169 | } 170 | 171 | public setCache(value: string) { 172 | return this.setProperty('X-Up-Clear-Cache', value) 173 | } 174 | 175 | public setContext(value: string) { 176 | return this.setProperty('X-Up-Context', value) 177 | } 178 | 179 | public setDismissLayer(value: string) { 180 | return this.setProperty('X-Up-Dismiss-Layer', value) 181 | } 182 | 183 | public setEvents(value: string) { 184 | return this.setProperty('X-Up-Events', value) 185 | } 186 | 187 | public setFailContext(value: string) { 188 | return this.setProperty('X-Up-Fail-Context', value) 189 | } 190 | 191 | public setFailMode(value: string) { 192 | return this.setProperty('X-Up-Fail-Mode', value) 193 | } 194 | 195 | public setFailTarget(value: string) { 196 | return this.setProperty('X-Up-Fail-Target', value) 197 | } 198 | 199 | public setLocation(value: string) { 200 | return this.setProperty('X-Up-Location', value) 201 | } 202 | 203 | public setMethod(value: string) { 204 | return this.setProperty('X-Up-Method', value) 205 | } 206 | 207 | public setMode(value: string) { 208 | return this.setProperty('X-Up-Mode', value) 209 | } 210 | 211 | public setReloadFromTime(value: string) { 212 | return this.setProperty('X-Up-Reload-From-Time', value) 213 | } 214 | 215 | public setTarget(value: string) { 216 | return this.setProperty('X-Up-Target', value) 217 | } 218 | 219 | public setTitle(value: string) { 220 | return this.setProperty('X-Up-Title', value) 221 | } 222 | 223 | public setValidate(value: string) { 224 | return this.setProperty('X-Up-Validate', value) 225 | } 226 | 227 | public setVersion(value: string) { 228 | return this.setProperty('X-Up-Version', value) 229 | } 230 | 231 | public fullReload() { 232 | return this.setProperty('X-Full-Reload', 'true') 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /public/assets/entrypoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "entrypoints": { 3 | "app": { 4 | "css": [ 5 | "http://localhost:8080/assets/app.css" 6 | ], 7 | "js": [ 8 | "http://localhost:8080/assets/app.js" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /public/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets/app.css": "http://localhost:8080/assets/app.css", 3 | "assets/app.js": "http://localhost:8080/assets/app.js", 4 | "assets/fonts/baloo.woff": "http://localhost:8080/assets/fonts/baloo.d71cebdd.woff" 5 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adonisjs-community/remix-jokes/dd55551f19287a3ce3c24e0451d59bb2082a802a/public/favicon.ico -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @font-face { 6 | font-family: "baloo"; 7 | src: url("../fonts/baloo.woff") format("woff"); 8 | font-weight: normal; 9 | font-style: normal; 10 | font-display: swap; 11 | } 12 | 13 | up-progress-bar { 14 | @apply bg-yellow-500; 15 | } 16 | 17 | a:focus, input:focus, textarea:focus, button:focus { 18 | @apply outline; 19 | } 20 | 21 | a { 22 | @apply text-yellow-500 hover:text-yellow-600; 23 | } 24 | 25 | a:hover, a.up-current { 26 | @apply underline; 27 | } 28 | 29 | .button { 30 | @apply bg-yellow-500 text-eggplant-900 font-bold text-lg shadow-button rounded font-display px-5 py-2 inline-flex items-center justify-center; 31 | 32 | /** 33 | * Not a big fan of Tailwind transition and transforms. Hence writing custom css 34 | */ 35 | transform: translateY(0); 36 | transition: background-color 50ms ease-out, box-shadow 50ms ease-out, 37 | transform 100ms cubic-bezier(0.3, 0.6, 0.8, 1.25); 38 | } 39 | 40 | .button:hover { 41 | @apply text-eggplant-900 shadow-raised-button no-underline; 42 | transform: translateY(-1px); 43 | } 44 | 45 | .text-shadow-gray-900 { 46 | text-shadow: 0 0.2em 0.5em rgb(0 0 0 / 50%), 0 5px 0 rgb(0 0 0 / 75%); 47 | } 48 | 49 | .text-shadow-gray-500 { 50 | text-shadow: 0 3px 0 rgb(0 0 0 / 75%); 51 | } 52 | -------------------------------------------------------------------------------- /resources/fonts/baloo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adonisjs-community/remix-jokes/dd55551f19287a3ce3c24e0451d59bb2082a802a/resources/fonts/baloo.woff -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import '../css/app.css' 2 | import 'unpoly' 3 | import 'unpoly/unpoly.css' 4 | 5 | up.fragment.config.mainTargets.push('[layout-main]') 6 | up.fragment.config.mainTargets.push('[layout-auth-form]') 7 | 8 | up.on('up:fragment:loaded', (event) => { 9 | let fullReload = event.response.getHeader('X-Full-Reload') 10 | 11 | if (fullReload) { 12 | // Prevent the fragment update and don't update browser history 13 | event.preventDefault() 14 | 15 | // Make a full page load for the same request. 16 | event.request.loadPage() 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /resources/views/components/layouts/app.edge: -------------------------------------------------------------------------------- 1 |
2 |
3 | @include('partials/header') 4 | 5 |
6 |
7 | @include('partials/sidebar') 8 | 9 |
10 | {{{ await $slots.main() }}} 11 |
12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /resources/views/components/layouts/auth.edge: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{{ await $slots.main() }}} 5 |
6 | {{{ await $slots.footer() }}} 7 |
8 |
9 | -------------------------------------------------------------------------------- /resources/views/components/layouts/root.edge: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title || 'Remix: So great, it\'s funny!' }} 7 | 8 | 12 | 16 | 17 | 18 | 19 | 20 | @entryPointStyles('app') 21 | @entryPointScripts('app') 22 | 23 | 24 | {{{ await $slots.main() }}} 25 | 26 | 27 | -------------------------------------------------------------------------------- /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/home.edge: -------------------------------------------------------------------------------- 1 | @layouts.root() 2 |
3 |
4 |

5 | Remix AdonisJS 6 | Jokes! 7 |

8 | 20 |
21 |
22 | @end 23 | -------------------------------------------------------------------------------- /resources/views/pages/jokes/create.edge: -------------------------------------------------------------------------------- 1 | @layouts.root() 2 | @layouts.app() 3 | @if(auth.isLoggedIn) 4 |
5 |

Add your own hilarious joke

6 | 7 |
14 |
15 | 16 | 23 | @if(flashMessages.has('errors.name')) 24 | 27 | @end 28 |
29 | 30 |
31 | 32 | 37 | 38 | @if(flashMessages.has('errors.content')) 39 | 42 | @end 43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 |
51 | @else 52 |
53 |

You must be logged in to create a joke.

54 | Login 55 |
56 | @end 57 | @end 58 | @end 59 | -------------------------------------------------------------------------------- /resources/views/pages/jokes/index.edge: -------------------------------------------------------------------------------- 1 | @layouts.root({ title: randomJoke?.name, description: randomJoke?.content }) 2 | @layouts.app() 3 | @if(!randomJoke) 4 |
5 |

There are no jokes to display.

6 | Add your own 7 |
8 | @else 9 |

Randomly selected joke

10 | 11 |

{{ randomJoke.name }}

12 |

{{ randomJoke.content }}

13 | 14 | 23 | @end 24 | @end 25 | @end 26 | -------------------------------------------------------------------------------- /resources/views/pages/jokes/show.edge: -------------------------------------------------------------------------------- 1 | @layouts.root({ title: joke.name, description: joke.content }) 2 | @layouts.app() 3 |

{{ joke.name }}

4 |

{{ joke.content }}

5 | 6 |
13 | 14 |
15 | @end 16 | @end 17 | -------------------------------------------------------------------------------- /resources/views/pages/login.edge: -------------------------------------------------------------------------------- 1 | @layouts.root({ title: 'AdonisJS Remix - Login' }) 2 | @layouts.auth() 3 |
4 |

Login

5 |
12 |
13 | 14 | 21 |
22 | 23 |
24 | 25 | 31 |
32 | 33 |
34 | @if(flashMessages.has('auth.errors')) 35 | 36 | @end 37 |
38 | 39 | 40 | 41 |
42 | Don't have an account? 43 | 48 | Register here 49 | 50 |
51 |
52 |
53 | 54 | @slot('footer') 55 |
56 | Back home 57 |
58 | @end 59 | @end 60 | @end 61 | -------------------------------------------------------------------------------- /resources/views/pages/signup.edge: -------------------------------------------------------------------------------- 1 | @layouts.root({ title: 'AdonisJS Remix - Signup' }) 2 | @layouts.auth() 3 |
4 |

Register

5 |
12 |
13 | 14 | 21 | @if(flashMessages.has('errors.username')) 22 | 25 | @end 26 |
27 | 28 |
29 | 30 | 36 | @if(flashMessages.has('errors.password')) 37 | 40 | @end 41 |
42 | 43 | 44 | 45 |
46 | Already have an account? 47 | 52 | Login here 53 | 54 |
55 |
56 |
57 | 58 | @slot('footer') 59 |
60 | Back home 61 |
62 | @end 63 | @end 64 | @end 65 | -------------------------------------------------------------------------------- /resources/views/partials/header.edge: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | 🤪 6 | 7 | 8 |

9 | 10 |
11 | @if(auth.isLoggedIn) 12 | Hi {{ auth.user.username }} 13 |
14 | 15 |
16 | @else 17 | 21 | Login 22 | 23 | @end 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /resources/views/partials/sidebar.edge: -------------------------------------------------------------------------------- 1 |
2 | @if(up.targetIncludes('[layout-sidebar]') && auth.isLoggedIn) 3 | 9 | Get a random joke 10 | 11 |

Here are a few more jokes to check out:

12 | 13 |
    14 | @each(joke in await loadJokes(auth.user)) 15 |
  • 16 | {{ joke.name }} 22 |
  • 23 | @end 24 |
25 | 26 | 32 | Add your own 33 | 34 | @end 35 |
36 | -------------------------------------------------------------------------------- /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 every HTTP requests. 21 | | 22 | */ 23 | Server.middleware.register([ 24 | () => import('@ioc:Adonis/Core/BodyParser'), 25 | () => import('App/Middleware/SilentAuth'), 26 | ]) 27 | 28 | /* 29 | |-------------------------------------------------------------------------- 30 | | Named middleware 31 | |-------------------------------------------------------------------------- 32 | | 33 | | Named middleware are defined as key-value pair. The value is the namespace 34 | | or middleware function and key is the alias. Later you can use these 35 | | alias on individual routes. For example: 36 | | 37 | | { auth: () => import('App/Middleware/Auth') } 38 | | 39 | | and then use it as follows 40 | | 41 | | Route.get('dashboard', 'UserController.dashboard').middleware('auth') 42 | | 43 | */ 44 | Server.middleware.registerNamed({ 45 | auth: () => import('App/Middleware/Auth'), 46 | }) 47 | -------------------------------------------------------------------------------- /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.ts` as follows 15 | | 16 | | import './routes/cart' 17 | | import './routes/customer'' 18 | | 19 | */ 20 | 21 | import Route from '@ioc:Adonis/Core/Route' 22 | 23 | Route.on('/').render('home') 24 | 25 | Route.get('login', 'SessionController.create') 26 | Route.post('login', 'SessionController.store') 27 | Route.post('logout', 'SessionController.destroy') 28 | 29 | Route.get('register', 'SignupController.create') 30 | Route.post('register', 'SignupController.store') 31 | 32 | Route.resource('jokes', 'JokesController') 33 | .except(['edit', 'update']) 34 | .middleware({ store: ['auth'], destroy: ['auth'], show: ['auth'] }) 35 | -------------------------------------------------------------------------------- /start/view.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Preloaded File 4 | |-------------------------------------------------------------------------- 5 | | 6 | | Any code written inside this file will be executed during the application 7 | | boot. 8 | | 9 | */ 10 | 11 | import User from 'App/Models/User' 12 | import View from '@ioc:Adonis/Core/View' 13 | 14 | View.global('loadJokes', function (user: User) { 15 | if (user instanceof User === false) { 16 | throw new Error('loadJokes expects first argument to be an instance of the user model') 17 | } 18 | return user.related('jokes').query() 19 | }) 20 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['resources/views/**/*.edge'], 3 | darkMode: false, // or 'media' or 'class' 4 | mode: 'jit', 5 | theme: { 6 | colors: { 7 | 'white': 'hsl(0, 0%, 100%)', 8 | 'translucent-light': 'hsl(0 0% 100% / 10%)', 9 | 'translucent-dark': 'hsl(0 0% 0% / 10%)', 10 | 'eggplant': { 11 | 900: 'hsl(278, 73%, 19%)', 12 | 600: 'hsl(277, 85%, 38%)', 13 | }, 14 | 'yellow': { 15 | 500: 'hsl(48, 100%, 50%)', 16 | 600: 'hsl(48, 100%, 45%)', 17 | }, 18 | 'red': { 19 | 700: 'hsla(356, 77%, 59%, 0.747)', 20 | 600: 'hsl(356, 100%, 71%)', 21 | }, 22 | }, 23 | backgroundImage: { 24 | 'eggplant-gradient': `radial-gradient( 25 | circle, 26 | rgba(152, 11, 238, 1) 0%, 27 | rgba(118, 15, 181, 1) 35%, 28 | rgba(58, 13, 85, 1) 100% 29 | )`, 30 | }, 31 | fontFamily: { 32 | body: ['-apple-system', '"Segoe UI"', 'Helvetica Neue', 'Helvetica'], 33 | display: ['baloo'], 34 | }, 35 | boxShadow: { 36 | 'DEFAULT': '0 0.2rem 1rem rgb(0 0 0 / 50%)', 37 | 'button': '0 3px 0 0 hsl(48, 100%, 30%)', 38 | 'raised-button': '0 4px 0 0 hsl(48, 100%, 30%)', 39 | }, 40 | extend: { 41 | maxWidth: { 42 | xxs: '12rem', 43 | }, 44 | outline: { 45 | DEFAULT: ['2px solid hsl(48, 100%, 50%)', '2px'], 46 | }, 47 | }, 48 | }, 49 | variants: { 50 | extend: {}, 51 | }, 52 | plugins: [], 53 | } 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/adonis-preset-ts/tsconfig", 3 | "include": [ 4 | "**/*" 5 | ], 6 | "exclude": [ 7 | "node_modules", 8 | "build" 9 | ], 10 | "compilerOptions": { 11 | "outDir": "build", 12 | "rootDir": "./", 13 | "sourceMap": true, 14 | "paths": { 15 | "App/*": [ 16 | "./app/*" 17 | ], 18 | "Config/*": [ 19 | "./config/*" 20 | ], 21 | "Contracts/*": [ 22 | "./contracts/*" 23 | ], 24 | "Database/*": [ 25 | "./database/*" 26 | ] 27 | }, 28 | "types": [ 29 | "@adonisjs/core", 30 | "@adonisjs/repl", 31 | "@adonisjs/session", 32 | "@adonisjs/view", 33 | "@adonisjs/shield", 34 | "@adonisjs/lucid", 35 | "@adonisjs/auth" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | const Encore = require('@symfony/webpack-encore') 3 | 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Encore runtime environment 7 | |-------------------------------------------------------------------------- 8 | */ 9 | if (!Encore.isRuntimeEnvironmentConfigured()) { 10 | Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev') 11 | } 12 | 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | Output path 16 | |-------------------------------------------------------------------------- 17 | | 18 | | The output path for writing the compiled files. It should always 19 | | be inside the public directory, so that AdonisJS can serve it. 20 | | 21 | */ 22 | Encore.setOutputPath('./public/assets') 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Public URI 27 | |-------------------------------------------------------------------------- 28 | | 29 | | The public URI to access the static files. It should always be 30 | | relative from the "public" directory. 31 | | 32 | */ 33 | if (Encore.isProduction()) { 34 | Encore.setPublicPath('https://polls-app.nyc3.cdn.digitaloceanspaces.com/jokes-app/assets') 35 | Encore.setManifestKeyPrefix('assets/') 36 | } else { 37 | Encore.setPublicPath('/assets') 38 | } 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Entrypoints 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Entrypoints are script files that boots your frontend application. Ideally 46 | | a single entrypoint is used by majority of applications. However, feel 47 | | free to add more (if required). 48 | | 49 | | Also, make sure to read the docs on "Assets bundler" to learn more about 50 | | entrypoints. 51 | | 52 | */ 53 | Encore.addEntry('app', './resources/js/app.js') 54 | 55 | /* 56 | |-------------------------------------------------------------------------- 57 | | Copy assets 58 | |-------------------------------------------------------------------------- 59 | | 60 | | Since the edge templates are not part of the Webpack compile lifecycle, any 61 | | images referenced by it will not be processed by Webpack automatically. Hence 62 | | we must copy them manually. 63 | | 64 | */ 65 | // Encore.copyFiles({ 66 | // from: './resources/images', 67 | // to: 'images/[path][name].[hash:8].[ext]', 68 | // }) 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Split shared code 73 | |-------------------------------------------------------------------------- 74 | | 75 | | Instead of bundling duplicate code in all the bundles, generate a separate 76 | | bundle for the shared code. 77 | | 78 | | https://symfony.com/doc/current/frontend/encore/split-chunks.html 79 | | https://webpack.js.org/plugins/split-chunks-plugin/ 80 | | 81 | */ 82 | // Encore.splitEntryChunks() 83 | 84 | /* 85 | |-------------------------------------------------------------------------- 86 | | Isolated entrypoints 87 | |-------------------------------------------------------------------------- 88 | | 89 | | Treat each entry point and its dependencies as its own isolated module. 90 | | 91 | */ 92 | Encore.disableSingleRuntimeChunk() 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | Cleanup output folder 97 | |-------------------------------------------------------------------------- 98 | | 99 | | It is always nice to cleanup the build output before creating a build. It 100 | | will ensure that all unused files from the previous build are removed. 101 | | 102 | */ 103 | Encore.cleanupOutputBeforeBuild() 104 | 105 | /* 106 | |-------------------------------------------------------------------------- 107 | | Source maps 108 | |-------------------------------------------------------------------------- 109 | | 110 | | Enable source maps in production 111 | | 112 | */ 113 | Encore.enableSourceMaps(!Encore.isProduction()) 114 | 115 | /* 116 | |-------------------------------------------------------------------------- 117 | | Assets versioning 118 | |-------------------------------------------------------------------------- 119 | | 120 | | Enable assets versioning to leverage lifetime browser and CDN cache 121 | | 122 | */ 123 | Encore.enableVersioning(Encore.isProduction()) 124 | 125 | /* 126 | |-------------------------------------------------------------------------- 127 | | Configure dev server 128 | |-------------------------------------------------------------------------- 129 | | 130 | | Here we configure the dev server to enable live reloading for edge templates. 131 | | Remember edge templates are not processed by Webpack and hence we need 132 | | to watch them explicitly and livereload the browser. 133 | | 134 | */ 135 | Encore.configureDevServerOptions((options) => { 136 | /** 137 | * Normalize "options.static" property to an array 138 | */ 139 | if (!options.static) { 140 | options.static = [] 141 | } else if (!Array.isArray(options.static)) { 142 | options.static = [options.static] 143 | } 144 | 145 | /** 146 | * Enable live reload and add views directory 147 | */ 148 | options.liveReload = true 149 | options.static.push({ 150 | directory: join(__dirname, './resources/views'), 151 | watch: true, 152 | }) 153 | }) 154 | 155 | /* 156 | |-------------------------------------------------------------------------- 157 | | CSS precompilers support 158 | |-------------------------------------------------------------------------- 159 | | 160 | | Uncomment one of the following lines of code to enable support for your 161 | | favorite CSS precompiler 162 | | 163 | */ 164 | // Encore.enableSassLoader() 165 | // Encore.enableLessLoader() 166 | // Encore.enableStylusLoader() 167 | 168 | /* 169 | |-------------------------------------------------------------------------- 170 | | CSS loaders 171 | |-------------------------------------------------------------------------- 172 | | 173 | | Uncomment one of the following line of code to enable support for 174 | | PostCSS or CSS. 175 | | 176 | */ 177 | Encore.enablePostCssLoader() 178 | // Encore.configureCssLoader(() => {}) 179 | 180 | /* 181 | |-------------------------------------------------------------------------- 182 | | Enable Vue loader 183 | |-------------------------------------------------------------------------- 184 | | 185 | | Uncomment the following lines of code to enable support for vue. Also make 186 | | sure to install the required dependencies. 187 | | 188 | */ 189 | // Encore.enableVueLoader(() => {}, { 190 | // version: 3, 191 | // runtimeCompilerBuild: false, 192 | // useJsx: false 193 | // }) 194 | 195 | /* 196 | |-------------------------------------------------------------------------- 197 | | Configure logging 198 | |-------------------------------------------------------------------------- 199 | | 200 | | To keep the terminal clean from unnecessary info statements , we only 201 | | log warnings and errors. If you want all the logs, you can change 202 | | the level to "info". 203 | | 204 | */ 205 | const config = Encore.getWebpackConfig() 206 | config.infrastructureLogging = { 207 | level: 'warn', 208 | } 209 | config.stats = 'errors-warnings' 210 | 211 | /* 212 | |-------------------------------------------------------------------------- 213 | | Export config 214 | |-------------------------------------------------------------------------- 215 | | 216 | | Export config for webpack to do its job 217 | | 218 | */ 219 | module.exports = config 220 | --------------------------------------------------------------------------------