initial commit
This commit is contained in:
commit
4fc3bb0a01
66
.adonisrc.json
Normal file
66
.adonisrc.json
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"typescript": true,
|
||||||
|
"commands": [
|
||||||
|
"./commands",
|
||||||
|
"@adonisjs/core/build/commands/index.js",
|
||||||
|
"@adonisjs/repl/build/commands",
|
||||||
|
"@eidellev/inertia-adonisjs/build/commands",
|
||||||
|
"@adonisjs/lucid/build/commands"
|
||||||
|
],
|
||||||
|
"exceptionHandlerNamespace": "App/Exceptions/Handler",
|
||||||
|
"aliases": {
|
||||||
|
"App": "app",
|
||||||
|
"Config": "config",
|
||||||
|
"Database": "database",
|
||||||
|
"Contracts": "contracts"
|
||||||
|
},
|
||||||
|
"preloads": [
|
||||||
|
"./start/routes",
|
||||||
|
"./start/kernel",
|
||||||
|
{
|
||||||
|
"file": "./start/inertia",
|
||||||
|
"environment": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"providers": [
|
||||||
|
"./providers/AppProvider",
|
||||||
|
"@adonisjs/core",
|
||||||
|
"@adonisjs/session",
|
||||||
|
"@adonisjs/view",
|
||||||
|
"@adonisjs/shield",
|
||||||
|
"@eidellev/inertia-adonisjs",
|
||||||
|
"@adonisjs/lucid",
|
||||||
|
"@adonisjs/auth",
|
||||||
|
"@eidellev/adonis-stardust",
|
||||||
|
"./providers/QueryBuilderProvider"
|
||||||
|
],
|
||||||
|
"metaFiles": [
|
||||||
|
{
|
||||||
|
"pattern": "public/**",
|
||||||
|
"reloadServer": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "resources/views/**/*.edge",
|
||||||
|
"reloadServer": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aceProviders": [
|
||||||
|
"@adonisjs/repl"
|
||||||
|
],
|
||||||
|
"tests": {
|
||||||
|
"suites": [
|
||||||
|
{
|
||||||
|
"name": "functional",
|
||||||
|
"files": [
|
||||||
|
"tests/functional/**/*.spec(.ts|.js)"
|
||||||
|
],
|
||||||
|
"timeout": 60000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"testProviders": [
|
||||||
|
"@japa/preset-adonis/TestsProvider"
|
||||||
|
]
|
||||||
|
}
|
37
.babelrc
Normal file
37
.babelrc
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"edge": "17",
|
||||||
|
"firefox": "60",
|
||||||
|
"chrome": "67",
|
||||||
|
"safari": "11.1"
|
||||||
|
}
|
||||||
|
// "useBuiltIns": "usage",
|
||||||
|
// "corejs": "3.16"
|
||||||
|
// "targets":{"node":"16"}
|
||||||
|
// "useBuiltIns": "entry",
|
||||||
|
// "targets": "> 0.25%, not dead"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// "@babel/preset-env",
|
||||||
|
"@babel/preset-typescript"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
// [
|
||||||
|
// "@babel/plugin-transform-typescript", {
|
||||||
|
// "allowDeclareFields": true
|
||||||
|
// }],
|
||||||
|
[
|
||||||
|
"@babel/plugin-proposal-decorators",
|
||||||
|
{
|
||||||
|
"legacy": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"@babel/proposal-class-properties"
|
||||||
|
// "@babel/proposal-object-rest-spread"
|
||||||
|
]
|
||||||
|
}
|
148
.dockerignore
Normal file
148
.dockerignore
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
# Configuration
|
||||||
|
.editorconfig
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
|
||||||
|
# Docker stuff
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
15
.editorconfig
Normal file
15
.editorconfig
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
13
.env.example
Normal file
13
.env.example
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
PORT=3333
|
||||||
|
HOST=0.0.0.0
|
||||||
|
NODE_ENV=development
|
||||||
|
APP_KEY=pvmU1vuAZDkSwarb7yh9pgZ-RxaX4zS7
|
||||||
|
DRIVE_DISK=local
|
||||||
|
SESSION_DRIVER=cookie
|
||||||
|
CACHE_VIEWS=false
|
||||||
|
DB_CONNECTION=pg
|
||||||
|
PG_HOST=localhost
|
||||||
|
PG_PORT=5432
|
||||||
|
PG_USER=lucid
|
||||||
|
PG_PASSWORD=
|
||||||
|
PG_DB_NAME=lucid
|
3
.env.test
Normal file
3
.env.test
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
NODE_ENV=test
|
||||||
|
ASSETS_DRIVER=fake
|
||||||
|
SESSION_DRIVER=memory
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
node_modules
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
.vscode
|
||||||
|
.DS_STORE
|
||||||
|
.env
|
||||||
|
tmp
|
||||||
|
docker-compose.yml
|
1
.prettierignore
Normal file
1
.prettierignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
build
|
55
Dockerfile
Normal file
55
Dockerfile
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
################## First Stage - Creating base #########################
|
||||||
|
|
||||||
|
# Created a variable to hold our node base image
|
||||||
|
ARG NODE_IMAGE=node:18.14.2-alpine
|
||||||
|
|
||||||
|
|
||||||
|
# Using the variable to create our base image
|
||||||
|
FROM $NODE_IMAGE AS base
|
||||||
|
# Running a command to install dumb-init to handle processes
|
||||||
|
RUN apk --no-cache add dumb-init
|
||||||
|
# Creating folders and changing ownerships
|
||||||
|
RUN mkdir -p /home/node/app && chown node:node /home/node/app
|
||||||
|
# Setting the working directory
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
# Changing the current active user to "node"
|
||||||
|
USER node
|
||||||
|
# # Creating a new folder "tmp"
|
||||||
|
# RUN mkdir tmp
|
||||||
|
|
||||||
|
################## Second Stage - Installing dependencies ##########
|
||||||
|
# In this stage, we will start installing dependencies
|
||||||
|
FROM base AS dependencies
|
||||||
|
# We copy all package.* files to the working directory
|
||||||
|
COPY --chown=node:node ./package*.json ./
|
||||||
|
# We run NPM CI to install the exact versions of dependencies
|
||||||
|
RUN npm ci
|
||||||
|
# Lastly, we copy all the files with active user
|
||||||
|
COPY --chown=node:node . .
|
||||||
|
|
||||||
|
|
||||||
|
################## Third Stage - Building Stage #####################
|
||||||
|
# In this stage, we will start building dependencies
|
||||||
|
FROM dependencies AS build
|
||||||
|
# We run "node ace build" to build the app for production
|
||||||
|
RUN node ace build --production
|
||||||
|
|
||||||
|
|
||||||
|
################## Final Stage - Production #########################
|
||||||
|
# In this final stage, we will start running the application
|
||||||
|
FROM base AS production
|
||||||
|
# Here, we include all the required environment variables
|
||||||
|
# ENV NODE_ENV=production
|
||||||
|
# ENV PORT=$PORT
|
||||||
|
# ENV HOST=0.0.0.0
|
||||||
|
|
||||||
|
# Copy package.* to the working directory with active user
|
||||||
|
COPY --chown=node:node ./package*.json ./
|
||||||
|
# We run NPM CI to install the exact versions of dependencies
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
# Copy files to the working directory from the build folder the user
|
||||||
|
COPY --chown=node:node --from=build /home/node/app/build .
|
||||||
|
# Expose port
|
||||||
|
EXPOSE $PORT
|
||||||
|
# Run the command to start the server using "dumb-init"
|
||||||
|
CMD [ "dumb-init", "node", "server.js" ]
|
16
ace
Normal file
16
ace
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Ace Commands
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This file is the entry point for running ace commands.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('reflect-metadata')
|
||||||
|
require('source-map-support').install({ handleUncaughtExceptions: false })
|
||||||
|
|
||||||
|
const { Ignitor } = require('@adonisjs/core/build/standalone')
|
||||||
|
new Ignitor(__dirname)
|
||||||
|
.ace()
|
||||||
|
.handle(process.argv.slice(2))
|
579
ace-manifest.json
Normal file
579
ace-manifest.json
Normal file
|
@ -0,0 +1,579 @@
|
||||||
|
{
|
||||||
|
"commands": {
|
||||||
|
"validate:checksum": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true,
|
||||||
|
"stayAlive": false
|
||||||
|
},
|
||||||
|
"commandPath": "./commands/ValidateChecksum",
|
||||||
|
"commandName": "validate:checksum",
|
||||||
|
"description": "",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"dump:rcfile": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/core/build/commands/DumpRc",
|
||||||
|
"commandName": "dump:rcfile",
|
||||||
|
"description": "Dump contents of .adonisrc.json file along with defaults",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"list:routes": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true,
|
||||||
|
"stayAlive": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/core/build/commands/ListRoutes/index",
|
||||||
|
"commandName": "list:routes",
|
||||||
|
"description": "List application routes",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "verbose",
|
||||||
|
"propertyName": "verbose",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Display more information"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reverse",
|
||||||
|
"propertyName": "reverse",
|
||||||
|
"type": "boolean",
|
||||||
|
"alias": "r",
|
||||||
|
"description": "Reverse routes display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "methods",
|
||||||
|
"propertyName": "methodsFilter",
|
||||||
|
"type": "array",
|
||||||
|
"alias": "m",
|
||||||
|
"description": "Filter routes by method"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "patterns",
|
||||||
|
"propertyName": "patternsFilter",
|
||||||
|
"type": "array",
|
||||||
|
"alias": "p",
|
||||||
|
"description": "Filter routes by the route pattern"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "names",
|
||||||
|
"propertyName": "namesFilter",
|
||||||
|
"type": "array",
|
||||||
|
"alias": "n",
|
||||||
|
"description": "Filter routes by route name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "json",
|
||||||
|
"propertyName": "json",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Output as JSON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "table",
|
||||||
|
"propertyName": "table",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Output as Table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "max-width",
|
||||||
|
"propertyName": "maxWidth",
|
||||||
|
"type": "number",
|
||||||
|
"description": "Specify maximum rendering width. Ignored for JSON Output"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"generate:key": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/core/build/commands/GenerateKey",
|
||||||
|
"commandName": "generate:key",
|
||||||
|
"description": "Generate a new APP_KEY secret",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"repl": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true,
|
||||||
|
"environment": "repl",
|
||||||
|
"stayAlive": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/repl/build/commands/AdonisRepl",
|
||||||
|
"commandName": "repl",
|
||||||
|
"description": "Start a new REPL session",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"ssr:build": {
|
||||||
|
"settings": {
|
||||||
|
"stayAlive": true
|
||||||
|
},
|
||||||
|
"commandPath": "@eidellev/inertia-adonisjs/build/commands/Build",
|
||||||
|
"commandName": "ssr:build",
|
||||||
|
"description": "Build and watch files for changes",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"ssr:watch": {
|
||||||
|
"settings": {
|
||||||
|
"stayAlive": true
|
||||||
|
},
|
||||||
|
"commandPath": "@eidellev/inertia-adonisjs/build/commands/Watch",
|
||||||
|
"commandName": "ssr:watch",
|
||||||
|
"description": "Build and watch files for changes",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"db:seed": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/DbSeed",
|
||||||
|
"commandName": "db:seed",
|
||||||
|
"description": "Execute database seeders",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection for the seeders",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "interactive",
|
||||||
|
"propertyName": "interactive",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run seeders in interactive mode",
|
||||||
|
"alias": "i"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "files",
|
||||||
|
"propertyName": "files",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Define a custom set of seeders files names to run",
|
||||||
|
"alias": "f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compact-output",
|
||||||
|
"propertyName": "compactOutput",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "A compact single-line output"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"db:wipe": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/DbWipe",
|
||||||
|
"commandName": "db:wipe",
|
||||||
|
"description": "Drop all tables, views and types in database",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "drop-views",
|
||||||
|
"propertyName": "dropViews",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Drop all views"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "drop-types",
|
||||||
|
"propertyName": "dropTypes",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Drop all custom types (Postgres only)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explicitly force command to run in production"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"db:truncate": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/DbTruncate",
|
||||||
|
"commandName": "db:truncate",
|
||||||
|
"description": "Truncate all tables in database",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explicitly force command to run in production"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"make:model": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/MakeModel",
|
||||||
|
"commandName": "make:model",
|
||||||
|
"description": "Make a new Lucid model",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "name",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"description": "Name of the model class"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "migration",
|
||||||
|
"propertyName": "migration",
|
||||||
|
"type": "boolean",
|
||||||
|
"alias": "m",
|
||||||
|
"description": "Generate the migration for the model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "controller",
|
||||||
|
"propertyName": "controller",
|
||||||
|
"type": "boolean",
|
||||||
|
"alias": "c",
|
||||||
|
"description": "Generate the controller for the model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "factory",
|
||||||
|
"propertyName": "factory",
|
||||||
|
"type": "boolean",
|
||||||
|
"alias": "f",
|
||||||
|
"description": "Generate a factory for the model"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"make:migration": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/MakeMigration",
|
||||||
|
"commandName": "make:migration",
|
||||||
|
"description": "Make a new migration file",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "name",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"description": "Name of the migration file"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "The connection flag is used to lookup the directory for the migration file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder",
|
||||||
|
"propertyName": "folder",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Pre-select a migration directory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "create",
|
||||||
|
"propertyName": "create",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define the table name for creating a new table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "table",
|
||||||
|
"propertyName": "table",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define the table name for altering an existing table"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"make:seeder": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/MakeSeeder",
|
||||||
|
"commandName": "make:seeder",
|
||||||
|
"description": "Make a new Seeder file",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "name",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"description": "Name of the seeder class"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"make:factory": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/MakeFactory",
|
||||||
|
"commandName": "make:factory",
|
||||||
|
"description": "Make a new factory",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "model",
|
||||||
|
"name": "model",
|
||||||
|
"required": true,
|
||||||
|
"description": "The name of the model"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "model-path",
|
||||||
|
"propertyName": "modelPath",
|
||||||
|
"type": "string",
|
||||||
|
"description": "The path to the model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exact",
|
||||||
|
"propertyName": "exact",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Create the factory with the exact name as provided",
|
||||||
|
"alias": "e"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:run": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Run",
|
||||||
|
"commandName": "migration:run",
|
||||||
|
"description": "Migrate database by running pending migrations",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explicitly force to run migrations in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dry-run",
|
||||||
|
"propertyName": "dryRun",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not run actual queries. Instead view the SQL output"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compact-output",
|
||||||
|
"propertyName": "compactOutput",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "A compact single-line output"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:rollback": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Rollback",
|
||||||
|
"commandName": "migration:rollback",
|
||||||
|
"description": "Rollback migrations to a specific batch number",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explictly force to run migrations in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dry-run",
|
||||||
|
"propertyName": "dryRun",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not run actual queries. Instead view the SQL output"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "batch",
|
||||||
|
"propertyName": "batch",
|
||||||
|
"type": "number",
|
||||||
|
"description": "Define custom batch number for rollback. Use 0 to rollback to initial state"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "compact-output",
|
||||||
|
"propertyName": "compactOutput",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "A compact single-line output"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:status": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Status",
|
||||||
|
"commandName": "migration:status",
|
||||||
|
"description": "View migrations status",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:reset": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Reset",
|
||||||
|
"commandName": "migration:reset",
|
||||||
|
"description": "Rollback all migrations",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explicitly force command to run in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dry-run",
|
||||||
|
"propertyName": "dryRun",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not run actual queries. Instead view the SQL output"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:refresh": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Refresh",
|
||||||
|
"commandName": "migration:refresh",
|
||||||
|
"description": "Rollback and migrate database",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explicitly force command to run in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dry-run",
|
||||||
|
"propertyName": "dryRun",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not run actual queries. Instead view the SQL output"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"propertyName": "seed",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run seeders"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:fresh": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Fresh",
|
||||||
|
"commandName": "migration:fresh",
|
||||||
|
"description": "Drop all tables and re-migrate the database",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explicitly force command to run in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"propertyName": "seed",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run seeders"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "drop-views",
|
||||||
|
"propertyName": "dropViews",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Drop all views"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "drop-types",
|
||||||
|
"propertyName": "dropTypes",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Drop all custom types (Postgres only)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aliases": {}
|
||||||
|
}
|
10
app/Controllers/Http/Admin/FileController.ts
Normal file
10
app/Controllers/Http/Admin/FileController.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
// import File from 'App/Models/File';
|
||||||
|
// import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
|
||||||
|
// export default class HomeController {
|
||||||
|
|
||||||
|
// public async index({ auth, request, inertia }: HttpContextContract) {
|
||||||
|
// let files: ModelQueryBuilderContract<typeof File, File> = File.query();
|
||||||
|
// }
|
||||||
|
// }
|
17
app/Controllers/Http/Admin/HomeController.ts
Normal file
17
app/Controllers/Http/Admin/HomeController.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
|
export default class HomeController {
|
||||||
|
public async index({}: HttpContextContract) {}
|
||||||
|
|
||||||
|
public async create({}: HttpContextContract) {}
|
||||||
|
|
||||||
|
public async store({}: HttpContextContract) {}
|
||||||
|
|
||||||
|
public async show({}: HttpContextContract) {}
|
||||||
|
|
||||||
|
public async edit({}: HttpContextContract) {}
|
||||||
|
|
||||||
|
public async update({}: HttpContextContract) {}
|
||||||
|
|
||||||
|
public async destroy({}: HttpContextContract) {}
|
||||||
|
}
|
143
app/Controllers/Http/Admin/RoleController.ts
Normal file
143
app/Controllers/Http/Admin/RoleController.ts
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
|
import Permission from 'App/Models/Permission';
|
||||||
|
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import CreateRoleValidator from 'App/Validators/CreateRoleValidator';
|
||||||
|
import UpdateRoleValidator from 'App/Validators/UpdateRoleValidator';
|
||||||
|
import { RenderResponse } from '@ioc:EidelLev/Inertia';
|
||||||
|
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
|
||||||
|
export default class RoleController {
|
||||||
|
public async index({ auth, request, inertia }: HttpContextContract) {
|
||||||
|
let roles: ModelQueryBuilderContract<typeof Role, Role> = Role.query();
|
||||||
|
|
||||||
|
if (request.input('search')) {
|
||||||
|
// users = users.whereRaw('name like %?%', [request.input('search')])
|
||||||
|
const searchTerm = request.input('search');
|
||||||
|
roles.where('name', 'ilike', `%${searchTerm}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.input('sort')) {
|
||||||
|
type SortOrder = 'asc' | 'desc' | undefined;
|
||||||
|
let attribute = request.input('sort');
|
||||||
|
let sort_order: SortOrder = 'asc';
|
||||||
|
|
||||||
|
if (attribute.substr(0, 1) == '-') {
|
||||||
|
sort_order = 'desc';
|
||||||
|
// attribute = substr(attribute, 1);
|
||||||
|
attribute = attribute.substr(1);
|
||||||
|
}
|
||||||
|
roles.orderBy(attribute, sort_order);
|
||||||
|
} else {
|
||||||
|
// users.orderBy('created_at', 'desc');
|
||||||
|
roles.orderBy('id', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// const users = await User.query().orderBy('login').paginate(page, limit);
|
||||||
|
let rolesResult = await roles;
|
||||||
|
|
||||||
|
return inertia.render('Admin/Role/Index', {
|
||||||
|
// testing: 'this is a test',
|
||||||
|
roles: rolesResult,
|
||||||
|
filters: request.all(),
|
||||||
|
can: {
|
||||||
|
create: await auth.user?.can(['user-create']),
|
||||||
|
edit: await auth.user?.can(['user-edit']),
|
||||||
|
delete: await auth.user?.can(['user-delete']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create({ inertia }: HttpContextContract) {
|
||||||
|
const permissions = await Permission.query().select('id', 'name').pluck('name', 'id');
|
||||||
|
return inertia.render('Admin/Role/Create', {
|
||||||
|
permissions: permissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async store({ request, response, session }: HttpContextContract) {
|
||||||
|
// node ace make:validator CreateUser
|
||||||
|
try {
|
||||||
|
// Step 2 - Validate request body against the schema
|
||||||
|
await request.validate(CreateRoleValidator);
|
||||||
|
// await request.validate({ schema: roleSchema });
|
||||||
|
// console.log({ payload });
|
||||||
|
} catch (error) {
|
||||||
|
// Step 3 - Handle errors
|
||||||
|
// return response.badRequest(error.messages);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const input = request.only(['name', 'display_name', 'description']);
|
||||||
|
const role = await Role.create(input);
|
||||||
|
|
||||||
|
if (request.input('permissions')) {
|
||||||
|
const permissions: Array<number> = request.input('permissions');
|
||||||
|
await role.related('permissions').attach(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.flash('message', `Role ${role.name} has been created successfully`);
|
||||||
|
return response.redirect().toRoute('role.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async show({ request, inertia }: HttpContextContract): RenderResponse {
|
||||||
|
const id = request.param('id');
|
||||||
|
const role = await Role.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
const permissions = await Permission.query().pluck('name', 'id');
|
||||||
|
// const userHasRoles = user.roles;
|
||||||
|
const rolePermsissions = await role.related('permissions').query().orderBy('name').pluck('id');
|
||||||
|
|
||||||
|
return inertia.render('Admin/Role/Show', {
|
||||||
|
permissions: permissions,
|
||||||
|
role: role,
|
||||||
|
roleHasPermissions: rolePermsissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async edit({ request, inertia }: HttpContextContract) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const role = await Role.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
const permissions = await Permission.query().pluck('name', 'id');
|
||||||
|
// const userHasRoles = user.roles;
|
||||||
|
const rolerHasPermissions = await role.related('permissions').query().orderBy('name').pluck('id');
|
||||||
|
|
||||||
|
return inertia.render('Admin/Role/Edit', {
|
||||||
|
permissions: permissions,
|
||||||
|
role: role,
|
||||||
|
roleHasPermissions: Object.keys(rolerHasPermissions).map((key) => rolerHasPermissions[key]), //convert object to array with role ids
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update({ request, response, session }: HttpContextContract) {
|
||||||
|
// node ace make:validator UpdateUser
|
||||||
|
const id = request.param('id');
|
||||||
|
const role = await Role.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
// validate update form
|
||||||
|
await request.validate(UpdateRoleValidator);
|
||||||
|
|
||||||
|
// password is optional
|
||||||
|
|
||||||
|
const input = request.only(['name', 'description']);
|
||||||
|
await role.merge(input).save();
|
||||||
|
// await user.save();
|
||||||
|
|
||||||
|
if (request.input('permissions')) {
|
||||||
|
const permissions: Array<number> = request.input('permissions');
|
||||||
|
await role.related('permissions').sync(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.flash('message', 'Role has been updated successfully');
|
||||||
|
return response.redirect().toRoute('role.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destroy({ request, response, session }: HttpContextContract) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const role = await Role.findOrFail(id);
|
||||||
|
await role.delete();
|
||||||
|
|
||||||
|
session.flash('message', `Role ${role.name} has been deleted.`);
|
||||||
|
return response.redirect().toRoute('role.index');
|
||||||
|
}
|
||||||
|
}
|
204
app/Controllers/Http/Admin/UsersController.ts
Normal file
204
app/Controllers/Http/Admin/UsersController.ts
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import User from 'App/Models/User';
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
|
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import CreateUserValidator from 'App/Validators/CreateUserValidator';
|
||||||
|
import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
|
||||||
|
import { RenderResponse } from '@ioc:EidelLev/Inertia';
|
||||||
|
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
|
||||||
|
export default class UsersController {
|
||||||
|
public async index({ auth, request, inertia }: HttpContextContract) {
|
||||||
|
const page = request.input('page', 1);
|
||||||
|
// const limit = 10
|
||||||
|
|
||||||
|
let users: ModelQueryBuilderContract<typeof User, User> = User.query();
|
||||||
|
|
||||||
|
if (request.input('search')) {
|
||||||
|
// users = users.whereRaw('name like %?%', [request.input('search')])
|
||||||
|
const searchTerm = request.input('search');
|
||||||
|
users.where('login', 'ilike', `%${searchTerm}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.input('sort')) {
|
||||||
|
type SortOrder = 'asc' | 'desc' | undefined;
|
||||||
|
let attribute = request.input('sort');
|
||||||
|
let sort_order: SortOrder = 'asc';
|
||||||
|
|
||||||
|
// if (strncmp($attribute, '-', 1) === 0) {
|
||||||
|
if (attribute.substr(0, 1) == '-') {
|
||||||
|
sort_order = 'desc';
|
||||||
|
// attribute = substr(attribute, 1);
|
||||||
|
attribute = attribute.substr(1);
|
||||||
|
}
|
||||||
|
// $users->orderBy($attribute, $sort_order);
|
||||||
|
users.orderBy(attribute, sort_order);
|
||||||
|
} else {
|
||||||
|
// users.orderBy('created_at', 'desc');
|
||||||
|
users.orderBy('id', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// const users = await User.query().orderBy('login').paginate(page, limit);
|
||||||
|
|
||||||
|
let usersResult = await users // User.query()
|
||||||
|
// .orderBy('login')
|
||||||
|
// .filter(qs)
|
||||||
|
// .preload('focusInterests')
|
||||||
|
// .preload('role')
|
||||||
|
.paginate(page, 5);
|
||||||
|
|
||||||
|
// var test = request.all();
|
||||||
|
|
||||||
|
return inertia.render('Admin/User/Index', {
|
||||||
|
// testing: 'this is a test',
|
||||||
|
users: usersResult.toJSON(),
|
||||||
|
filters: request.all(),
|
||||||
|
can: {
|
||||||
|
create: await auth.user?.can(['user-create']),
|
||||||
|
edit: await auth.user?.can(['user-edit']),
|
||||||
|
delete: await auth.user?.can(['user-delete']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create({ inertia }: HttpContextContract) {
|
||||||
|
// let rolesPluck = {};
|
||||||
|
// (await Role.query().select('id', 'name')).forEach((user) => {
|
||||||
|
// rolesPluck[user.id] = user.name;
|
||||||
|
// });
|
||||||
|
const roles = await Role.query().select('id', 'name').pluck('name', 'id');
|
||||||
|
|
||||||
|
return inertia.render('Admin/User/Create', {
|
||||||
|
roles: roles,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async store({ request, response, session }: HttpContextContract) {
|
||||||
|
// node ace make:validator CreateUser
|
||||||
|
try {
|
||||||
|
// Step 2 - Validate request body against the schema
|
||||||
|
await request.validate(CreateUserValidator);
|
||||||
|
// console.log({ payload });
|
||||||
|
} catch (error) {
|
||||||
|
// Step 3 - Handle errors
|
||||||
|
// return response.badRequest(error.messages);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const input = request.only(['login', 'email', 'password']);
|
||||||
|
const user = await User.create(input);
|
||||||
|
if (request.input('roles')) {
|
||||||
|
const roles: Array<number> = request.input('roles');
|
||||||
|
await user.related('roles').attach(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.flash('message', 'User has been created successfully');
|
||||||
|
return response.redirect().toRoute('user.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async show({ request, inertia }: HttpContextContract) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
const roles = await Role.query().pluck('name', 'id');
|
||||||
|
// const userHasRoles = user.roles;
|
||||||
|
const userRoles = await user.related('roles').query().orderBy('name').pluck('id');
|
||||||
|
|
||||||
|
return inertia.render('Admin/User/Show', {
|
||||||
|
roles: roles,
|
||||||
|
user: user,
|
||||||
|
userHasRoles: userRoles,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async edit({ request, inertia }: HttpContextContract) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
const roles = await Role.query().pluck('name', 'id');
|
||||||
|
// const userHasRoles = user.roles;
|
||||||
|
const userHasRoles = await user.related('roles').query().orderBy('name').pluck('id');
|
||||||
|
|
||||||
|
return inertia.render('Admin/User/Edit', {
|
||||||
|
roles: roles,
|
||||||
|
user: user,
|
||||||
|
userHasRoles: Object.keys(userHasRoles).map((key) => userHasRoles[key]), //convert object to array with role ids
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update({ request, response, session }: HttpContextContract) {
|
||||||
|
// node ace make:validator UpdateUser
|
||||||
|
const id = request.param('id');
|
||||||
|
const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
// validate update form
|
||||||
|
await request.validate(UpdateUserValidator);
|
||||||
|
|
||||||
|
// password is optional
|
||||||
|
let input;
|
||||||
|
if (request.input('password')) {
|
||||||
|
input = request.only(['login', 'email', 'password']);
|
||||||
|
} else {
|
||||||
|
input = request.only(['login', 'email']);
|
||||||
|
}
|
||||||
|
await user.merge(input).save();
|
||||||
|
// await user.save();
|
||||||
|
|
||||||
|
if (request.input('roles')) {
|
||||||
|
const roles: Array<number> = request.input('roles');
|
||||||
|
await user.related('roles').sync(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.flash('message', 'User has been updated successfully');
|
||||||
|
return response.redirect().toRoute('user.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destroy({ request, response, session }: HttpContextContract) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const user = await User.findOrFail(id);
|
||||||
|
await user.delete();
|
||||||
|
|
||||||
|
session.flash('message', `User ${user.login} has been deleted.`);
|
||||||
|
return response.redirect().toRoute('user.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the user a form to change their personal information & password.
|
||||||
|
*
|
||||||
|
* @return — \Inertia\Response
|
||||||
|
*/
|
||||||
|
public accountInfo({ inertia, auth }: HttpContextContract): RenderResponse {
|
||||||
|
const user = auth.user;
|
||||||
|
// const id = request.param('id');
|
||||||
|
// const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
return inertia.render('Admin/User/AccountInfo', {
|
||||||
|
user: user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the modified personal information for a user.
|
||||||
|
*
|
||||||
|
* @param HttpContextContract ctx
|
||||||
|
* @return : RedirectContract
|
||||||
|
*/
|
||||||
|
public async accountInfoStore({ request, response, auth, session }: HttpContextContract) {
|
||||||
|
// validate update form
|
||||||
|
await request.validate(UpdateUserValidator);
|
||||||
|
|
||||||
|
const payload = request.only(['login', 'email']);
|
||||||
|
auth.user?.merge(payload);
|
||||||
|
const user = await auth.user?.save();
|
||||||
|
// $user = \Auth::user()->update($request->except(['_token']));
|
||||||
|
let message;
|
||||||
|
if (user) {
|
||||||
|
message = 'Account updated successfully.';
|
||||||
|
} else {
|
||||||
|
message = 'Error while saving. Please try again.';
|
||||||
|
}
|
||||||
|
|
||||||
|
session.flash(message);
|
||||||
|
return response.redirect().toRoute('admin.account.info');
|
||||||
|
//->with('message', __($message));
|
||||||
|
}
|
||||||
|
}
|
23
app/Controllers/Http/Api/AuthorsController.ts
Normal file
23
app/Controllers/Http/Api/AuthorsController.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Person from 'App/Models/Person';
|
||||||
|
// import Dataset from 'App/Models/Dataset';
|
||||||
|
|
||||||
|
// node ace make:controller Author
|
||||||
|
export default class AuthorsController {
|
||||||
|
|
||||||
|
public async index({}: HttpContextContract) {
|
||||||
|
// select * from gba.persons
|
||||||
|
// where exists (select * from gba.documents inner join gba.link_documents_persons on "documents"."id" = "link_documents_persons"."document_id"
|
||||||
|
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
|
||||||
|
const authors = await Person.query()
|
||||||
|
.whereHas('datasets', (dQuery) => {
|
||||||
|
dQuery.wherePivot('role', 'author')
|
||||||
|
})
|
||||||
|
.withCount('datasets', (query) => {
|
||||||
|
query.as('datasets_count')
|
||||||
|
});
|
||||||
|
|
||||||
|
return authors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
app/Controllers/Http/Api/DatasetController.ts
Normal file
23
app/Controllers/Http/Api/DatasetController.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
// import Person from 'App/Models/Person';
|
||||||
|
import Dataset from 'App/Models/Dataset';
|
||||||
|
|
||||||
|
// node ace make:controller Author
|
||||||
|
export default class DatasetController {
|
||||||
|
|
||||||
|
public async index({}: HttpContextContract) {
|
||||||
|
// select * from gba.persons
|
||||||
|
// where exists (select * from gba.documents inner join gba.link_documents_persons on "documents"."id" = "link_documents_persons"."document_id"
|
||||||
|
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
|
||||||
|
const datasets = await Dataset
|
||||||
|
.query()
|
||||||
|
.where('server_state', 'published')
|
||||||
|
.orWhere('server_state', 'deleted');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return datasets;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
app/Controllers/Http/Auth/AuthController.ts
Normal file
42
app/Controllers/Http/Auth/AuthController.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
// import User from 'App/Models/User';
|
||||||
|
// import Hash from '@ioc:Adonis/Core/Hash';
|
||||||
|
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
|
||||||
|
import AuthValidator from 'App/Validators/AuthValidator';
|
||||||
|
|
||||||
|
export default class AuthController {
|
||||||
|
// login function
|
||||||
|
public async login({ request, response, auth, session }: HttpContextContract) {
|
||||||
|
// console.log({
|
||||||
|
// registerBody: request.body(),
|
||||||
|
// });
|
||||||
|
await request.validate(AuthValidator);
|
||||||
|
|
||||||
|
const plainPassword = await request.input('password');
|
||||||
|
const email = await request.input('email');
|
||||||
|
// grab uid and password values off request body
|
||||||
|
// const { email, password } = request.only(['email', 'password'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// attempt to login
|
||||||
|
await auth.use("web").attempt(email, plainPassword);
|
||||||
|
} catch (error) {
|
||||||
|
// if login fails, return vague form message and redirect back
|
||||||
|
session.flash('message', 'Your username, email, or password is incorrect')
|
||||||
|
return response.redirect().back()
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, redirect todashboard
|
||||||
|
response.redirect('/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
// logout function
|
||||||
|
public async logout({ auth, response }: HttpContextContract) {
|
||||||
|
// await auth.logout();
|
||||||
|
await auth.use('web').logout();
|
||||||
|
response.redirect('/app/login');
|
||||||
|
// return response.status(200);
|
||||||
|
}
|
||||||
|
}
|
61
app/Exceptions/Handler.ts
Normal file
61
app/Exceptions/Handler.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Http Exception Handler
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| AdonisJs will forward all exceptions occurred during an HTTP request to
|
||||||
|
| the following class. You can learn more about exception handling by
|
||||||
|
| reading docs.
|
||||||
|
|
|
||||||
|
| The exception handler extends a base `HttpExceptionHandler` which is not
|
||||||
|
| mandatory, however it can do lot of heavy lifting to handle the errors
|
||||||
|
| properly.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Logger from '@ioc:Adonis/Core/Logger';
|
||||||
|
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler';
|
||||||
|
|
||||||
|
export default class ExceptionHandler extends HttpExceptionHandler {
|
||||||
|
protected statusPages = {
|
||||||
|
'401,403': 'errors/unauthorized',
|
||||||
|
'404': 'errors/not-found',
|
||||||
|
'500..599': 'errors/server-error',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle(error: any, ctx: HttpContextContract) {
|
||||||
|
const { response, request, inertia } = ctx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle failed authentication attempt
|
||||||
|
*/
|
||||||
|
// if (['E_INVALID_AUTH_PASSWORD', 'E_INVALID_AUTH_UID'].includes(error.code)) {
|
||||||
|
// session.flash('errors', { login: error.message });
|
||||||
|
// return response.redirect('/login');
|
||||||
|
// }
|
||||||
|
// if ([401].includes(error.status)) {
|
||||||
|
// session.flash('errors', { login: error.message });
|
||||||
|
// return response.redirect('/dashboard');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// https://github.com/inertiajs/inertia-laravel/issues/56
|
||||||
|
if (request.header('X-Inertia') && [500, 503, 404, 403, 401].includes(response.getStatus())) {
|
||||||
|
return inertia.render('Error', {
|
||||||
|
status: response.getStatus(),
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
// ->toResponse($request)
|
||||||
|
// ->setStatusCode($response->status());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward rest of the exceptions to the parent class
|
||||||
|
*/
|
||||||
|
return super.handle(error, ctx);
|
||||||
|
}
|
||||||
|
}
|
68
app/Exceptions/InvalidCredentialException.ts
Normal file
68
app/Exceptions/InvalidCredentialException.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { Exception } from "@adonisjs/core/build/standalone";
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Exception
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The Exception class imported from '@adonisjs/core' allows defining
|
||||||
|
| a status code and error code for every exception.
|
||||||
|
|
|
||||||
|
| @example
|
||||||
|
| new InvalidCredentialException('message', 403, 'E_RUNTIME_EXCEPTION')
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class InvalidCredentialException extends Exception {
|
||||||
|
// constructor() {
|
||||||
|
// super(...arguments);
|
||||||
|
// // this.responseText = this.message;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unable to find user
|
||||||
|
*/
|
||||||
|
static invalidUid() {
|
||||||
|
const error = new this("User not found", 400, "E_INVALID_AUTH_UID");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Invalid user password
|
||||||
|
*/
|
||||||
|
static invalidPassword() {
|
||||||
|
const error = new this("Password mis-match", 400, "E_INVALID_AUTH_PASSWORD");
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flash error message and redirect the user back
|
||||||
|
*/
|
||||||
|
private respondWithRedirect(error, ctx) {
|
||||||
|
// if (!ctx.session) {
|
||||||
|
// return ctx.response.status(this.status).send(this.responseText);
|
||||||
|
// }
|
||||||
|
ctx.session.flashExcept(["_csrf"]);
|
||||||
|
ctx.session.flash("auth", {
|
||||||
|
error: error,
|
||||||
|
/**
|
||||||
|
* Will be removed in the future
|
||||||
|
*/
|
||||||
|
errors: {
|
||||||
|
uid: this.code === "E_INVALID_AUTH_UID" ? ["Invalid login id"] : null,
|
||||||
|
password: this.code === "E_INVALID_AUTH_PASSWORD" ? ["Invalid password"] : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ctx.response.redirect("back", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle this exception by itself
|
||||||
|
*/
|
||||||
|
|
||||||
|
handle(error, ctx) {
|
||||||
|
// return response.status(403).view.render("errors/unauthorized", {
|
||||||
|
// error: error,
|
||||||
|
// });
|
||||||
|
this.respondWithRedirect(error, ctx);
|
||||||
|
}
|
||||||
|
}
|
76
app/Middleware/Auth.ts
Normal file
76
app/Middleware/Auth.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { AuthenticationException } from '@adonisjs/auth/build/standalone'
|
||||||
|
import type { GuardsList } from '@ioc:Adonis/Addons/Auth'
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth middleware is meant to restrict un-authenticated access to a given route
|
||||||
|
* or a group of routes.
|
||||||
|
*
|
||||||
|
* You must register this middleware inside `start/kernel.ts` file under the list
|
||||||
|
* of named middleware.
|
||||||
|
*/
|
||||||
|
export default class AuthMiddleware {
|
||||||
|
/**
|
||||||
|
* The URL to redirect to when request is Unauthorized
|
||||||
|
*/
|
||||||
|
protected redirectTo = '/app/login'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates the current HTTP request against a custom set of defined
|
||||||
|
* guards.
|
||||||
|
*
|
||||||
|
* The authentication loop stops as soon as the user is authenticated using any
|
||||||
|
* of the mentioned guards and that guard will be used by the rest of the code
|
||||||
|
* during the current request.
|
||||||
|
*/
|
||||||
|
protected async authenticate(auth: HttpContextContract['auth'], guards: (keyof GuardsList)[]) {
|
||||||
|
/**
|
||||||
|
* Hold reference to the guard last attempted within the for loop. We pass
|
||||||
|
* the reference of the guard to the "AuthenticationException", so that
|
||||||
|
* it can decide the correct response behavior based upon the guard
|
||||||
|
* driver
|
||||||
|
*/
|
||||||
|
let guardLastAttempted: string | undefined
|
||||||
|
|
||||||
|
for (let guard of guards) {
|
||||||
|
guardLastAttempted = guard
|
||||||
|
|
||||||
|
if (await auth.use(guard).check()) {
|
||||||
|
/**
|
||||||
|
* Instruct auth to use the given guard as the default guard for
|
||||||
|
* the rest of the request, since the user authenticated
|
||||||
|
* succeeded here
|
||||||
|
*/
|
||||||
|
auth.defaultGuard = guard
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unable to authenticate using any guard
|
||||||
|
*/
|
||||||
|
throw new AuthenticationException(
|
||||||
|
'Unauthorized access',
|
||||||
|
'E_UNAUTHORIZED_ACCESS',
|
||||||
|
guardLastAttempted,
|
||||||
|
this.redirectTo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle request
|
||||||
|
*/
|
||||||
|
public async handle (
|
||||||
|
{ auth }: HttpContextContract,
|
||||||
|
next: () => Promise<void>,
|
||||||
|
customGuards: (keyof GuardsList)[]
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Uses the user defined guards or the default guard mentioned in
|
||||||
|
* the config file
|
||||||
|
*/
|
||||||
|
const guards = customGuards.length ? customGuards : [auth.name]
|
||||||
|
await this.authenticate(auth, guards)
|
||||||
|
await next()
|
||||||
|
}
|
||||||
|
}
|
85
app/Middleware/Can.ts
Normal file
85
app/Middleware/Can.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Config from '@ioc:Adonis/Core/Config';
|
||||||
|
import Database from '@ioc:Adonis/Lucid/Database';
|
||||||
|
import User from 'App/Models/User';
|
||||||
|
import { Exception } from '@adonisjs/core/build/standalone';
|
||||||
|
|
||||||
|
const permissionTable = Config.get('rolePermission.permission_table', 'permissions');
|
||||||
|
const rolePermissionTable = Config.get('rolePermission.role_permission_table', 'role_has_permissions');
|
||||||
|
const roleTable = Config.get('rolePermission.role_table', 'roles');
|
||||||
|
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permission authentication to check if user has any of the specified permissions
|
||||||
|
*
|
||||||
|
* Should be called after auth middleware
|
||||||
|
*/
|
||||||
|
export default class Can {
|
||||||
|
/**
|
||||||
|
* Handle request
|
||||||
|
*/
|
||||||
|
public async handle(
|
||||||
|
{ auth, response }: HttpContextContract,
|
||||||
|
next: () => Promise<void>,
|
||||||
|
permissionNames: string[]
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Check if user is logged-in
|
||||||
|
*/
|
||||||
|
let user = await auth.user;
|
||||||
|
if (!user) {
|
||||||
|
return response.unauthorized({ error: 'Must be logged in' });
|
||||||
|
}
|
||||||
|
let hasPermission = await this.checkHasPermissions(user, permissionNames);
|
||||||
|
if (!hasPermission) {
|
||||||
|
// return response.unauthorized({
|
||||||
|
// error: `Doesn't have required role(s): ${permissionNames.join(',')}`,
|
||||||
|
// });
|
||||||
|
throw new Exception(`Doesn't have required permission(s): ${permissionNames.join(',')}`, 401);
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkHasPermissions(user: User, permissionNames: Array<string>): Promise<boolean> {
|
||||||
|
let rolePlaceHolder = '(';
|
||||||
|
let placeholders = new Array(permissionNames.length).fill('?');
|
||||||
|
rolePlaceHolder += placeholders.join(',');
|
||||||
|
rolePlaceHolder += ')';
|
||||||
|
|
||||||
|
// let test = user
|
||||||
|
// .related('roles')
|
||||||
|
// .query()
|
||||||
|
// .count('permissions.name')
|
||||||
|
// .innerJoin('gba.role_has_permissions', function () {
|
||||||
|
// this.on('gba.role_has_permissions.role_id', 'roles.id');
|
||||||
|
// })
|
||||||
|
// .innerJoin('gba.permissions', function () {
|
||||||
|
// this.on('role_has_permissions.permission_id', 'permissions.id');
|
||||||
|
// })
|
||||||
|
// .andWhereIn('permissions.name', permissionNames);
|
||||||
|
|
||||||
|
// select "permissions"."name"
|
||||||
|
// from "gba"."roles"
|
||||||
|
// inner join "gba"."link_accounts_roles" on "roles"."id" = "link_accounts_roles"."role_id"
|
||||||
|
// inner join "gba"."role_has_permissions" on "gba"."role_has_permissions"."role_id" = "roles"."id"
|
||||||
|
// inner join "gba"."permissions" on "role_has_permissions"."permission_id" = "permissions"."id"
|
||||||
|
// where ("permissions"."name" in ('dataset-list', 'dataset-publish'))
|
||||||
|
// and ("link_accounts_roles"."account_id" = 1)
|
||||||
|
|
||||||
|
let {
|
||||||
|
rows: {
|
||||||
|
0: { permissioncount },
|
||||||
|
},
|
||||||
|
} = await Database.rawQuery(
|
||||||
|
'SELECT count("p"."name") as permissionCount FROM ' + roleTable +
|
||||||
|
' r INNER JOIN ' + userRoleTable + ' ur ON ur.role_id=r.id AND "ur"."account_id"=? ' +
|
||||||
|
' INNER JOIN ' + rolePermissionTable + ' rp ON rp.role_id=r.id ' +
|
||||||
|
' INNER JOIN ' + permissionTable + ' p ON rp.permission_id=p.id AND "p"."name" in ' +
|
||||||
|
rolePlaceHolder +
|
||||||
|
' LIMIT 1',
|
||||||
|
[user.id, ...permissionNames]
|
||||||
|
);
|
||||||
|
|
||||||
|
return permissioncount > 0;
|
||||||
|
}
|
||||||
|
}
|
66
app/Middleware/Is.ts
Normal file
66
app/Middleware/Is.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import Config from '@ioc:Adonis/Core/Config'
|
||||||
|
import Database from '@ioc:Adonis/Lucid/Database'
|
||||||
|
import User from 'App/Models/User'
|
||||||
|
// import { Exception } from '@adonisjs/core/build/standalone'
|
||||||
|
|
||||||
|
const roleTable = Config.get('rolePermission.role_table', 'roles')
|
||||||
|
const userRoleTable = Config.get('rolePermission.user_role_table', 'user_roles')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Role authentication to check if user has any of the specified roles
|
||||||
|
*
|
||||||
|
* Should be called after auth middleware
|
||||||
|
*/
|
||||||
|
export default class Is {
|
||||||
|
/**
|
||||||
|
* Handle request
|
||||||
|
*/
|
||||||
|
public async handle(
|
||||||
|
{ auth, response }: HttpContextContract,
|
||||||
|
next: () => Promise<void>,
|
||||||
|
roleNames: string[]
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Check if user is logged-in or not.
|
||||||
|
*/
|
||||||
|
let user = await auth.user
|
||||||
|
if (!user) {
|
||||||
|
return response.unauthorized({ error: 'Must be logged in' })
|
||||||
|
}
|
||||||
|
let hasRole = await this.checkHasRoles(user, roleNames)
|
||||||
|
if (!hasRole) {
|
||||||
|
return response.unauthorized({
|
||||||
|
error: `Doesn't have required role(s): ${roleNames.join(',')}`,
|
||||||
|
})
|
||||||
|
// return new Exception(`Doesn't have required role(s): ${roleNames.join(',')}`,
|
||||||
|
// 401,
|
||||||
|
// "E_INVALID_AUTH_UID");
|
||||||
|
}
|
||||||
|
await next()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkHasRoles(user: User, roleNames: Array<string>): Promise<boolean> {
|
||||||
|
let rolePlaceHolder = '('
|
||||||
|
let placeholders = new Array(roleNames.length).fill('?')
|
||||||
|
rolePlaceHolder += placeholders.join(',')
|
||||||
|
rolePlaceHolder += ')'
|
||||||
|
|
||||||
|
let {
|
||||||
|
0: {
|
||||||
|
0: { roleCount },
|
||||||
|
},
|
||||||
|
} = await Database.rawQuery(
|
||||||
|
'SELECT count(`ur`.`id`) as roleCount FROM ' +
|
||||||
|
userRoleTable +
|
||||||
|
' ur INNER JOIN ' +
|
||||||
|
roleTable +
|
||||||
|
' r ON ur.role_id=r.id WHERE `ur`.`user_id`=? AND `r`.`name` in ' +
|
||||||
|
rolePlaceHolder +
|
||||||
|
' LIMIT 1',
|
||||||
|
[user.id, ...roleNames]
|
||||||
|
)
|
||||||
|
|
||||||
|
return roleCount > 0
|
||||||
|
}
|
||||||
|
}
|
83
app/Middleware/Role.ts
Normal file
83
app/Middleware/Role.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Database from '@ioc:Adonis/Lucid/Database';
|
||||||
|
import Config from '@ioc:Adonis/Core/Config';
|
||||||
|
import User from 'app/Models/User';
|
||||||
|
import { Exception } from '@adonisjs/core/build/standalone';
|
||||||
|
|
||||||
|
const roleTable = Config.get('rolePermission.role_table', 'roles');
|
||||||
|
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
|
||||||
|
|
||||||
|
|
||||||
|
// node ace make:middleware role
|
||||||
|
export default class Role {
|
||||||
|
// .middleware(['auth', 'role:admin,moderator'])
|
||||||
|
public async handle(
|
||||||
|
{ auth, response }: HttpContextContract,
|
||||||
|
next: () => Promise<void>,
|
||||||
|
userRoles: string[]
|
||||||
|
) {
|
||||||
|
// Check if user is logged-in or not.
|
||||||
|
// let expression = "";
|
||||||
|
// if (Array.isArray(args)) {
|
||||||
|
// expression = args.join(" || ");
|
||||||
|
// }
|
||||||
|
|
||||||
|
let user = await auth.user;
|
||||||
|
if (!user) {
|
||||||
|
return response.unauthorized({ error: 'Must be logged in' });
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasRole = await this.checkHasRoles(user, userRoles);
|
||||||
|
if (!hasRole) {
|
||||||
|
// return response.unauthorized({
|
||||||
|
// error: `Doesn't have required role(s): ${userRoles.join(',')}`,
|
||||||
|
// // error: `Doesn't have required role(s)`,
|
||||||
|
// });
|
||||||
|
throw new Exception(`Doesn't have required role(s): ${userRoles.join(',')}`, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// code for middleware goes here. ABOVE THE NEXT CALL
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkHasRoles(user: User, userRoles: string[]): Promise<boolean> {
|
||||||
|
// await user.load("roles");
|
||||||
|
// const ok = user.roles.map((role) => role.name);
|
||||||
|
// const roles = await user.getRoles();
|
||||||
|
|
||||||
|
let rolePlaceHolder = '(';
|
||||||
|
let placeholders = new Array(userRoles.length).fill('?');
|
||||||
|
rolePlaceHolder += placeholders.join(',');
|
||||||
|
rolePlaceHolder += ')';
|
||||||
|
|
||||||
|
// const roles = await user
|
||||||
|
// .related('roles')
|
||||||
|
// .query()
|
||||||
|
// .count('*') // .select('name')
|
||||||
|
// .whereIn('name', userRoles);
|
||||||
|
// // .groupBy('name');
|
||||||
|
|
||||||
|
// select count(*) as roleCount
|
||||||
|
// from gba.roles
|
||||||
|
// inner join gba.link_accounts_roles
|
||||||
|
// on "roles"."id" = "link_accounts_roles"."role_id"
|
||||||
|
// where ("name" in ('administrator', 'editor')) and ("link_accounts_roles"."account_id" = 1)
|
||||||
|
|
||||||
|
let {
|
||||||
|
rows: {
|
||||||
|
0: { rolecount },
|
||||||
|
},
|
||||||
|
} = await Database.rawQuery(
|
||||||
|
'SELECT count("r"."id") as roleCount FROM ' +
|
||||||
|
roleTable +
|
||||||
|
' r INNER JOIN ' +
|
||||||
|
userRoleTable +
|
||||||
|
' ur ON r.id=ur.role_id WHERE "ur"."account_id"=? AND "r"."name" in ' +
|
||||||
|
rolePlaceHolder +
|
||||||
|
' LIMIT 1',
|
||||||
|
[user.id, ...userRoles]
|
||||||
|
);
|
||||||
|
|
||||||
|
return rolecount > 0;
|
||||||
|
}
|
||||||
|
}
|
21
app/Middleware/SilentAuth.ts
Normal file
21
app/Middleware/SilentAuth.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silent auth middleware can be used as a global middleware to silent check
|
||||||
|
* if the user is logged-in or not.
|
||||||
|
*
|
||||||
|
* The request continues as usual, even when the user is not logged-in.
|
||||||
|
*/
|
||||||
|
export default class SilentAuthMiddleware {
|
||||||
|
/**
|
||||||
|
* Handle request
|
||||||
|
*/
|
||||||
|
public async handle({ auth }: HttpContextContract, next: () => Promise<void>) {
|
||||||
|
/**
|
||||||
|
* Check if user is logged-in or not. If yes, then `ctx.auth.user` will be
|
||||||
|
* set to the instance of the currently logged in user.
|
||||||
|
*/
|
||||||
|
await auth.check()
|
||||||
|
await next()
|
||||||
|
}
|
||||||
|
}
|
52
app/Models/Dataset.ts
Normal file
52
app/Models/Dataset.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import {
|
||||||
|
column,
|
||||||
|
BaseModel,
|
||||||
|
SnakeCaseNamingStrategy,
|
||||||
|
// computed,
|
||||||
|
manyToMany,
|
||||||
|
ManyToMany,
|
||||||
|
} from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import Person from './Person';
|
||||||
|
|
||||||
|
|
||||||
|
export default class Dataset extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'id';
|
||||||
|
public static table = 'documents';
|
||||||
|
public static selfAssignPrimaryKey = false;
|
||||||
|
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public server_state: boolean;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public publisherName: string;
|
||||||
|
|
||||||
|
@column.dateTime({ columnName: 'embargo_date' })
|
||||||
|
public EmbargoDate: DateTime;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public type: string;
|
||||||
|
|
||||||
|
|
||||||
|
@column.dateTime({ columnName: 'server_date_published' })
|
||||||
|
public ServerDatePublished: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, columnName: 'created_at' })
|
||||||
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||||
|
public updatedAt: DateTime;
|
||||||
|
|
||||||
|
@manyToMany(() => Person, {
|
||||||
|
pivotForeignKey: 'document_id',
|
||||||
|
pivotRelatedForeignKey: 'person_id',
|
||||||
|
pivotTable: 'link_documents_persons',
|
||||||
|
pivotColumns: ['role', 'sort_order', 'allow_email_contact']
|
||||||
|
})
|
||||||
|
public persons: ManyToMany<typeof Person>;
|
||||||
|
|
||||||
|
}
|
62
app/Models/File.ts
Normal file
62
app/Models/File.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import {
|
||||||
|
column,
|
||||||
|
BaseModel,
|
||||||
|
hasMany, HasMany,
|
||||||
|
// manyToMany,
|
||||||
|
// ManyToMany,
|
||||||
|
SnakeCaseNamingStrategy,
|
||||||
|
} from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import HashValue from './HashValue';
|
||||||
|
|
||||||
|
export default class File extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'id';
|
||||||
|
public static table = 'document_files';
|
||||||
|
public static selfAssignPrimaryKey = false;
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public pathName: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public label: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public comment: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public mimetype: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public language: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public fileSize: bigint;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public visibleInOai: boolean;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public sortOrder: number;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true })
|
||||||
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||||
|
public updatedAt: DateTime;
|
||||||
|
|
||||||
|
// public function hashvalues()
|
||||||
|
// {
|
||||||
|
// return $this->hasMany(HashValue::class, 'file_id', 'id');
|
||||||
|
// }
|
||||||
|
|
||||||
|
@hasMany(() => HashValue, {
|
||||||
|
foreignKey: 'file_id',
|
||||||
|
})
|
||||||
|
public hashvalues: HasMany<typeof HashValue>;
|
||||||
|
}
|
41
app/Models/HashValue.ts
Normal file
41
app/Models/HashValue.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import {
|
||||||
|
column,
|
||||||
|
BaseModel,
|
||||||
|
belongsTo,
|
||||||
|
BelongsTo,
|
||||||
|
SnakeCaseNamingStrategy,
|
||||||
|
} from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import File from './File';
|
||||||
|
|
||||||
|
export default class HashValue extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'file_id, type';
|
||||||
|
public static table = 'file_hashvalues';
|
||||||
|
|
||||||
|
// static get primaryKey () {
|
||||||
|
// return 'type, value'
|
||||||
|
// }
|
||||||
|
|
||||||
|
static get incrementing () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// @column({
|
||||||
|
// isPrimary: true,
|
||||||
|
// })
|
||||||
|
// public id: number;
|
||||||
|
|
||||||
|
// Foreign key is still on the same model
|
||||||
|
@column({})
|
||||||
|
public file_id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public type: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public value: string;
|
||||||
|
|
||||||
|
@belongsTo(() => File)
|
||||||
|
public file: BelongsTo<typeof File>
|
||||||
|
|
||||||
|
}
|
100
app/Models/Permission.ts
Normal file
100
app/Models/Permission.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import {
|
||||||
|
column,
|
||||||
|
BaseModel,
|
||||||
|
manyToMany,
|
||||||
|
ManyToMany,
|
||||||
|
SnakeCaseNamingStrategy,
|
||||||
|
beforeUpdate,
|
||||||
|
beforeCreate,
|
||||||
|
} from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
|
|
||||||
|
export default class Permission extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'id';
|
||||||
|
public static table = 'permissions';
|
||||||
|
public static selfAssignPrimaryKey = false;
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public role_id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public display_name: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
})
|
||||||
|
public created_at: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
autoUpdate: true,
|
||||||
|
})
|
||||||
|
public updated_at: DateTime;
|
||||||
|
|
||||||
|
@beforeCreate()
|
||||||
|
@beforeUpdate()
|
||||||
|
public static async resetDate(role) {
|
||||||
|
role.created_at = this.formatDateTime(role.created_at);
|
||||||
|
role.updated_at = this.formatDateTime(role.updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static boot() {
|
||||||
|
// super.boot()
|
||||||
|
|
||||||
|
// this.before('create', async (_modelInstance) => {
|
||||||
|
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
|
||||||
|
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
|
||||||
|
// })
|
||||||
|
// this.before('update', async (_modelInstance) => {
|
||||||
|
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
|
||||||
|
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static formatDateTime(datetime) {
|
||||||
|
let value = new Date(datetime);
|
||||||
|
return datetime
|
||||||
|
? value.getFullYear() +
|
||||||
|
'-' +
|
||||||
|
(value.getMonth() + 1) +
|
||||||
|
'-' +
|
||||||
|
value.getDate() +
|
||||||
|
' ' +
|
||||||
|
value.getHours() +
|
||||||
|
':' +
|
||||||
|
value.getMinutes() +
|
||||||
|
':' +
|
||||||
|
value.getSeconds()
|
||||||
|
: datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @belongsTo(() => Role)
|
||||||
|
// public role: BelongsTo<typeof Role>;
|
||||||
|
|
||||||
|
@manyToMany(() => Role, {
|
||||||
|
pivotForeignKey: 'permission_id',
|
||||||
|
pivotRelatedForeignKey: 'role_id',
|
||||||
|
pivotTable: 'role_has_permissions',
|
||||||
|
})
|
||||||
|
public roles: ManyToMany<typeof Role>;
|
||||||
|
}
|
83
app/Models/Person.ts
Normal file
83
app/Models/Person.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import {
|
||||||
|
column,
|
||||||
|
BaseModel,
|
||||||
|
SnakeCaseNamingStrategy,
|
||||||
|
computed,
|
||||||
|
manyToMany,
|
||||||
|
ManyToMany,
|
||||||
|
} from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import Dataset from './Dataset';
|
||||||
|
|
||||||
|
export default class Person extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'id';
|
||||||
|
public static table = 'persons';
|
||||||
|
public static selfAssignPrimaryKey = false;
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public academicTitle: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public firstName: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public lastName: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public identifierOrcid: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public status: boolean;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public nameType: string;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
})
|
||||||
|
public registeredAt: DateTime;
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'name'
|
||||||
|
})
|
||||||
|
public get fullName() {
|
||||||
|
return this.firstName + ' ' + this.lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed()
|
||||||
|
public get progress(): number {
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed()
|
||||||
|
public get created_at() {
|
||||||
|
return '2023-02-17 08:45:56';
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed()
|
||||||
|
public get datasetCount() {
|
||||||
|
const stock = this.$extras.datasets_count //my pivot column name was "stock"
|
||||||
|
return stock
|
||||||
|
}
|
||||||
|
|
||||||
|
@manyToMany(() => Dataset, {
|
||||||
|
pivotForeignKey: 'person_id',
|
||||||
|
pivotRelatedForeignKey: 'document_id',
|
||||||
|
pivotTable: 'link_documents_persons',
|
||||||
|
pivotColumns: ['role', 'sort_order', 'allow_email_contact']
|
||||||
|
})
|
||||||
|
public datasets: ManyToMany<typeof Dataset>;
|
||||||
|
}
|
105
app/Models/Role.ts
Normal file
105
app/Models/Role.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import {
|
||||||
|
column,
|
||||||
|
BaseModel,
|
||||||
|
SnakeCaseNamingStrategy,
|
||||||
|
manyToMany,
|
||||||
|
ManyToMany,
|
||||||
|
beforeCreate,
|
||||||
|
beforeUpdate,
|
||||||
|
} from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
// import moment from 'moment';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import User from './User';
|
||||||
|
import Permission from 'App/Models/Permission';
|
||||||
|
|
||||||
|
export default class Role extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'id';
|
||||||
|
public static table = 'roles';
|
||||||
|
public static selfAssignPrimaryKey = false;
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public display_name: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
// return value ? moment(value).format('MMMM Do YYYY, HH:mm:ss') : value;
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
})
|
||||||
|
public created_at: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
autoUpdate: true
|
||||||
|
})
|
||||||
|
public updated_at: DateTime;
|
||||||
|
|
||||||
|
@beforeCreate()
|
||||||
|
@beforeUpdate()
|
||||||
|
public static async resetDate(role) {
|
||||||
|
role.created_at = this.formatDateTime(role.created_at);
|
||||||
|
role.updated_at = this.formatDateTime(role.updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static boot() {
|
||||||
|
// super.boot();
|
||||||
|
|
||||||
|
// this.before('create', async (_modelInstance) => {
|
||||||
|
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at);
|
||||||
|
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at);
|
||||||
|
// });
|
||||||
|
// this.before('update', async (_modelInstance) => {
|
||||||
|
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at);
|
||||||
|
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static formatDateTime(datetime) {
|
||||||
|
let value = new Date(datetime);
|
||||||
|
return datetime
|
||||||
|
? value.getFullYear() +
|
||||||
|
'-' +
|
||||||
|
(value.getMonth() + 1) +
|
||||||
|
'-' +
|
||||||
|
value.getDate() +
|
||||||
|
' ' +
|
||||||
|
value.getHours() +
|
||||||
|
':' +
|
||||||
|
value.getMinutes() +
|
||||||
|
':' +
|
||||||
|
value.getSeconds()
|
||||||
|
: datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@manyToMany(() => User, {
|
||||||
|
pivotForeignKey: 'role_id',
|
||||||
|
pivotRelatedForeignKey: 'account_id',
|
||||||
|
pivotTable: 'link_accounts_roles',
|
||||||
|
})
|
||||||
|
public users: ManyToMany<typeof User>;
|
||||||
|
|
||||||
|
@manyToMany(() => Permission, {
|
||||||
|
pivotForeignKey: 'role_id',
|
||||||
|
pivotRelatedForeignKey: 'permission_id',
|
||||||
|
pivotTable: 'role_has_permissions',
|
||||||
|
})
|
||||||
|
public permissions: ManyToMany<typeof Permission>;
|
||||||
|
}
|
103
app/Models/User.ts
Normal file
103
app/Models/User.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { BaseModel, column, beforeSave, manyToMany, ManyToMany } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
import Hash from '@ioc:Adonis/Core/Hash';
|
||||||
|
import Role from './Role';
|
||||||
|
import Database from '@ioc:Adonis/Lucid/Database';
|
||||||
|
import Config from '@ioc:Adonis/Core/Config';
|
||||||
|
|
||||||
|
// export default interface IUser {
|
||||||
|
// id: number;
|
||||||
|
// login: string;
|
||||||
|
// email: string;
|
||||||
|
// // password: string;
|
||||||
|
// // createdAt: DateTime;
|
||||||
|
// // updatedAt: DateTime;
|
||||||
|
// // async (user): Promise<void>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
const permissionTable = Config.get('rolePermission.permission_table', 'permissions');
|
||||||
|
const rolePermissionTable = Config.get('rolePermission.role_permission_table', 'role_has_permissions');
|
||||||
|
|
||||||
|
const roleTable = Config.get('rolePermission.role_table', 'roles');
|
||||||
|
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
|
||||||
|
|
||||||
|
export default class User extends BaseModel {
|
||||||
|
public static table = 'accounts';
|
||||||
|
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public login: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
@column({ serializeAs: null })
|
||||||
|
public password: string;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true })
|
||||||
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||||
|
public updatedAt: DateTime;
|
||||||
|
|
||||||
|
@beforeSave()
|
||||||
|
public static async hashPassword(user) {
|
||||||
|
if (user.$dirty.password) {
|
||||||
|
user.password = await Hash.make(user.password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@manyToMany(() => Role, {
|
||||||
|
pivotForeignKey: 'account_id',
|
||||||
|
pivotRelatedForeignKey: 'role_id',
|
||||||
|
pivotTable: 'link_accounts_roles',
|
||||||
|
})
|
||||||
|
public roles: ManyToMany<typeof Role>;
|
||||||
|
|
||||||
|
// https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289
|
||||||
|
public async getRoles(this: User): Promise<string[]> {
|
||||||
|
const test = await this.related('roles').query();
|
||||||
|
return test.map((role) => role.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async can(permissionNames: Array<string>): Promise<boolean> {
|
||||||
|
// const permissions = await this.getPermissions()
|
||||||
|
// return Acl.check(expression, operand => _.includes(permissions, operand))
|
||||||
|
const hasPermission = await this.checkHasPermissions(this, permissionNames);
|
||||||
|
return hasPermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkHasPermissions(user: User, permissionNames: Array<string>): Promise<boolean> {
|
||||||
|
let permissionPlaceHolder = '(';
|
||||||
|
let placeholders = new Array(permissionNames.length).fill('?');
|
||||||
|
permissionPlaceHolder += placeholders.join(',');
|
||||||
|
permissionPlaceHolder += ')';
|
||||||
|
|
||||||
|
let {
|
||||||
|
rows: {
|
||||||
|
0: { permissioncount },
|
||||||
|
},
|
||||||
|
} = await Database.rawQuery(
|
||||||
|
'SELECT count("p"."name") as permissionCount FROM ' +
|
||||||
|
roleTable +
|
||||||
|
' r INNER JOIN ' +
|
||||||
|
userRoleTable +
|
||||||
|
' ur ON ur.role_id=r.id AND "ur"."account_id"=? ' +
|
||||||
|
' INNER JOIN ' +
|
||||||
|
rolePermissionTable +
|
||||||
|
' rp ON rp.role_id=r.id ' +
|
||||||
|
' INNER JOIN ' +
|
||||||
|
permissionTable +
|
||||||
|
' p ON rp.permission_id=p.id AND "p"."name" in ' +
|
||||||
|
permissionPlaceHolder +
|
||||||
|
' LIMIT 1',
|
||||||
|
[user.id, ...permissionNames]
|
||||||
|
);
|
||||||
|
|
||||||
|
return permissioncount > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export default User;
|
74
app/Models/UserRole.ts
Normal file
74
app/Models/UserRole.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import {column, BaseModel, belongsTo, BelongsTo, SnakeCaseNamingStrategy} from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
|
||||||
|
import User from 'App/Models/User'
|
||||||
|
import Role from 'App/Models/Role'
|
||||||
|
import { DateTime } from 'luxon'
|
||||||
|
// import moment from 'moment'
|
||||||
|
|
||||||
|
export default class UserRole extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy()
|
||||||
|
public static primaryKey = 'id'
|
||||||
|
public static table = 'user_roles'
|
||||||
|
public static selfAssignPrimaryKey = false
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public user_id: number
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public role_id: number
|
||||||
|
|
||||||
|
@column({
|
||||||
|
// serialize: (value: DateTime | null) => {
|
||||||
|
// return value ? moment(value).format('lll') : value
|
||||||
|
// },
|
||||||
|
})
|
||||||
|
public created_at: DateTime
|
||||||
|
|
||||||
|
@column({
|
||||||
|
// serialize: (value: DateTime | null) => {
|
||||||
|
// return value ? moment(value).format('lll') : value
|
||||||
|
// },
|
||||||
|
})
|
||||||
|
public updated_at: DateTime
|
||||||
|
|
||||||
|
public static boot() {
|
||||||
|
super.boot()
|
||||||
|
|
||||||
|
this.before('create', async (_modelInstance) => {
|
||||||
|
_modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
|
||||||
|
_modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
|
||||||
|
})
|
||||||
|
this.before('update', async (_modelInstance) => {
|
||||||
|
_modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
|
||||||
|
_modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private static formatDateTime(datetime) {
|
||||||
|
let value = new Date(datetime)
|
||||||
|
return datetime
|
||||||
|
? value.getFullYear() +
|
||||||
|
'-' +
|
||||||
|
(value.getMonth() + 1) +
|
||||||
|
'-' +
|
||||||
|
value.getDate() +
|
||||||
|
' ' +
|
||||||
|
value.getHours() +
|
||||||
|
':' +
|
||||||
|
value.getMinutes() +
|
||||||
|
':' +
|
||||||
|
value.getSeconds()
|
||||||
|
: datetime
|
||||||
|
}
|
||||||
|
|
||||||
|
@belongsTo(() => User)
|
||||||
|
public user: BelongsTo<typeof User>
|
||||||
|
|
||||||
|
@belongsTo(() => Role)
|
||||||
|
public role: BelongsTo<typeof Role>
|
||||||
|
}
|
22
app/Models/utils.ts
Normal file
22
app/Models/utils.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Database, {
|
||||||
|
// DatabaseQueryBuilderContract,
|
||||||
|
QueryClientContract,
|
||||||
|
TransactionClientContract,
|
||||||
|
} from "@ioc:Adonis/Lucid/Database";
|
||||||
|
import Config from "@ioc:Adonis/Core/Config";
|
||||||
|
|
||||||
|
export function getUserRoles(
|
||||||
|
userId: number,
|
||||||
|
trx?: TransactionClientContract
|
||||||
|
): Promise<Array<string>> {
|
||||||
|
const { userRole } = Config.get("acl.joinTables");
|
||||||
|
return ((trx || Database) as QueryClientContract | TransactionClientContract)
|
||||||
|
.query()
|
||||||
|
.from("roles")
|
||||||
|
.distinct("roles.slug")
|
||||||
|
.leftJoin(userRole, `${userRole}.role_id`, "roles.id")
|
||||||
|
.where(`${userRole}.user_id`, userId)
|
||||||
|
.then((res) => {
|
||||||
|
return res.map((r) => r.slug);
|
||||||
|
});
|
||||||
|
}
|
46
app/Validators/AuthValidator.ts
Normal file
46
app/Validators/AuthValidator.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
|
export default class AuthValidator {
|
||||||
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
* not contain special characters or numbers.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [ rules.alpha() ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. The email must be of data type string, formatted as a valid
|
||||||
|
* email. But also, not used by any other user.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [
|
||||||
|
* rules.email(),
|
||||||
|
* rules.unique({ table: 'users', column: 'email' }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public schema = schema.create({
|
||||||
|
email: schema.string({ trim: true }, [
|
||||||
|
rules.email(),
|
||||||
|
// rules.unique({ table: 'accounts', column: 'email' })
|
||||||
|
]),
|
||||||
|
password: schema.string({}, [rules.minLength(6)]),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
* children of an array. For example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* 'profile.username.required': 'Username is required',
|
||||||
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public messages: CustomMessages = {};
|
||||||
|
}
|
69
app/Validators/CreateRoleValidator.ts
Normal file
69
app/Validators/CreateRoleValidator.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
|
export default class CreateRoleValidator {
|
||||||
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
* not contain special characters or numbers.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [ rules.alpha() ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. The email must be of data type string, formatted as a valid
|
||||||
|
* email. But also, not used by any other user.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [
|
||||||
|
* rules.email(),
|
||||||
|
* rules.unique({ table: 'users', column: 'email' }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public schema = schema.create({
|
||||||
|
name: schema.string({ trim: true }, [
|
||||||
|
rules.minLength(3),
|
||||||
|
rules.maxLength(255),
|
||||||
|
rules.unique({ table: 'roles', column: 'name' }),
|
||||||
|
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
|
||||||
|
]),
|
||||||
|
display_name: schema.string.optional({ trim: true }, [
|
||||||
|
rules.minLength(3),
|
||||||
|
rules.maxLength(255),
|
||||||
|
rules.unique({ table: 'roles', column: 'name' }),
|
||||||
|
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
|
||||||
|
]),
|
||||||
|
description: schema.string.optional({}, [rules.minLength(3), rules.maxLength(255)]),
|
||||||
|
permissions: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new role
|
||||||
|
});
|
||||||
|
|
||||||
|
// emails: schema
|
||||||
|
// .array([rules.minLength(1)])
|
||||||
|
// .members(
|
||||||
|
// schema.object().members({ email: schema.string({}, [rules.email()]) })
|
||||||
|
// ),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
* children of an array. For example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* 'profile.username.required': 'Username is required',
|
||||||
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public messages: CustomMessages = {
|
||||||
|
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
|
||||||
|
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
|
||||||
|
'required': '{{ field }} is required',
|
||||||
|
'unique': '{{ field }} must be unique, and this value is already taken',
|
||||||
|
'confirmed': '{{ field }} is not correct',
|
||||||
|
'permissions.minLength': 'at least {{ options.minLength }} permission must be defined',
|
||||||
|
'permissions.*.number': 'Define roles as valid numbers',
|
||||||
|
};
|
||||||
|
}
|
64
app/Validators/CreateUserValidator.ts
Normal file
64
app/Validators/CreateUserValidator.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
|
export default class CreateUserValidator {
|
||||||
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
* not contain special characters or numbers.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [ rules.alpha() ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. The email must be of data type string, formatted as a valid
|
||||||
|
* email. But also, not used by any other user.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [
|
||||||
|
* rules.email(),
|
||||||
|
* rules.unique({ table: 'users', column: 'email' }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public schema = schema.create({
|
||||||
|
login: schema.string({ trim: true }, [
|
||||||
|
rules.minLength(3),
|
||||||
|
rules.maxLength(50),
|
||||||
|
rules.unique({ table: 'accounts', column: 'login' }),
|
||||||
|
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
|
||||||
|
]),
|
||||||
|
email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
|
||||||
|
password: schema.string([rules.confirmed(), rules.minLength(6)]),
|
||||||
|
roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
|
||||||
|
});
|
||||||
|
|
||||||
|
// emails: schema
|
||||||
|
// .array([rules.minLength(1)])
|
||||||
|
// .members(
|
||||||
|
// schema.object().members({ email: schema.string({}, [rules.email()]) })
|
||||||
|
// ),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
* children of an array. For example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* 'profile.username.required': 'Username is required',
|
||||||
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public messages: CustomMessages = {
|
||||||
|
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
|
||||||
|
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
|
||||||
|
'required': '{{ field }} is required',
|
||||||
|
'unique': '{{ field }} must be unique, and this value is already taken',
|
||||||
|
'confirmed': '{{ field }} is not correct',
|
||||||
|
'roles.minLength': 'at least {{ options.minLength }} role must be defined',
|
||||||
|
'roles.*.number': 'Define roles as valid numbers'
|
||||||
|
};
|
||||||
|
}
|
97
app/Validators/UpdateRoleValidator.ts
Normal file
97
app/Validators/UpdateRoleValidator.ts
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
// import { Request } from '@adonisjs/core/build/standalone';
|
||||||
|
|
||||||
|
export default class UpdateRoleValidator {
|
||||||
|
protected ctx: HttpContextContract;
|
||||||
|
public schema;
|
||||||
|
|
||||||
|
constructor(ctx: HttpContextContract) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.schema = this.createSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public get schema() {
|
||||||
|
// return this._schema;
|
||||||
|
// }
|
||||||
|
|
||||||
|
private createSchema() {
|
||||||
|
return schema.create({
|
||||||
|
name: schema.string({ trim: true }, [
|
||||||
|
rules.minLength(3),
|
||||||
|
rules.maxLength(50),
|
||||||
|
rules.unique({
|
||||||
|
table: 'roles',
|
||||||
|
column: 'name',
|
||||||
|
whereNot: { id: this.ctx?.params.id },
|
||||||
|
}),
|
||||||
|
rules.regex(/^[a-zA-Z0-9-_]+$/),
|
||||||
|
//Must be alphanumeric with hyphens or underscores
|
||||||
|
]),
|
||||||
|
description: schema.string.optional({}, [rules.minLength(3), rules.maxLength(255)]),
|
||||||
|
permissions: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one permission for the new role
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
* not contain special characters or numbers.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [ rules.alpha() ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. The email must be of data type string, formatted as a valid
|
||||||
|
* email. But also, not used by any other user.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [
|
||||||
|
* rules.email(),
|
||||||
|
* rules.unique({ table: 'users', column: 'email' }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
// public refs = schema.refs({
|
||||||
|
// id: this.ctx.params.id
|
||||||
|
// })
|
||||||
|
|
||||||
|
// public schema = schema.create({
|
||||||
|
// login: schema.string({ trim: true }, [
|
||||||
|
// rules.minLength(3),
|
||||||
|
// rules.maxLength(50),
|
||||||
|
// rules.unique({
|
||||||
|
// table: 'accounts',
|
||||||
|
// column: 'login',
|
||||||
|
// // whereNot: { id: this.refs.id }
|
||||||
|
// whereNot: { id: this.ctx?.params.id },
|
||||||
|
// }),
|
||||||
|
// // rules.regex(/^[a-zA-Z0-9-_]+$/),
|
||||||
|
// //Must be alphanumeric with hyphens or underscores
|
||||||
|
// ]),
|
||||||
|
// email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
|
||||||
|
// password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
|
||||||
|
// roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
|
||||||
|
// });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
* children of an array. For example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* 'profile.username.required': 'Username is required',
|
||||||
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public messages: CustomMessages = {
|
||||||
|
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
|
||||||
|
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
|
||||||
|
'required': '{{ field }} is required',
|
||||||
|
'unique': '{{ field }} must be unique, and this value is already taken',
|
||||||
|
'permissions.minLength': 'at least {{ options.minLength }} permission must be defined',
|
||||||
|
'permissions.*.number': 'Define permissions as valid numbers',
|
||||||
|
};
|
||||||
|
}
|
103
app/Validators/UpdateUserValidator.ts
Normal file
103
app/Validators/UpdateUserValidator.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
// import { Request } from '@adonisjs/core/build/standalone';
|
||||||
|
|
||||||
|
export default class UpdateUserValidator {
|
||||||
|
protected ctx: HttpContextContract;
|
||||||
|
public schema;
|
||||||
|
|
||||||
|
constructor(ctx: HttpContextContract) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.schema = this.createSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public get schema() {
|
||||||
|
// return this._schema;
|
||||||
|
// }
|
||||||
|
|
||||||
|
private createSchema() {
|
||||||
|
return schema.create({
|
||||||
|
login: schema.string({ trim: true }, [
|
||||||
|
rules.minLength(3),
|
||||||
|
rules.maxLength(50),
|
||||||
|
rules.unique({
|
||||||
|
table: 'accounts',
|
||||||
|
column: 'login',
|
||||||
|
// whereNot: { id: this.refs.id }
|
||||||
|
whereNot: { id: this.ctx?.params.id },
|
||||||
|
}),
|
||||||
|
// rules.regex(/^[a-zA-Z0-9-_]+$/),
|
||||||
|
//Must be alphanumeric with hyphens or underscores
|
||||||
|
]),
|
||||||
|
email: schema.string({}, [
|
||||||
|
rules.email(),
|
||||||
|
rules.unique({ table: 'accounts', column: 'email', whereNot: { id: this.ctx?.params.id } }),
|
||||||
|
]),
|
||||||
|
password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
|
||||||
|
roles: schema.array.optional([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
* not contain special characters or numbers.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [ rules.alpha() ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. The email must be of data type string, formatted as a valid
|
||||||
|
* email. But also, not used by any other user.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [
|
||||||
|
* rules.email(),
|
||||||
|
* rules.unique({ table: 'users', column: 'email' }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
// public refs = schema.refs({
|
||||||
|
// id: this.ctx.params.id
|
||||||
|
// })
|
||||||
|
|
||||||
|
// public schema = schema.create({
|
||||||
|
// login: schema.string({ trim: true }, [
|
||||||
|
// rules.minLength(3),
|
||||||
|
// rules.maxLength(50),
|
||||||
|
// rules.unique({
|
||||||
|
// table: 'accounts',
|
||||||
|
// column: 'login',
|
||||||
|
// // whereNot: { id: this.refs.id }
|
||||||
|
// whereNot: { id: this.ctx?.params.id },
|
||||||
|
// }),
|
||||||
|
// // rules.regex(/^[a-zA-Z0-9-_]+$/),
|
||||||
|
// //Must be alphanumeric with hyphens or underscores
|
||||||
|
// ]),
|
||||||
|
// email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
|
||||||
|
// password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
|
||||||
|
// roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
|
||||||
|
// });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
* children of an array. For example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* 'profile.username.required': 'Username is required',
|
||||||
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public messages: CustomMessages = {
|
||||||
|
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
|
||||||
|
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
|
||||||
|
'required': '{{ field }} is required',
|
||||||
|
'unique': '{{ field }} must be unique, and this value is already taken',
|
||||||
|
'confirmed': '{{ field }} is not correct',
|
||||||
|
'roles.minLength': 'at least {{ options.minLength }} role must be defined',
|
||||||
|
'roles.*.number': 'Define roles as valid numbers',
|
||||||
|
};
|
||||||
|
}
|
73
commands/ValidateChecksum.ts
Normal file
73
commands/ValidateChecksum.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { BaseCommand } from '@adonisjs/core/build/standalone';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
export default class ValidateChecksum extends BaseCommand {
|
||||||
|
/**
|
||||||
|
* Command name is used to run the command
|
||||||
|
*/
|
||||||
|
public static commandName = 'validate:checksum';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command description is displayed in the "help" output
|
||||||
|
*/
|
||||||
|
public static description = '';
|
||||||
|
|
||||||
|
public static settings = {
|
||||||
|
/**
|
||||||
|
* Set the following value to true, if you want to load the application
|
||||||
|
* before running the command. Don't forget to call `node ace generate:manifest`
|
||||||
|
* afterwards.
|
||||||
|
*/
|
||||||
|
loadApp: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the following value to true, if you want this command to keep running until
|
||||||
|
* you manually decide to exit the process. Don't forget to call
|
||||||
|
* `node ace generate:manifest` afterwards.
|
||||||
|
*/
|
||||||
|
stayAlive: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
// this.logger.info('Hello world!')
|
||||||
|
const { default: File } = await import('App/Models/File');
|
||||||
|
// const { default: HashValue } = await (await (import ('App/Models/HashValue')));
|
||||||
|
|
||||||
|
// query all files from database:
|
||||||
|
const files = await File.query().preload('hashvalues');
|
||||||
|
|
||||||
|
for (var file of files) {
|
||||||
|
let hashValue = await file.related('hashvalues').query().pluck('value', 'type');
|
||||||
|
|
||||||
|
const filePath = '/storage/app/public/' + file.pathName;
|
||||||
|
let calculatedMd5FileHash;
|
||||||
|
try {
|
||||||
|
calculatedMd5FileHash = await this.checksumFile(filePath, 'md5');
|
||||||
|
} catch (exception) {
|
||||||
|
this.logger.error(exception.message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashValue['md5'] == calculatedMd5FileHash) {
|
||||||
|
this.logger.info(
|
||||||
|
`File id ${file.id}: stored md5 checksum: ${calculatedMd5FileHash}, control md5 checksum: ${hashValue['md5']}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logError(
|
||||||
|
`File id ${file.id}: stored md5 checksum: ${calculatedMd5FileHash}, control md5 checksum: ${hashValue['md5']}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checksumFile(path, hashName = 'md5'): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const hash = crypto.createHash(hashName);
|
||||||
|
const stream = fs.createReadStream(path);
|
||||||
|
stream.on('error', (err) => reject(err));
|
||||||
|
stream.on('data', (chunk) => hash.update(chunk));
|
||||||
|
stream.on('end', () => resolve(hash.digest('hex')));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
19
commands/index.ts
Normal file
19
commands/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { listDirectoryFiles } from '@adonisjs/core/build/standalone';
|
||||||
|
import Application from '@ioc:Adonis/Core/Application';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Exporting an array of commands
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Instead of manually exporting each file from this directory, we use the
|
||||||
|
| helper `listDirectoryFiles` to recursively collect and export an array
|
||||||
|
| of filenames.
|
||||||
|
|
|
||||||
|
| Couple of things to note:
|
||||||
|
|
|
||||||
|
| 1. The file path must be relative from the project root and not this directory.
|
||||||
|
| 2. We must ignore this file to avoid getting into an infinite loop
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index']);
|
13
components.d.ts
vendored
Normal file
13
components.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// generated by unplugin-vue-components
|
||||||
|
// We suggest you to commit this file into source control
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
import '@vue/runtime-core';
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
NButton: typeof import('naive-ui')['NButton'];
|
||||||
|
NInput: typeof import('naive-ui')['NInput'];
|
||||||
|
}
|
||||||
|
}
|
276
config/app.ts
Normal file
276
config/app.ts
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JfefZ
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import proxyAddr from 'proxy-addr';
|
||||||
|
import Env from '@ioc:Adonis/Core/Env';
|
||||||
|
import Application from '@ioc:Adonis/Core/Application';
|
||||||
|
import type { ServerConfig } from '@ioc:Adonis/Core/Server';
|
||||||
|
import type { LoggerConfig } from '@ioc:Adonis/Core/Logger';
|
||||||
|
import type { ProfilerConfig } from '@ioc:Adonis/Core/Profiler';
|
||||||
|
import type { ValidatorConfig } from '@ioc:Adonis/Core/Validator';
|
||||||
|
import type { AssetsManagerConfig } from '@ioc:Adonis/Core/AssetsManager';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application secret key
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The secret to encrypt and sign different values in your application.
|
||||||
|
| Make sure to keep the `APP_KEY` as an environment variable and secure.
|
||||||
|
|
|
||||||
|
| Note: Changing the application key for an existing app will make all
|
||||||
|
| the cookies invalid and also the existing encrypted data will not
|
||||||
|
| be decrypted.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const appKey: string = Env.get('APP_KEY');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Http server configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The configuration for the HTTP(s) server. Make sure to go through all
|
||||||
|
| the config properties to make keep server secure.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const http: ServerConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Allow method spoofing
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Method spoofing enables defining custom HTTP methods using a query string
|
||||||
|
| `_method`. This is usually required when you are making traditional
|
||||||
|
| form requests and wants to use HTTP verbs like `PUT`, `DELETE` and
|
||||||
|
| so on.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
allowMethodSpoofing: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Subdomain offset
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
subdomainOffset: 2,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Request Ids
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Setting this value to `true` will generate a unique request id for each
|
||||||
|
| HTTP request and set it as `x-request-id` header.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
generateRequestId: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Trusting proxy servers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define the proxy servers that AdonisJs must trust for reading `X-Forwarded`
|
||||||
|
| headers.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
trustProxy: proxyAddr.compile('loopback'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Generating Etag
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Whether or not to generate an etag for every response.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
etag: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| JSONP Callback
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
jsonpCallbackName: 'callback',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cookie settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
cookie: {
|
||||||
|
domain: '',
|
||||||
|
path: '/',
|
||||||
|
maxAge: '2h',
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false,
|
||||||
|
sameSite: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Logger
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
export const logger: LoggerConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The name of the application you want to add to the log. It is recommended
|
||||||
|
| to always have app name in every log line.
|
||||||
|
|
|
||||||
|
| The `APP_NAME` environment variable is automatically set by AdonisJS by
|
||||||
|
| reading the `name` property from the `package.json` file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
name: Env.get('APP_NAME'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Toggle logger
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Enable or disable logger application wide
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Logging level
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The level from which you want the logger to flush logs. It is recommended
|
||||||
|
| to make use of the environment variable, so that you can define log levels
|
||||||
|
| at deployment level and not code level.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
level: Env.get('LOG_LEVEL', 'info'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pretty print
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| It is highly advised NOT to use `prettyPrint` in production, since it
|
||||||
|
| can have huge impact on performance.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
prettyPrint: Env.get('NODE_ENV') === 'development',
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Profiler
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
export const profiler: ProfilerConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Toggle profiler
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Enable or disable profiler
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Blacklist actions/row labels
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define an array of actions or row labels that you want to disable from
|
||||||
|
| getting profiled.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
blacklist: [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Whitelist actions/row labels
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define an array of actions or row labels that you want to whitelist for
|
||||||
|
| the profiler. When whitelist is defined, then `blacklist` is ignored.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
whitelist: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Validator
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure the global configuration for the validator. Here's the reference
|
||||||
|
| to the default config https://git.io/JT0WE
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const validator: ValidatorConfig = {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Assets
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure the asset manager you are using to compile the frontend assets
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const assets: AssetsManagerConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Currently we only support webpack encore and may introduce more drivers
|
||||||
|
| in the future
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
driver: Env.get('ASSETS_DRIVER'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Public path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Directory to search for the "manifest.json" and the "entrypoints.json"
|
||||||
|
| files
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
publicPath: Application.publicPath('assets'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Script tag
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define attributes for the entryPointScripts tags
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
script: {
|
||||||
|
attributes: {
|
||||||
|
defer: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Style tag
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define attributes for the entryPointStyles tags
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
style: {
|
||||||
|
attributes: {},
|
||||||
|
},
|
||||||
|
};
|
86
config/auth.ts
Normal file
86
config/auth.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JY0mp
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AuthConfig } from '@ioc:Adonis/Addons/Auth';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Mapping
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| List of available authentication mapping. You must first define them
|
||||||
|
| inside the `contracts/auth.ts` file before mentioning them here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
const authConfig: AuthConfig = {
|
||||||
|
guard: 'web',
|
||||||
|
guards: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Web Guard
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Web guard uses classic old school sessions for authenticating users.
|
||||||
|
| If you are building a standard web application, it is recommended to
|
||||||
|
| use web guard with session driver
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
web: {
|
||||||
|
driver: 'session',
|
||||||
|
|
||||||
|
provider: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Name of the driver
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
driver: 'lucid',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Identifier key
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The identifier key is the unique key on the model. In most cases specifying
|
||||||
|
| the primary key is the right choice.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
identifierKey: 'id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Uids
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uids are used to search a user against one of the mentioned columns. During
|
||||||
|
| login, the auth module will search the user mentioned value against one
|
||||||
|
| of the mentioned columns to find their user record.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
uids: ['email'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Model
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The model to use for fetching or finding users. The model is imported
|
||||||
|
| lazily since the config files are read way earlier in the lifecycle
|
||||||
|
| of booting the app and the models may not be in a usable state at
|
||||||
|
| that time.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
model: () => import('App/Models/User'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authConfig;
|
205
config/bodyparser.ts
Normal file
205
config/bodyparser.ts
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/Jfefn
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BodyParserConfig } from '@ioc:Adonis/Core/BodyParser';
|
||||||
|
|
||||||
|
const bodyParserConfig: BodyParserConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| White listed methods
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| HTTP methods for which body parsing must be performed. It is a good practice
|
||||||
|
| to avoid body parsing for `GET` requests.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
whitelistedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| JSON parser settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The settings for the JSON parser. The types defines the request content
|
||||||
|
| types which gets processed by the JSON parser.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
json: {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
limit: '1mb',
|
||||||
|
strict: true,
|
||||||
|
types: [
|
||||||
|
'application/json',
|
||||||
|
'application/json-patch+json',
|
||||||
|
'application/vnd.api+json',
|
||||||
|
'application/csp-report',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Form parser settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The settings for the `application/x-www-form-urlencoded` parser. The types
|
||||||
|
| defines the request content types which gets processed by the form parser.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
form: {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
limit: '1mb',
|
||||||
|
queryString: {},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Convert empty strings to null
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Convert empty form fields to null. HTML forms results in field string
|
||||||
|
| value when the field is left blank. This option normalizes all the blank
|
||||||
|
| field values to "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
convertEmptyStringsToNull: true,
|
||||||
|
|
||||||
|
types: ['application/x-www-form-urlencoded'],
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Raw body parser settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Raw body just reads the request body stream as a plain text, which you
|
||||||
|
| can process by hand. This must be used when request body type is not
|
||||||
|
| supported by the body parser.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
raw: {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
limit: '1mb',
|
||||||
|
queryString: {},
|
||||||
|
types: ['text/*'],
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Multipart parser settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The settings for the `multipart/form-data` parser. The types defines the
|
||||||
|
| request content types which gets processed by the form parser.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
multipart: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Auto process
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The auto process option will process uploaded files and writes them to
|
||||||
|
| the `tmp` folder. You can turn it off and then manually use the stream
|
||||||
|
| to pipe stream to a different destination.
|
||||||
|
|
|
||||||
|
| It is recommended to keep `autoProcess=true`. Unless you are processing bigger
|
||||||
|
| file sizes.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
autoProcess: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Files to be processed manually
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You can turn off `autoProcess` for certain routes by defining
|
||||||
|
| routes inside the following array.
|
||||||
|
|
|
||||||
|
| NOTE: Make sure the route pattern starts with a leading slash.
|
||||||
|
|
|
||||||
|
| Correct
|
||||||
|
| ```js
|
||||||
|
| /projects/:id/file
|
||||||
|
| ```
|
||||||
|
|
|
||||||
|
| Incorrect
|
||||||
|
| ```js
|
||||||
|
| projects/:id/file
|
||||||
|
| ```
|
||||||
|
*/
|
||||||
|
processManually: [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Temporary file name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When auto processing is on. We will use this method to compute the temporary
|
||||||
|
| file name. AdonisJs will compute a unique `tmpPath` for you automatically,
|
||||||
|
| However, you can also define your own custom method.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
// tmpFileName () {
|
||||||
|
// },
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Encoding
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Request body encoding
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
encoding: 'utf-8',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Convert empty strings to null
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Convert empty form fields to null. HTML forms results in field string
|
||||||
|
| value when the field is left blank. This option normalizes all the blank
|
||||||
|
| field values to "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
convertEmptyStringsToNull: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Max Fields
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The maximum number of fields allowed in the request body. The field includes
|
||||||
|
| text inputs and files both.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
maxFields: 1000,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Request body limit
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The total limit to the multipart body. This includes all request files
|
||||||
|
| and fields data.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
limit: '20mb',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Types
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The types that will be considered and parsed as multipart body.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
types: ['multipart/form-data'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default bodyParserConfig;
|
127
config/cors.ts
Normal file
127
config/cors.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JfefC
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { CorsConfig } from '@ioc:Adonis/Core/Cors';
|
||||||
|
|
||||||
|
const corsConfig: CorsConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enabled
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| A boolean to enable or disable CORS integration from your AdonisJs
|
||||||
|
| application.
|
||||||
|
|
|
||||||
|
| Setting the value to `true` will enable the CORS for all HTTP request. However,
|
||||||
|
| you can define a function to enable/disable it on per request basis as well.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
enabled: false,
|
||||||
|
|
||||||
|
// You can also use a function that return true or false.
|
||||||
|
// enabled: (request) => request.url().startsWith('/api')
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Origin
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set a list of origins to be allowed for `Access-Control-Allow-Origin`.
|
||||||
|
| The value can be one of the following:
|
||||||
|
|
|
||||||
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
||||||
|
|
|
||||||
|
| Boolean (true) - Allow current request origin.
|
||||||
|
| Boolean (false) - Disallow all.
|
||||||
|
| String - Comma separated list of allowed origins.
|
||||||
|
| Array - An array of allowed origins.
|
||||||
|
| String (*) - A wildcard (*) to allow all request origins.
|
||||||
|
| Function - Receives the current origin string and should return
|
||||||
|
| one of the above values.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
origin: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Methods
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| An array of allowed HTTP methods for CORS. The `Access-Control-Request-Method`
|
||||||
|
| is checked against the following list.
|
||||||
|
|
|
||||||
|
| Following is the list of default methods. Feel free to add more.
|
||||||
|
*/
|
||||||
|
methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Headers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| List of headers to be allowed for `Access-Control-Allow-Headers` header.
|
||||||
|
| The value can be one of the following:
|
||||||
|
|
|
||||||
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers
|
||||||
|
|
|
||||||
|
| Boolean(true) - Allow all headers mentioned in `Access-Control-Request-Headers`.
|
||||||
|
| Boolean(false) - Disallow all headers.
|
||||||
|
| String - Comma separated list of allowed headers.
|
||||||
|
| Array - An array of allowed headers.
|
||||||
|
| Function - Receives the current header and should return one of the above values.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
headers: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Expose Headers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| A list of headers to be exposed by setting `Access-Control-Expose-Headers`.
|
||||||
|
| header. By default following 6 simple response headers are exposed.
|
||||||
|
|
|
||||||
|
| Cache-Control
|
||||||
|
| Content-Language
|
||||||
|
| Content-Type
|
||||||
|
| Expires
|
||||||
|
| Last-Modified
|
||||||
|
| Pragma
|
||||||
|
|
|
||||||
|
| In order to add more headers, simply define them inside the following array.
|
||||||
|
|
|
||||||
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
exposeHeaders: ['cache-control', 'content-language', 'content-type', 'expires', 'last-modified', 'pragma'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Credentials
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Toggle `Access-Control-Allow-Credentials` header. If value is set to `true`,
|
||||||
|
| then header will be set, otherwise not.
|
||||||
|
|
|
||||||
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
credentials: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| MaxAge
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define `Access-Control-Max-Age` header in seconds.
|
||||||
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
maxAge: 90,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default corsConfig;
|
55
config/database.ts
Normal file
55
config/database.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JesV9
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Env from '@ioc:Adonis/Core/Env';
|
||||||
|
import { DatabaseConfig } from '@ioc:Adonis/Lucid/Database';
|
||||||
|
|
||||||
|
const databaseConfig: DatabaseConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Connection
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The primary connection for making database queries across the application
|
||||||
|
| You can use any key from the `connections` object defined in this same
|
||||||
|
| file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
connection: Env.get('DB_CONNECTION'),
|
||||||
|
|
||||||
|
connections: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| PostgreSQL config
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configuration for PostgreSQL database. Make sure to install the driver
|
||||||
|
| from npm when using this connection
|
||||||
|
|
|
||||||
|
| npm i pg
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
pg: {
|
||||||
|
client: 'pg',
|
||||||
|
connection: {
|
||||||
|
host: Env.get('PG_HOST'),
|
||||||
|
port: Env.get('PG_PORT'),
|
||||||
|
user: Env.get('PG_USER'),
|
||||||
|
password: Env.get('PG_PASSWORD', ''),
|
||||||
|
database: Env.get('PG_DB_NAME'),
|
||||||
|
},
|
||||||
|
searchPath: ['gba'],
|
||||||
|
migrations: {
|
||||||
|
naturalSort: true,
|
||||||
|
},
|
||||||
|
healthCheck: false,
|
||||||
|
debug: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default databaseConfig;
|
149
config/drive.ts
Normal file
149
config/drive.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JBt3o
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Env from '@ioc:Adonis/Core/Env';
|
||||||
|
import { driveConfig } from '@adonisjs/core/build/config';
|
||||||
|
import Application from '@ioc:Adonis/Core/Application';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Drive Config
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The `DriveConfig` relies on the `DisksList` interface which is
|
||||||
|
| defined inside the `contracts` directory.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export default driveConfig({
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default disk
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The default disk to use for managing file uploads. The value is driven by
|
||||||
|
| the `DRIVE_DISK` environment variable.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
disk: Env.get('DRIVE_DISK'),
|
||||||
|
|
||||||
|
disks: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Local
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uses the local file system to manage files. Make sure to turn off serving
|
||||||
|
| files when not using this disk.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
local: {
|
||||||
|
driver: 'local',
|
||||||
|
visibility: 'public',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Storage root - Local driver only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define an absolute path to the storage directory from where to read the
|
||||||
|
| files.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
root: Application.tmpPath('uploads'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Serve files - Local driver only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When this is set to true, AdonisJS will configure a files server to serve
|
||||||
|
| files from the disk root. This is done to mimic the behavior of cloud
|
||||||
|
| storage services that has inbuilt capabilities to serve files.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
serveFiles: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Base path - Local driver only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Base path is always required when "serveFiles = true". Also make sure
|
||||||
|
| the `basePath` is unique across all the disks using "local" driver and
|
||||||
|
| you are not registering routes with this prefix.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
basePath: '/uploads',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| S3 Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uses the S3 cloud storage to manage files. Make sure to install the s3
|
||||||
|
| drive separately when using it.
|
||||||
|
|
|
||||||
|
|**************************************************************************
|
||||||
|
| npm i @adonisjs/drive-s3
|
||||||
|
|**************************************************************************
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
// s3: {
|
||||||
|
// driver: 's3',
|
||||||
|
// visibility: 'public',
|
||||||
|
// key: Env.get('S3_KEY'),
|
||||||
|
// secret: Env.get('S3_SECRET'),
|
||||||
|
// region: Env.get('S3_REGION'),
|
||||||
|
// bucket: Env.get('S3_BUCKET'),
|
||||||
|
// endpoint: Env.get('S3_ENDPOINT'),
|
||||||
|
//
|
||||||
|
// // For minio to work
|
||||||
|
// // forcePathStyle: true,
|
||||||
|
// },
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| GCS Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uses the Google cloud storage to manage files. Make sure to install the GCS
|
||||||
|
| drive separately when using it.
|
||||||
|
|
|
||||||
|
|**************************************************************************
|
||||||
|
| npm i @adonisjs/drive-gcs
|
||||||
|
|**************************************************************************
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
// gcs: {
|
||||||
|
// driver: 'gcs',
|
||||||
|
// visibility: 'public',
|
||||||
|
// keyFilename: Env.get('GCS_KEY_FILENAME'),
|
||||||
|
// bucket: Env.get('GCS_BUCKET'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Uniform ACL - Google cloud storage only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using the Uniform ACL on the bucket, the "visibility" option is
|
||||||
|
| ignored. Since, the files ACL is managed by the google bucket policies
|
||||||
|
| directly.
|
||||||
|
|
|
||||||
|
|**************************************************************************
|
||||||
|
| Learn more: https://cloud.google.com/storage/docs/uniform-bucket-level-access
|
||||||
|
|**************************************************************************
|
||||||
|
|
|
||||||
|
| The following option just informs drive whether your bucket is using uniform
|
||||||
|
| ACL or not. The actual setting needs to be toggled within the Google cloud
|
||||||
|
| console.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
// usingUniformAcl: false,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
});
|
96
config/hash.ts
Normal file
96
config/hash.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JfefW
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Env from '@ioc:Adonis/Core/Env';
|
||||||
|
import { hashConfig } from '@adonisjs/core/build/config';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Hash Config
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The `HashConfig` relies on the `HashList` interface which is
|
||||||
|
| defined inside `contracts` directory.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export default hashConfig({
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default hasher
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default we make use of the argon hasher to hash values. However, feel
|
||||||
|
| free to change the default value
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
default: Env.get('HASH_DRIVER', 'scrypt'),
|
||||||
|
|
||||||
|
list: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| scrypt
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Scrypt mapping uses the Node.js inbuilt crypto module for creating
|
||||||
|
| hashes.
|
||||||
|
|
|
||||||
|
| We are using the default configuration recommended within the Node.js
|
||||||
|
| documentation.
|
||||||
|
| https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
scrypt: {
|
||||||
|
driver: 'scrypt',
|
||||||
|
cost: 16384,
|
||||||
|
blockSize: 8,
|
||||||
|
parallelization: 1,
|
||||||
|
saltSize: 16,
|
||||||
|
keyLength: 64,
|
||||||
|
maxMemory: 32 * 1024 * 1024,
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Argon
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Argon mapping uses the `argon2` driver to hash values.
|
||||||
|
|
|
||||||
|
| Make sure you install the underlying dependency for this driver to work.
|
||||||
|
| https://www.npmjs.com/package/phc-argon2.
|
||||||
|
|
|
||||||
|
| npm install phc-argon2
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
argon: {
|
||||||
|
driver: 'argon2',
|
||||||
|
variant: 'id',
|
||||||
|
iterations: 3,
|
||||||
|
memory: 4096,
|
||||||
|
parallelism: 1,
|
||||||
|
saltSize: 16,
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Bcrypt
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Bcrypt mapping uses the `bcrypt` driver to hash values.
|
||||||
|
|
|
||||||
|
| Make sure you install the underlying dependency for this driver to work.
|
||||||
|
| https://www.npmjs.com/package/phc-bcrypt.
|
||||||
|
|
|
||||||
|
| npm install phc-bcrypt
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
bcrypt: {
|
||||||
|
driver: 'bcrypt',
|
||||||
|
rounds: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
20
config/inertia.ts
Normal file
20
config/inertia.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Feel free to let me know via PR,
|
||||||
|
* if you find something broken in this config file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { InertiaConfig } from '@ioc:EidelLev/Inertia';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Inertia-AdonisJS config
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const inertia: InertiaConfig = {
|
||||||
|
view: 'app',
|
||||||
|
// ssr: {
|
||||||
|
// enabled: false,
|
||||||
|
// },
|
||||||
|
};
|
8
config/rolePermission.ts
Normal file
8
config/rolePermission.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const RolePermission: Object = {
|
||||||
|
role_table: 'roles',
|
||||||
|
permission_table: 'permissions',
|
||||||
|
user_role_table: 'link_accounts_roles',
|
||||||
|
user_permission_table: 'user_permission_table',
|
||||||
|
role_permission_table: 'role_has_permissions',
|
||||||
|
};
|
||||||
|
export default RolePermission;
|
116
config/session.ts
Normal file
116
config/session.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JeYHp
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Env from '@ioc:Adonis/Core/Env';
|
||||||
|
import Application from '@ioc:Adonis/Core/Application';
|
||||||
|
import { sessionConfig } from '@adonisjs/session/build/config';
|
||||||
|
|
||||||
|
export default sessionConfig({
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enable/Disable sessions
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Setting the following property to "false" will disable the session for the
|
||||||
|
| entire application
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The session driver to use. You can choose between one of the following
|
||||||
|
| drivers.
|
||||||
|
|
|
||||||
|
| - cookie (Uses signed cookies to store session values)
|
||||||
|
| - file (Uses filesystem to store session values)
|
||||||
|
| - redis (Uses redis. Make sure to install "@adonisjs/redis" as well)
|
||||||
|
|
|
||||||
|
| Note: Switching drivers will make existing sessions invalid.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
driver: Env.get('SESSION_DRIVER'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cookie name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The name of the cookie that will hold the session id.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
cookieName: 'adonis-session',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Clear session when browser closes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Whether or not you want to destroy the session when browser closes. Setting
|
||||||
|
| this value to `true` will ignore the `age`.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
clearWithBrowser: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Session age
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The duration for which session stays active after no activity. A new HTTP
|
||||||
|
| request to the server is considered as activity.
|
||||||
|
|
|
||||||
|
| The value can be a number in milliseconds or a string that must be valid
|
||||||
|
| as per https://npmjs.org/package/ms package.
|
||||||
|
|
|
||||||
|
| Example: `2 days`, `2.5 hrs`, `1y`, `5s` and so on.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
age: '2h',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cookie values
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The cookie settings are used to setup the session id cookie and also the
|
||||||
|
| driver will use the same values.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
cookie: {
|
||||||
|
path: '/',
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Configuration for the file driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The file driver needs absolute path to the directory in which sessions
|
||||||
|
| must be stored.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
file: {
|
||||||
|
location: Application.tmpPath('sessions'),
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Redis driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The redis connection you want session driver to use. The same connection
|
||||||
|
| must be defined inside `config/redis.ts` file as well.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
redisConnection: 'local',
|
||||||
|
});
|
237
config/shield.ts
Normal file
237
config/shield.ts
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/Jvwvt
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import Env from '@ioc:Adonis/Core/Env'
|
||||||
|
import { ShieldConfig } from '@ioc:Adonis/Addons/Shield';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Content Security Policy
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Content security policy filters out the origins not allowed to execute
|
||||||
|
| and load resources like scripts, styles and fonts. There are wide
|
||||||
|
| variety of options to choose from.
|
||||||
|
*/
|
||||||
|
export const csp: ShieldConfig['csp'] = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enable/disable CSP
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The CSP rules are disabled by default for seamless onboarding.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
enabled: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Directives
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| All directives are defined in camelCase and here is the list of
|
||||||
|
| available directives and their possible values.
|
||||||
|
|
|
||||||
|
| https://content-security-policy.com
|
||||||
|
|
|
||||||
|
| @example
|
||||||
|
| directives: {
|
||||||
|
| defaultSrc: ["'self'", '@nonce', 'cdnjs.cloudflare.com']
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
directives: {},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Report only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Setting `reportOnly=true` will not block the scripts from running and
|
||||||
|
| instead report them to a URL.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
reportOnly: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| CSRF Protection
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| CSRF Protection adds another layer of security by making sure, actionable
|
||||||
|
| routes does have a valid token to execute an action.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const csrf: ShieldConfig['csrf'] = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enable/Disable CSRF
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Routes to Ignore
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define an array of route patterns that you want to ignore from CSRF
|
||||||
|
| validation. Make sure the route patterns are started with a leading
|
||||||
|
| slash. Example:
|
||||||
|
|
|
||||||
|
| `/foo/bar`
|
||||||
|
|
|
||||||
|
| Also you can define a function that is evaluated on every HTTP Request.
|
||||||
|
| ```
|
||||||
|
| exceptRoutes: ({ request }) => request.url().includes('/api')
|
||||||
|
| ```
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
exceptRoutes: [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enable Sharing Token Via Cookie
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When the following flag is enabled, AdonisJS will drop `XSRF-TOKEN`
|
||||||
|
| cookie that frontend frameworks can read and return back as a
|
||||||
|
| `X-XSRF-TOKEN` header.
|
||||||
|
|
|
||||||
|
| The cookie has `httpOnly` flag set to false, so it is little insecure and
|
||||||
|
| can be turned off when you are not using a frontend framework making
|
||||||
|
| AJAX requests.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
enableXsrfCookie: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Methods to Validate
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define an array of HTTP methods to be validated for a valid CSRF token.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| DNS Prefetching
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| DNS prefetching allows browsers to proactively perform domain name
|
||||||
|
| resolution in background.
|
||||||
|
|
|
||||||
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const dnsPrefetch: ShieldConfig['dnsPrefetch'] = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enable/disable this feature
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Allow or Dis-Allow Explicitly
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The `enabled` boolean does not set `X-DNS-Prefetch-Control` header. However
|
||||||
|
| the `allow` boolean controls the value of `X-DNS-Prefetch-Control` header.
|
||||||
|
|
|
||||||
|
| - When `allow = true`, then `X-DNS-Prefetch-Control = 'on'`
|
||||||
|
| - When `allow = false`, then `X-DNS-Prefetch-Control = 'off'`
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
allow: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Iframe Options
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| xFrame defines whether or not your website can be embedded inside an
|
||||||
|
| iframe. Choose from one of the following options.
|
||||||
|
|
|
||||||
|
| - DENY
|
||||||
|
| - SAMEORIGIN
|
||||||
|
| - ALLOW-FROM http://example.com
|
||||||
|
|
|
||||||
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||||
|
*/
|
||||||
|
export const xFrame: ShieldConfig['xFrame'] = {
|
||||||
|
enabled: true,
|
||||||
|
action: 'DENY',
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Http Strict Transport Security
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| A security to ensure that a browser always makes a connection over
|
||||||
|
| HTTPS.
|
||||||
|
|
|
||||||
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const hsts: ShieldConfig['hsts'] = {
|
||||||
|
enabled: true,
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Max Age
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Control, how long the browser should remember that a site is only to be
|
||||||
|
| accessed using HTTPS.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
maxAge: '180 days',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Include Subdomains
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Apply rules on the subdomains as well.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
includeSubDomains: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Preloading
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Google maintains a service to register your domain and it will preload
|
||||||
|
| the HSTS policy. Learn more https://hstspreload.org/
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
preload: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| No Sniff
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Browsers have a habit of sniffing content-type of a response. Which means
|
||||||
|
| files with .txt extension containing Javascript code will be executed as
|
||||||
|
| Javascript. You can disable this behavior by setting nosniff to false.
|
||||||
|
|
|
||||||
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export const contentTypeSniffing: ShieldConfig['contentTypeSniffing'] = {
|
||||||
|
enabled: true,
|
||||||
|
};
|
89
config/static.ts
Normal file
89
config/static.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/Jfefl
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AssetsConfig } from '@ioc:Adonis/Core/Static';
|
||||||
|
|
||||||
|
const staticConfig: AssetsConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enabled
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| A boolean to enable or disable serving static files. The static files
|
||||||
|
| are served from the `public` directory inside the application root.
|
||||||
|
| However, you can override the default path inside `.adonisrc.json`
|
||||||
|
| file.
|
||||||
|
|
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Handling Dot Files
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Decide how you want the static assets server to handle the `dotfiles`.
|
||||||
|
| By default, we ignore them as if they don't exists. However, you
|
||||||
|
| can choose between one of the following options.
|
||||||
|
|
|
||||||
|
| - ignore: Behave as if the file doesn't exists. Results in 404.
|
||||||
|
| - deny: Deny access to the file. Results in 403.
|
||||||
|
| - allow: Serve the file contents
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
dotFiles: 'ignore',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Generating Etag
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Handle whether or not to generate etags for the files. Etag allows browser
|
||||||
|
| to utilize the cache when file hasn't been changed.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
etag: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Set Last Modified
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Whether or not to set the `Last-Modified` header in the response. Uses
|
||||||
|
| the file system's last modified value.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
lastModified: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Max age
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set the value for the max-age directive. Set a higher value in production
|
||||||
|
| if you fingerprint your assets.
|
||||||
|
|
|
||||||
|
| Learn more: https://docs.adonisjs.com/guides/deployment#serving-static-assets
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
maxAge: 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Immutable
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set the immutable directive. Set it to `true` if the assets are generated
|
||||||
|
| with a fingerprint. In others words the file name changes when the file
|
||||||
|
| contents change.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
immutable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default staticConfig;
|
73
contracts/auth.ts
Normal file
73
contracts/auth.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* Contract source: https://git.io/JOdz5
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import User from 'App/Models/User';
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Addons/Auth' {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The providers are used to fetch users. The Auth module comes pre-bundled
|
||||||
|
| with two providers that are `Lucid` and `Database`. Both uses database
|
||||||
|
| to fetch user details.
|
||||||
|
|
|
||||||
|
| You can also create and register your own custom providers.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
interface ProvidersList {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| User Provider
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The following provider uses Lucid models as a driver for fetching user
|
||||||
|
| details from the database for authentication.
|
||||||
|
|
|
||||||
|
| You can create multiple providers using the same underlying driver with
|
||||||
|
| different Lucid models.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
user: {
|
||||||
|
implementation: LucidProviderContract<typeof User>;
|
||||||
|
config: LucidProviderConfig<typeof User>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The guards are used for authenticating users using different drivers.
|
||||||
|
| The auth module comes with 3 different guards.
|
||||||
|
|
|
||||||
|
| - SessionGuardContract
|
||||||
|
| - BasicAuthGuardContract
|
||||||
|
| - OATGuardContract ( Opaque access token )
|
||||||
|
|
|
||||||
|
| Every guard needs a provider for looking up users from the database.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
interface GuardsList {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Web Guard
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The web guard uses sessions for maintaining user login state. It uses
|
||||||
|
| the `user` provider for fetching user details.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
web: {
|
||||||
|
implementation: SessionGuardContract<'user', 'web'>;
|
||||||
|
config: SessionGuardConfig<'user'>;
|
||||||
|
client: SessionClientContract<'user'>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
13
contracts/drive.ts
Normal file
13
contracts/drive.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Contract source: https://git.io/JBt3I
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this contract
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { InferDisksFromConfig } from '@adonisjs/core/build/config';
|
||||||
|
import type driveConfig from '../config/drive';
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Core/Drive' {
|
||||||
|
interface DisksList extends InferDisksFromConfig<typeof driveConfig> {}
|
||||||
|
}
|
23
contracts/env.ts
Normal file
23
contracts/env.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Contract source: https://git.io/JTm6U
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this contract
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Core/Env' {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Getting types for validated environment variables
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The `default` export from the "../env.ts" file exports types for the
|
||||||
|
| validated environment variables. Here we merge them with the `EnvTypes`
|
||||||
|
| interface so that you can enjoy intellisense when using the "Env"
|
||||||
|
| module.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
type CustomTypes = typeof import('../env').default;
|
||||||
|
interface EnvTypes extends CustomTypes {}
|
||||||
|
}
|
31
contracts/events.ts
Normal file
31
contracts/events.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* Contract source: https://git.io/JfefG
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this contract
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Core/Event' {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Define typed events
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You can define types for events inside the following interface and
|
||||||
|
| AdonisJS will make sure that all listeners and emit calls adheres
|
||||||
|
| to the defined types.
|
||||||
|
|
|
||||||
|
| For example:
|
||||||
|
|
|
||||||
|
| interface EventsList {
|
||||||
|
| 'new:user': UserModel
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
| Now calling `Event.emit('new:user')` will statically ensure that passed value is
|
||||||
|
| an instance of the the UserModel only.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
interface EventsList {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
13
contracts/hash.ts
Normal file
13
contracts/hash.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Contract source: https://git.io/Jfefs
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this contract
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { InferListFromConfig } from '@adonisjs/core/build/config';
|
||||||
|
import type hashConfig from '../config/hash';
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Core/Hash' {
|
||||||
|
interface HashersList extends InferListFromConfig<typeof hashConfig> {}
|
||||||
|
}
|
5
contracts/orm.ts
Normal file
5
contracts/orm.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
declare module '@ioc:Adonis/Lucid/Orm' {
|
||||||
|
interface ModelQueryBuilderContract<Model extends LucidModel, Result = InstanceType<Model>> {
|
||||||
|
pluck(value: string, id?: string): Promise<Object>;
|
||||||
|
}
|
||||||
|
}
|
18
contracts/tests.ts
Normal file
18
contracts/tests.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Contract source: https://bit.ly/3DP1ypf
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this contract
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import '@japa/runner';
|
||||||
|
|
||||||
|
declare module '@japa/runner' {
|
||||||
|
interface TestContext {
|
||||||
|
// Extend context
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Test<TestData> {
|
||||||
|
// Extend test
|
||||||
|
}
|
||||||
|
}
|
1
database/factories/index.ts
Normal file
1
database/factories/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
// import Factory from '@ioc:Adonis/Lucid/Factory'
|
25
database/migrations/acl_1_roles.ts
Normal file
25
database/migrations/acl_1_roles.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
import Config from '@ioc:Adonis/Core/Config'
|
||||||
|
|
||||||
|
export default class Roles extends BaseSchema {
|
||||||
|
protected tableName = Config.get('rolePermission.role_table', 'roles')
|
||||||
|
|
||||||
|
public async up () {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table.string('name', 191).unique()
|
||||||
|
table.string('slug', 191).nullable().unique()
|
||||||
|
table.string('description', 191).nullable()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
|
||||||
|
*/
|
||||||
|
table.timestamp('created_at', { useTz: true }).nullable()
|
||||||
|
table.timestamp('updated_at', { useTz: true }).nullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down () {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
25
database/migrations/acl_2_permissions.ts
Normal file
25
database/migrations/acl_2_permissions.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
import Config from '@ioc:Adonis/Core/Config'
|
||||||
|
|
||||||
|
export default class Permissions extends BaseSchema {
|
||||||
|
protected tableName = Config.get('rolePermission.permission_table', 'permissions')
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table.string('name', 191).unique()
|
||||||
|
table.string('slug', 191).nullable().unique()
|
||||||
|
table.string('description', 191).nullable()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
|
||||||
|
*/
|
||||||
|
table.timestamp('created_at', { useTz: true }).nullable()
|
||||||
|
table.timestamp('updated_at', { useTz: true }).nullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
32
database/migrations/acl_3_role_permissions.ts
Normal file
32
database/migrations/acl_3_role_permissions.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
import Config from '@ioc:Adonis/Core/Config'
|
||||||
|
|
||||||
|
export default class RolePermissions extends BaseSchema {
|
||||||
|
protected tableName = Config.get('rolePermission.role_permission_table', 'role_permissions')
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table
|
||||||
|
.integer('role_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable(Config.get('rolePermission.role_table', 'roles'))
|
||||||
|
table
|
||||||
|
.integer('permission_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable(Config.get('rolePermission.permission_table', 'permissions'))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
|
||||||
|
*/
|
||||||
|
table.timestamp('created_at', { useTz: true }).nullable()
|
||||||
|
table.timestamp('updated_at', { useTz: true }).nullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
32
database/migrations/acl_4_user_permissions.ts
Normal file
32
database/migrations/acl_4_user_permissions.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
import Config from '@ioc:Adonis/Core/Config'
|
||||||
|
|
||||||
|
export default class UserPermissions extends BaseSchema {
|
||||||
|
protected tableName = Config.get('rolePermission.user_permission_table', 'user_permissions')
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table
|
||||||
|
.integer('user_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable(Config.get('rolePermission.user_table', 'users'))
|
||||||
|
table
|
||||||
|
.integer('permission_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable(Config.get('rolePermission.permission_table', 'permissions'))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
|
||||||
|
*/
|
||||||
|
table.timestamp('created_at', { useTz: true }).nullable()
|
||||||
|
table.timestamp('updated_at', { useTz: true }).nullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
32
database/migrations/acl_5_user_roles.ts
Normal file
32
database/migrations/acl_5_user_roles.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
import Config from '@ioc:Adonis/Core/Config'
|
||||||
|
|
||||||
|
export default class UserRoles extends BaseSchema {
|
||||||
|
protected tableName = Config.get('rolePermission.user_role_table', 'user_roles')
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table
|
||||||
|
.integer('user_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable(Config.get('rolePermission.user_table', 'users'))
|
||||||
|
table
|
||||||
|
.integer('role_id')
|
||||||
|
.unsigned()
|
||||||
|
.references('id')
|
||||||
|
.inTable(Config.get('rolePermission.role_table', 'roles'))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
|
||||||
|
*/
|
||||||
|
table.timestamp('created_at', { useTz: true }).nullable()
|
||||||
|
table.timestamp('updated_at', { useTz: true }).nullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
32
env.ts
Normal file
32
env.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Validating Environment Variables
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| In this file we define the rules for validating environment variables.
|
||||||
|
| By performing validation we ensure that your application is running in
|
||||||
|
| a stable environment with correct configuration values.
|
||||||
|
|
|
||||||
|
| This file is read automatically by the framework during the boot lifecycle
|
||||||
|
| and hence do not rename or move this file to a different location.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Env from '@ioc:Adonis/Core/Env'
|
||||||
|
|
||||||
|
export default Env.rules({
|
||||||
|
HOST: Env.schema.string({ format: 'host' }),
|
||||||
|
PORT: Env.schema.number(),
|
||||||
|
APP_KEY: Env.schema.string(),
|
||||||
|
APP_NAME: Env.schema.string(),
|
||||||
|
CACHE_VIEWS: Env.schema.boolean(),
|
||||||
|
SESSION_DRIVER: Env.schema.string(),
|
||||||
|
DRIVE_DISK: Env.schema.enum(['local'] as const),
|
||||||
|
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
|
||||||
|
DB_CONNECTION: Env.schema.string(),
|
||||||
|
PG_HOST: Env.schema.string({ format: 'host' }),
|
||||||
|
PG_PORT: Env.schema.number(),
|
||||||
|
PG_USER: Env.schema.string(),
|
||||||
|
PG_PASSWORD: Env.schema.string.optional(),
|
||||||
|
PG_DB_NAME: Env.schema.string(),
|
||||||
|
})
|
5
index.d.ts
vendored
Normal file
5
index.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from "vue"
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
16690
package-lock.json
generated
Normal file
16690
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
107
package.json
Normal file
107
package.json
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
{
|
||||||
|
"name": "myapp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"dev": "node ace serve --watch",
|
||||||
|
"build": "node ace build --production",
|
||||||
|
"start": "node server.js",
|
||||||
|
"lint": "eslint . --ext=.ts",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format-check": "prettier --check ./**/*.ts"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"plugin:adonis/typescriptApp",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslintIgnore": [
|
||||||
|
"build"
|
||||||
|
],
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"useTabs": false,
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"printWidth": 120
|
||||||
|
},
|
||||||
|
"alias": {
|
||||||
|
"vue": "./node_modules/vue/dist/vue.esm-bundler.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@adonisjs/assembler": "^5.7.0",
|
||||||
|
"@babel/core": "^7.20.12",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
|
"@babel/plugin-proposal-decorators": "^7.20.13",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.19.6",
|
||||||
|
"@babel/preset-env": "^7.20.2",
|
||||||
|
"@babel/preset-typescript": "^7.18.6",
|
||||||
|
"@japa/preset-adonis": "^1.0.16",
|
||||||
|
"@japa/runner": "^2.0.8",
|
||||||
|
"@mdi/js": "^7.1.96",
|
||||||
|
"@symfony/webpack-encore": "^4.2.0",
|
||||||
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
|
"@tailwindcss/line-clamp": "^0.4.0",
|
||||||
|
"@types/node": "^18.14.4",
|
||||||
|
"@types/proxy-addr": "^2.0.0",
|
||||||
|
"@types/source-map-support": "^0.5.6",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"adonis-preset-ts": "^2.1.0",
|
||||||
|
"autoprefixer": "^10.4.13",
|
||||||
|
"babel-preset-typescript-vue3": "^2.0.17",
|
||||||
|
"chart.js": "^4.2.0",
|
||||||
|
"eslint": "^8.32.0",
|
||||||
|
"eslint-config-prettier": "^8.6.0",
|
||||||
|
"eslint-plugin-adonis": "^2.1.1",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"naive-ui": "^2.34.3",
|
||||||
|
"numeral": "^2.0.6",
|
||||||
|
"pinia": "^2.0.30",
|
||||||
|
"pino-pretty": "^9.1.1",
|
||||||
|
"postcss-loader": "^7.0.2",
|
||||||
|
"prettier": "^2.8.3",
|
||||||
|
"tailwindcss": "^3.2.4",
|
||||||
|
"ts-loader": "^9.4.2",
|
||||||
|
"typescript": "^4.9.4",
|
||||||
|
"vue": "^3.2.47",
|
||||||
|
"vue-loader": "^17.0.1",
|
||||||
|
"youch": "^3.2.0",
|
||||||
|
"youch-terminal": "^2.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@adonisjs/auth": "^8.2.3",
|
||||||
|
"@adonisjs/core": "^5.8.3",
|
||||||
|
"@adonisjs/lucid": "^18.3.0",
|
||||||
|
"@adonisjs/repl": "^3.1.11",
|
||||||
|
"@adonisjs/session": "^6.4.0",
|
||||||
|
"@adonisjs/shield": "^7.1.0",
|
||||||
|
"@adonisjs/view": "^6.1.5",
|
||||||
|
"@eidellev/adonis-stardust": "^3.0.0",
|
||||||
|
"@eidellev/inertia-adonisjs": "^7.4.0",
|
||||||
|
"@inertiajs/inertia": "^0.11.1",
|
||||||
|
"@inertiajs/vue3": "^1.0.0",
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"dayjs": "^1.11.7",
|
||||||
|
"luxon": "^3.2.1",
|
||||||
|
"pg": "^8.9.0",
|
||||||
|
"proxy-addr": "^2.0.7",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"vue-facing-decorator": "^2.1.13"
|
||||||
|
}
|
||||||
|
}
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
27
providers/AppProvider.ts
Normal file
27
providers/AppProvider.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import type { ApplicationContract } from '@ioc:Adonis/Core/Application';
|
||||||
|
import Hash from '@ioc:Adonis/Core/Hash';
|
||||||
|
import { LaravelHash } from './HashDriver';
|
||||||
|
|
||||||
|
export default class AppProvider {
|
||||||
|
constructor(protected app: ApplicationContract) {}
|
||||||
|
|
||||||
|
public register() {
|
||||||
|
// Register your own bindings
|
||||||
|
}
|
||||||
|
|
||||||
|
public async boot() {
|
||||||
|
// IoC container is ready
|
||||||
|
const hashInstance: typeof Hash = this.app.container.use('Adonis/Core/Hash');
|
||||||
|
hashInstance.extend('bcrypt', () => {
|
||||||
|
return new LaravelHash();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ready() {
|
||||||
|
// App is ready
|
||||||
|
}
|
||||||
|
|
||||||
|
public async shutdown() {
|
||||||
|
// Cleanup, since app is going down
|
||||||
|
}
|
||||||
|
}
|
21
providers/HashDriver/index.ts
Normal file
21
providers/HashDriver/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { HashDriverContract } from "@ioc:Adonis/Core/Hash";
|
||||||
|
// const bcrypt = require("bcrypt");
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
|
||||||
|
const saltRounds = 10;
|
||||||
|
export class LaravelHash implements HashDriverContract {
|
||||||
|
public async make(value: string) {
|
||||||
|
const _hashedValue = bcrypt.hashSync(value, saltRounds);
|
||||||
|
return _hashedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async verify(hashedValue: string, plainValue: string) {
|
||||||
|
let newHash: string;
|
||||||
|
if (hashedValue.includes("$2y$10$")) {
|
||||||
|
newHash = hashedValue.replace("$2y$10$", "$2a$10$");
|
||||||
|
} else {
|
||||||
|
newHash = hashedValue;
|
||||||
|
}
|
||||||
|
return await bcrypt.compareSync(plainValue, newHash);
|
||||||
|
}
|
||||||
|
}
|
57
providers/QueryBuilderProvider.ts
Normal file
57
providers/QueryBuilderProvider.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import type { ApplicationContract } from '@ioc:Adonis/Core/Application';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Provider
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Your application is not ready when this file is loaded by the framework.
|
||||||
|
| Hence, the top level imports relying on the IoC container will not work.
|
||||||
|
| You must import them inside the life-cycle methods defined inside
|
||||||
|
| the provider class.
|
||||||
|
|
|
||||||
|
| @example:
|
||||||
|
|
|
||||||
|
| public async ready () {
|
||||||
|
| const Database = this.app.container.resolveBinding('Adonis/Lucid/Database')
|
||||||
|
| const Event = this.app.container.resolveBinding('Adonis/Core/Event')
|
||||||
|
| Event.on('db:query', Database.prettyPrint)
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export default class QueryBuilderProvider {
|
||||||
|
constructor(protected app: ApplicationContract) {}
|
||||||
|
|
||||||
|
public register() {
|
||||||
|
// Register your own bindings
|
||||||
|
}
|
||||||
|
|
||||||
|
public async boot() {
|
||||||
|
// All bindings are ready, feel free to use them
|
||||||
|
const { ModelQueryBuilder } = this.app.container.resolveBinding('Adonis/Lucid/Database');
|
||||||
|
|
||||||
|
ModelQueryBuilder.macro('pluck', async function (valueColumn: string, id?: string) {
|
||||||
|
let rolesPluck = {};
|
||||||
|
(await this).forEach((user, index) => {
|
||||||
|
let idc;
|
||||||
|
if (!id) {
|
||||||
|
idc = index;
|
||||||
|
} else {
|
||||||
|
idc = user[id];
|
||||||
|
}
|
||||||
|
const value = user[valueColumn];
|
||||||
|
// rolesPluck[idc] = user.name;
|
||||||
|
rolesPluck[idc] = value;
|
||||||
|
});
|
||||||
|
return rolesPluck;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ready() {
|
||||||
|
// App is ready
|
||||||
|
}
|
||||||
|
|
||||||
|
public async shutdown() {
|
||||||
|
// Cleanup, since app is going down
|
||||||
|
}
|
||||||
|
}
|
12
public/assets/entrypoints.json
Normal file
12
public/assets/entrypoints.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"entrypoints": {
|
||||||
|
"app": {
|
||||||
|
"css": [
|
||||||
|
"http://localhost:8080/assets/app.css"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"http://localhost:8080/assets/app.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
public/assets/manifest.json
Normal file
4
public/assets/manifest.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"assets/app.css": "http://localhost:8080/assets/app.css",
|
||||||
|
"assets/app.js": "http://localhost:8080/assets/app.js"
|
||||||
|
}
|
218
public/data-sources/clients.json
Normal file
218
public/data-sources/clients.json
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"email": "m.moser@univie.ac.at",
|
||||||
|
"name": "Michael Moser",
|
||||||
|
"city": "Vienna",
|
||||||
|
"progress": 70,
|
||||||
|
"created": "Mar 3, 2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"email": "manfred.linner@geologie.ac.at",
|
||||||
|
"name": "Manfred Linner",
|
||||||
|
"city": "Vienna",
|
||||||
|
"progress": 68,
|
||||||
|
"created": "Dec 1, 2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Nelson-Jerde.svg",
|
||||||
|
"login": "geovanni.kessler",
|
||||||
|
"name": "Nelson Jerde",
|
||||||
|
"company": "Nitzsche LLC",
|
||||||
|
"city": "Jailynbury",
|
||||||
|
"progress": 49,
|
||||||
|
"created": "May 18, 2021",
|
||||||
|
"created_mm_dd_yyyy": "05-18-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Kim-Weimann.svg",
|
||||||
|
"login": "macejkovic.dashawn",
|
||||||
|
"name": "Kim Weimann",
|
||||||
|
"company": "Brown-Lueilwitz",
|
||||||
|
"city": "New Emie",
|
||||||
|
"progress": 38,
|
||||||
|
"created": "May 4, 2021",
|
||||||
|
"created_mm_dd_yyyy": "05-04-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Justice-OReilly.svg",
|
||||||
|
"login": "hilpert.leora",
|
||||||
|
"name": "Justice O'Reilly",
|
||||||
|
"company": "Lakin-Muller",
|
||||||
|
"city": "New Kacie",
|
||||||
|
"progress": 38,
|
||||||
|
"created": "Mar 27, 2021",
|
||||||
|
"created_mm_dd_yyyy": "03-27-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Adrienne-Mayer-III.svg",
|
||||||
|
"login": "ferry.sophia",
|
||||||
|
"name": "Adrienne Mayer III",
|
||||||
|
"company": "Kozey, McLaughlin and Kuhn",
|
||||||
|
"city": "Howardbury",
|
||||||
|
"progress": 39,
|
||||||
|
"created": "Mar 29, 2021",
|
||||||
|
"created_mm_dd_yyyy": "03-29-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Mr.-Julien-Ebert.svg",
|
||||||
|
"login": "gokuneva",
|
||||||
|
"name": "Mr. Julien Ebert",
|
||||||
|
"company": "Cormier LLC",
|
||||||
|
"city": "South Serenaburgh",
|
||||||
|
"progress": 29,
|
||||||
|
"created": "Jun 25, 2021",
|
||||||
|
"created_mm_dd_yyyy": "06-25-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 47,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Lenna-Smitham.svg",
|
||||||
|
"login": "paolo.walter",
|
||||||
|
"name": "Lenna Smitham",
|
||||||
|
"company": "King Inc",
|
||||||
|
"city": "McCulloughfort",
|
||||||
|
"progress": 59,
|
||||||
|
"created": "Oct 8, 2021",
|
||||||
|
"created_mm_dd_yyyy": "10-08-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Travis-Davis.svg",
|
||||||
|
"login": "lkessler",
|
||||||
|
"name": "Travis Davis",
|
||||||
|
"company": "Leannon and Sons",
|
||||||
|
"city": "West Frankton",
|
||||||
|
"progress": 52,
|
||||||
|
"created": "Oct 20, 2021",
|
||||||
|
"created_mm_dd_yyyy": "10-20-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Prof.-Esteban-Steuber.svg",
|
||||||
|
"login": "shana.lang",
|
||||||
|
"name": "Prof. Esteban Steuber",
|
||||||
|
"company": "Langosh-Ernser",
|
||||||
|
"city": "East Sedrick",
|
||||||
|
"progress": 34,
|
||||||
|
"created": "May 16, 2021",
|
||||||
|
"created_mm_dd_yyyy": "05-16-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Russell-Goodwin-V.svg",
|
||||||
|
"login": "jewel07",
|
||||||
|
"name": "Russell Goodwin V",
|
||||||
|
"company": "Nolan-Stracke",
|
||||||
|
"city": "Williamsonmouth",
|
||||||
|
"progress": 55,
|
||||||
|
"created": "Apr 22, 2021",
|
||||||
|
"created_mm_dd_yyyy": "04-22-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Ms.-Cassidy-Wiegand-DVM.svg",
|
||||||
|
"login": "burnice.okuneva",
|
||||||
|
"name": "Ms. Cassidy Wiegand DVM",
|
||||||
|
"company": "Kuhlman-Hahn",
|
||||||
|
"city": "New Ruthiehaven",
|
||||||
|
"progress": 76,
|
||||||
|
"created": "Sep 16, 2021",
|
||||||
|
"created_mm_dd_yyyy": "09-16-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Mr.-Watson-Brakus-PhD.svg",
|
||||||
|
"login": "oconnell.juanita",
|
||||||
|
"name": "Mr. Watson Brakus PhD",
|
||||||
|
"company": "Osinski, Bins and Kuhn",
|
||||||
|
"city": "Lake Gloria",
|
||||||
|
"progress": 58,
|
||||||
|
"created": "Jun 22, 2021",
|
||||||
|
"created_mm_dd_yyyy": "06-22-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 46,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Mr.-Garrison-Friesen-V.svg",
|
||||||
|
"login": "vgutmann",
|
||||||
|
"name": "Mr. Garrison Friesen V",
|
||||||
|
"company": "VonRueden, Rippin and Pfeffer",
|
||||||
|
"city": "Port Cieloport",
|
||||||
|
"progress": 39,
|
||||||
|
"created": "Oct 19, 2021",
|
||||||
|
"created_mm_dd_yyyy": "10-19-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Ms.-Sister-Morar.svg",
|
||||||
|
"login": "veum.lucio",
|
||||||
|
"name": "Ms. Sister Morar",
|
||||||
|
"company": "Gusikowski, Altenwerth and Abbott",
|
||||||
|
"city": "Lake Macville",
|
||||||
|
"progress": 34,
|
||||||
|
"created": "Jun 11, 2021",
|
||||||
|
"created_mm_dd_yyyy": "06-11-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 40,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Ms.-Laisha-Reinger.svg",
|
||||||
|
"login": "edietrich",
|
||||||
|
"name": "Ms. Laisha Reinger",
|
||||||
|
"company": "Boehm PLC",
|
||||||
|
"city": "West Alexiemouth",
|
||||||
|
"progress": 73,
|
||||||
|
"created": "Nov 2, 2021",
|
||||||
|
"created_mm_dd_yyyy": "11-02-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Cameron-Lind.svg",
|
||||||
|
"login": "mose44",
|
||||||
|
"name": "Cameron Lind",
|
||||||
|
"company": "Tremblay, Padberg and Pouros",
|
||||||
|
"city": "Naderview",
|
||||||
|
"progress": 59,
|
||||||
|
"created": "Sep 14, 2021",
|
||||||
|
"created_mm_dd_yyyy": "09-14-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Sarai-Little.svg",
|
||||||
|
"login": "rau.abelardo",
|
||||||
|
"name": "Sarai Little",
|
||||||
|
"company": "Deckow LLC",
|
||||||
|
"city": "Jeanieborough",
|
||||||
|
"progress": 49,
|
||||||
|
"created": "Jun 13, 2021",
|
||||||
|
"created_mm_dd_yyyy": "06-13-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Shyann-Kautzer.svg",
|
||||||
|
"login": "imurazik",
|
||||||
|
"name": "Shyann Kautzer",
|
||||||
|
"company": "Osinski, Boehm and Kihn",
|
||||||
|
"city": "New Alvera",
|
||||||
|
"progress": 41,
|
||||||
|
"created": "Feb 15, 2021",
|
||||||
|
"created_mm_dd_yyyy": "02-15-2021"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"avatar": "https://avatars.dicebear.com/v2/gridy/Lorna-Christiansen.svg",
|
||||||
|
"login": "annalise97",
|
||||||
|
"name": "Lorna Christiansen",
|
||||||
|
"company": "Altenwerth-Friesen",
|
||||||
|
"city": "Port Elbertland",
|
||||||
|
"progress": 36,
|
||||||
|
"created": "Mar 9, 2021",
|
||||||
|
"created_mm_dd_yyyy": "03-09-2021"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
36
public/data-sources/history.json
Normal file
36
public/data-sources/history.json
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"amount": 375.53,
|
||||||
|
"account": "45721474",
|
||||||
|
"name": "Home Loan Account",
|
||||||
|
"date": "3 days ago",
|
||||||
|
"type": "deposit",
|
||||||
|
"business": "Turcotte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": 470.26,
|
||||||
|
"account": "94486537",
|
||||||
|
"name": "Savings Account",
|
||||||
|
"date": "3 days ago",
|
||||||
|
"type": "payment",
|
||||||
|
"business": "Murazik - Graham"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": 971.34,
|
||||||
|
"account": "63189893",
|
||||||
|
"name": "Checking Account",
|
||||||
|
"date": "5 days ago",
|
||||||
|
"type": "invoice",
|
||||||
|
"business": "Fahey - Keebler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": 374.63,
|
||||||
|
"account": "74828780",
|
||||||
|
"name": "Auto Loan Account",
|
||||||
|
"date": "7 days ago",
|
||||||
|
"type": "withdrawal",
|
||||||
|
"business": "Collier - Hintz"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
12323
public/docs/HandbuchTethys.pdf
Normal file
12323
public/docs/HandbuchTethys.pdf
Normal file
File diff suppressed because one or more lines are too long
BIN
public/docs/geopackage_v01.pdf
Normal file
BIN
public/docs/geopackage_v01.pdf
Normal file
Binary file not shown.
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
62
resources/css/_checkbox-radio-switch.css
Normal file
62
resources/css/_checkbox-radio-switch.css
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
.checkbox, .radio, .switch {
|
||||||
|
@apply inline-flex items-center cursor-pointer relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type=checkbox], .radio input[type=radio], .switch input[type=checkbox] {
|
||||||
|
@apply absolute left-0 opacity-0 -z-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type=checkbox]+.check, .radio input[type=radio]+.check, .switch input[type=checkbox]+.check {
|
||||||
|
@apply border-gray-700 border transition-colors duration-200 dark:bg-slate-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type=checkbox]:focus+.check, .radio input[type=radio]:focus+.check, .switch input[type=checkbox]:focus+.check {
|
||||||
|
@apply ring ring-blue-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type=checkbox]+.check, .radio input[type=radio]+.check {
|
||||||
|
@apply block w-5 h-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type=checkbox]+.check {
|
||||||
|
@apply rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input[type=checkbox]+.check {
|
||||||
|
@apply flex items-center shrink-0 w-12 h-6 p-0.5 bg-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio input[type=radio]+.check, .switch input[type=checkbox]+.check, .switch input[type=checkbox]+.check:before {
|
||||||
|
@apply rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type=checkbox]:checked+.check, .radio input[type=radio]:checked+.check {
|
||||||
|
@apply bg-no-repeat bg-center bg-blue-600 border-blue-600 border-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type=checkbox]:checked+.check {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio input[type=radio]:checked+.check {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input[type=checkbox]:checked+.check {
|
||||||
|
@apply bg-blue-600 border-blue-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input[type=checkbox]+.check:before {
|
||||||
|
content: '';
|
||||||
|
@apply block w-5 h-5 bg-white border border-gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input[type=checkbox]:checked+.check:before {
|
||||||
|
transform: translate3d(110%, 0 ,0);
|
||||||
|
@apply border-blue-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox .control-label, .radio .control-label, .switch .control-label {
|
||||||
|
@apply pl-2;
|
||||||
|
}
|
||||||
|
|
20
resources/css/_progress.css
Normal file
20
resources/css/_progress.css
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
progress {
|
||||||
|
@apply h-3 rounded-full overflow-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-webkit-progress-bar {
|
||||||
|
@apply bg-blue-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-webkit-progress-value {
|
||||||
|
@apply bg-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-moz-progress-bar {
|
||||||
|
@apply bg-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-ms-fill {
|
||||||
|
@apply bg-blue-500 border-0;
|
||||||
|
}
|
||||||
|
|
38
resources/css/_scrollbars.css
Normal file
38
resources/css/_scrollbars.css
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
html {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #9ca3af #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-track {
|
||||||
|
@apply bg-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-gray-400 rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::-webkit-scrollbar-thumb:hover {
|
||||||
|
@apply bg-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark-scrollbars {
|
||||||
|
scrollbar-color: #374151 #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark-scrollbars::-webkit-scrollbar-track {
|
||||||
|
@apply bg-gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark-scrollbars::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark-scrollbars::-webkit-scrollbar-thumb:hover {
|
||||||
|
@apply bg-gray-600;
|
||||||
|
}
|
||||||
|
|
47
resources/css/_table.css
Normal file
47
resources/css/_table.css
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
table {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
@apply hidden lg:table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
@apply max-w-full block relative border-b-4 border-gray-100
|
||||||
|
lg:table-row lg:border-b-0 dark:border-slate-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:last-child {
|
||||||
|
@apply border-b-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:not(:first-child) {
|
||||||
|
@apply lg:border-l lg:border-t-0 lg:border-r-0 lg:border-b-0 lg:border-gray-100 lg:dark:border-slate-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
@apply lg:text-left lg:p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
@apply flex justify-between text-right py-3 px-4 align-top border-b border-gray-100
|
||||||
|
lg:table-cell lg:text-left lg:p-3 lg:align-middle lg:border-b-0 dark:border-slate-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:last-child {
|
||||||
|
@apply border-b-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr, tbody tr:nth-child(odd) {
|
||||||
|
@apply lg:hover:bg-gray-100 lg:dark:hover:bg-slate-700/70;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(odd) {
|
||||||
|
@apply lg:bg-gray-50 lg:dark:bg-slate-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:before {
|
||||||
|
content: attr(data-label);
|
||||||
|
@apply font-semibold pr-3 text-left lg:hidden;
|
||||||
|
}
|
||||||
|
|
95
resources/css/app.css
Normal file
95
resources/css/app.css
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap'); */
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,600,700');
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@import "_checkbox-radio-switch.css";
|
||||||
|
@import "_progress.css";
|
||||||
|
@import "_scrollbars.css";
|
||||||
|
@import "_table.css";
|
||||||
|
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
/* height: 100vh; */
|
||||||
|
color: #46444c;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.px-6 {
|
||||||
|
padding-left: 0;
|
||||||
|
/* padding-right: 1.5rem; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-md {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* body:before {
|
||||||
|
content: '';
|
||||||
|
background: #5A45FF;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 6px;
|
||||||
|
position: absolute;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* * {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* a {
|
||||||
|
color: #5A45FF;
|
||||||
|
text-decoration: none;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* main {
|
||||||
|
max-width: 620px;
|
||||||
|
margin: auto;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0 30px;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* .title {
|
||||||
|
font-size: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #17161A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 26px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* main ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li:before {
|
||||||
|
content: '—';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
} */
|
||||||
|
|
||||||
|
main code {
|
||||||
|
font-size: 16px;
|
||||||
|
background: #e6e2ff;
|
||||||
|
}
|
52
resources/js/Components/Admin/MenuItemList.vue
Normal file
52
resources/js/Components/Admin/MenuItemList.vue
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<script setup>
|
||||||
|
import { Link } from "@inertiajs/vue3"
|
||||||
|
import BaseButton from "@/Components/BaseButton.vue"
|
||||||
|
import BaseButtons from "@/Components/BaseButtons.vue"
|
||||||
|
import {
|
||||||
|
mdiSquareEditOutline,
|
||||||
|
mdiTrashCan,
|
||||||
|
} from "@mdi/js"
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
can: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
level: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tr :key="item.id">
|
||||||
|
<td data-label="Name">
|
||||||
|
<div :style="{ 'margin-left': level * 20 + 'px' }">{{ item.name }}</div>
|
||||||
|
</td>
|
||||||
|
<td data-label="Description">
|
||||||
|
{{ item.description }}
|
||||||
|
</td>
|
||||||
|
<td data-label="Enabled">
|
||||||
|
{{ item.enabled }}
|
||||||
|
</td>
|
||||||
|
<td v-if="can.edit || can.delete" class="before:hidden lg:w-1 whitespace-nowrap">
|
||||||
|
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||||
|
<BaseButton v-if="can.edit" :route-name="route('menu.item.edit', { menu: menu.id, item: item.id })"
|
||||||
|
color="info" :icon="mdiSquareEditOutline" small />
|
||||||
|
<BaseButton v-if="can.delete" color="danger" :icon="mdiTrashCan" small @click="destroy(item.id)" />
|
||||||
|
</BaseButtons>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<template v-for="item in item.children">
|
||||||
|
<MenuItemList :item="item" :menu="menu" :can="can" :level="level + 1" />
|
||||||
|
</template>
|
||||||
|
</template>
|
161
resources/js/Components/Admin/Pagination.vue
Normal file
161
resources/js/Components/Admin/Pagination.vue
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
<script setup>
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const nextPageLink = computed(() => {
|
||||||
|
let url = new URL(document.location);
|
||||||
|
url.searchParams.set('page', props.data.current_page + 1);
|
||||||
|
return url.href;
|
||||||
|
// return url + '&page=' + (Number(props.data.current_page) + 1);
|
||||||
|
});
|
||||||
|
const prevPageLink = computed(() => {
|
||||||
|
let url = new URL(document.location);
|
||||||
|
url.searchParams.set('page', props.data.current_page - 1);
|
||||||
|
return url.href;
|
||||||
|
// return url + '&page=' + (props.data.current_page - 1);
|
||||||
|
});
|
||||||
|
const toPage = computed(() => {
|
||||||
|
let currentPage = props.data.current_page;
|
||||||
|
let perPage = props.data.per_page;
|
||||||
|
|
||||||
|
if (props.data.current_page == props.data.last_page) {
|
||||||
|
return props.data.total;
|
||||||
|
} else {
|
||||||
|
return currentPage * perPage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const fromPage = computed(() => {
|
||||||
|
let currentPage = props.data.current_page;
|
||||||
|
let perPage = props.data.per_page;
|
||||||
|
return currentPage * perPage - (perPage - 1);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- current_page:
|
||||||
|
1
|
||||||
|
first_page:
|
||||||
|
1
|
||||||
|
first_page_url:
|
||||||
|
'/?page=1'
|
||||||
|
last_page:
|
||||||
|
3
|
||||||
|
last_page_url:
|
||||||
|
'/?page=3'
|
||||||
|
next_page_url:
|
||||||
|
'/?page=2'
|
||||||
|
per_page:
|
||||||
|
5
|
||||||
|
previous_page_url:
|
||||||
|
null
|
||||||
|
total:
|
||||||
|
15 -->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- <nav v-if="data.links.length > 3" -->
|
||||||
|
<nav v-if="data.total > 3" role="navigation" aria-label="Pagination Navigation"
|
||||||
|
class="flex items-center justify-between">
|
||||||
|
<div class="flex justify-between flex-1 sm:hidden">
|
||||||
|
<span v-if="data.current_page <= 1"
|
||||||
|
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
|
||||||
|
Previous
|
||||||
|
</span>
|
||||||
|
<Link v-else :href="data.previous_page_url"
|
||||||
|
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||||
|
Previous
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link v-if="data.current_page < data.last_page" :href="data.next_page_url"
|
||||||
|
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||||
|
Next
|
||||||
|
</Link>
|
||||||
|
<span v-else
|
||||||
|
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
|
||||||
|
Next
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-700 leading-5">
|
||||||
|
Showing
|
||||||
|
<span class="font-medium">{{ fromPage }}</span>
|
||||||
|
to
|
||||||
|
<span class="font-medium">{{ toPage }}</span>
|
||||||
|
of
|
||||||
|
<span class="font-medium">{{ data.total }}</span>
|
||||||
|
results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="relative z-0 inline-flex shadow-sm rounded-md">
|
||||||
|
<span v-if="props.data.current_page <= 1" aria-disabled="true" aria-label="Previous">
|
||||||
|
<span
|
||||||
|
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5"
|
||||||
|
aria-hidden="true">
|
||||||
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- <Link v-else :href="data.previous_page_url" rel="prev" -->
|
||||||
|
<Link v-else :href="prevPageLink" rel="prev"
|
||||||
|
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
|
||||||
|
aria-label="Previous">
|
||||||
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<!-- <template v-for="(link, key) in data.links">
|
||||||
|
<template v-if="key > 0 && key < data.last_page + 1">
|
||||||
|
<span v-if="!link.active && link.url === null" :key="key" aria-disabled="true">
|
||||||
|
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5">{{ link.label }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else-if="link.active" :key="`current-${key}`" aria-current="page">
|
||||||
|
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5">{{ link.label }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Link v-else :key="`link-${key}`" :href="link.url" v-html="link.label" class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:text-gray-500 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150" aria-label="`Go to page ${link.label}`" />
|
||||||
|
</template>
|
||||||
|
</template> -->
|
||||||
|
|
||||||
|
<Link v-if="props.data.current_page < props.data.last_page" :href="nextPageLink" rel="next"
|
||||||
|
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
|
||||||
|
aria-label="Next">
|
||||||
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="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"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
<!-- else disabled link -->
|
||||||
|
<span v-else aria-disabled="true" aria-label="Next">
|
||||||
|
<span
|
||||||
|
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5"
|
||||||
|
aria-hidden="true">
|
||||||
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="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"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
76
resources/js/Components/Admin/Sort.vue
Normal file
76
resources/js/Components/Admin/Sort.vue
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
attribute: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const downFill = ref('lightgray');
|
||||||
|
const upFill = ref('lightgray');
|
||||||
|
|
||||||
|
const sortLink = computed(() => {
|
||||||
|
let url = new URL(document.location);
|
||||||
|
let sortValue = url.searchParams.get('sort');
|
||||||
|
|
||||||
|
if (sortValue == props.attribute) {
|
||||||
|
url.searchParams.set('sort', '-' + props.attribute);
|
||||||
|
upFill.value = 'black';
|
||||||
|
} else if (sortValue === '-' + props.attribute) {
|
||||||
|
url.searchParams.set('sort', props.attribute);
|
||||||
|
downFill.value = 'black';
|
||||||
|
} else {
|
||||||
|
url.searchParams.set('sort', props.attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.search == '') {
|
||||||
|
url.searchParams.delete('search');
|
||||||
|
} else {
|
||||||
|
url.searchParams.set('search', props.search);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.href;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<Link :href="sortLink" class="no-underline hover:underline text-cyan-600 dark:text-cyan-400">
|
||||||
|
{{ label }}
|
||||||
|
</Link>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<svg
|
||||||
|
class="inline-block"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="15px"
|
||||||
|
height="15px"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path d="M7.5 3L15 11H0L7.5 3Z" :fill="upFill" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
class="inline-block"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="15px"
|
||||||
|
height="15px"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path d="M7.49988 12L-0.00012207 4L14.9999 4L7.49988 12Z" :fill="downFill" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
30
resources/js/Components/AsideMenu.vue
Normal file
30
resources/js/Components/AsideMenu.vue
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup>
|
||||||
|
// import { reactive, computed } from 'vue';
|
||||||
|
// import { usePage } from '@inertiajs/vue3'
|
||||||
|
// import { usePage } from '@inertiajs/inertia-vue3';
|
||||||
|
import { LayoutService } from '@/Stores/layout.js';
|
||||||
|
import menu from '@/menu.js'
|
||||||
|
import AsideMenuLayer from '@/Components/AsideMenuLayer.vue';
|
||||||
|
import OverlayLayer from '@/Components/OverlayLayer.vue';
|
||||||
|
|
||||||
|
// let menu = reactive({});
|
||||||
|
// menu = computed(() => usePage().props.navigation?.menu);
|
||||||
|
|
||||||
|
const layoutService = LayoutService();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AsideMenuLayer
|
||||||
|
v-if="menu && Object.keys(menu).length"
|
||||||
|
:menu="menu"
|
||||||
|
:class="[
|
||||||
|
layoutService.isAsideMobileExpanded ? 'left-0' : '-left-60 lg:left-0',
|
||||||
|
{ 'lg:hidden xl:flex': !layoutService.isAsideLgActive },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<!-- <OverlayLayer
|
||||||
|
v-show="layoutService.isAsideLgActive"
|
||||||
|
z-index="z-30"
|
||||||
|
@overlay-click="layoutService.isAsideLgActive = false"
|
||||||
|
/> -->
|
||||||
|
</template>
|
93
resources/js/Components/AsideMenuItem.vue
Normal file
93
resources/js/Components/AsideMenuItem.vue
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
// import { Link } from '@inertiajs/inertia-vue3';
|
||||||
|
|
||||||
|
import { StyleService } from '@/Stores/style.js';
|
||||||
|
import { mdiMinus, mdiPlus } from '@mdi/js';
|
||||||
|
import { getButtonColor } from '@/colors.js';
|
||||||
|
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||||
|
import AsideMenuList from '@/Components/AsideMenuList.vue';
|
||||||
|
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isDropdownList: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemRoute = computed(() => (props.item && props.item.route ? stardust.route(props.item.route): ''));
|
||||||
|
// const isCurrentRoute = computed(() => (props.item && props.item.route ? stardust.isCurrent(props.item.route): false));
|
||||||
|
const itemHref = computed(() => (props.item && props.item.href ? props.item.href : ''));
|
||||||
|
|
||||||
|
const emit = defineEmits(['menu-click']);
|
||||||
|
|
||||||
|
const styleService = StyleService();
|
||||||
|
|
||||||
|
const hasColor = computed(() => props.item && props.item.color);
|
||||||
|
|
||||||
|
const isDropdownActive = ref(false);
|
||||||
|
|
||||||
|
const componentClass = computed(() => [
|
||||||
|
props.isDropdownList ? 'py-3 px-6 text-sm' : 'py-3 px-6',
|
||||||
|
hasColor.value ? getButtonColor(props.item.color, false, true) : styleService.asideMenuItemStyle,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hasDropdown = computed(() => props.item.children);
|
||||||
|
|
||||||
|
const menuClick = (event) => {
|
||||||
|
emit('menu-click', event, props.item);
|
||||||
|
|
||||||
|
if (hasDropdown.value) {
|
||||||
|
isDropdownActive.value = !isDropdownActive.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeInactiveStyle = computed(() => {
|
||||||
|
if (props.item.route && stardust.isCurrent(props.item.route)) {
|
||||||
|
// console.log(props.item.route);
|
||||||
|
return styleService.asideMenuItemActiveStyle;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const is = computed(() => {
|
||||||
|
if (props.item.href) {
|
||||||
|
return 'a'
|
||||||
|
}
|
||||||
|
if (props.item.route) {
|
||||||
|
return Link
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'div'
|
||||||
|
})
|
||||||
|
|
||||||
|
// props.routeName && stardust.isCurrent(props.routeName) ? props.activeColor : null
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- :target="props.item.target ?? null" -->
|
||||||
|
<template>
|
||||||
|
<li>
|
||||||
|
<!-- <component :is="itemHref ? 'div' : Link" :href="itemHref ? itemHref : itemRoute" -->
|
||||||
|
<component :is="is" :href="itemRoute ? stardust.route(props.item.route) : props.item.href"
|
||||||
|
class="flex cursor-pointer dark:text-slate-300 dark:hover:text-white" :class="componentClass"
|
||||||
|
@click="menuClick" v-bind:target="props.item.target ?? null">
|
||||||
|
<BaseIcon v-if="item.icon" :path="item.icon" class="flex-none" :class="activeInactiveStyle" w="w-16"
|
||||||
|
:size="18" />
|
||||||
|
<span class="grow text-ellipsis line-clamp-1" :class="activeInactiveStyle">
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
<!-- <BaseIcon v-if="hasDropdown" :path="isDropdownActive ? mdiMinus : mdiPlus" class="flex-none"
|
||||||
|
:class="activeInactiveStyle" w="w-12" /> -->
|
||||||
|
</component>
|
||||||
|
<!-- <AsideMenuList v-if="hasDropdown" :menu="item.children" :class="[
|
||||||
|
styleService.asideMenuDropdownStyle,
|
||||||
|
isDropdownActive ? 'block dark:bg-slate-800/50' : 'hidden',
|
||||||
|
]" is-dropdown-list /> -->
|
||||||
|
</li>
|
||||||
|
</template>
|
66
resources/js/Components/AsideMenuLayer.vue
Normal file
66
resources/js/Components/AsideMenuLayer.vue
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<script setup>
|
||||||
|
import { router } from '@inertiajs/vue3'
|
||||||
|
// import { Inertia } from '@inertiajs/inertia';
|
||||||
|
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||||
|
import { mdiLogout, mdiClose } from '@mdi/js';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { LayoutService } from '@/Stores/layout.js';
|
||||||
|
import { StyleService } from '@/Stores/style.js';
|
||||||
|
import AsideMenuList from '@/Components/AsideMenuList.vue';
|
||||||
|
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
|
||||||
|
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
menu: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['menu-click']);
|
||||||
|
|
||||||
|
const layoutStore = LayoutService();
|
||||||
|
|
||||||
|
const styleStore = StyleService();
|
||||||
|
|
||||||
|
const logoutItem = computed(() => ({
|
||||||
|
name: 'Logout',
|
||||||
|
label: 'Logout',
|
||||||
|
icon: mdiLogout,
|
||||||
|
color: 'info',
|
||||||
|
link: '#',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const logoutItemClick = async () => {
|
||||||
|
// router.post(route('logout'));
|
||||||
|
await router.post(stardust.route('logout'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const menuClick = (event, item) => {
|
||||||
|
emit('menu-click', event, item);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside id="aside" class="lg:py-2 lg:pl-2 w-60 fixed flex z-40 top-0 h-screen transition-position overflow-hidden">
|
||||||
|
<div :class="styleStore.asideStyle" class="lg:rounded-xl flex-1 flex flex-col overflow-hidden dark:bg-slate-900">
|
||||||
|
<div :class="styleStore.asideBrandStyle"
|
||||||
|
class="flex flex-row h-14 items-center justify-between dark:bg-slate-900">
|
||||||
|
<div class="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
|
||||||
|
<b class="font-black">Menu</b>
|
||||||
|
</div>
|
||||||
|
<button class="hidden lg:inline-block xl:hidden p-3" @click.prevent="layoutStore.isAsideLgActive = false">
|
||||||
|
<BaseIcon :path="mdiClose" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div :class="styleStore.darkMode ? 'aside-scrollbars-[slate]' : styleStore.asideScrollbarsStyle"
|
||||||
|
class="flex-1 overflow-y-auto overflow-x-hidden">
|
||||||
|
<AsideMenuList v-bind:menu="menu" @menu-click="menuClick" />
|
||||||
|
</div>
|
||||||
|
<!-- <p class="menu-label">About</p>> -->
|
||||||
|
<ul class="menu-list">
|
||||||
|
<AsideMenuItem :item="logoutItem" @menu-click="logoutItemClick" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</template>
|
29
resources/js/Components/AsideMenuList.vue
Normal file
29
resources/js/Components/AsideMenuList.vue
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup>
|
||||||
|
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
isDropdownList: Boolean,
|
||||||
|
menu: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['menu-click'])
|
||||||
|
|
||||||
|
const menuClick = (event, item) => {
|
||||||
|
emit('menu-click', event, item)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<AsideMenuItem
|
||||||
|
v-for="(item, index) in menu"
|
||||||
|
:key="index"
|
||||||
|
v-bind:item="item"
|
||||||
|
:is-dropdown-list="isDropdownList"
|
||||||
|
@menu-click="menuClick"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
117
resources/js/Components/BaseButton.vue
Normal file
117
resources/js/Components/BaseButton.vue
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
// import { Link } from '@inertiajs/inertia-vue3';
|
||||||
|
import { getButtonColor } from '@/colors.js';
|
||||||
|
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
label: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
routeName: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'white',
|
||||||
|
},
|
||||||
|
as: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
small: Boolean,
|
||||||
|
outline: Boolean,
|
||||||
|
active: Boolean,
|
||||||
|
disabled: Boolean,
|
||||||
|
roundedFull: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
const is = computed(() => {
|
||||||
|
if (props.as) {
|
||||||
|
return props.as;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.routeName) {
|
||||||
|
return Link;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.href) {
|
||||||
|
return 'a';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'button';
|
||||||
|
});
|
||||||
|
|
||||||
|
const computedType = computed(() => {
|
||||||
|
if (is.value === 'button') {
|
||||||
|
return props.type ?? 'button';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelClass = computed(() => (props.small && props.icon ? 'px-1' : 'px-2'));
|
||||||
|
|
||||||
|
const componentClass = computed(() => {
|
||||||
|
const base = [
|
||||||
|
'inline-flex',
|
||||||
|
'cursor-pointer',
|
||||||
|
'justify-center',
|
||||||
|
'items-center',
|
||||||
|
'whitespace-nowrap',
|
||||||
|
'focus:outline-none',
|
||||||
|
'transition-colors',
|
||||||
|
'focus:ring',
|
||||||
|
'duration-150',
|
||||||
|
'border',
|
||||||
|
props.roundedFull ? 'rounded-full' : 'rounded',
|
||||||
|
props.active ? 'ring ring-black dark:ring-white' : 'ring-blue-700',
|
||||||
|
getButtonColor(props.color, props.outline, !props.disabled),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (props.small) {
|
||||||
|
base.push('text-sm', props.roundedFull ? 'px-3 py-1' : 'p-1');
|
||||||
|
} else {
|
||||||
|
base.push('py-2', props.roundedFull ? 'px-6' : 'px-3');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.disabled) {
|
||||||
|
base.push('cursor-not-allowed', props.outline ? 'opacity-50' : 'opacity-70');
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="is"
|
||||||
|
:class="componentClass"
|
||||||
|
:href="routeName ? routeName : href"
|
||||||
|
:type="computedType"
|
||||||
|
:target="target"
|
||||||
|
:disabled="disabled"
|
||||||
|
>
|
||||||
|
<BaseIcon v-if="icon" :path="icon" />
|
||||||
|
<span v-if="label" :class="labelClass">{{ label }}</span>
|
||||||
|
</component>
|
||||||
|
</template>
|
55
resources/js/Components/BaseButtons.vue
Normal file
55
resources/js/Components/BaseButtons.vue
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<script>
|
||||||
|
import { h, defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BaseButtons',
|
||||||
|
props: {
|
||||||
|
noWrap: Boolean,
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'justify-start'
|
||||||
|
},
|
||||||
|
classAddon: {
|
||||||
|
type: String,
|
||||||
|
default: 'mr-3 last:mr-0 mb-3'
|
||||||
|
},
|
||||||
|
mb: {
|
||||||
|
type: String,
|
||||||
|
default: '-mb-3'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const hasSlot = this.$slots && this.$slots.default
|
||||||
|
|
||||||
|
const parentClass = [
|
||||||
|
'flex',
|
||||||
|
'items-center',
|
||||||
|
this.type,
|
||||||
|
this.noWrap ? 'flex-nowrap' : 'flex-wrap'
|
||||||
|
]
|
||||||
|
|
||||||
|
if (this.mb) {
|
||||||
|
parentClass.push(this.mb)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ class: parentClass },
|
||||||
|
hasSlot
|
||||||
|
? this.$slots.default().map(element => {
|
||||||
|
if (element && element.children && typeof element.children === 'object') {
|
||||||
|
return h(
|
||||||
|
element,
|
||||||
|
{},
|
||||||
|
element.children.map(child => {
|
||||||
|
return h(child, { class: [this.classAddon] })
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(element, { class: [this.classAddon] })
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
12
resources/js/Components/BaseDivider.vue
Normal file
12
resources/js/Components/BaseDivider.vue
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
navBar: Boolean
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<hr
|
||||||
|
:class="props.navBar ? 'hidden lg:block lg:my-0.5 dark:border-slate-700' : 'my-6 -mx-6 dark:border-slate-800'"
|
||||||
|
class="border-t border-gray-100"
|
||||||
|
>
|
||||||
|
</template>
|
40
resources/js/Components/BaseIcon.vue
Normal file
40
resources/js/Components/BaseIcon.vue
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
w: {
|
||||||
|
type: String,
|
||||||
|
default: 'w-6'
|
||||||
|
},
|
||||||
|
h: {
|
||||||
|
type: String,
|
||||||
|
default: 'h-6'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 16
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const spanClass = computed(() => `inline-flex justify-center items-center ${props.w} ${props.h}`)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span :class="spanClass">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
class="inline-block"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
:d="path"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</template>
|
37
resources/js/Components/BaseLevel.vue
Normal file
37
resources/js/Components/BaseLevel.vue
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<script>
|
||||||
|
import { h, defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BaseLevel',
|
||||||
|
props: {
|
||||||
|
mobile: Boolean,
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'justify-between'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const parentClass = [this.type, 'items-center']
|
||||||
|
|
||||||
|
const parentMobileClass = ['flex']
|
||||||
|
|
||||||
|
const parentBaseClass = ['block', 'md:flex']
|
||||||
|
|
||||||
|
const childBaseClass = ['flex', 'shrink-0', 'grow-0', 'items-center', 'justify-center']
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ class: parentClass.concat(this.mobile ? parentMobileClass : parentBaseClass) },
|
||||||
|
this.$slots.default().map((element, index) => {
|
||||||
|
const childClass = (!this.mobile && this.$slots.default().length > index + 1)
|
||||||
|
? childBaseClass.concat(['mb-6', 'md:mb-0'])
|
||||||
|
: childBaseClass
|
||||||
|
|
||||||
|
return h('div', { class: childClass }, [
|
||||||
|
element
|
||||||
|
])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
111
resources/js/Components/CardBox.vue
Normal file
111
resources/js/Components/CardBox.vue
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<script setup>
|
||||||
|
import { mdiCog } from '@mdi/js'
|
||||||
|
import { computed, useSlots } from 'vue'
|
||||||
|
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
headerIcon: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
rounded: {
|
||||||
|
type: String,
|
||||||
|
default: 'rounded-xl'
|
||||||
|
},
|
||||||
|
hasTable: Boolean,
|
||||||
|
empty: Boolean,
|
||||||
|
form: Boolean,
|
||||||
|
hoverable: Boolean,
|
||||||
|
modal: Boolean
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['header-icon-click', 'submit'])
|
||||||
|
|
||||||
|
const is = computed(() => props.form ? 'form' : 'div')
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
|
const footer = computed(() => slots.footer && !!slots.footer())
|
||||||
|
|
||||||
|
const componentClass = computed(() => {
|
||||||
|
const base = [
|
||||||
|
props.rounded,
|
||||||
|
props.modal ? 'dark:bg-slate-900' : 'dark:bg-slate-900/70'
|
||||||
|
]
|
||||||
|
|
||||||
|
if (props.hoverable) {
|
||||||
|
base.push('hover:shadow-lg transition-shadow duration-500')
|
||||||
|
}
|
||||||
|
|
||||||
|
return base
|
||||||
|
})
|
||||||
|
|
||||||
|
const computedHeaderIcon = computed(() => props.headerIcon ?? mdiCog)
|
||||||
|
|
||||||
|
const headerIconClick = () => {
|
||||||
|
emit('header-icon-click')
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = e => {
|
||||||
|
emit('submit', e)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="is"
|
||||||
|
:class="componentClass"
|
||||||
|
class="bg-white flex flex-col"
|
||||||
|
@submit="submit"
|
||||||
|
>
|
||||||
|
<header
|
||||||
|
v-if="title"
|
||||||
|
class="flex items-stretch border-b border-gray-100 dark:border-slate-800"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center py-3 grow font-bold"
|
||||||
|
:class="[ icon ? 'px-4' : 'px-6' ]"
|
||||||
|
>
|
||||||
|
<BaseIcon
|
||||||
|
v-if="icon"
|
||||||
|
:path="icon"
|
||||||
|
class="mr-3"
|
||||||
|
/>
|
||||||
|
{{ title }}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring"
|
||||||
|
@click="headerIconClick"
|
||||||
|
>
|
||||||
|
<BaseIcon :path="computedHeaderIcon" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
v-if="empty"
|
||||||
|
class="text-center py-24 text-gray-500 dark:text-slate-400"
|
||||||
|
>
|
||||||
|
<p>Nothing's here…</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex-1"
|
||||||
|
:class="{'p-6':!hasTable}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="footer"
|
||||||
|
class="p-6 border-t border-gray-100 dark:border-slate-800"
|
||||||
|
>
|
||||||
|
<slot name="footer" />
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user