init base frontend

This commit is contained in:
Anthony Mineo 2020-03-29 11:31:16 -04:00
parent ade7f7e288
commit cfa645120d
52 changed files with 19366 additions and 9 deletions

6
.gitignore vendored
View file

@ -1,5 +1,9 @@
.env
node_modules
notes.md
app/t2-stat-parser/serverStats/stats
docker-compose.deploy.yml
docker-compose.deploy.yml
docker-secrets/adonis.appkey.*

View file

@ -15,7 +15,7 @@ import (
func main() {
fmt.Println("Starting FTP stat file download")
initFTP()
// initFTP()
fmt.Println("Stat files downloaded!")
fmt.Println("Starting stat parser")

3
app/webapp/.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"plugins": ["macros"]
}

9
app/webapp/.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

12
app/webapp/.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# Adonis directory for storing tmp files
tmp
# Don't store build
build
# Environment variables, never commit this file
.env
public/hot
public/mix-manifest.json
public/dist

9
app/webapp/README.md Normal file
View file

@ -0,0 +1,9 @@
# Adonis Slim App
The Adonis slim app is the tinest boilerplate to create Adonisjs applications with minimal footprint and you get all the goodies of Adonis IoC container, autoloading, ace commands etc.
## What's next?
This project structure can scale as you go, simply execute the `ace` commands to create **Controllers**, **Models**, etc for you.
Also make sure to read the [guides](http://dev.adonisjs.com/docs/4.0/installation)

21
app/webapp/ace Normal file
View file

@ -0,0 +1,21 @@
'use strict'
/*
|--------------------------------------------------------------------------
| Ace Commands
|--------------------------------------------------------------------------
|
| The ace file is just a regular Javascript file but with no extension. You
| can call `node ace` followed by the command name and it just works.
|
| Also you can use `adonis` followed by the command name, since the adonis
| global proxy all the ace commands.
|
*/
const { Ignitor } = require('@adonisjs/ignitor')
new Ignitor(require('@adonisjs/fold'))
.appRoot(__dirname)
.fireAce()
.catch(console.error)

View file

@ -0,0 +1,6 @@
'use strict'
class GameController {
}
module.exports = GameController

View file

@ -0,0 +1,12 @@
'use strict'
class IndexController {
index({ inertia }) {
const pageTitle = "Home!"
return inertia.render('Index', { pageTitle }, { edgeVar: 'server-variable' })
}
}
module.exports = IndexController

View file

@ -0,0 +1,40 @@
'use strict'
const Database = use('Database')
class PlayerController {
// Main Players List
async index({ inertia }) {
const pageTitle = "All Players"
const players = await Database.table('players')
.distinct('player_guid',
'player_name',
'total_games_ctfgame',
'total_games_dmgame',
'total_games_lakrabbitgame',
'total_games_sctfgame',
'updated_at')
.groupBy('player_guid')
.orderBy('player_name', 'asc')
.limit(2500)
return inertia.render('Players/Main', { pageTitle, players }, { edgeVar: 'server-variable' })
}
// Player Detail
async player({ inertia, request }) {
const playerInfo = await Database.from('players').where({ player_guid: request.params.player_guid })
const playerStatData = await Database.from('games').where({ player_guid: request.params.player_guid })
let playerData = {
player: playerInfo[0],
stats: playerStatData
}
const pageTitle = playerData.player.player_name
return inertia.render('Players/Player', { pageTitle, playerData }, { edgeVar: 'server-variable' })
}
}
module.exports = PlayerController

View file

@ -0,0 +1,10 @@
// babel-plugin-macros.config.js
module.exports = {
twin: {
config: './tailwind.config.js',
styled: '@emotion/styled',
format: 'auto',
hasSuggestions: true,
debug: false
}
}

243
app/webapp/config/app.js Normal file
View file

@ -0,0 +1,243 @@
'use strict'
/** @type {import('@adonisjs/framework/src/Env')} */
const Env = use('Env')
module.exports = {
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application and can used when you
| need to place the application's name in a email, view or
| other location.
|
*/
name: Env.get('APP_NAME', 'AdonisJs'),
/*
|--------------------------------------------------------------------------
| App Key
|--------------------------------------------------------------------------
|
| App key is a randomly generated 16 or 32 characters long string required
| to encrypted cookies, sessions and other sensitive data.
|
*/
appKey: Env.getOrFail('APP_KEY'),
http: {
/*
|--------------------------------------------------------------------------
| Allow Method Spoofing
|--------------------------------------------------------------------------
|
| Method spoofing allows to make requests by spoofing the http verb.
| Which means you can make a GET request but instruct the server to
| treat as a POST or PUT request. If you want this feature, set the
| below value to true.
|
*/
allowMethodSpoofing: true,
/*
|--------------------------------------------------------------------------
| Trust Proxy
|--------------------------------------------------------------------------
|
| Trust proxy defines whether X-Forwaded-* headers should be trusted or not.
| When your application is behind a proxy server like nginx, these values
| are set automatically and should be trusted. Apart from setting it
| to true or false Adonis supports handful or ways to allow proxy
| values. Read documentation for that.
|
*/
trustProxy: false,
/*
|--------------------------------------------------------------------------
| Subdomains
|--------------------------------------------------------------------------
|
| Offset to be used for returning subdomains for a given request.For
| majority of applications it will be 2, until you have nested
| sudomains.
| cheatsheet.adonisjs.com - offset - 2
| virk.cheatsheet.adonisjs.com - offset - 3
|
*/
subdomainOffset: 2,
/*
|--------------------------------------------------------------------------
| JSONP Callback
|--------------------------------------------------------------------------
|
| Default jsonp callback to be used when callback query string is missing
| in request url.
|
*/
jsonpCallback: 'callback',
/*
|--------------------------------------------------------------------------
| Etag
|--------------------------------------------------------------------------
|
| Set etag on all HTTP response. In order to disable for selected routes,
| you can call the `response.send` with an options object as follows.
|
| response.send('Hello', { ignoreEtag: true })
|
*/
etag: false
},
views: {
/*
|--------------------------------------------------------------------------
| Cache Views
|--------------------------------------------------------------------------
|
| Define whether or not to cache the compiled view. Set it to true in
| production to optimize view loading time.
|
*/
cache: Env.get('CACHE_VIEWS', true)
},
static: {
/*
|--------------------------------------------------------------------------
| Dot Files
|--------------------------------------------------------------------------
|
| Define how to treat dot files when trying to server static resources.
| By default it is set to ignore, which will pretend that dotfiles
| does not exists.
|
| Can be one of the following
| ignore, deny, allow
|
*/
dotfiles: 'ignore',
/*
|--------------------------------------------------------------------------
| ETag
|--------------------------------------------------------------------------
|
| Enable or disable etag generation
|
*/
etag: true,
/*
|--------------------------------------------------------------------------
| Extensions
|--------------------------------------------------------------------------
|
| Set file extension fallbacks. When set, if a file is not found, the given
| extensions will be added to the file name and search for. The first
| that exists will be served. Example: ['html', 'htm'].
|
*/
extensions: false
},
locales: {
/*
|--------------------------------------------------------------------------
| Loader
|--------------------------------------------------------------------------
|
| The loader to be used for fetching and updating locales. Below is the
| list of available options.
|
| file, database
|
*/
loader: 'file',
/*
|--------------------------------------------------------------------------
| Default Locale
|--------------------------------------------------------------------------
|
| Default locale to be used by Antl provider. You can always switch drivers
| in runtime or use the official Antl middleware to detect the driver
| based on HTTP headers/query string.
|
*/
locale: 'en'
},
logger: {
/*
|--------------------------------------------------------------------------
| Transport
|--------------------------------------------------------------------------
|
| Transport to be used for logging messages. You can have multiple
| transports using same driver.
|
| Available drivers are: `file` and `console`.
|
*/
transport: 'console',
/*
|--------------------------------------------------------------------------
| Console Transport
|--------------------------------------------------------------------------
|
| Using `console` driver for logging. This driver writes to `stdout`
| and `stderr`
|
*/
console: {
driver: 'console',
name: 'adonis-app',
level: 'info'
},
/*
|--------------------------------------------------------------------------
| File Transport
|--------------------------------------------------------------------------
|
| File transport uses file driver and writes log messages for a given
| file inside `tmp` directory for your app.
|
| For a different directory, set an absolute path for the filename.
|
*/
file: {
driver: 'file',
name: 'adonis-app',
filename: 'adonis.log',
level: 'info'
}
},
/*
|--------------------------------------------------------------------------
| Generic Cookie Options
|--------------------------------------------------------------------------
|
| The following cookie options are generic settings used by AdonisJs to create
| cookies. However, some parts of the application like `sessions` can have
| seperate settings for cookies inside `config/session.js`.
|
*/
cookie: {
httpOnly: true,
sameSite: false,
path: '/',
maxAge: 7200
}
}

View file

@ -0,0 +1,157 @@
'use strict'
module.exports = {
/*
|--------------------------------------------------------------------------
| JSON Parser
|--------------------------------------------------------------------------
|
| Below settings are applied when the request body contains a JSON payload.
| If you want body parser to ignore JSON payloads, then simply set `types`
| to an empty array.
*/
json: {
/*
|--------------------------------------------------------------------------
| limit
|--------------------------------------------------------------------------
|
| Defines the limit of JSON that can be sent by the client. If payload
| is over 1mb it will not be processed.
|
*/
limit: '1mb',
/*
|--------------------------------------------------------------------------
| strict
|--------------------------------------------------------------------------
|
| When `strict` is set to true, body parser will only parse Arrays and
| Object. Otherwise everything parseable by `JSON.parse` is parsed.
|
*/
strict: true,
/*
|--------------------------------------------------------------------------
| types
|--------------------------------------------------------------------------
|
| Which content types are processed as JSON payloads. You are free to
| add your own types here, but the request body should be parseable
| by `JSON.parse` method.
|
*/
types: [
'application/json',
'application/json-patch+json',
'application/vnd.api+json',
'application/csp-report'
]
},
/*
|--------------------------------------------------------------------------
| Raw Parser
|--------------------------------------------------------------------------
|
|
|
*/
raw: {
types: [
'text/*'
]
},
/*
|--------------------------------------------------------------------------
| Form Parser
|--------------------------------------------------------------------------
|
|
|
*/
form: {
types: [
'application/x-www-form-urlencoded'
]
},
/*
|--------------------------------------------------------------------------
| Files Parser
|--------------------------------------------------------------------------
|
|
|
*/
files: {
types: [
'multipart/form-data'
],
/*
|--------------------------------------------------------------------------
| Max Size
|--------------------------------------------------------------------------
|
| Below value is the max size of all the files uploaded to the server. It
| is validated even before files have been processed and hard exception
| is thrown.
|
| Consider setting a reasonable value here, otherwise people may upload GB's
| of files which will keep your server busy.
|
| Also this value is considered when `autoProcess` is set to true.
|
*/
maxSize: '20mb',
/*
|--------------------------------------------------------------------------
| Auto Process
|--------------------------------------------------------------------------
|
| Whether or not to auto-process files. Since HTTP servers handle files via
| couple of specific endpoints. It is better to set this value off and
| manually process the files when required.
|
| This value can contain a boolean or an array of route patterns
| to be autoprocessed.
*/
autoProcess: true,
/*
|--------------------------------------------------------------------------
| Process Manually
|--------------------------------------------------------------------------
|
| The list of routes that should not process files and instead rely on
| manual process. This list should only contain routes when autoProcess
| is to true. Otherwise everything is processed manually.
|
*/
processManually: []
/*
|--------------------------------------------------------------------------
| Temporary file name
|--------------------------------------------------------------------------
|
| Define a function, which should return a string to be used as the
| tmp file name.
|
| If not defined, Bodyparser will use `uuid` as the tmp file name.
|
| To be defined as. If you are defining the function, then do make sure
| to return a value from it.
|
| tmpFileName () {
| return 'some-unique-value'
| }
|
*/
}
}

View file

@ -0,0 +1,42 @@
'use strict'
/** @type {import('@adonisjs/framework/src/Env')} */
const Env = use('Env')
/** @type {import('@adonisjs/ignitor/src/Helpers')} */
const Helpers = use('Helpers')
module.exports = {
/*
|--------------------------------------------------------------------------
| Default Connection
|--------------------------------------------------------------------------
|
| Connection defines the default connection settings to be used while
| interacting with SQL databases.
|
*/
connection: Env.get('DB_CONNECTION', 'pg'),
/*
|--------------------------------------------------------------------------
| PostgreSQL
|--------------------------------------------------------------------------
|
| Here we define connection settings for PostgreSQL database.
|
| npm i --save pg
|
*/
pg: {
client: 'pg',
connection: {
host: Env.get('DB_HOST', 'localhost'),
port: Env.get('DB_PORT', ''),
user: Env.get('DB_USER', 'root'),
password: Env.get('DB_PASSWORD', ''),
database: Env.get('DB_DATABASE', 'adonis')
},
debug: Env.get('DB_DEBUG', false)
}
}

49
app/webapp/config/hash.js Normal file
View file

@ -0,0 +1,49 @@
'use strict'
/** @type {import('@adonisjs/framework/src/Env')} */
const Env = use('Env')
module.exports = {
/*
|--------------------------------------------------------------------------
| Driver
|--------------------------------------------------------------------------
|
| Driver to be used for hashing values. The same driver is used by the
| auth module too.
|
*/
driver: Env.get('HASH_DRIVER', 'bcrypt'),
/*
|--------------------------------------------------------------------------
| Bcrypt
|--------------------------------------------------------------------------
|
| Config related to bcrypt hashing. https://www.npmjs.com/package/bcrypt
| package is used internally.
|
*/
bcrypt: {
rounds: 10
},
/*
|--------------------------------------------------------------------------
| Argon
|--------------------------------------------------------------------------
|
| Config related to argon. https://www.npmjs.com/package/argon2 package is
| used internally.
|
| Since argon is optional, you will have to install the dependency yourself
|
|============================================================================
| npm i argon2
|============================================================================
|
*/
argon: {
type: 1
}
}

View file

@ -0,0 +1,19 @@
module.exports = {
apps : [{
name: process.env.APP_NAME,
script: 'server.js',
// Options reference: https://pm2.keymetrics.io/docs/usage/application-declaration/
instances: 1,
autorestart: true,
watch: true,
ignore_watch:['node_modules','public','resources/js','resources/scss']
},
{
name: process.env.APP_NAME + '_react',
script: 'node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js "--watch"',
// Options reference: https://pm2.keymetrics.io/docs/usage/application-declaration/
instances: 1,
autorestart: false,
watch: false,
}]
};

View file

@ -0,0 +1,19 @@
module.exports = {
apps : [{
name: process.env.APP_NAME,
script: 'server.js',
// Options reference: https://pm2.keymetrics.io/docs/usage/application-declaration/
instances: 1,
autorestart: true,
watch: true,
ignore_watch:['node_modules','public','resources/js','resources/scss']
},
{
name: process.env.APP_NAME + '_react_hot',
script: 'node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js',
// Options reference: https://pm2.keymetrics.io/docs/usage/application-declaration/
instances: 1,
autorestart: false,
watch: false,
}]
};

View file

@ -0,0 +1,10 @@
module.exports = {
apps: [
{
name: process.env.APP_NAME,
script: 'server.js',
autorestart: true,
instances: 1
}
]
};

17743
app/webapp/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

78
app/webapp/package.json Normal file
View file

@ -0,0 +1,78 @@
{
"name": "adonis-inertia-react-starter",
"version": "4.1.0",
"adonis-version": "4.1.0",
"description": "Adonis, Inertia, React, Emotion, TailwindCSS, Webpack Mix, PostCSS",
"main": "index.js",
"scripts": {
"start": "node server.js",
"test": "node ace test",
"webpack-server": "/opt/node_modules/webpack/bin/webpack.js --progress --hide-modules --config=/opt/node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run webpack-server -- --watch"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"keywords": [
"adonisjs",
"adonis-app",
"inertajs",
"react"
],
"author": "",
"license": "UNLICENSED",
"private": true,
"dependencies": {
"@adonisjs/ace": "^5.0.8",
"@adonisjs/bodyparser": "^2.0.9",
"@adonisjs/cors": "^1.0.7",
"@adonisjs/fold": "^4.0.9",
"@adonisjs/framework": "^5.0.9",
"@adonisjs/ignitor": "^2.0.8",
"@adonisjs/lucid": "^6.1.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@inertiajs/inertia": "^0.1.7",
"@inertiajs/inertia-react": "^0.1.4",
"@tailwindcss/ui": "^0.1.3",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"inertia-adonis": "^0.1.2",
"node-cookie": "^2.1.2",
"pg": "^7.18.2",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-scripts": "3.4.0",
"tailwindcss": "^1.2.0"
},
"devDependencies": {
"@babel/plugin-transform-react-jsx": "^7.9.1",
"@babel/preset-react": "^7.9.1",
"@emotion/core": "^10.0.28",
"@emotion/styled": "^10.0.27",
"autoprefixer": "^9.7.4",
"babel-plugin-macros": "^2.8.0",
"laravel-mix": "^5.0.4",
"laravel-mix-purgecss": "^5.0.0-rc.1",
"laravel-mix-tailwind": "^0.1.0",
"postcss-cli": "^7.1.0",
"postcss-loader": "^3.0.0",
"sass": "^1.26.3",
"twin.macro": "^1.0.0-alpha.7"
},
"autoload": {
"App": "./app"
}
}

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="36px" height="33px" viewBox="0 0 36 33" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="translate(0 .5)" fill="none" fill-rule="evenodd"><path d="M20 2.236L5.618 31h28.764L20 2.236z" stroke="#FFFFFF" stroke-width="2"/><path fill="#FFFFFF" d="M12 2l12 24H0"/></g></svg>

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="186px" height="31px" viewBox="0 0 186 31" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M20.25 6.106V.1H.72v6.006h5.712v17.388H.72V29.5h19.53v-6.006h-5.712V6.106h5.712zm18.774 7.35v-5.46h-6.846V1.528h-7.182v3.108c0 2.226-1.218 3.36-3.444 3.36h-.084v5.46h3.528v8.904c0 4.578 3.528 7.686 8.82 7.686 1.932 0 3.276-.126 5.208-1.05v-5.964c-2.016.882-2.982.966-3.822.966-1.806 0-3.024-1.008-3.024-2.898v-7.644h6.846zm37.17-5.46l-3.612 13.692L68.76 7.996H63.3l-3.822 13.692-3.654-13.692h-7.308L55.068 29.5h7.266l3.696-12.516L69.684 29.5h7.308l6.552-21.504h-7.35zM95.43 7.45c6.846 0 11.424 4.746 11.424 11.256 0 6.552-4.578 11.34-11.424 11.34-6.888 0-11.466-4.788-11.466-11.34 0-6.51 4.578-11.256 11.466-11.256zm0 5.628c-2.898 0-4.62 2.352-4.62 5.628 0 3.318 1.722 5.712 4.62 5.712 2.856 0 4.578-2.394 4.578-5.712 0-3.276-1.722-5.628-4.578-5.628zm22.092.714V7.996h-7.182V29.5h7.182v-7.518c0-5.376 1.89-7.728 8.946-6.972V7.534c-4.788 0-7.686 1.806-8.946 6.258zM145.158 29.5h8.4l-7.812-11.886 7.14-9.618h-8.316l-4.284 6.552h-3.612V.1h-7.182v29.4h7.182v-8.442h3.612l4.872 8.442zm22.092-14.196h6.384c-.462-5.46-4.326-7.854-9.87-7.854-6.09 0-9.534 2.436-9.534 6.804 0 4.998 4.494 5.586 8.19 6.426 3.822.882 4.704 1.134 4.704 2.478 0 1.512-1.428 1.932-2.982 1.932-2.394 0-3.822-.966-4.074-3.486h-6.384c.336 5.88 4.536 8.442 10.542 8.442 6.132 0 9.66-2.688 9.66-7.14 0-4.998-4.41-5.628-8.736-6.594-3.234-.672-4.326-.882-4.326-2.31 0-1.134 1.176-1.848 2.856-1.848 2.268 0 3.276.882 3.57 3.15zm11.424 4.536h6.258L185.94.1h-8.316l1.05 19.74zm-.63 9.66h7.518v-6.51h-7.518v6.51z" fill="#FFFFFF" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,15 @@
import React from 'react'
const FrameHeading = (props) => {
return (
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<h1 className="text-lg leading-6 font-semibold text-gray-900">
{props.heading}
</h1>
</div>
</header>
);
};
export default FrameHeading;

View file

@ -0,0 +1 @@
export { default } from './FrameHeading';

View file

@ -0,0 +1,71 @@
import React from 'react'
const returnTotalGames = (player) => {
let sum = Number(player.ctf) +
Number(player.dm) +
Number(player.lak) +
Number(player.spawnctf);
return sum
};
const GameTypesPlayedBoxes = (props) => {
return (
<div>
<h3 className="text-md leading-6 font-medium text-gray-900">
Games Played {returnTotalGames(props)}
</h3>
<div className="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-4">
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<dl>
<dt className="text-sm leading-5 font-medium text-gray-500 truncate">
Capture the Flag
</dt>
<dd className="mt-1 text-3xl leading-9 font-semibold text-gray-900">
{props.ctf}
</dd>
</dl>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<dl>
<dt className="text-sm leading-5 font-medium text-gray-500 truncate">
LAK Rabbit
</dt>
<dd className="mt-1 text-3xl leading-9 font-semibold text-gray-900">
{props.lak}
</dd>
</dl>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<dl>
<dt className="text-sm leading-5 font-medium text-gray-500 truncate">
Spawn CTF
</dt>
<dd className="mt-1 text-3xl leading-9 font-semibold text-gray-900">
{props.spawnctf}
</dd>
</dl>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<dl>
<dt className="text-sm leading-5 font-medium text-gray-500 truncate">
Deathmatch
</dt>
<dd className="mt-1 text-3xl leading-9 font-semibold text-gray-900">
{props.dm}
</dd>
</dl>
</div>
</div>
</div>
</div>
);
};
export default GameTypesPlayedBoxes;

View file

@ -0,0 +1 @@
export { default } from './GameTypesPlayedBoxes';

View file

@ -0,0 +1,56 @@
import React from 'react'
import { InertiaLink } from '@inertiajs/inertia-react'
const navItems = () =>
<div className="ml-10 flex items-baseline">
<InertiaLink href="/" className="px-3 py-2 rounded-md text-sm font-medium text-white bg-gray-900 focus:outline-none focus:text-white focus:bg-gray-700">Home</InertiaLink>
<InertiaLink href="/games" className="ml-4 px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Games</InertiaLink>
<InertiaLink href="/players" className="ml-4 px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Players</InertiaLink>
</div>;
const TopNav = () => {
return (
<nav data-todo-x-data="{ open: false }" data-todo-at-keydown-window-escape="open = false" className="bg-gray-800">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center">
<div className="flex-shrink-0">
<img className="h-8 w-8" src="https://tailwindui.com/img/logos/workflow-mark-on-dark.svg" alt="Workflow logo" />
</div>
<div className="hidden md:block">
{ navItems() }
</div>
</div>
<div className="hidden md:block">
<div className="ml-4 flex items-center md:ml-6">
<div data-todo-at-click-away="open = false" className="ml-3 relative" data-todo-x-data="{ open: false }">
<span className="inline-flex rounded-md shadow-sm">
<button type="button" className="inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Discord
</button>
</span>
</div>
</div>
</div>
{/* {Mobile nav} */}
<div className="-mr-2 flex md:hidden">
<button data-todo-at-click="open = !open" className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:bg-gray-700 focus:text-white" data-todo-x-bind-aria-label="open ? 'Close main menu' data-todo-colon- 'Main menu'" data-todo-x-bind-aria-expanded="open">
<svg className="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path data-todo-colon-className="{'hidden': open, 'inline-flex': !open }" className="inline-flex" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
<path data-todo-colon-className="{'hidden': !open, 'inline-flex': open }" className="hidden" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<div data-todo-colon-className="{'block': open, 'hidden': !open}" className="hidden md:hidden">
<div className="px-2 pt-2 pb-3 sm:px-3">
{ navItems() }
</div>
</div>
</nav>
);
};
export default TopNav;

View file

@ -0,0 +1 @@
export { default } from './TopNav';

View file

@ -0,0 +1,13 @@
import React from 'react'
import Layout from '@/Shared/Layout'
export default function Index(props) {
return (
<Layout title={props.pageTitle}>
<div className="border-4 border-dashed border-gray-300 rounded-lg h-96">
</div>
</Layout>
)
}

View file

@ -0,0 +1,59 @@
import React from 'react'
import { InertiaLink } from '@inertiajs/inertia-react'
import Layout from '@/Shared/Layout'
const returnTotalSumGames = (player) => {
let sum = Number(player.total_games_ctfgame) +
Number(player.total_games_dmgame) +
Number(player.total_games_lakrabbitgame) +
Number(player.total_games_sctfgame);
return sum
};
const PlayerRow = (player, index) =>
<li key={index}>
<InertiaLink href={`/player/${player.player_guid}`} className="block hover:bg-gray-50 focus:outline-none focus:bg-gray-50 transition duration-150 ease-in-out">
<div className="flex items-center px-4 py-4 sm:px-6">
<div className="min-w-0 flex-1 flex items-center">
<div className="min-w-0 flex-1 md:grid md:grid-cols-2 md:gap-4">
<div>
<div className="text-sm leading-5 font-medium text-indigo-600 truncate">{player.player_name}</div>
<div className="mt-2 flex items-center text-sm leading-5 text-gray-500">
<span className="truncate">Last Active: {player.updated_at.split(/[T]/)[0]}</span>
</div>
</div>
<div className="hidden md:block">
<div>
<div className="text-sm leading-5 text-gray-900">
Total Games Played
</div>
<div className="mt-2 flex items-center">
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-gray-100 text-gray-800">{ returnTotalSumGames(player) }</span>
</div>
</div>
</div>
</div>
</div>
<div>
<svg className="h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd"/>
</svg>
</div>
</div>
</InertiaLink>
</li>;
export default function Players(props) {
return (
<Layout title={props.pageTitle}>
<div className="bg-white shadow overflow-hidden sm:rounded-md">
<ul>
{ props.players.map((player, index) => PlayerRow(player, index)) }
</ul>
</div>
</Layout>
)
}

View file

@ -0,0 +1,25 @@
import React from 'react'
import Layout from '@/Shared/Layout'
import GameTypesPlayedBoxes from '@/Components/GameTypesPlayedBoxes'
export default function Player(props) {
return (
<Layout title={props.pageTitle}>
<GameTypesPlayedBoxes ctf={props.playerData.player.total_games_ctfgame}
dm={props.playerData.player.total_games_dmgame}
lak={props.playerData.player.total_games_lakrabbitgame}
spawnctf={props.playerData.player.total_games_sctfgame}
/>
<div className="bg-white shadow overflow-hidden sm:rounded-md">
<div>{JSON.stringify(props.playerData.player)}</div>
<div> {JSON.stringify(props.playerData.stats)}</div>
</div>
</Layout>
)
}

View file

@ -0,0 +1,25 @@
import React, { useEffect } from 'react'
import TopNav from '../Components/TopNav';
import FrameHeading from '../Components/FrameHeading'
export default function Layout({ title, children }) {
useEffect(() => {
document.title = title;
}, [title])
return (
<>
<TopNav />
<FrameHeading heading={title} />
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-4 sm:px-0">
{ children }
</div>
</main>
</>
)
}

View file

@ -0,0 +1,18 @@
import { InertiaApp } from '@inertiajs/inertia-react'
import React from 'react'
import { render } from 'react-dom'
const app = document.getElementById('app')
render(
<InertiaApp
initialPage={JSON.parse(app.dataset.page)}
resolveComponent={name => import(`@/Pages/${name}`).then(module => module.default)}
/>,
app
)
// This is required to enable HMR
if (module.hot) {
module.hot.accept();
}

View file

@ -0,0 +1,5 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{ edgeVar }}</title>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
@if(node_env().env === "production")
{{ style(versioncss('main')) }}
@else
{{ style('dist/css/main') }}
@endif
</head>
<body class="antialiased font-sans bg-gray-200">
{{{ startTag }}}
{{ edgeVar }}
@if(node_env().env === "production" || node_env().hmr === "false")
{{ script(versionjs('app')) }}
@else
{{ script('http://localhost:8081/dist/js/app.js') }}
@endif
</body>
</html>

25
app/webapp/server.js Normal file
View file

@ -0,0 +1,25 @@
'use strict'
/*
|--------------------------------------------------------------------------
| Http server
|--------------------------------------------------------------------------
|
| This file bootstrap Adonisjs to start the HTTP server. You are free to
| customize the process of booting the http server.
|
| """ Loading ace commands """
| At times you may want to load ace commands when starting the HTTP server.
| Same can be done by chaining `loadCommands()` method after
|
| """ Preloading files """
| Also you can preload files by calling `preLoad('path/to/file')` method.
| Make sure to pass relative path from the project root.
*/
const { Ignitor } = require('@adonisjs/ignitor')
new Ignitor(require('@adonisjs/fold'))
.appRoot(__dirname)
.fireHttpServer()
.catch(console.error)

56
app/webapp/start/app.js Normal file
View file

@ -0,0 +1,56 @@
'use strict'
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| Providers are building blocks for your Adonis app. Anytime you install
| a new Adonis specific package, chances are you will register the
| provider here.
|
*/
const providers = [
'@adonisjs/framework/providers/AppProvider',
'@adonisjs/framework/providers/ViewProvider',
'@adonisjs/lucid/providers/LucidProvider',
'inertia-adonis/providers/InertiaProvider'
]
/*
|--------------------------------------------------------------------------
| Ace Providers
|--------------------------------------------------------------------------
|
| Ace providers are required only when running ace commands. For example
| Providers for migrations, tests etc.
|
*/
const aceProviders = [
]
/*
|--------------------------------------------------------------------------
| Aliases
|--------------------------------------------------------------------------
|
| Aliases are short unique names for IoC container bindings. You are free
| to create your own aliases.
|
| For example:
| { Route: 'Adonis/Src/Route' }
|
*/
const aliases = {}
/*
|--------------------------------------------------------------------------
| Commands
|--------------------------------------------------------------------------
|
| Here you store ace commands for your package
|
*/
const commands = []
module.exports = { providers, aceProviders, aliases, commands }

37
app/webapp/start/hooks.js Normal file
View file

@ -0,0 +1,37 @@
const { hooks } = require('@adonisjs/ignitor')
const { version } = require('../package.json')
const Helpers = use('Helpers')
const mixManifest = require(Helpers.publicPath('mix-manifest.json'))
hooks.after.providersBooted(async () => {
const View = use('View')
View.global('node_env', () => {
return { "env": process.env.NODE_ENV, "hmr": process.env.WEBPACK_HMR }
})
View.global('versionjs', (filename) => {
filename = `/dist/js/${filename}.js`
if (!mixManifest.hasOwnProperty(filename)) {
throw new Error('Could not find asset for versioning' + filename)
}
return mixManifest[filename]
})
View.global('versioncss', (filename) => {
filename = `/dist/css/${filename}.css`
if (!mixManifest.hasOwnProperty(filename)) {
throw new Error('Could not find asset for versioning' + filename)
}
return mixManifest[filename]
})
const Inertia = use('Adonis/Addons/Inertia')
// these vars get pushed to the inertia frontend
Inertia.setVersion(version)
Inertia.share('app.name', process.env.APP_NAME)
})

View file

@ -0,0 +1,41 @@
'use strict'
/** @type {import('@adonisjs/framework/src/Server')} */
const Server = use('Server')
/*
|--------------------------------------------------------------------------
| Global Middleware
|--------------------------------------------------------------------------
|
| Global middleware are executed on each http request only when the routes
| match.
|
*/
const globalMiddleware = [
'Adonis/Middleware/Inertia'
]
/*
|--------------------------------------------------------------------------
| Named Middleware
|--------------------------------------------------------------------------
|
| Named middleware is key/value object to conditionally add middleware on
| specific routes or group of routes.
|
| // define
| {
| auth: 'Adonis/Middleware/Auth'
| }
|
| // use
| Route.get().middleware('auth')
|
*/
const namedMiddleware = {}
Server
.registerGlobal(globalMiddleware)
.registerNamed(namedMiddleware)
.use(['Adonis/Middleware/Static'])

View file

@ -0,0 +1,35 @@
'use strict'
/*
|--------------------------------------------------------------------------
| Routes
|--------------------------------------------------------------------------
|
| Http routes are entry points to your web application. You can create
| routes for different URLs and bind Controller actions to them.
|
| A complete guide on routing is available here.
| http://adonisjs.com/docs/4.0/routing
|
*/
/** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */
const Route = use('Route')
// [ Routes ]
Route.get('/', 'IndexController.index').as('home')
// [ player ]
Route.get('/players', 'PlayerController.index')
Route.get('/player/:player_guid', 'PlayerController.player')
// [ game ]
Route.get('/games', 'GameController.index')
//Route.get('/game/:game_id', 'GameController.game')
// Container Healthcheck route
Route.get('/healthz', () => {
return { status: "healthy" };
})

View file

@ -0,0 +1,15 @@
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
},
},
variants: {},
plugins: [
require('@tailwindcss/ui'),
]
}

134
app/webapp/webpack.mix.js Normal file
View file

@ -0,0 +1,134 @@
const mix = require('laravel-mix');
//require('laravel-mix-tailwind');
require('laravel-mix-purgecss');
const tailwindcss = require('tailwindcss');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
// transpiling, babelling, minifying and creating the public/js/main.js out of our assets
mix.setPublicPath('public')
.react('resources/js/app.js', 'public/dist/js')
// .sass('resources/scss/main.scss', 'public/dist/css')
.postCss('resources/scss/main.css', 'public/dist/css')
// .tailwind('./tailwind.config.js');
mix.webpackConfig({
output: {
chunkFilename: './dist/js/[name].[contenthash].js'
},
module: {
rules: [
{
test: /\.(css)/,
use: [
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
tailwindcss('./tailwind.config.js'),
require('autoprefixer'),
],
},
},
],
}
],
},
resolve: {
alias: {
"@": path.resolve(
__dirname,
"resources/js"
),
"@sass": path.resolve(
__dirname,
"resources/scss"
)
}
},
devServer: {
proxy: {
host: '0.0.0.0', // host machine ip
port: 8080,
}
}
});
mix.options({
hmrOptions: {
host: '0.0.0.0', // site's host name
port: 8081
}
});
if (mix.inProduction()) {
mix.purgeCss({
content: [
'./resources/**/*.edge',
'./resources/**/*.js',
'./resources/**/*.jsx',
'./resources/**/*.ts',
'./resources/**/*.tsx'
],
// default in mix
//defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || [],
// default tailwind
defaultExtractor: content => content.match(/[\w-/.:]+(?<!:)/g) || [],
whitelistPatterns: [/-active$/, /-enter$/, /-leave-to$/]
});
mix.version();
}else{
// use SourceMaps on dev
mix.sourceMaps();
}
// Full mix API
// mix.js(src, output);
// mix.react(src, output); <-- Identical to mix.js(), but registers React Babel compilation.
// mix.preact(src, output); <-- Identical to mix.js(), but registers Preact compilation.
// mix.coffee(src, output); <-- Identical to mix.js(), but registers CoffeeScript compilation.
// mix.ts(src, output); <-- TypeScript support. Requires tsconfig.json to exist in the same folder as webpack.mix.js
// mix.extract(vendorLibs);
// mix.sass(src, output);
// mix.standaloneSass('src', output); <-- Faster, but isolated from Webpack.
// mix.fastSass('src', output); <-- Alias for mix.standaloneSass().
// mix.less(src, output);
// mix.stylus(src, output);
// mix.postCss(src, output, [require('postcss-some-plugin')()]);
// mix.browserSync('my-site.test');
// mix.combine(files, destination);
// mix.babel(files, destination); <-- Identical to mix.combine(), but also includes Babel compilation.
// mix.copy(from, to);
// mix.copyDirectory(fromDir, toDir);
// mix.minify(file);
// mix.sourceMaps(); // Enable sourcemaps
// mix.version(); // Enable versioning.
// mix.disableNotifications();
// mix.setPublicPath('path/to/public');
// mix.setResourceRoot('prefix/for/resource/locators');
// mix.autoload({}); <-- Will be passed to Webpack's ProvidePlugin.
// mix.webpackConfig({}); <-- Override webpack.config.js, without editing the file directly.
// mix.babelConfig({}); <-- Merge extra Babel configuration (plugins, etc.) with Mix's default.
// mix.then(function () {}) <-- Will be triggered each time Webpack finishes building.
// mix.extend(name, handler) <-- Extend Mix's API with your own components.
// mix.options({
// extractVueStyles: false, // Extract .vue component styling to file, rather than inline.
// globalVueStyles: file, // Variables file to be imported in every component.
// processCssUrls: true, // Process/optimize relative stylesheet url()'s. Set to false, if you don't want them touched.
// purifyCss: false, // Remove unused CSS selectors.
// uglify: {}, // Uglify-specific options. https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
// postCss: [] // Post-CSS options: https://github.com/postcss/postcss/blob/master/docs/plugins.md
// });

View file

@ -29,8 +29,8 @@ RUN apk update && apk add --no-cache wget tzdata
#Set TimeZone
ENV TZ=America/New_York
#Set cron schedule (every day at 9:30am est)
RUN echo '30 9 * * * /app/start.sh' > /etc/crontabs/root
#Set cron schedule (every day at 11:30am est)
RUN echo '30 11 * * * /app/start.sh' > /etc/crontabs/root
WORKDIR /app

View file

@ -0,0 +1,3 @@
.git
node_modules
public/dist

67
build/webapp/Dockerfile Normal file
View file

@ -0,0 +1,67 @@
# [ Stage 1 ] - install node modules
FROM node:12.2-alpine as builder
RUN apk update
WORKDIR /build
COPY ./app/webapp/package.json /build/package.json
COPY ./app/webapp/package-lock.json /build/package-lock.json
RUN npm install
RUN npm i --global @adonisjs/cli
RUN npx adonis key:generate --echo
# ============================
# ============================
# [ Stage 2 ]
FROM node:12.2-alpine
LABEL maintainer="Anthony Mineo <anthonymineo@gmail.com>"
RUN apk update && apk add --no-cache bash curl
# Default envs as prod
ENV ENV_SILENT=true \
HOST=0.0.0.0 \
PORT=8080 \
HASH_DRIVER=bcrypt \
NODE_ENV=production \
WEBPACK_HMR=false \
CACHE_VIEWS=true \
APP_NAME=AdonisJs
ENV APP_URL=http://${HOST}:${PORT}
#ENV APP_KEY=you-need-to-generate-this
# secrets path _FILE prefix or ENV K=V
# Setup pm2 as our node process manager
# https://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/
RUN npm install pm2 -g
# Set node modules outside our app to keep it clean
COPY --from=builder /build/node_modules/ /opt/node_modules/
ENV PATH /opt/node_modules/.bin:$PATH
# Start script and config
COPY ./build/webapp/entrypoint.sh /entrypoint.sh
# Our App
WORKDIR /app
COPY ./app/webapp /app
RUN ln -nsf /opt/node_modules /app
# Bake prod assets into image
RUN rm -rf /app/public/dist && npm run webpack-server
EXPOSE 8080
HEALTHCHECK --interval=20s --timeout=30s --start-period=5s --retries=5 \
CMD curl -f http://localhost:8080/healthz || exit 1
ENTRYPOINT [ "/entrypoint.sh" ]

57
build/webapp/entrypoint.sh Executable file
View file

@ -0,0 +1,57 @@
#!/bin/bash
echo "Node ENV: $NODE_ENV"
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
file_env 'APP_KEY'
# Exit if APP_KEY is missing, this key is important!
if [ -z "$APP_KEY" ]
then
echo >&2 "[ERROR] No ENV for APP_KEY or APP_KEY_FILE found!"
echo >&2 "Run the ./generate-adonis-app-key.sh script or provide one at runtime"
exit 1
fi
if [[ "$NODE_ENV" == "development" ]]; then
echo "Prepping container for dev environment"
echo "Setting a symlink for ./node_modules -> /opt/node_modules"
ln -nsf /opt/node_modules /app
echo "Cleaning dist assets..."
rm -rf /app/public/dist
npm run webpack-server
fi
# Generate a dist assets if they don't exist -- (If they were deleted on the volume mount)
[ ! -d "/app/public/dist" ] && npm run webpack-server
if [[ "$NODE_ENV" == "production" ]]; then
pm2-runtime start /app/ecosystem._PROD_.config.js
elif [[ "$WEBPACK_HMR" == "true" ]]; then
pm2-runtime start /app/ecosystem._DEV_HOT_.config.js
else
pm2-runtime start /app/ecosystem._DEV_.config.js
fi

View file

@ -19,12 +19,31 @@ services:
environment:
DATABASE_URL: "postgres://dev:dev@db:5432/t2_stats"
ports:
- "8080:8080"
- "8000:8080"
volumes:
- ./app/t2-stat-parser:/app
web:
environment:
DB_USER: "dev"
DB_PASSWORD: "dev"
CACHE_VIEWS: "false"
NODE_ENV: "development" # auto-reloads app on save, dev builds
WEBPACK_HMR: "true" # default is false -- this is useful for when you're focused on Frontend Dev
ports:
- "8080:8080" # adonis http port
- "8081:8081" # webpack-dev-server port
volumes:
- ./app/webapp:/app
# api:
# volumes:
# - ./app/api:/home/node/app
# - ./app/api:/home/node/app
secrets:
adonis.appkey:
external: false
file: ./docker-secrets/adonis.appkey.v1

View file

@ -39,7 +39,6 @@ services:
- db
networks:
- internal
- external
deploy:
labels:
- traefik.enable=false
@ -47,6 +46,38 @@ services:
replicas: 1
web:
image: "amineo/t2-stats-web:v0.1.3"
build:
context: .
dockerfile: ./build/webapp/Dockerfile
environment:
NODE_ENV: "production" # set as default in image
CACHE_VIEWS: "true" # set as default in image
APP_NAME: "Web" # set as default in image
# APP_KEY: "You-need-to-generate-this (npx adonis key:generate --echo)"
APP_KEY_FILE: /run/secrets/adonis.appkey
DB_HOST: "db"
DB_PORT: 5432
DB_USER: ""
DB_PASSWORD: ""
DB_DATABASE: t2_stats
secrets:
- adonis.appkey
ports:
- "8080:8080"
networks:
- internal
- external
deploy:
# labels:
# - traefik.enable=false
mode: replicated
replicas: 1
# api:
# image: "node:12-alpine"
# depends_on:
@ -68,4 +99,10 @@ volumes:
networks:
external:
internal:
internal:
secrets:
adonis.appkey:
external: true
name: adonis.appkey.v1

0
docker-secrets/empty.txt Normal file
View file

28
generate-adonis-app-key.sh Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
echo "Checking to see if @adonisjs/cli has been installed..."
package='@adonisjs/cli'
if [ `npm list -g | grep -c $package` -eq 0 ]; then
while true; do
echo "-----------------------------------------------"
echo " Adonis CLI not installed"
echo "-----------------------------------------------"
read -p "Would you like to install this now? [Y/N] " yn
case $yn in
[Yy]* ) npm i --global @adonisjs/cli; break;;
[Nn]* ) break;;
* ) echo "Please answer yes or no.";;
esac
done
else
echo "Looks good!"
fi
echo "Generating key..."
APPKEY=$(npx adonis key:generate --echo)
echo -n "${APPKEY/APP_KEY=}" > ./docker-secrets/adonis.appkey.v1
echo "Done! Don't share it with anyone!"
echo "The key is located here: ./docker-secrets/adonis.appkey.v1"