forked from geolba/tethys.backend
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
49bd96ee77 | |||
2235f3905a | |||
b06ccae603 | |||
010bead723 | |||
f4854d70b9 | |||
49ea0fc967 | |||
005df2e454 | |||
ac473b1e72 | |||
770e791613 | |||
ec17d79cf2 | |||
08c2edca3b | |||
a29865b781 | |||
bee76f8d5b | |||
296c8fd46e | |||
cb51a4136f | |||
f828ca4491 | |||
b2dce0259a | |||
4efa53673f | |||
68928b5e07 | |||
8cef7390d7 | |||
c9ba7d6adc | |||
ebc62d9117 | |||
18635f77b3 | |||
c70fa4a0d8 | |||
87e9314b00 | |||
cefd9081ae | |||
ae0c471e93 | |||
0d51002903 | |||
6fef581dd0 | |||
c1e056b9fc | |||
bf9d25ae3e | |||
b6fdfbff41 | |||
d8bdce1369 | |||
a7142f694f | |||
7bc9f90cca | |||
2360a81d1e | |||
cf859ba402 | |||
7915f66dd6 | |||
2a7480d2ed |
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "./start/validator",
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
70
.babelrc
70
.babelrc
|
@ -1,37 +1,37 @@
|
||||||
{
|
{
|
||||||
"presets": [
|
"presets": [
|
||||||
[
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
"targets": {
|
"targets": {
|
||||||
"edge": "17",
|
"edge": "17",
|
||||||
"firefox": "60",
|
"firefox": "60",
|
||||||
"chrome": "67",
|
"chrome": "67",
|
||||||
"safari": "11.1"
|
"safari": "11.1"
|
||||||
}
|
}
|
||||||
// "useBuiltIns": "usage",
|
// "useBuiltIns": "usage",
|
||||||
// "corejs": "3.16"
|
// "corejs": "3.16"
|
||||||
// "targets":{"node":"16"}
|
// "targets":{"node":"16"}
|
||||||
// "useBuiltIns": "entry",
|
// "useBuiltIns": "entry",
|
||||||
// "targets": "> 0.25%, not dead"
|
// "targets": "> 0.25%, not dead"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
// "@babel/preset-env",
|
// "@babel/preset-env",
|
||||||
"@babel/preset-typescript"
|
"@babel/preset-typescript"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
// [
|
// [
|
||||||
// "@babel/plugin-transform-typescript", {
|
// "@babel/plugin-transform-typescript", {
|
||||||
// "allowDeclareFields": true
|
// "allowDeclareFields": true
|
||||||
// }],
|
// }],
|
||||||
[
|
[
|
||||||
"@babel/plugin-proposal-decorators",
|
"@babel/plugin-proposal-decorators",
|
||||||
{
|
{
|
||||||
"legacy": true
|
"legacy": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"@babel/proposal-class-properties"
|
"@babel/proposal-class-properties"
|
||||||
// "@babel/proposal-object-rest-spread"
|
// "@babel/proposal-object-rest-spread"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 4
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
|
@ -11,3 +11,10 @@ PG_PORT=5432
|
||||||
PG_USER=lucid
|
PG_USER=lucid
|
||||||
PG_PASSWORD=
|
PG_PASSWORD=
|
||||||
PG_DB_NAME=lucid
|
PG_DB_NAME=lucid
|
||||||
|
REDIS_CONNECTION=local
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=
|
||||||
|
RESEND_API_KEY=
|
|
@ -1,24 +1,14 @@
|
||||||
{
|
{
|
||||||
"extends": [
|
"extends": ["plugin:adonis/typescriptApp", "prettier"],
|
||||||
"plugin:adonis/typescriptApp",
|
"plugins": ["prettier"],
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": [
|
"prettier/prettier": ["error", { "singleQuote": true }],
|
||||||
"error",
|
"@typescript-eslint/indent": ["error", 4, { "ignoredNodes": ["PropertyDefinition", "TSUnionType"] }],
|
||||||
{ "singleQuote": true }
|
|
||||||
],
|
|
||||||
"@typescript-eslint/indent": ["error", 4, { "ignoredNodes": ["PropertyDefinition", "TSUnionType"] }],
|
|
||||||
"@typescript-eslint/naming-convention": [
|
"@typescript-eslint/naming-convention": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
"selector": "interface",
|
"selector": "interface",
|
||||||
"format": [
|
"format": ["PascalCase"],
|
||||||
"PascalCase"
|
|
||||||
],
|
|
||||||
"custom": {
|
"custom": {
|
||||||
"regex": "^I[A-Z]",
|
"regex": "^I[A-Z]",
|
||||||
"match": false
|
"match": false
|
||||||
|
@ -26,4 +16,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ jobs:
|
||||||
# run build on latest ubuntu
|
# run build on latest ubuntu
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
container: node:16-bullseye
|
container: node:18-bullseye
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mydb:
|
mydb:
|
||||||
image: postgres:latest
|
image: postgres:latest
|
||||||
container_name: mydb
|
# container_name: mydb
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: alice
|
POSTGRES_USER: alice
|
||||||
POSTGRES_PASSWORD: iEx4Vj7zBb6
|
POSTGRES_PASSWORD: iEx4Vj7zBb6
|
||||||
|
@ -27,8 +27,7 @@ jobs:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
# Set health checks to wait until postgres has started
|
# Set health checks to wait until postgres has started
|
||||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# this will check out the current branch (https://github.com/actions/checkout#Push-a-commit-using-the-built-in-token)
|
# this will check out the current branch (https://github.com/actions/checkout#Push-a-commit-using-the-built-in-token)
|
||||||
- name: 1 Check out repository code
|
- name: 1 Check out repository code
|
||||||
|
@ -66,7 +65,7 @@ jobs:
|
||||||
&& echo "HASH_DRIVER=bcrypt" >> .env.test
|
&& echo "HASH_DRIVER=bcrypt" >> .env.test
|
||||||
&& echo "HOST=127.0.0.1" >> .env.test
|
&& echo "HOST=127.0.0.1" >> .env.test
|
||||||
&& echo "PORT=3333" >> .env.test
|
&& echo "PORT=3333" >> .env.test
|
||||||
&& echo "APP_NAME=AdonisJs" >> .env.test
|
&& echo "APP_NAME=TethysCloud" >> .env.test
|
||||||
&& echo "APP_URL=http://${HOST}:${PORT}" >> .env.test
|
&& echo "APP_URL=http://${HOST}:${PORT}" >> .env.test
|
||||||
&& echo "CACHE_VIEWS=false" >> .env.test
|
&& echo "CACHE_VIEWS=false" >> .env.test
|
||||||
&& echo "APP_KEY=pfi5N2ACN4tMJ5d8d8BPHfh3FEuvleej" >> .env.test
|
&& echo "APP_KEY=pfi5N2ACN4tMJ5d8d8BPHfh3FEuvleej" >> .env.test
|
||||||
|
|
13
Dockerfile
13
Dockerfile
|
@ -1,7 +1,7 @@
|
||||||
################## First Stage - Creating base #########################
|
################## First Stage - Creating base #########################
|
||||||
|
|
||||||
# Created a variable to hold our node base image
|
# Created a variable to hold our node base image
|
||||||
ARG NODE_IMAGE=node:18-bookworm-slim
|
ARG NODE_IMAGE=node:20-bookworm-slim
|
||||||
|
|
||||||
FROM $NODE_IMAGE AS base
|
FROM $NODE_IMAGE AS base
|
||||||
# Install dumb-init and ClamAV, and perform ClamAV database update
|
# Install dumb-init and ClamAV, and perform ClamAV database update
|
||||||
|
@ -44,7 +44,7 @@ USER node
|
||||||
# initial update of av databases
|
# initial update of av databases
|
||||||
RUN freshclam
|
RUN freshclam
|
||||||
|
|
||||||
VOLUME /var/lib/clamav
|
# VOLUME /var/lib/clamav
|
||||||
COPY --chown=node:clamav docker-entrypoint.sh /home/node/app/docker-entrypoint.sh
|
COPY --chown=node:clamav docker-entrypoint.sh /home/node/app/docker-entrypoint.sh
|
||||||
RUN chmod +x /home/node/app/docker-entrypoint.sh
|
RUN chmod +x /home/node/app/docker-entrypoint.sh
|
||||||
ENV TZ="Europe/Vienna"
|
ENV TZ="Europe/Vienna"
|
||||||
|
@ -66,8 +66,11 @@ COPY --chown=node:node . .
|
||||||
################## Third Stage - Building Stage #####################
|
################## Third Stage - Building Stage #####################
|
||||||
# In this stage, we will start building dependencies
|
# In this stage, we will start building dependencies
|
||||||
FROM dependencies AS build
|
FROM dependencies AS build
|
||||||
|
ENV NODE_ENV=production
|
||||||
# We run "node ace build" to build the app (dist folder) for production
|
# We run "node ace build" to build the app (dist folder) for production
|
||||||
RUN node ace build --production
|
RUN node ace build --ignore-ts-errors
|
||||||
|
# RUN node ace build --production
|
||||||
|
# RUN node ace build --ignore-ts-errors
|
||||||
|
|
||||||
|
|
||||||
################## Final Stage - Production #########################
|
################## Final Stage - Production #########################
|
||||||
|
@ -85,7 +88,7 @@ RUN npm ci --omit=dev
|
||||||
# Copy files to the working directory from the build folder the user
|
# Copy files to the working directory from the build folder the user
|
||||||
COPY --chown=node:node --from=build /home/node/app/build .
|
COPY --chown=node:node --from=build /home/node/app/build .
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE $PORT
|
EXPOSE 3333
|
||||||
ENTRYPOINT ["/home/node/app/docker-entrypoint.sh"]
|
ENTRYPOINT ["/home/node/app/docker-entrypoint.sh"]
|
||||||
# Run the command to start the server using "dumb-init"
|
# Run the command to start the server using "dumb-init"
|
||||||
CMD [ "dumb-init", "node", "server.js" ]
|
CMD [ "dumb-init", "node", "bin/server.js" ]
|
16
ace
16
ace
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| 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))
|
|
|
@ -1,609 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "disable-locks",
|
|
||||||
"propertyName": "disableLocks",
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Disable locks acquired to run migrations safely"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "disable-locks",
|
|
||||||
"propertyName": "disableLocks",
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Disable locks acquired to run migrations safely"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "disable-locks",
|
|
||||||
"propertyName": "disableLocks",
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Disable locks acquired to run migrations safely"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "disable-locks",
|
|
||||||
"propertyName": "disableLocks",
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Disable locks acquired to run migrations safely"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "disable-locks",
|
|
||||||
"propertyName": "disableLocks",
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Disable locks acquired to run migrations safely"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"aliases": {}
|
|
||||||
}
|
|
24
ace.js
Normal file
24
ace.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| JavaScript entrypoint for running ace commands
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Since, we cannot run TypeScript source code using "node" binary, we need
|
||||||
|
| a JavaScript entrypoint to run ace commands.
|
||||||
|
|
|
||||||
|
| This file registers the "ts-node/esm" hook with the Node.js module system
|
||||||
|
| and then imports the "bin/console.ts" file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register hook to process TypeScript files using ts-node
|
||||||
|
*/
|
||||||
|
import { register } from 'node:module'
|
||||||
|
register('ts-node/esm', import.meta.url)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import ace console entrypoint
|
||||||
|
*/
|
||||||
|
await import('./bin/console.js')
|
113
adonisrc.ts
Normal file
113
adonisrc.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { defineConfig } from '@adonisjs/core/app'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Commands
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| List of ace commands to register from packages. The application commands
|
||||||
|
| will be scanned automatically from the "./commands" directory.
|
||||||
|
|
||||||
|
*/
|
||||||
|
commands: [
|
||||||
|
() => import('@adonisjs/core/commands'),
|
||||||
|
() => import('@adonisjs/lucid/commands'),
|
||||||
|
() => import('@adonisjs/mail/commands')
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Preloads
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| List of modules to import before starting the application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
preloads: [
|
||||||
|
() => import('./start/routes.js'),
|
||||||
|
() => import('./start/kernel.js'),
|
||||||
|
() => import('#start/validator'),
|
||||||
|
() => import('#start/rules/unique'),
|
||||||
|
() => import('#start/rules/translated_language'),
|
||||||
|
() => import('#start/rules/unique_person'),
|
||||||
|
() => import('#start/rules/file_length'),
|
||||||
|
() => import('#start/rules/file_scan')
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Service providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| List of service providers to import and register when booting the
|
||||||
|
| application
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
providers: [
|
||||||
|
// () => import('./providers/AppProvider.js'),
|
||||||
|
() => import('@adonisjs/core/providers/app_provider'),
|
||||||
|
() => import('@adonisjs/core/providers/hash_provider'),
|
||||||
|
{
|
||||||
|
file: () => import('@adonisjs/core/providers/repl_provider'),
|
||||||
|
environment: ['repl', 'test'],
|
||||||
|
},
|
||||||
|
() => import('@adonisjs/session/session_provider'),
|
||||||
|
() => import('@adonisjs/core/providers/edge_provider'),
|
||||||
|
() => import('@adonisjs/shield/shield_provider'),
|
||||||
|
// () => import('@eidellev/inertia-adonisjs'),
|
||||||
|
// () => import('@adonisjs/inertia/inertia_provider'),
|
||||||
|
() => import('#providers/app_provider'),
|
||||||
|
() => import('#providers/inertia_provider'),
|
||||||
|
() => import('@adonisjs/lucid/database_provider'),
|
||||||
|
() => import('@adonisjs/auth/auth_provider'),
|
||||||
|
// () => import('@eidellev/adonis-stardust'),
|
||||||
|
() => import('@adonisjs/redis/redis_provider'),
|
||||||
|
() => import('@adonisjs/encore/encore_provider'),
|
||||||
|
() => import('@adonisjs/static/static_provider'),
|
||||||
|
() => import('#providers/stardust_provider'),
|
||||||
|
() => import('#providers/query_builder_provider'),
|
||||||
|
() => import('#providers/token_worker_provider'),
|
||||||
|
// () => import('#providers/validator_provider'),
|
||||||
|
() => import('#providers/drive/provider/drive_provider'),
|
||||||
|
// () => import('@adonisjs/core/providers/vinejs_provider'),
|
||||||
|
() => import('#providers/vinejs_provider'),
|
||||||
|
() => import('@adonisjs/mail/mail_provider')
|
||||||
|
// () => import('#providers/mail_provider'),
|
||||||
|
],
|
||||||
|
metaFiles: [
|
||||||
|
{
|
||||||
|
pattern: 'public/**',
|
||||||
|
reloadServer: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: 'resources/views/**/*.edge',
|
||||||
|
reloadServer: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Tests
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| List of test suites to organize tests by their type. Feel free to remove
|
||||||
|
| and add additional suites.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
tests: {
|
||||||
|
suites: [
|
||||||
|
{
|
||||||
|
files: ['tests/unit/**/*.spec(.ts|.js)'],
|
||||||
|
name: 'unit',
|
||||||
|
timeout: 2000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['tests/functional/**/*.spec(.ts|.js)'],
|
||||||
|
name: 'functional',
|
||||||
|
timeout: 30000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
forceExit: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
|
@ -1,14 +1,14 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
import User from 'App/Models/User';
|
import User from '#models/user';
|
||||||
import Role from 'App/Models/Role';
|
import Role from '#models/role';
|
||||||
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
|
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
||||||
import CreateUserValidator from 'App/Validators/CreateUserValidator';
|
import { createUserValidator, updateUserValidator } from '#validators/user';
|
||||||
import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
|
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
import { RenderResponse } from '@ioc:EidelLev/Inertia';
|
// import Hash from '@ioc:Adonis/Core/Hash';
|
||||||
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
|
||||||
export default class UsersController {
|
export default class AdminuserController {
|
||||||
public async index({ auth, request, inertia }: HttpContextContract) {
|
public async index({ auth, request, inertia }: HttpContext) {
|
||||||
const page = request.input('page', 1);
|
const page = request.input('page', 1);
|
||||||
// const limit = 10
|
// const limit = 10
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ export default class UsersController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create({ inertia }: HttpContextContract) {
|
public async create({ inertia }: HttpContext) {
|
||||||
// let rolesPluck = {};
|
// let rolesPluck = {};
|
||||||
// (await Role.query().select('id', 'name')).forEach((user) => {
|
// (await Role.query().select('id', 'name')).forEach((user) => {
|
||||||
// rolesPluck[user.id] = user.name;
|
// rolesPluck[user.id] = user.name;
|
||||||
|
@ -73,18 +73,19 @@ export default class UsersController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async store({ request, response, session }: HttpContextContract) {
|
public async store({ request, response, session }: HttpContext) {
|
||||||
// node ace make:validator CreateUser
|
// node ace make:validator CreateUser
|
||||||
try {
|
try {
|
||||||
// Step 2 - Validate request body against the schema
|
// Step 2 - Validate request body against the schema
|
||||||
await request.validate(CreateUserValidator);
|
// await request.validate(CreateUserValidator);
|
||||||
|
await request.validateUsing(createUserValidator);
|
||||||
// console.log({ payload });
|
// console.log({ payload });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Step 3 - Handle errors
|
// Step 3 - Handle errors
|
||||||
// return response.badRequest(error.messages);
|
// return response.badRequest(error.messages);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const input = request.only(['login', 'email', 'password']);
|
const input = request.only(['login', 'email', 'password', 'first_name', 'last_name']);
|
||||||
const user = await User.create(input);
|
const user = await User.create(input);
|
||||||
if (request.input('roles')) {
|
if (request.input('roles')) {
|
||||||
const roles: Array<number> = request.input('roles');
|
const roles: Array<number> = request.input('roles');
|
||||||
|
@ -92,10 +93,10 @@ export default class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.flash('message', 'User has been created successfully');
|
session.flash('message', 'User has been created successfully');
|
||||||
return response.redirect().toRoute('user.index');
|
return response.redirect().toRoute('settings.user.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async show({ request, inertia }: HttpContextContract) {
|
public async show({ request, inertia }: HttpContext) {
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const user = await User.query().where('id', id).firstOrFail();
|
const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ export default class UsersController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async edit({ request, inertia }: HttpContextContract) {
|
public async edit({ request, inertia }: HttpContext) {
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const user = await User.query().where('id', id).firstOrFail();
|
const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
@ -125,20 +126,24 @@ export default class UsersController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update({ request, response, session }: HttpContextContract) {
|
public async update({ request, response, session }: HttpContext) {
|
||||||
// node ace make:validator UpdateUser
|
// node ace make:validator UpdateUser
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const user = await User.query().where('id', id).firstOrFail();
|
const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
// validate update form
|
// validate update form
|
||||||
await request.validate(UpdateUserValidator);
|
await request.validateUsing(updateUserValidator, {
|
||||||
|
meta: {
|
||||||
|
objId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// password is optional
|
// password is optional
|
||||||
let input;
|
let input;
|
||||||
if (request.input('password')) {
|
if (request.input('password')) {
|
||||||
input = request.only(['login', 'email', 'password']);
|
input = request.only(['login', 'email', 'password', 'first_name', 'last_name']);
|
||||||
} else {
|
} else {
|
||||||
input = request.only(['login', 'email']);
|
input = request.only(['login', 'email', 'first_name', 'last_name']);
|
||||||
}
|
}
|
||||||
await user.merge(input).save();
|
await user.merge(input).save();
|
||||||
// await user.save();
|
// await user.save();
|
||||||
|
@ -149,61 +154,20 @@ export default class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.flash('message', 'User has been updated successfully');
|
session.flash('message', 'User has been updated successfully');
|
||||||
return response.redirect().toRoute('user.index');
|
return response.redirect().toRoute('settings.user.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async destroy({ request, response, session }: HttpContextContract) {
|
public async destroy({ request, response, session }: HttpContext) {
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const user = await User.findOrFail(id);
|
const user = await User.findOrFail(id);
|
||||||
await user.delete();
|
await user.delete();
|
||||||
|
|
||||||
session.flash('message', `User ${user.login} has been deleted.`);
|
session.flash('message', `User ${user.login} has been deleted.`);
|
||||||
return response.redirect().toRoute('user.index');
|
return response.redirect().toRoute('settings.user.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// private async syncRoles(objId: number, roleIds: Array<number>) {
|
||||||
* Show the user a form to change their personal information & password.
|
// const user = await User.findOrFail(objId)
|
||||||
*
|
|
||||||
* @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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// private async syncRoles(userId: number, roleIds: Array<number>) {
|
|
||||||
// const user = await User.findOrFail(userId)
|
|
||||||
// // const roles: Role[] = await Role.query().whereIn('id', roleIds);
|
// // const roles: Role[] = await Role.query().whereIn('id', roleIds);
|
||||||
|
|
||||||
// // await user.roles().sync(roles.rows.map(role => role.id))
|
// // await user.roles().sync(roles.rows.map(role => role.id))
|
|
@ -1,17 +1,17 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
|
||||||
export default class HomeController {
|
export default class HomeController {
|
||||||
public async index({}: HttpContextContract) {}
|
public async index({}: HttpContext) {}
|
||||||
|
|
||||||
public async create({}: HttpContextContract) {}
|
public async create({}: HttpContext) {}
|
||||||
|
|
||||||
public async store({}: HttpContextContract) {}
|
public async store({}: HttpContext) {}
|
||||||
|
|
||||||
public async show({}: HttpContextContract) {}
|
public async show({}: HttpContext) {}
|
||||||
|
|
||||||
public async edit({}: HttpContextContract) {}
|
public async edit({}: HttpContext) {}
|
||||||
|
|
||||||
public async update({}: HttpContextContract) {}
|
public async update({}: HttpContext) {}
|
||||||
|
|
||||||
public async destroy({}: HttpContextContract) {}
|
public async destroy({}: HttpContext) {}
|
||||||
}
|
}
|
||||||
|
|
51
app/Controllers/Http/Admin/LicenseController.ts
Normal file
51
app/Controllers/Http/Admin/LicenseController.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import License from '#models/license';
|
||||||
|
|
||||||
|
export default class LicenseController {
|
||||||
|
public async index({ auth, inertia }: HttpContext) {
|
||||||
|
const direction = 'asc'; // or 'desc'
|
||||||
|
const licenses = await License.query().orderBy('sort_order', direction).exec();
|
||||||
|
|
||||||
|
return inertia.render('Admin/License/Index', {
|
||||||
|
licenses: licenses,
|
||||||
|
can: {
|
||||||
|
edit: await auth.user?.can(['settings']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down({ request, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const license = await License.findOrFail(id);
|
||||||
|
license.active = false;
|
||||||
|
await license.save();
|
||||||
|
|
||||||
|
// session.flash({ message: 'person has been deactivated!' });
|
||||||
|
return response.flash('License has been deactivated!', 'message').toRoute('settings.license.index')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async up({ request, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const license = await License.findOrFail(id);
|
||||||
|
license.active = true;
|
||||||
|
await license.save();
|
||||||
|
|
||||||
|
// session.flash({ message: 'person has been activated!' });
|
||||||
|
return response.flash('License has been activated!', 'message').toRoute('settings.license.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async edit({ request, inertia }: HttpContext) {
|
||||||
|
// const id = request.param('id');
|
||||||
|
// const license = await License.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/License/Edit', {
|
||||||
|
// // permissions: permissions,
|
||||||
|
// license: license,
|
||||||
|
// // roleHasPermissions: Object.keys(rolerHasPermissions).map((key) => rolerHasPermissions[key]), //convert object to array with role ids
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
141
app/Controllers/Http/Admin/MimetypeController.ts
Normal file
141
app/Controllers/Http/Admin/MimetypeController.ts
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import MimeType from '#models/mime_type';
|
||||||
|
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
||||||
|
|
||||||
|
export default class MimetypeController {
|
||||||
|
public async index({ auth, inertia }: HttpContext) {
|
||||||
|
const direction = 'asc'; // or 'desc'
|
||||||
|
const mimetypes = await MimeType.query().orderBy('name', direction).exec();
|
||||||
|
|
||||||
|
return inertia.render('Admin/Mimetype/Index', {
|
||||||
|
mimetypes: mimetypes,
|
||||||
|
can: {
|
||||||
|
create: await auth.user?.can(['settings']),
|
||||||
|
edit: await auth.user?.can(['settings']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create({ inertia }: HttpContext) {
|
||||||
|
// const permissions = await Permission.query().select('id', 'name').pluck('name', 'id');
|
||||||
|
return inertia.render('Admin/Mimetype/Create', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async store({ request, response, session }: HttpContext) {
|
||||||
|
const newDatasetSchema = vine.object({
|
||||||
|
name: vine.string().trim().isUnique({ table: 'mime_types', column: 'name' }),
|
||||||
|
file_extension: vine.array(vine.string()).minLength(1), // define at least one extension for the new mimetype
|
||||||
|
enabled: vine.boolean(),
|
||||||
|
});
|
||||||
|
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
|
||||||
|
try {
|
||||||
|
// Step 2 - Validate request body against the schema
|
||||||
|
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
|
||||||
|
const validator = vine.compile(newDatasetSchema);
|
||||||
|
validator.messagesProvider = new SimpleMessagesProvider(this.messages);
|
||||||
|
await request.validateUsing(validator);
|
||||||
|
} catch (error) {
|
||||||
|
// Step 3 - Handle errors
|
||||||
|
// return response.badRequest(error.messages);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const input = request.only(['name', 'enabled', 'file_extension']);
|
||||||
|
// Concatenate the file_extensions array into a string with '|' as the separator
|
||||||
|
if (Array.isArray(input.file_extension)) {
|
||||||
|
input.file_extension = input.file_extension.join('|');
|
||||||
|
}
|
||||||
|
await MimeType.create(input);
|
||||||
|
// if (request.input('roles')) {
|
||||||
|
// const roles: Array<number> = request.input('roles');
|
||||||
|
// await user.related('roles').attach(roles);
|
||||||
|
// }
|
||||||
|
|
||||||
|
session.flash('message', 'MimeType has been created successfully');
|
||||||
|
return response.redirect().toRoute('settings.mimetype.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public messages = {
|
||||||
|
'minLength': '{{ field }} must be at least {{ min }} characters long',
|
||||||
|
'maxLength': '{{ field }} must be less then {{ max }} characters long',
|
||||||
|
'isUnique': '{{ field }} must be unique, and this value is already taken',
|
||||||
|
'required': '{{ field }} is required',
|
||||||
|
'file_extension.minLength': 'at least {{ min }} mimetypes must be defined',
|
||||||
|
'file_extension.*.string': 'Each file extension must be a valid string', // Adjusted to match the type
|
||||||
|
};
|
||||||
|
|
||||||
|
public async edit({ request, inertia }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const mimetype = await MimeType.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/Mimetype/Edit', {
|
||||||
|
mimetype: mimetype,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async update({ request, response, session }: HttpContext) {
|
||||||
|
// // 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);
|
||||||
|
// await request.validateUsing(updateRoleValidator, {
|
||||||
|
// meta: {
|
||||||
|
// roleId: role.id,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // 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('settings.role.index');
|
||||||
|
// }
|
||||||
|
|
||||||
|
public async down({ request, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const mimetype = await MimeType.findOrFail(id);
|
||||||
|
mimetype.enabled = false;
|
||||||
|
await mimetype.save();
|
||||||
|
|
||||||
|
// session.flash({ message: 'person has been deactivated!' });
|
||||||
|
return response.flash('mimetype has been deactivated!', 'message').toRoute('settings.mimetype.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async up({ request, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const mimetype = await MimeType.findOrFail(id);
|
||||||
|
mimetype.enabled = true;
|
||||||
|
await mimetype.save();
|
||||||
|
|
||||||
|
// session.flash({ message: 'person has been activated!' });
|
||||||
|
return response.flash('mimetype has been activated!', 'message').toRoute('settings.mimetype.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async edit({ request, inertia }: HttpContext) {
|
||||||
|
// const id = request.param('id');
|
||||||
|
// const license = await License.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/License/Edit', {
|
||||||
|
// // permissions: permissions,
|
||||||
|
// license: license,
|
||||||
|
// // roleHasPermissions: Object.keys(rolerHasPermissions).map((key) => rolerHasPermissions[key]), //convert object to array with role ids
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
|
@ -1,14 +1,13 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
import Role from 'App/Models/Role';
|
import Role from '#models/role';
|
||||||
import Permission from 'App/Models/Permission';
|
import Permission from '#models/permission';
|
||||||
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
|
import { createRoleValidator, updateRoleValidator } from '#validators/role';
|
||||||
import CreateRoleValidator from 'App/Validators/CreateRoleValidator';
|
import type { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
||||||
import UpdateRoleValidator from 'App/Validators/UpdateRoleValidator';
|
|
||||||
import { RenderResponse } from '@ioc:EidelLev/Inertia';
|
|
||||||
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
|
|
||||||
export default class RoleController {
|
export default class RoleController {
|
||||||
public async index({ auth, request, inertia }: HttpContextContract) {
|
public async index({ auth, request, inertia }: HttpContext) {
|
||||||
let roles: ModelQueryBuilderContract<typeof Role, Role> = Role.query();
|
let roles: ModelQueryBuilderContract<typeof Role, Role> = Role.query();
|
||||||
|
|
||||||
if (request.input('search')) {
|
if (request.input('search')) {
|
||||||
|
@ -48,18 +47,19 @@ export default class RoleController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create({ inertia }: HttpContextContract) {
|
public async create({ inertia }: HttpContext) {
|
||||||
const permissions = await Permission.query().select('id', 'name').pluck('name', 'id');
|
const permissions = await Permission.query().select('id', 'name').pluck('name', 'id');
|
||||||
return inertia.render('Admin/Role/Create', {
|
return inertia.render('Admin/Role/Create', {
|
||||||
permissions: permissions,
|
permissions: permissions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async store({ request, response, session }: HttpContextContract) {
|
public async store({ request, response, session }: HttpContext) {
|
||||||
// node ace make:validator CreateUser
|
// node ace make:validator CreateUser
|
||||||
try {
|
try {
|
||||||
// Step 2 - Validate request body against the schema
|
// Step 2 - Validate request body against the schema
|
||||||
await request.validate(CreateRoleValidator);
|
// await request.validate(CreateRoleValidator);
|
||||||
|
await request.validateUsing(createRoleValidator);
|
||||||
// await request.validate({ schema: roleSchema });
|
// await request.validate({ schema: roleSchema });
|
||||||
// console.log({ payload });
|
// console.log({ payload });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -76,10 +76,10 @@ export default class RoleController {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.flash('message', `Role ${role.name} has been created successfully`);
|
session.flash('message', `Role ${role.name} has been created successfully`);
|
||||||
return response.redirect().toRoute('role.index');
|
return response.redirect().toRoute('settings.role.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async show({ request, inertia }: HttpContextContract): RenderResponse {
|
public async show({ request, inertia }: HttpContext) {
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const role = await Role.query().where('id', id).firstOrFail();
|
const role = await Role.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ export default class RoleController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async edit({ request, inertia }: HttpContextContract) {
|
public async edit({ request, inertia }: HttpContext) {
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const role = await Role.query().where('id', id).firstOrFail();
|
const role = await Role.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
@ -109,13 +109,18 @@ export default class RoleController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update({ request, response, session }: HttpContextContract) {
|
public async update({ request, response, session }: HttpContext) {
|
||||||
// node ace make:validator UpdateUser
|
// node ace make:validator UpdateUser
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const role = await Role.query().where('id', id).firstOrFail();
|
const role = await Role.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
// validate update form
|
// validate update form
|
||||||
await request.validate(UpdateRoleValidator);
|
// await request.validate(UpdateRoleValidator);
|
||||||
|
await request.validateUsing(updateRoleValidator, {
|
||||||
|
meta: {
|
||||||
|
roleId: role.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// password is optional
|
// password is optional
|
||||||
|
|
||||||
|
@ -129,15 +134,15 @@ export default class RoleController {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.flash('message', 'Role has been updated successfully');
|
session.flash('message', 'Role has been updated successfully');
|
||||||
return response.redirect().toRoute('role.index');
|
return response.redirect().toRoute('settings.role.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async destroy({ request, response, session }: HttpContextContract) {
|
public async destroy({ request, response, session }: HttpContext) {
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const role = await Role.findOrFail(id);
|
const role = await Role.findOrFail(id);
|
||||||
await role.delete();
|
await role.delete();
|
||||||
|
|
||||||
session.flash('message', `Role ${role.name} has been deleted.`);
|
session.flash('message', `Role ${role.name} has been deleted.`);
|
||||||
return response.redirect().toRoute('role.index');
|
return response.redirect().toRoute('settings.role.index');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
103
app/Controllers/Http/Admin/mailsettings_controller.ts
Normal file
103
app/Controllers/Http/Admin/mailsettings_controller.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import vine from '@vinejs/vine';
|
||||||
|
import AppConfig from '#models/appconfig';
|
||||||
|
import mail from '@adonisjs/mail/services/main';
|
||||||
|
// import config from '@adonisjs/core/services/config';
|
||||||
|
// import { configProvider } from '@adonisjs/core';
|
||||||
|
// import app from '@adonisjs/core/services/app';
|
||||||
|
|
||||||
|
export default class MailSettingsController {
|
||||||
|
/**
|
||||||
|
* Save the email server settings
|
||||||
|
*/
|
||||||
|
public async setMailSettings({ request, response }: HttpContext) {
|
||||||
|
const settingsSchema = vine.compile(
|
||||||
|
vine.object({
|
||||||
|
mail_domain: vine.string(),
|
||||||
|
mail_from_address: vine.string(),
|
||||||
|
mail_smtp_mode: vine.string(),
|
||||||
|
mail_smtpsecure: vine.string().optional(),
|
||||||
|
mail_smtphost: vine.string(),
|
||||||
|
mail_smtpport: vine.string(),
|
||||||
|
mail_smtpauth: vine.boolean(),
|
||||||
|
// mail_sendmailmode: vine.string().optional(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const validatedData = await request.validateUsing(settingsSchema);
|
||||||
|
|
||||||
|
const configData: any = { ...validatedData };
|
||||||
|
|
||||||
|
if (!validatedData.mail_smtpauth) {
|
||||||
|
configData.mail_smtpname = null;
|
||||||
|
configData.mail_smtppassword = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the settings to be saved
|
||||||
|
const settingsToSave = [
|
||||||
|
{ appid: 'settings', configkey: 'default', configvalue: validatedData.mail_smtp_mode, type: 1, lazy: 0 },
|
||||||
|
{ appid: 'settings', configkey: 'host', configvalue: validatedData.mail_smtphost, type: 1, lazy: 0 },
|
||||||
|
{ appid: 'settings', configkey: 'port', configvalue: validatedData.mail_smtpport, type: 1, lazy: 0 },
|
||||||
|
{
|
||||||
|
appid: 'settings',
|
||||||
|
configkey: 'from.address',
|
||||||
|
configvalue: `${validatedData.mail_from_address}@${validatedData.mail_domain}`,
|
||||||
|
type: 1,
|
||||||
|
lazy: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// if (validatedData.mail_smtpauth) {
|
||||||
|
// settingsToSave.push(
|
||||||
|
// { appid: 'settings', configkey: 'smtp_user', configvalue: validatedData.mail_smtpname, type: 1, lazy: 0 },
|
||||||
|
// { appid: 'settings', configkey: 'smtp_password', configvalue: validatedData.mail_smtppassword, type: 1, lazy: 0 },
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// settingsToSave.push(
|
||||||
|
// { appid: 'settings', configkey: 'smtp_user', configvalue: null, type: 1, lazy: 0 },
|
||||||
|
// { appid: 'settings', configkey: 'smtp_password', configvalue: null, type: 1, lazy: 0 },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Save or update the settings in the database
|
||||||
|
for (const setting of settingsToSave) {
|
||||||
|
await AppConfig.updateOrCreate(
|
||||||
|
{ appid: setting.appid, configkey: setting.configkey },
|
||||||
|
{ configvalue: setting.configvalue, type: setting.type, lazy: setting.lazy },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json({ success: true, message: 'Mail settings updated successfully' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a test email to ensure settings work
|
||||||
|
*/
|
||||||
|
public async sendTestMail({ response, auth }: HttpContext) {
|
||||||
|
const user = auth.user!;
|
||||||
|
const userEmail = user.email;
|
||||||
|
|
||||||
|
// let mailManager = await app.container.make('mail.manager');
|
||||||
|
// let iwas = mailManager.use();
|
||||||
|
// let test = mail.config.mailers.smtp();
|
||||||
|
if (!userEmail) {
|
||||||
|
return response.badRequest({ message: 'User email is not set. Please update your profile.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mail.send((message) => {
|
||||||
|
message
|
||||||
|
// .from(Config.get('mail.from.address'))
|
||||||
|
.from('tethys@geosphere.at')
|
||||||
|
.to(userEmail)
|
||||||
|
.subject('Test Email')
|
||||||
|
.html('<p>If you received this email, the email configuration seems to be correct.</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.json({ success: true, message: 'Test email sent successfully' });
|
||||||
|
// return response.flash('Test email sent successfully!', 'message').redirect().back();
|
||||||
|
} catch (error) {
|
||||||
|
return response.internalServerError({ message: `Error sending test email: ${error.message}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
import Person from 'App/Models/Person';
|
import Person from '#models/person';
|
||||||
// import Dataset from 'App/Models/Dataset';
|
// import Dataset from 'App/Models/Dataset';
|
||||||
|
|
||||||
// node ace make:controller Author
|
// node ace make:controller Author
|
||||||
export default class AuthorsController {
|
export default class AuthorsController {
|
||||||
public async index({}: HttpContextContract) {
|
public async index({}: HttpContext) {
|
||||||
// select * from gba.persons
|
// 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 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"));
|
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
|
||||||
|
@ -19,7 +19,7 @@ export default class AuthorsController {
|
||||||
return authors;
|
return authors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async persons({ request }: HttpContextContract) {
|
public async persons({ request }: HttpContext) {
|
||||||
const authors = Person.query().where('status', true);
|
const authors = Person.query().where('status', true);
|
||||||
|
|
||||||
if (request.input('filter')) {
|
if (request.input('filter')) {
|
||||||
|
|
65
app/Controllers/Http/Api/AvatarController.ts
Normal file
65
app/Controllers/Http/Api/AvatarController.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
// import * as fs from 'fs';
|
||||||
|
// import * as path from 'path';
|
||||||
|
|
||||||
|
const prefixes = ['von', 'van'];
|
||||||
|
|
||||||
|
// node ace make:controller Author
|
||||||
|
export default class AvatarController {
|
||||||
|
public async generateAvatar({ request, response }: HttpContext) {
|
||||||
|
try {
|
||||||
|
const { name, background, textColor, size } = request.only(['name', 'background', 'textColor', 'size']);
|
||||||
|
|
||||||
|
// Generate initials
|
||||||
|
// const initials = name
|
||||||
|
// .split(' ')
|
||||||
|
// .map((part) => part.charAt(0).toUpperCase())
|
||||||
|
// .join('');
|
||||||
|
const initials = this.getInitials(name);
|
||||||
|
|
||||||
|
// Define SVG content with dynamic values for initials, background color, text color, and size
|
||||||
|
const svgContent = `
|
||||||
|
<svg width="${size || 50}" height="${size || 50}" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="100%" height="100%" fill="#${background || '7F9CF5'}"/>
|
||||||
|
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-weight="bold" font-family="Arial, sans-serif" font-size="${
|
||||||
|
(size / 100) * 40 || 25
|
||||||
|
}" fill="#${textColor || 'ffffff'}">${initials}</text>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Set response headers for SVG content
|
||||||
|
response.header('Content-type', 'image/svg+xml');
|
||||||
|
response.header('Cache-Control', 'no-cache');
|
||||||
|
response.header('Pragma', 'no-cache');
|
||||||
|
response.header('Expires', '0');
|
||||||
|
|
||||||
|
return response.send(svgContent);
|
||||||
|
} catch (error) {
|
||||||
|
return response.status(StatusCodes.OK).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getInitials(name: string) {
|
||||||
|
const parts = name.split(' ');
|
||||||
|
let initials = '';
|
||||||
|
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
const firstName = parts[0];
|
||||||
|
const lastName = parts[parts.length - 1];
|
||||||
|
|
||||||
|
const firstInitial = firstName.charAt(0).toUpperCase();
|
||||||
|
const lastInitial = lastName.charAt(0).toUpperCase();
|
||||||
|
|
||||||
|
if (prefixes.includes(lastName.toLowerCase()) && lastName === lastName.toUpperCase()) {
|
||||||
|
initials = firstInitial + lastName.charAt(1).toUpperCase();
|
||||||
|
} else {
|
||||||
|
initials = firstInitial + lastInitial;
|
||||||
|
}
|
||||||
|
} else if (parts.length === 1) {
|
||||||
|
initials = parts[0].substring(0, 2).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return initials;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
// import Person from 'App/Models/Person';
|
// import Person from 'App/Models/Person';
|
||||||
import Dataset from 'App/Models/Dataset';
|
import Dataset from '#models/dataset';
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
// node ace make:controller Author
|
// node ace make:controller Author
|
||||||
export default class DatasetController {
|
export default class DatasetController {
|
||||||
public async index({}: HttpContextContract) {
|
public async index({}: HttpContext) {
|
||||||
// select * from gba.persons
|
// 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 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"));
|
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
|
||||||
|
@ -14,7 +14,7 @@ export default class DatasetController {
|
||||||
return datasets;
|
return datasets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findAll({ response }: HttpContextContract) {
|
public async findAll({ response }: HttpContext) {
|
||||||
try {
|
try {
|
||||||
const datasets = await Dataset.query()
|
const datasets = await Dataset.query()
|
||||||
.where('server_state', 'published')
|
.where('server_state', 'published')
|
||||||
|
@ -29,7 +29,7 @@ export default class DatasetController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findOne({ params }: HttpContextContract) {
|
public async findOne({ params }: HttpContext) {
|
||||||
const datasets = await Dataset.query()
|
const datasets = await Dataset.query()
|
||||||
.where('publish_id', params.publish_id)
|
.where('publish_id', params.publish_id)
|
||||||
.preload('titles')
|
.preload('titles')
|
||||||
|
|
54
app/Controllers/Http/Api/FileController.ts
Normal file
54
app/Controllers/Http/Api/FileController.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import File from '#models/file';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
// node ace make:controller Author
|
||||||
|
export default class FileController {
|
||||||
|
// @Get("download/:id")
|
||||||
|
public async findOne({ response, params }: HttpContext) {
|
||||||
|
const id = params.id;
|
||||||
|
const file = await File.findOrFail(id);
|
||||||
|
// const file = await File.findOne({
|
||||||
|
// where: { id: id },
|
||||||
|
// });
|
||||||
|
if (file) {
|
||||||
|
const filePath = '/storage/app/public/' + file.pathName;
|
||||||
|
const ext = path.extname(filePath);
|
||||||
|
const fileName = file.label + ext;
|
||||||
|
try {
|
||||||
|
fs.accessSync(filePath, fs.constants.R_OK); //| fs.constants.W_OK);
|
||||||
|
// console.log("can read/write:", path);
|
||||||
|
|
||||||
|
response
|
||||||
|
.header('Cache-Control', 'no-cache private')
|
||||||
|
.header('Content-Description', 'File Transfer')
|
||||||
|
.header('Content-Type', file.mimeType)
|
||||||
|
.header('Content-Disposition', 'inline; filename=' + fileName)
|
||||||
|
.header('Content-Transfer-Encoding', 'binary')
|
||||||
|
.header('Access-Control-Allow-Origin', '*')
|
||||||
|
.header('Access-Control-Allow-Methods', 'GET,POST');
|
||||||
|
|
||||||
|
response.status(StatusCodes.OK).download(filePath);
|
||||||
|
} catch (err) {
|
||||||
|
// console.log("no access:", path);
|
||||||
|
response.status(StatusCodes.NOT_FOUND).send({
|
||||||
|
message: `File with id ${id} doesn't exist on file server`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// res.status(StatusCodes.OK).sendFile(filePath, (err) => {
|
||||||
|
// // res.setHeader("Content-Type", "application/json");
|
||||||
|
// // res.removeHeader("Content-Disposition");
|
||||||
|
// res.status(StatusCodes.NOT_FOUND).send({
|
||||||
|
// message: `File with id ${id} doesn't exist on file server`,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
} else {
|
||||||
|
response.status(StatusCodes.NOT_FOUND).send({
|
||||||
|
message: `Cannot find File with id=${id}.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
import Database from '@ioc:Adonis/Lucid/Database';
|
import db from '@adonisjs/lucid/services/db';
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
export default class HomeController {
|
export default class HomeController {
|
||||||
public async findDocumentsPerYear({ response, params }: HttpContextContract) {
|
public async findDocumentsPerYear({ response, params }: HttpContext) {
|
||||||
const year = params.year;
|
const year = params.year;
|
||||||
const from = parseInt(year);
|
const from = parseInt(year);
|
||||||
const serverState = 'published';
|
const serverState = 'published';
|
||||||
|
@ -17,8 +17,8 @@ export default class HomeController {
|
||||||
// .preload('authors')
|
// .preload('authors')
|
||||||
// .orderBy('server_date_published');
|
// .orderBy('server_date_published');
|
||||||
|
|
||||||
const datasets = await Database.from('documents as doc')
|
const datasets = await db.from('documents as doc')
|
||||||
.select(['publish_id', 'server_date_published', Database.raw(`date_part('year', server_date_published) as pub_year`)])
|
.select(['publish_id', 'server_date_published', db.raw(`date_part('year', server_date_published) as pub_year`)])
|
||||||
.where('server_state', serverState)
|
.where('server_state', serverState)
|
||||||
.innerJoin('link_documents_persons as ba', 'doc.id', 'ba.document_id')
|
.innerJoin('link_documents_persons as ba', 'doc.id', 'ba.document_id')
|
||||||
.andWhereRaw(`date_part('year', server_date_published) = ?`, [from])
|
.andWhereRaw(`date_part('year', server_date_published) = ?`, [from])
|
||||||
|
@ -32,17 +32,17 @@ export default class HomeController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findYears({ response }: HttpContextContract) {
|
public async findYears({ response }: HttpContext) {
|
||||||
const serverState = 'published';
|
const serverState = 'published';
|
||||||
// Use raw SQL queries to select all cars which belongs to the user
|
// Use raw SQL queries to select all cars which belongs to the user
|
||||||
try {
|
try {
|
||||||
const datasets = await Database.rawQuery(
|
const datasets = await db.rawQuery(
|
||||||
'SELECT distinct EXTRACT(YEAR FROM server_date_published) as published_date FROM gba.documents WHERE server_state = ?',
|
'SELECT distinct EXTRACT(YEAR FROM server_date_published) as published_date FROM gba.documents WHERE server_state = ?',
|
||||||
[serverState],
|
[serverState],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pluck the ids of the cars
|
// Pluck the ids of the cars
|
||||||
const years = datasets.rows.map((dataset) => dataset.published_date);
|
const years = datasets.rows.map((dataset: any) => dataset.published_date);
|
||||||
// check if the cars is returned
|
// check if the cars is returned
|
||||||
// if (years.length > 0) {
|
// if (years.length > 0) {
|
||||||
return response.status(StatusCodes.OK).json(years);
|
return response.status(StatusCodes.OK).json(years);
|
||||||
|
@ -53,4 +53,91 @@ export default class HomeController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findPublicationsPerMonth({ response }: HttpContext) {
|
||||||
|
const serverState = 'published';
|
||||||
|
// const year = params.year;
|
||||||
|
// const from = parseInt(year);
|
||||||
|
try {
|
||||||
|
|
||||||
|
// const datasets = await Database.from('documents as doc')
|
||||||
|
// .select([Database.raw(`date_part('month', server_date_published) as pub_month`), Database.raw('COUNT(*) as count')])
|
||||||
|
// .where('server_state', serverState)
|
||||||
|
// .innerJoin('link_documents_persons as ba', 'doc.id', 'ba.document_id')
|
||||||
|
// .andWhereRaw(`date_part('year', server_date_published) = ?`, [from])
|
||||||
|
// .groupBy('pub_month');
|
||||||
|
// // .orderBy('server_date_published');
|
||||||
|
|
||||||
|
const years = [2021, 2022, 2023]; // Add the second year
|
||||||
|
|
||||||
|
const result = await db.from('documents as doc')
|
||||||
|
.select([
|
||||||
|
db.raw(`date_part('year', server_date_published) as pub_year`),
|
||||||
|
db.raw(`date_part('month', server_date_published) as pub_month`),
|
||||||
|
db.raw('COUNT(*) as count'),
|
||||||
|
])
|
||||||
|
.where('server_state', serverState)
|
||||||
|
// .innerJoin('link_documents_persons as ba', 'doc.id', 'ba.document_id')
|
||||||
|
// .whereIn('pub_year', years) // Filter by both years
|
||||||
|
.whereRaw(`date_part('year', server_date_published) IN (${years.join(',')})`) // Filter by both years
|
||||||
|
.groupBy('pub_year', 'pub_month')
|
||||||
|
.orderBy('pub_year', 'asc')
|
||||||
|
.orderBy('pub_month', 'asc');
|
||||||
|
|
||||||
|
const labels = Array.from({ length: 12 }, (_, i) => i + 1); // Assuming 12 months
|
||||||
|
|
||||||
|
const inputDatasets: Map<string, ChartDataset> = result.reduce((acc, item) => {
|
||||||
|
const { pub_year, pub_month, count } = item;
|
||||||
|
|
||||||
|
if (!acc[pub_year]) {
|
||||||
|
acc[pub_year] = {
|
||||||
|
data: Array.from({ length: 12 }).fill(0),
|
||||||
|
label: pub_year.toString(),
|
||||||
|
borderColor: this.getRandomHexColor, // pub_year === 2022 ? '#3e95cd' : '#8e5ea2',
|
||||||
|
fill: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[pub_year].data[pub_month - 1] = parseInt(count);
|
||||||
|
|
||||||
|
return acc ;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const outputDatasets = Object.entries(inputDatasets).map(([year, data]) => ({
|
||||||
|
data: data.data,
|
||||||
|
label: year,
|
||||||
|
borderColor: data.borderColor,
|
||||||
|
fill: data.fill
|
||||||
|
}));
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: labels,
|
||||||
|
datasets: outputDatasets,
|
||||||
|
};
|
||||||
|
|
||||||
|
return response.json(data);
|
||||||
|
} catch (error) {
|
||||||
|
return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
||||||
|
message: error.message || 'Some error occurred while retrieving datasets.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRandomHexColor() {
|
||||||
|
const letters = '0123456789ABCDEF';
|
||||||
|
let color = '#';
|
||||||
|
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
color += letters[Math.floor(Math.random() * 16)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface ChartDataset {
|
||||||
|
data: Array<number>;
|
||||||
|
label: string;
|
||||||
|
borderColor: string;
|
||||||
|
fill: boolean;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
114
app/Controllers/Http/Api/UserController.ts
Normal file
114
app/Controllers/Http/Api/UserController.ts
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import User from '#models/user';
|
||||||
|
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
import { InvalidArgumentException } from 'node-exceptions';
|
||||||
|
import { TotpState } from '#contracts/enums';
|
||||||
|
import BackupCodeStorage, { SecureRandom } from '#services/backup_code_storage';
|
||||||
|
import BackupCode from '#models/backup_code';
|
||||||
|
|
||||||
|
// Here we are generating secret and recovery codes for the user that’s enabling 2FA and storing them to our database.
|
||||||
|
export default class UserController {
|
||||||
|
public async enable({ auth, response, request }: HttpContext) {
|
||||||
|
const user = (await User.find(auth.user?.id)) as User;
|
||||||
|
// await user.load('totp_secret');
|
||||||
|
// if (!user.totp_secret) {
|
||||||
|
// let totpSecret = new TotpSecret();
|
||||||
|
// user.related('totp_secret').save(totpSecret);
|
||||||
|
// await user.load('totp_secret');
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('user not available');
|
||||||
|
}
|
||||||
|
const state: number = request.input('state');
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (state) {
|
||||||
|
case TotpState.STATE_DISABLED:
|
||||||
|
// user.twoFactorSecret = null;
|
||||||
|
// user.twoFactorRecoveryCodes = null;
|
||||||
|
await BackupCode.deleteCodes(user);
|
||||||
|
user.twoFactorSecret = '';
|
||||||
|
// user.twoFactorRecoveryCodes = [''];
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
user.state = TotpState.STATE_DISABLED;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
let storage = new BackupCodeStorage(new SecureRandom());
|
||||||
|
let backupState = await storage.getBackupCodesState(user);
|
||||||
|
|
||||||
|
return response.status(StatusCodes.OK).json({
|
||||||
|
state: TotpState.STATE_DISABLED,
|
||||||
|
backupState: backupState,
|
||||||
|
});
|
||||||
|
case TotpState.STATE_CREATED:
|
||||||
|
user.twoFactorSecret = TwoFactorAuthProvider.generateSecret(user);
|
||||||
|
user.state = TotpState.STATE_CREATED;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
let qrcode = await TwoFactorAuthProvider.generateQrCode(user);
|
||||||
|
// throw new InvalidArgumentException('code is missing');
|
||||||
|
return response.status(StatusCodes.OK).json({
|
||||||
|
state: user.state,
|
||||||
|
secret: user.twoFactorSecret,
|
||||||
|
url: qrcode.url,
|
||||||
|
svg: qrcode.svg,
|
||||||
|
});
|
||||||
|
case TotpState.STATE_ENABLED:
|
||||||
|
let code: string = request.input('code');
|
||||||
|
if (!code) {
|
||||||
|
throw new InvalidArgumentException('code is missing');
|
||||||
|
}
|
||||||
|
const success = await TwoFactorAuthProvider.enable(user, code);
|
||||||
|
|
||||||
|
return response.status(StatusCodes.OK).json({
|
||||||
|
state: success ? TotpState.STATE_ENABLED : TotpState.STATE_CREATED,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Invalid TOTP state');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
|
||||||
|
message: 'Invalid TOTP state',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async fetchRecoveryCodes({ auth, view }) {
|
||||||
|
// const user = auth?.user;
|
||||||
|
|
||||||
|
// return view.render('pages/settings', {
|
||||||
|
// twoFactorEnabled: user.isTwoFactorEnabled,
|
||||||
|
// recoveryCodes: user.twoFactorRecoveryCodes,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @PasswordConfirmationRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public async createCodes({ auth, response }: HttpContext) {
|
||||||
|
// $user = $this->userSession->getUser();
|
||||||
|
const user = (await User.find(auth.user?.id)) as User;
|
||||||
|
|
||||||
|
// let codes = TwoFactorAuthProvider.generateRecoveryCodes();
|
||||||
|
let storage = new BackupCodeStorage(new SecureRandom());
|
||||||
|
// $codes = $this->storage->createCodes($user);
|
||||||
|
const codes = await storage.createCodes(user);
|
||||||
|
|
||||||
|
let backupState = await storage.getBackupCodesState(user);
|
||||||
|
// return new JSONResponse([
|
||||||
|
// 'codes' => $codes,
|
||||||
|
// 'state' => $this->storage->getBackupCodesState($user),
|
||||||
|
// ]);
|
||||||
|
return response.status(StatusCodes.OK).json({
|
||||||
|
codes: codes,
|
||||||
|
// state: success ? TotpState.STATE_ENABLED : TotpState.STATE_CREATED,
|
||||||
|
backupState: backupState, //storage.getBackupCodesState(user),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,55 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
// import User from 'App/Models/User';
|
import User from '#models/user';
|
||||||
|
import BackupCode from '#models/backup_code';
|
||||||
// import Hash from '@ioc:Adonis/Core/Hash';
|
// import Hash from '@ioc:Adonis/Core/Hash';
|
||||||
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
|
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
|
||||||
import AuthValidator from 'App/Validators/AuthValidator';
|
import { authValidator } from '#validators/auth';
|
||||||
|
import hash from '@adonisjs/core/services/hash';
|
||||||
|
|
||||||
|
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||||
|
// import { Authenticator } from '@adonisjs/auth';
|
||||||
|
// import { LoginState } from 'Contracts/enums';
|
||||||
|
// import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
|
// interface MyHttpsContext extends HttpContext {
|
||||||
|
// auth: Authenticator<User>
|
||||||
|
// }
|
||||||
export default class AuthController {
|
export default class AuthController {
|
||||||
// login function
|
// login function{ request, auth, response }:HttpContext
|
||||||
public async login({ request, response, auth, session }: HttpContextContract) {
|
public async login({ request, response, auth, session }: HttpContext) {
|
||||||
// console.log({
|
// console.log({
|
||||||
// registerBody: request.body(),
|
// registerBody: request.body(),
|
||||||
// });
|
// });
|
||||||
await request.validate(AuthValidator);
|
// await request.validate(AuthValidator);
|
||||||
|
await request.validateUsing(authValidator);
|
||||||
|
|
||||||
const plainPassword = await request.input('password');
|
// const plainPassword = await request.input('password');
|
||||||
const email = await request.input('email');
|
// const email = await request.input('email');
|
||||||
// grab uid and password values off request body
|
// grab uid and password values off request body
|
||||||
// const { email, password } = request.only(['email', 'password'])
|
const { email, password } = request.only(['email', 'password']);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// attempt to login
|
// // attempt to verify credential and login user
|
||||||
await auth.use('web').attempt(email, plainPassword);
|
// await auth.use('web').attempt(email, plainPassword);
|
||||||
|
|
||||||
|
// const user = await auth.use('web').verifyCredentials(email, password);
|
||||||
|
const user = await User.verifyCredentials(email, password);
|
||||||
|
|
||||||
|
if (user.isTwoFactorEnabled) {
|
||||||
|
// session.put("login.id", user.id);
|
||||||
|
// return view.render("pages/two-factor-challenge");
|
||||||
|
|
||||||
|
session.flash('user_id', user.id);
|
||||||
|
return response.redirect().back();
|
||||||
|
|
||||||
|
// let state = LoginState.STATE_VALIDATED;
|
||||||
|
// return response.status(StatusCodes.OK).json({
|
||||||
|
// state: state,
|
||||||
|
// new_user_id: user.id,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
await auth.use('web').login(user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// if login fails, return vague form message and redirect back
|
// if login fails, return vague form message and redirect back
|
||||||
session.flash('message', 'Your username, email, or password is incorrect');
|
session.flash('message', 'Your username, email, or password is incorrect');
|
||||||
|
@ -27,14 +57,68 @@ export default class AuthController {
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, redirect todashboard
|
// otherwise, redirect todashboard
|
||||||
response.redirect('/dashboard');
|
response.redirect('/apps/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async twoFactorChallenge({ request, session, auth, response }: HttpContext) {
|
||||||
|
const { code, backup_code, login_id } = request.only(['code', 'backup_code', 'login_id']);
|
||||||
|
const user = await User.query().where('id', login_id).firstOrFail();
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
const isValid = await TwoFactorAuthProvider.validate(user, code);
|
||||||
|
if (isValid) {
|
||||||
|
// login user and redirect to dashboard
|
||||||
|
await auth.use('web').login(user);
|
||||||
|
response.redirect('/apps/dashboard');
|
||||||
|
} else {
|
||||||
|
session.flash('message', 'Your two-factor code is incorrect');
|
||||||
|
return response.redirect().back();
|
||||||
|
}
|
||||||
|
} else if (backup_code) {
|
||||||
|
const codes: BackupCode[] = await user.getBackupCodes();
|
||||||
|
|
||||||
|
// const verifiedBackupCodes = await Promise.all(
|
||||||
|
// codes.map(async (backupCode) => {
|
||||||
|
// let isVerified = await hash.verify(backupCode.code, backup_code);
|
||||||
|
// if (isVerified) {
|
||||||
|
// return backupCode;
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
// const backupCodeToDelete = verifiedBackupCodes.find(Boolean);
|
||||||
|
|
||||||
|
let backupCodeToDelete = null;
|
||||||
|
for (const backupCode of codes) {
|
||||||
|
const isVerified = await hash.verify(backupCode.code, backup_code);
|
||||||
|
if (isVerified) {
|
||||||
|
backupCodeToDelete = backupCode;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backupCodeToDelete) {
|
||||||
|
if (backupCodeToDelete.used === false) {
|
||||||
|
backupCodeToDelete.used = true;
|
||||||
|
await backupCodeToDelete.save();
|
||||||
|
console.log(`BackupCode with id ${backupCodeToDelete.id} has been marked as used.`);
|
||||||
|
await auth.use('web').login(user);
|
||||||
|
response.redirect('/apps/dashboard');
|
||||||
|
} else {
|
||||||
|
session.flash('message', 'BackupCode already used');
|
||||||
|
return response.redirect().back();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session.flash('message', 'BackupCode not found');
|
||||||
|
return response.redirect().back();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// logout function
|
// logout function
|
||||||
public async logout({ auth, response }: HttpContextContract) {
|
public async logout({ auth, response }: HttpContext) {
|
||||||
// await auth.logout();
|
// await auth.logout();
|
||||||
await auth.use('web').logout();
|
await auth.use('web').logout();
|
||||||
response.redirect('/app/login');
|
return response.redirect('/app/login');
|
||||||
// return response.status(200);
|
// return response.status(200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
138
app/Controllers/Http/Auth/UserController.ts
Normal file
138
app/Controllers/Http/Auth/UserController.ts
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import User from '#models/user';
|
||||||
|
// import { RenderResponse } from '@ioc:EidelLev/Inertia';
|
||||||
|
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||||
|
import hash from '@adonisjs/core/services/hash';
|
||||||
|
// import { schema, rules } from '@adonisjs/validator';
|
||||||
|
import vine from '@vinejs/vine';
|
||||||
|
import BackupCodeStorage, { SecureRandom } from '#services/backup_code_storage';
|
||||||
|
|
||||||
|
// Here we are generating secret and recovery codes for the user that’s enabling 2FA and storing them to our database.
|
||||||
|
export default class UserController {
|
||||||
|
/**
|
||||||
|
* Show the user a form to change their personal information & password.
|
||||||
|
*
|
||||||
|
* @return — \Inertia\Response
|
||||||
|
*/
|
||||||
|
public async accountInfo({ inertia, auth }: HttpContext) {
|
||||||
|
// const user = auth.user;
|
||||||
|
const user = (await User.find(auth.user?.id)) as User;
|
||||||
|
// const id = request.param('id');
|
||||||
|
// const user = await User.query().where('id', id).firstOrFail();
|
||||||
|
|
||||||
|
let storage = new BackupCodeStorage(new SecureRandom());
|
||||||
|
// const codes= user.isTwoFactorEnabled? (await user.getBackupCodes()).map((role) => role.code) : [];
|
||||||
|
let backupState = await storage.getBackupCodesState(user);
|
||||||
|
|
||||||
|
return inertia.render('Auth/AccountInfo', {
|
||||||
|
user: user,
|
||||||
|
twoFactorEnabled: user.isTwoFactorEnabled,
|
||||||
|
// code: await TwoFactorAuthProvider.generateQrCode(user),
|
||||||
|
backupState: backupState,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async accountInfoStore({ auth, request, response, session }: HttpContext) {
|
||||||
|
// const passwordSchema = schema.create({
|
||||||
|
// old_password: schema.string({ trim: true }, [rules.required()]),
|
||||||
|
// new_password: schema.string({ trim: true }, [rules.minLength(8), rules.maxLength(255), rules.confirmed('confirm_password')]),
|
||||||
|
// confirm_password: schema.string({ trim: true }, [rules.required()]),
|
||||||
|
// });
|
||||||
|
const passwordSchema = vine.object({
|
||||||
|
// first step
|
||||||
|
old_password: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^[a-zA-Z0-9]+$/),
|
||||||
|
new_password: vine.string().confirmed({ confirmationField: 'confirm_password' }).trim().minLength(8).maxLength(255),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// await request.validate({ schema: passwordSchema });
|
||||||
|
const validator = vine.compile(passwordSchema);
|
||||||
|
await request.validateUsing(validator);
|
||||||
|
} catch (error) {
|
||||||
|
// return response.badRequest(error.messages);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await auth.user as User;
|
||||||
|
const { old_password, new_password } = request.only(['old_password', 'new_password']);
|
||||||
|
|
||||||
|
// if (!(old_password && new_password && confirm_password)) {
|
||||||
|
// return response.status(400).send({ warning: 'Old password and new password are required.' });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Verify if the provided old password matches the user's current password
|
||||||
|
const isSame = await hash.verify(user.password, old_password);
|
||||||
|
if (!isSame) {
|
||||||
|
return response.flash('warning', 'Old password is incorrect.').redirect().back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the new password before updating the user's password
|
||||||
|
user.password = new_password;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
// return response.status(200).send({ message: 'Password updated successfully.' });
|
||||||
|
session.flash({ message: 'Password updated successfully.' });
|
||||||
|
return response.redirect().toRoute('settings.user');
|
||||||
|
} catch (error) {
|
||||||
|
// return response.status(500).send({ message: 'Internal server error.' });
|
||||||
|
return response.flash('warning', `Invalid server state. Internal server error.`).redirect().back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async enableTwoFactorAuthentication({ auth, response, session }: HttpContext): Promise<void> {
|
||||||
|
// const user: User | undefined = auth?.user;
|
||||||
|
const user = (await User.find(auth.user?.id)) as User;
|
||||||
|
|
||||||
|
user.twoFactorSecret = TwoFactorAuthProvider.generateSecret(user);
|
||||||
|
user.twoFactorRecoveryCodes = await TwoFactorAuthProvider.generateRecoveryCodes();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
session.flash('message', 'Two factor authentication enabled.');
|
||||||
|
return response.redirect().back();
|
||||||
|
// return inertia.render('Auth/AccountInfo', {
|
||||||
|
// // status: {
|
||||||
|
// // type: 'success',
|
||||||
|
// // message: 'Two factor authentication enabled.',
|
||||||
|
// // },
|
||||||
|
// user: user,
|
||||||
|
// twoFactorEnabled: user.isTwoFactorEnabled,
|
||||||
|
// code: await TwoFactorAuthProvider.generateQrCode(user),
|
||||||
|
// recoveryCodes: user.twoFactorRecoveryCodes,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disableTwoFactorAuthentication({ auth, response, session }: HttpContext): Promise<void> {
|
||||||
|
const user: User | undefined = auth.user;
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
user.twoFactorSecret = null;
|
||||||
|
user.twoFactorRecoveryCodes = null;
|
||||||
|
await user.save();
|
||||||
|
session.flash('message', 'Two-factor authentication disabled.');
|
||||||
|
} else {
|
||||||
|
session.flash('error', 'User not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.redirect().back();
|
||||||
|
// return inertia.render('Auth/AccountInfo', {
|
||||||
|
// // status: {
|
||||||
|
// // type: 'success',
|
||||||
|
// // message: 'Two factor authentication disabled.',
|
||||||
|
// // },
|
||||||
|
// user: user,
|
||||||
|
// twoFactorEnabled: user.isTwoFactorEnabled,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async fetchRecoveryCodes({ auth, view }) {
|
||||||
|
// const user = auth?.user;
|
||||||
|
|
||||||
|
// return view.render('pages/settings', {
|
||||||
|
// twoFactorEnabled: user.isTwoFactorEnabled,
|
||||||
|
// recoveryCodes: user.twoFactorRecoveryCodes,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
682
app/Controllers/Http/Editor/DatasetController.ts
Normal file
682
app/Controllers/Http/Editor/DatasetController.ts
Normal file
|
@ -0,0 +1,682 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import { Client } from '@opensearch-project/opensearch';
|
||||||
|
import User from '#models/user';
|
||||||
|
import Dataset from '#models/dataset';
|
||||||
|
import DatasetIdentifier from '#models/dataset_identifier';
|
||||||
|
import XmlModel from '#app/Library/XmlModel';
|
||||||
|
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
|
||||||
|
import { create } from 'xmlbuilder2';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import SaxonJS from 'saxon-js';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import Index from '#app/Library/Utils/Index';
|
||||||
|
import { getDomain } from '#app/utils/utility-functions';
|
||||||
|
import { DoiClient } from '#app/Library/Doi/DoiClient';
|
||||||
|
import DoiClientException from '#app/exceptions/DoiClientException';
|
||||||
|
import logger from '@adonisjs/core/services/logger';
|
||||||
|
import { HttpException } from 'node-exceptions';
|
||||||
|
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
||||||
|
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
||||||
|
import mail from '@adonisjs/mail/services/main';
|
||||||
|
// import { resolveMx } from 'dns/promises';
|
||||||
|
// import * as net from 'net';
|
||||||
|
import { validate } from 'deep-email-validator';
|
||||||
|
// Create a new instance of the client
|
||||||
|
const client = new Client({ node: 'http://localhost:9200' }); // replace with your OpenSearch endpoint
|
||||||
|
|
||||||
|
export default class DatasetsController {
|
||||||
|
private proc;
|
||||||
|
public messages = {
|
||||||
|
// 'required': '{{ field }} is required',
|
||||||
|
// 'licenses.minLength': 'at least {{ options.minLength }} permission must be defined',
|
||||||
|
'reviewer_id.required': 'reviewer_id must be defined',
|
||||||
|
'publisher_name.required': 'publisher name must be defined',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.proc = readFileSync('public/assets2/solr.sef.json');
|
||||||
|
// Load the XSLT file
|
||||||
|
// this.proc = readFileSync('public/assets2/datasetxml2oai.sef.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async index({}: HttpContextContract) {}
|
||||||
|
public async index({ auth, request, inertia }: HttpContext) {
|
||||||
|
const user = (await User.find(auth.user?.id)) as User;
|
||||||
|
const page = request.input('page', 1);
|
||||||
|
let datasets: ModelQueryBuilderContract<typeof Dataset, Dataset> = Dataset.query();
|
||||||
|
|
||||||
|
// if (request.input('search')) {
|
||||||
|
// // users = users.whereRaw('name like %?%', [request.input('search')])
|
||||||
|
// const searchTerm = request.input('search');
|
||||||
|
// datasets.where('name', 'ilike', `%${searchTerm}%`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (request.input('sort')) {
|
||||||
|
type SortOrder = 'asc' | 'desc' | undefined;
|
||||||
|
let attribute = request.input('sort');
|
||||||
|
let sortOrder: SortOrder = 'asc';
|
||||||
|
|
||||||
|
if (attribute.substr(0, 1) === '-') {
|
||||||
|
sortOrder = 'desc';
|
||||||
|
// attribute = substr(attribute, 1);
|
||||||
|
attribute = attribute.substr(1);
|
||||||
|
}
|
||||||
|
datasets.orderBy(attribute, sortOrder);
|
||||||
|
} else {
|
||||||
|
// users.orderBy('created_at', 'desc');
|
||||||
|
datasets.orderBy('id', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// const users = await User.query().orderBy('login').paginate(page, limit);
|
||||||
|
const myDatasets = await datasets
|
||||||
|
.where('server_state', 'released')
|
||||||
|
.orWhere((dQuery) => {
|
||||||
|
dQuery
|
||||||
|
.whereIn('server_state', ['editor_accepted', 'rejected_reviewer', 'reviewed', 'published'])
|
||||||
|
.where('editor_id', user.id)
|
||||||
|
.doesntHave('identifier', 'and');
|
||||||
|
})
|
||||||
|
// .preload('identifier')
|
||||||
|
.preload('titles')
|
||||||
|
.preload('user', (query) => query.select('id', 'login'))
|
||||||
|
.preload('editor', (query) => query.select('id', 'login'))
|
||||||
|
.paginate(page, 10);
|
||||||
|
|
||||||
|
return inertia.render('Editor/Dataset/Index', {
|
||||||
|
datasets: myDatasets.serialize(),
|
||||||
|
filters: request.all(),
|
||||||
|
can: {
|
||||||
|
receive: await auth.user?.can(['dataset-receive']),
|
||||||
|
approve: await auth.user?.can(['dataset-approve']),
|
||||||
|
reject: await auth.user?.can(['dataset-editor-reject']),
|
||||||
|
edit: await auth.user?.can(['dataset-editor-update']),
|
||||||
|
delete: await auth.user?.can(['dataset-editor-delete']),
|
||||||
|
publish: await auth.user?.can(['dataset-publish']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async receive({ request, inertia, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
.preload('titles')
|
||||||
|
.preload('descriptions')
|
||||||
|
.preload('user', (builder) => {
|
||||||
|
builder.select('id', 'login');
|
||||||
|
})
|
||||||
|
|
||||||
|
.firstOrFail();
|
||||||
|
|
||||||
|
const validStates = ['released'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// session.flash('errors', 'Invalid server state!');
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be received. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return inertia.render('Editor/Dataset/Receive', {
|
||||||
|
dataset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async receiveUpdate({ auth, request, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
// const { id } = params;
|
||||||
|
const dataset = await Dataset.findOrFail(id);
|
||||||
|
|
||||||
|
const validStates = ['released'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// throw new Error('Invalid server state!');
|
||||||
|
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be received by editor. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.toRoute('editor.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset.server_state = 'editor_accepted';
|
||||||
|
const user = (await User.find(auth.user?.id)) as User;
|
||||||
|
// dataset.editor().associate(user).save();
|
||||||
|
try {
|
||||||
|
await dataset.related('editor').associate(user); // speichert schon ab
|
||||||
|
// await dataset.save();
|
||||||
|
return response.toRoute('editor.dataset.list').flash(`You have accepted dataset ${dataset.id}!`, 'message');
|
||||||
|
} catch (error) {
|
||||||
|
// Handle any errors
|
||||||
|
console.error(error);
|
||||||
|
return response.status(500).json({ error: 'An error occurred while accepting the data.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async approve({ request, inertia, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
// $dataset = Dataset::with('user:id,login')->findOrFail($id);
|
||||||
|
const dataset = await Dataset.findOrFail(id);
|
||||||
|
|
||||||
|
const validStates = ['editor_accepted', 'rejected_reviewer'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// session.flash('errors', 'Invalid server state!');
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be approved. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const reviewers = await User.query()
|
||||||
|
.whereHas('roles', (builder) => {
|
||||||
|
builder.where('name', 'reviewer');
|
||||||
|
})
|
||||||
|
.pluck('login', 'id');
|
||||||
|
|
||||||
|
return inertia.render('Editor/Dataset/Approve', {
|
||||||
|
dataset,
|
||||||
|
reviewers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async approveUpdate({ request, response }: HttpContext) {
|
||||||
|
const approveDatasetSchema = vine.object({
|
||||||
|
reviewer_id: vine.number(),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// await request.validate({ schema: approveDatasetSchema, messages: this.messages });
|
||||||
|
const validator = vine.compile(approveDatasetSchema);
|
||||||
|
await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
|
||||||
|
} catch (error) {
|
||||||
|
// return response.badRequest(error.messages);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.findOrFail(id);
|
||||||
|
|
||||||
|
const validStates = ['editor_accepted', 'rejected_reviewer'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// session.flash('errors', 'Invalid server state!');
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be approved. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset.server_state = 'approved';
|
||||||
|
if (dataset.reject_reviewer_note != null) {
|
||||||
|
dataset.reject_reviewer_note = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//save main and additional titles
|
||||||
|
const reviewer_id = request.input('reviewer_id', null);
|
||||||
|
dataset.reviewer_id = reviewer_id;
|
||||||
|
|
||||||
|
if (await dataset.save()) {
|
||||||
|
return response.toRoute('editor.dataset.list').flash('message', 'You have approved one dataset!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reject({ request, inertia, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
// .preload('titles')
|
||||||
|
// .preload('descriptions')
|
||||||
|
.preload('user', (builder) => {
|
||||||
|
builder.select('id', 'login', 'email');
|
||||||
|
})
|
||||||
|
.firstOrFail();
|
||||||
|
|
||||||
|
const validStates = ['editor_accepted', 'rejected_reviewer'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// session.flash('errors', 'Invalid server state!');
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.toRoute('editor.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
return inertia.render('Editor/Dataset/Reject', {
|
||||||
|
dataset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// private async checkEmailDomain(email: string): Promise<boolean> {
|
||||||
|
// const domain = email.split('@')[1];
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// // Step 1: Check MX records for the domain
|
||||||
|
// const mxRecords = await resolveMx(domain);
|
||||||
|
// if (mxRecords.length === 0) {
|
||||||
|
// return false; // No MX records, can't send email
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Sort MX records by priority
|
||||||
|
// mxRecords.sort((a, b) => a.priority - b.priority);
|
||||||
|
|
||||||
|
// // Step 2: Attempt SMTP connection to the first available mail server
|
||||||
|
// const smtpServer = mxRecords[0].exchange;
|
||||||
|
|
||||||
|
// return await this.checkMailboxExists(smtpServer, email);
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error during MX lookup or SMTP validation:', error);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
//// Helper function to check if the mailbox exists using SMTP
|
||||||
|
// private async checkMailboxExists(smtpServer: string, email: string): Promise<boolean> {
|
||||||
|
// return new Promise((resolve, reject) => {
|
||||||
|
// const socket = net.createConnection(25, smtpServer);
|
||||||
|
|
||||||
|
// socket.on('connect', () => {
|
||||||
|
// socket.write(`HELO ${smtpServer}\r\n`);
|
||||||
|
// socket.write(`MAIL FROM: <test@example.com>\r\n`);
|
||||||
|
// socket.write(`RCPT TO: <${email}>\r\n`);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// socket.on('data', (data) => {
|
||||||
|
// const response = data.toString();
|
||||||
|
// if (response.includes('250')) {
|
||||||
|
// // 250 is an SMTP success code
|
||||||
|
// socket.end();
|
||||||
|
// resolve(true); // Email exists
|
||||||
|
// } else if (response.includes('550')) {
|
||||||
|
// // 550 means the mailbox doesn't exist
|
||||||
|
// socket.end();
|
||||||
|
// resolve(false); // Email doesn't exist
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// socket.on('error', (error) => {
|
||||||
|
// console.error('SMTP connection error:', error);
|
||||||
|
// socket.end();
|
||||||
|
// resolve(false);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// socket.on('end', () => {
|
||||||
|
// // SMTP connection closed
|
||||||
|
// });
|
||||||
|
|
||||||
|
// socket.setTimeout(5000, () => {
|
||||||
|
// // Timeout after 5 seconds
|
||||||
|
// socket.end();
|
||||||
|
// resolve(false); // Assume email doesn't exist if no response
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
public async rejectUpdate({ request, response, auth }: HttpContext) {
|
||||||
|
const authUser = auth.user!;
|
||||||
|
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
.preload('user', (builder) => {
|
||||||
|
builder.select('id', 'login', 'email');
|
||||||
|
})
|
||||||
|
.firstOrFail();
|
||||||
|
|
||||||
|
const newSchema = vine.object({
|
||||||
|
server_state: vine.string().trim(),
|
||||||
|
reject_editor_note: vine.string().trim().minLength(10).maxLength(500),
|
||||||
|
send_mail: vine.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// await request.validate({ schema: newSchema });
|
||||||
|
const validator = vine.compile(newSchema);
|
||||||
|
await request.validateUsing(validator);
|
||||||
|
} catch (error) {
|
||||||
|
// return response.badRequest(error.messages);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validStates = ['editor_accepted', 'rejected_reviewer'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// throw new Error('Invalid server state!');
|
||||||
|
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
|
||||||
|
'warning'
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.toRoute('editor.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset.server_state = 'rejected_editor';
|
||||||
|
const rejectEditorNote = request.input('reject_editor_note', '');
|
||||||
|
dataset.reject_editor_note = rejectEditorNote;
|
||||||
|
|
||||||
|
// add logic for sending reject message
|
||||||
|
const sendMail = request.input('send_email', false);
|
||||||
|
// const validRecipientEmail = await this.checkEmailDomain('arno.kaimbacher@outlook.at');
|
||||||
|
const validationResult = await validate({
|
||||||
|
email: dataset.user.email,
|
||||||
|
validateSMTP: false,
|
||||||
|
});
|
||||||
|
const validRecipientEmail: boolean = validationResult.valid;
|
||||||
|
|
||||||
|
let emailStatusMessage = '';
|
||||||
|
|
||||||
|
if (sendMail == true) {
|
||||||
|
if (dataset.user.email && validRecipientEmail) {
|
||||||
|
try {
|
||||||
|
await mail.send((message) => {
|
||||||
|
message.to(dataset.user.email).subject('Dataset Rejection Notification').html(`
|
||||||
|
<p>Dear ${dataset.user.login},</p>
|
||||||
|
<p>Your dataset with ID ${dataset.id} has been rejected.</p>
|
||||||
|
<p>Reason for rejection: ${rejectEditorNote}</p>
|
||||||
|
<p>Best regards,<br>Your Tethys editor: ${authUser.login}</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
emailStatusMessage = ` A rejection email was successfully sent to ${dataset.user.email}.`;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return response.flash('Dataset has not been rejected due to an email error: ' + error.message, 'error').toRoute('editor.dataset.list');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emailStatusMessage = ` However, the email could not be sent because the submitter's email address (${dataset.user.email}) is not valid.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dataset.save();
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
`You have successfully rejected dataset ${dataset.id} submitted by ${dataset.user.login}.${emailStatusMessage}`,
|
||||||
|
'message',
|
||||||
|
)
|
||||||
|
.toRoute('editor.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async publish({ request, inertia, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
.preload('titles')
|
||||||
|
.preload('authors')
|
||||||
|
// .preload('persons', (builder) => {
|
||||||
|
// builder.wherePivot('role', 'author')
|
||||||
|
// })
|
||||||
|
.firstOrFail();
|
||||||
|
|
||||||
|
const validStates = ['reviewed'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// session.flash('errors', 'Invalid server state!');
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be published. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return inertia.render('Editor/Dataset/Publish', {
|
||||||
|
dataset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async publishUpdate({ request, response }: HttpContext) {
|
||||||
|
const publishDatasetSchema = vine.object({
|
||||||
|
publisher_name: vine.string().trim(),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// await request.validate({ schema: publishDatasetSchema, messages: this.messages });
|
||||||
|
const validator = vine.compile(publishDatasetSchema);
|
||||||
|
await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.findOrFail(id);
|
||||||
|
|
||||||
|
// let test = await Dataset.getMax('publish_id');
|
||||||
|
// const maxPublishId = await Database.from('documents').max('publish_id as max_publish_id').first();
|
||||||
|
// const max = maxPublishId.max_publish_id;
|
||||||
|
const max = await Dataset.getMax('publish_id');
|
||||||
|
let publish_id = 0;
|
||||||
|
if (max != null) {
|
||||||
|
publish_id = max + 1;
|
||||||
|
} else {
|
||||||
|
publish_id = publish_id + 1;
|
||||||
|
}
|
||||||
|
dataset.publish_id = publish_id;
|
||||||
|
dataset.server_state = 'published';
|
||||||
|
dataset.server_date_published = DateTime.now();
|
||||||
|
|
||||||
|
const publisherName = request.input('publisher_name', 'Tethys');
|
||||||
|
dataset.publisher_name = publisherName;
|
||||||
|
|
||||||
|
if (await dataset.save()) {
|
||||||
|
const index_name = 'tethys-records';
|
||||||
|
await Index.indexDocument(dataset, index_name);
|
||||||
|
return response.toRoute('editor.dataset.list').flash('message', 'You have successfully published the dataset!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async doiCreate({ request, inertia }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
.preload('titles')
|
||||||
|
.preload('descriptions')
|
||||||
|
// .preload('identifier')
|
||||||
|
.preload('authors')
|
||||||
|
.firstOrFail();
|
||||||
|
return inertia.render('Editor/Dataset/Doi', {
|
||||||
|
dataset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async doiStore({ request, response }: HttpContext) {
|
||||||
|
const dataId = request.param('publish_id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
// .preload('xmlCache')
|
||||||
|
.where('publish_id', dataId)
|
||||||
|
.firstOrFail();
|
||||||
|
const xmlMeta = (await Index.getDoiRegisterString(dataset)) as string;
|
||||||
|
|
||||||
|
let prefix = '';
|
||||||
|
let base_domain = '';
|
||||||
|
// const datacite_environment = process.env.DATACITE_ENVIRONMENT || 'debug';
|
||||||
|
prefix = process.env.DATACITE_PREFIX || '';
|
||||||
|
base_domain = process.env.BASE_DOMAIN || '';
|
||||||
|
|
||||||
|
// register DOI:
|
||||||
|
const doiValue = prefix + '/tethys.' + dataset.publish_id; //'10.21388/tethys.213'
|
||||||
|
const landingPageUrl = 'https://doi.' + getDomain(base_domain) + '/' + prefix + '/tethys.' + dataset.publish_id; //https://doi.dev.tethys.at/10.21388/tethys.213
|
||||||
|
const doiClient = new DoiClient();
|
||||||
|
const dataciteResponse = await doiClient.registerDoi(doiValue, xmlMeta, landingPageUrl);
|
||||||
|
|
||||||
|
if (dataciteResponse?.status === 201) {
|
||||||
|
// if response OK 201; save the Identifier value into db
|
||||||
|
const doiIdentifier = new DatasetIdentifier();
|
||||||
|
doiIdentifier.value = doiValue;
|
||||||
|
doiIdentifier.dataset_id = dataset.id;
|
||||||
|
doiIdentifier.type = 'doi';
|
||||||
|
doiIdentifier.status = 'findable';
|
||||||
|
// save modified date of datset for re-caching model in db an update the search index
|
||||||
|
dataset.server_date_modified = DateTime.now();
|
||||||
|
|
||||||
|
// save updated dataset to db an index to OpenSearch
|
||||||
|
try {
|
||||||
|
await dataset.related('identifier').save(doiIdentifier);
|
||||||
|
const index_name = 'tethys-records';
|
||||||
|
await Index.indexDocument(dataset, index_name);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`${__filename}: Indexing document ${dataset.id} failed: ${error.message}`);
|
||||||
|
// Log the error or handle it as needed
|
||||||
|
throw new HttpException(error.message);
|
||||||
|
}
|
||||||
|
return response.toRoute('editor.dataset.list').flash('message', 'You have successfully created a DOI for the dataset!');
|
||||||
|
} else {
|
||||||
|
const message = `Unexpected DataCite MDS response code ${dataciteResponse?.status}`;
|
||||||
|
// Log the error or handle it as needed
|
||||||
|
throw new DoiClientException(dataciteResponse?.status, message);
|
||||||
|
}
|
||||||
|
// return response.toRoute('editor.dataset.list').flash('message', xmlMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async show({}: HttpContext) {}
|
||||||
|
|
||||||
|
public async edit({}: HttpContext) {}
|
||||||
|
|
||||||
|
// public async update({}: HttpContextContract) {}
|
||||||
|
public async update({ response }: HttpContext) {
|
||||||
|
const id = 273; //request.param('id');
|
||||||
|
const dataset = await Dataset.query().preload('xmlCache').where('id', id).firstOrFail();
|
||||||
|
// add xml elements
|
||||||
|
let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
||||||
|
const datasetNode = xml.root().ele('Dataset');
|
||||||
|
await this.createXmlRecord(dataset, datasetNode);
|
||||||
|
// const domNode = await this.getDatasetXmlDomNode(dataset);
|
||||||
|
// const xmlString = xml.end({ prettyPrint: true });
|
||||||
|
|
||||||
|
// const data = request.only(['field1', 'field2']); // get it from xslt
|
||||||
|
|
||||||
|
// Create an index with non-default settings.
|
||||||
|
var index_name = 'tethys-features';
|
||||||
|
|
||||||
|
const xmlString = xml.end({ prettyPrint: false });
|
||||||
|
let doc = '';
|
||||||
|
try {
|
||||||
|
const result = await SaxonJS.transform({
|
||||||
|
// stylesheetFileName: `${config.TMP_BASE_DIR}/data-quality/rules/iati.sef.json`,
|
||||||
|
stylesheetText: this.proc,
|
||||||
|
destination: 'serialized',
|
||||||
|
// sourceFileName: sourceFile,
|
||||||
|
sourceText: xmlString,
|
||||||
|
// stylesheetParams: xsltParameter,
|
||||||
|
// logLevel: 10,
|
||||||
|
});
|
||||||
|
doc = result.principalResult;
|
||||||
|
} catch (error) {
|
||||||
|
return response.status(500).json({
|
||||||
|
message: 'An error occurred while creating the user',
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// var settings = {
|
||||||
|
// settings: {
|
||||||
|
// index: {
|
||||||
|
// number_of_shards: 4,
|
||||||
|
// number_of_replicas: 3,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// var test = await client.indices.create({
|
||||||
|
// index: index_name,
|
||||||
|
// body: settings,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// var document = {
|
||||||
|
// title: 'Sample Document',
|
||||||
|
// authors: [
|
||||||
|
// {
|
||||||
|
// first_name: 'John',
|
||||||
|
// last_name: 'Doe',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// first_name: 'Jane',
|
||||||
|
// last_name: 'Smith',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// year: '2018',
|
||||||
|
// genre: 'Crime fiction',
|
||||||
|
// };
|
||||||
|
|
||||||
|
// http://localhost:9200/datastets/_doc/1
|
||||||
|
|
||||||
|
// var id = '1';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// console.log(doc);
|
||||||
|
let document = JSON.parse(`${doc}`);
|
||||||
|
|
||||||
|
// https://opensearch.org/docs/2.1/opensearch/supported-field-types/geo-shape/
|
||||||
|
// Define the new document
|
||||||
|
// const document = {
|
||||||
|
// title: 'Your Document Name',
|
||||||
|
// id: dataset.publish_id,
|
||||||
|
// doctype: 'GIS',
|
||||||
|
// // "location" : {
|
||||||
|
// // "type" : "point",
|
||||||
|
// // "coordinates" : [74.00, 40.71]
|
||||||
|
// // },
|
||||||
|
// geo_location: {
|
||||||
|
// type: 'linestring',
|
||||||
|
// coordinates: [
|
||||||
|
// [-77.03653, 38.897676],
|
||||||
|
// [-77.009051, 38.889939],
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// // geo_location: 'BBOX (71.0589, 74.0060, 42.3601, 40.7128)'
|
||||||
|
// // geo_location: {
|
||||||
|
// // type: 'envelope',
|
||||||
|
// // coordinates: [
|
||||||
|
// // [13.0, 53.0],
|
||||||
|
// // [14.0, 52.0],
|
||||||
|
// // ], // Define your BBOX coordinates
|
||||||
|
// // },
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Update the document
|
||||||
|
var test = await client.index({
|
||||||
|
id: dataset.publish_id?.toString(),
|
||||||
|
index: index_name,
|
||||||
|
body: document,
|
||||||
|
refresh: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the result
|
||||||
|
return response.json(test.body);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle any errors
|
||||||
|
console.error(error);
|
||||||
|
return response.status(500).json({ error: 'An error occurred while updating the data.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destroy({}: HttpContext) {}
|
||||||
|
|
||||||
|
private async createXmlRecord(dataset: Dataset, datasetNode: XMLBuilder) {
|
||||||
|
const domNode = await this.getDatasetXmlDomNode(dataset);
|
||||||
|
if (domNode) {
|
||||||
|
datasetNode.import(domNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDatasetXmlDomNode(dataset: Dataset) {
|
||||||
|
const xmlModel = new XmlModel(dataset);
|
||||||
|
// xmlModel.setModel(dataset);
|
||||||
|
xmlModel.excludeEmptyFields();
|
||||||
|
xmlModel.caching = true;
|
||||||
|
// const cache = dataset.xmlCache ? dataset.xmlCache : null;
|
||||||
|
// dataset.load('xmlCache');
|
||||||
|
if (dataset.xmlCache) {
|
||||||
|
xmlModel.xmlCache = dataset.xmlCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return cache.getDomDocument();
|
||||||
|
const domDocument: XMLBuilder | null = await xmlModel.getDomDocument();
|
||||||
|
return domDocument;
|
||||||
|
}
|
||||||
|
}
|
65
app/Controllers/Http/Editor/deleteDoiMetadata.xml
Normal file
65
app/Controllers/Http/Editor/deleteDoiMetadata.xml
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resource xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://datacite.org/schema/kernel-4"
|
||||||
|
xsi:schemaLocation="http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4.3/metadata.xsd">
|
||||||
|
<identifier identifierType="DOI">10.21388/tethys.213</identifier>
|
||||||
|
<creators>
|
||||||
|
<creator>
|
||||||
|
<creatorName nameType="Personal">Moser, Michael</creatorName>
|
||||||
|
<givenName>Michael</givenName>
|
||||||
|
<familyName>Moser</familyName>
|
||||||
|
<affiliation>GBA</affiliation>
|
||||||
|
</creator>
|
||||||
|
</creators>
|
||||||
|
<titles>
|
||||||
|
<title xml:lang="en">rewerewr</title>
|
||||||
|
</titles>
|
||||||
|
<publisher>Tethys RDR</publisher>
|
||||||
|
<publicationYear>2024</publicationYear>
|
||||||
|
<subjects>
|
||||||
|
<subject xml:lang="de">Aletshausen-Langenneufnach Störung</subject>
|
||||||
|
<subject xml:lang="de">Wolfersberg-Moosach Störung</subject>
|
||||||
|
<subject xml:lang="en">wefwef</subject>
|
||||||
|
</subjects>
|
||||||
|
<language>en</language>
|
||||||
|
<contributors>
|
||||||
|
<contributor contributorType="RegistrationAuthority">
|
||||||
|
<contributorName>Jürgen Reitner</contributorName>
|
||||||
|
</contributor>
|
||||||
|
</contributors>
|
||||||
|
<dates>
|
||||||
|
<date dateType="Created">2023-11-30</date>
|
||||||
|
</dates>
|
||||||
|
<version>1</version>
|
||||||
|
<resourceType resourceTypeGeneral="Dataset">Dataset</resourceType>
|
||||||
|
<alternateIdentifiers>
|
||||||
|
<alternateIdentifier alternateIdentifierType="url">https://www.tethys.at/dataset/213</alternateIdentifier>
|
||||||
|
</alternateIdentifiers>
|
||||||
|
<rightsList>
|
||||||
|
<rights xml:lang="" rightsURI="https://creativecommons.org/licenses/by/4.0/deed.en"
|
||||||
|
schemeURI="https://spdx.org/licenses/" rightsIdentifierScheme="SPDX"
|
||||||
|
rightsIdentifier="CC-BY-4.0">Creative Commons Attribution 4.0 International (CC BY 4.0)</rights>
|
||||||
|
<rights rightsURI="info:eu-repo/semantics/openAccess">Open Access</rights>
|
||||||
|
</rightsList>
|
||||||
|
<sizes>
|
||||||
|
<size>1 datasets</size>
|
||||||
|
</sizes>
|
||||||
|
<formats>
|
||||||
|
<format>image/png</format>
|
||||||
|
</formats>
|
||||||
|
<descriptions>
|
||||||
|
<description xml:lang="en" descriptionType="Abstract">rewrewr</description>
|
||||||
|
</descriptions>
|
||||||
|
<geoLocations>
|
||||||
|
<geoLocation>
|
||||||
|
<geoLocationBox>
|
||||||
|
<westBoundLongitude>11.71142578125</westBoundLongitude>
|
||||||
|
<eastBoundLongitude>14.414062500000002</eastBoundLongitude>
|
||||||
|
<southBoundLatitude>46.58906908309185</southBoundLatitude>
|
||||||
|
<northBoundLatitude>47.45780853075031</northBoundLatitude>
|
||||||
|
</geoLocationBox>
|
||||||
|
</geoLocation>
|
||||||
|
</geoLocations>
|
||||||
|
</resource>
|
|
@ -1,16 +1,31 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
import { RequestContract } from '@ioc:Adonis/Core/Request';
|
// import { RequestContract } from '@ioc:Adonis/Core/Request';
|
||||||
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
import { Request } from '@adonisjs/core/http';
|
||||||
|
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
|
||||||
import { create } from 'xmlbuilder2';
|
import { create } from 'xmlbuilder2';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import utc from 'dayjs/plugin/utc';
|
import utc from 'dayjs/plugin/utc.js';
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
import timezone from 'dayjs/plugin/timezone.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
import { transform } from 'saxon-js';
|
import SaxonJS from 'saxon-js';
|
||||||
// import { Xslt, xmlParse } from 'xslt-processor'
|
// import { Xslt, xmlParse } from 'xslt-processor'
|
||||||
import { OaiErrorCodes, OaiModelError } from 'App/Exceptions/OaiErrorCodes';
|
import { OaiErrorCodes, OaiModelError } from '#app/exceptions/OaiErrorCodes';
|
||||||
import { OaiModelException } from 'App/Exceptions/OaiModelException';
|
import { OaiModelException, BadOaiModelException } from '#app/exceptions/OaiModelException';
|
||||||
|
import Dataset from '#models/dataset';
|
||||||
|
import Collection from '#models/collection';
|
||||||
|
import { getDomain, preg_match } from '#app/utils/utility-functions';
|
||||||
|
import XmlModel from '#app/Library/XmlModel';
|
||||||
|
import logger from '@adonisjs/core/services/logger';
|
||||||
|
import ResumptionToken from '#app/Library/Oai/ResumptionToken';
|
||||||
|
// import Config from '@ioc:Adonis/Core/Config';
|
||||||
|
import config from '@adonisjs/core/services/config'
|
||||||
|
// import { inject } from '@adonisjs/fold';
|
||||||
|
import { inject } from '@adonisjs/core'
|
||||||
|
// import { TokenWorkerContract } from "MyApp/Models/TokenWorker";
|
||||||
|
import TokenWorkerContract from '#library/Oai/TokenWorkerContract';
|
||||||
|
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
||||||
|
|
||||||
|
|
||||||
interface XslTParameter {
|
interface XslTParameter {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
@ -20,10 +35,20 @@ interface Dictionary {
|
||||||
[index: string]: string;
|
[index: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ListParameter {
|
||||||
|
cursor: number;
|
||||||
|
totalIds: number;
|
||||||
|
start: number;
|
||||||
|
reldocIds: (number | null)[];
|
||||||
|
metadataPrefix: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@inject()
|
||||||
export default class OaiController {
|
export default class OaiController {
|
||||||
// private deliveringDocumentStates = ["published", "deleted"];
|
private deliveringDocumentStates = ['published', 'deleted'];
|
||||||
// private sampleRegEx = /^[A-Za-zäüÄÜß0-9\-_.!~]+$/;
|
private sampleRegEx = /^[A-Za-zäüÄÜß0-9\-_.!~]+$/;
|
||||||
private xsltParameter: XslTParameter;
|
private xsltParameter: XslTParameter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds xml representation of document information to be processed.
|
* Holds xml representation of document information to be processed.
|
||||||
*
|
*
|
||||||
|
@ -32,23 +57,20 @@ export default class OaiController {
|
||||||
private xml: XMLBuilder;
|
private xml: XMLBuilder;
|
||||||
private proc;
|
private proc;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
|
constructor(public tokenWorker: TokenWorkerContract) {
|
||||||
// Load the XSLT file
|
// Load the XSLT file
|
||||||
this.proc = readFileSync('public/assets2/datasetxml2oai.sef.json');
|
this.proc = readFileSync('public/assets2/datasetxml2oai.sef.json');
|
||||||
// tests
|
|
||||||
// const xslPath = 'assets/datasetxml2oai-pmh.xslt'; // Replace with the actual path to your XSLT file
|
|
||||||
// this.proc = readFileSync(xslPath, 'utf-8');
|
|
||||||
// this.configuration = new Configuration();
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async index({ response, request }: HttpContextContract): Promise<void> {
|
public async index({ response, request }: HttpContext): Promise<void> {
|
||||||
this.xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
this.xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
||||||
|
|
||||||
// this.proc = new XSLTProcessor();
|
// this.proc = new XSLTProcessor();
|
||||||
// const stylesheet = readFileSync(__dirname + "/datasetxml2oai.sef.json");
|
// const stylesheet = readFileSync(__dirname + "/datasetxml2oai.sef.json");
|
||||||
const xsltParameter = (this.xsltParameter = {});
|
const xsltParameter: XslTParameter = (this.xsltParameter = {});
|
||||||
|
|
||||||
let oaiRequest: Dictionary = {};
|
let oaiRequest: Dictionary = {};
|
||||||
if (request.method() === 'POST') {
|
if (request.method() === 'POST') {
|
||||||
|
@ -59,9 +81,16 @@ export default class OaiController {
|
||||||
xsltParameter['oai_error_code'] = 'unknown';
|
xsltParameter['oai_error_code'] = 'unknown';
|
||||||
xsltParameter['oai_error_message'] = 'Only POST and GET methods are allowed for OAI-PMH.';
|
xsltParameter['oai_error_message'] = 'Only POST and GET methods are allowed for OAI-PMH.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let earliestDateFromDb;
|
||||||
// const oaiRequest: OaiParameter = request.body;
|
// const oaiRequest: OaiParameter = request.body;
|
||||||
try {
|
try {
|
||||||
this.handleRequest(oaiRequest, request);
|
const firstPublishedDataset: Dataset | null = await Dataset.earliestPublicationDate();
|
||||||
|
firstPublishedDataset != null &&
|
||||||
|
(earliestDateFromDb = firstPublishedDataset.server_date_published.toFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"));
|
||||||
|
this.xsltParameter['earliestDatestamp'] = earliestDateFromDb;
|
||||||
|
// start the request
|
||||||
|
await this.handleRequest(oaiRequest, request);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof OaiModelException) {
|
if (error instanceof OaiModelException) {
|
||||||
const code = error.oaiCode;
|
const code = error.oaiCode;
|
||||||
|
@ -80,9 +109,9 @@ export default class OaiController {
|
||||||
|
|
||||||
const xmlString = this.xml.end({ prettyPrint: true });
|
const xmlString = this.xml.end({ prettyPrint: true });
|
||||||
|
|
||||||
let xmlOutput;
|
let xmlOutput; // = xmlString;
|
||||||
try {
|
try {
|
||||||
const result = await transform({
|
const result = await SaxonJS.transform({
|
||||||
// stylesheetFileName: `${config.TMP_BASE_DIR}/data-quality/rules/iati.sef.json`,
|
// stylesheetFileName: `${config.TMP_BASE_DIR}/data-quality/rules/iati.sef.json`,
|
||||||
stylesheetText: this.proc,
|
stylesheetText: this.proc,
|
||||||
destination: 'serialized',
|
destination: 'serialized',
|
||||||
|
@ -106,7 +135,7 @@ export default class OaiController {
|
||||||
response.status(StatusCodes.OK).send(xmlOutput);
|
response.status(StatusCodes.OK).send(xmlOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleRequest(oaiRequest: Dictionary, request: RequestContract) {
|
protected async handleRequest(oaiRequest: Dictionary, request: Request) {
|
||||||
// Setup stylesheet
|
// Setup stylesheet
|
||||||
// $this->loadStyleSheet('datasetxml2oai-pmh.xslt');
|
// $this->loadStyleSheet('datasetxml2oai-pmh.xslt');
|
||||||
|
|
||||||
|
@ -116,7 +145,7 @@ export default class OaiController {
|
||||||
this.xsltParameter['unixTimestamp'] = now.unix();
|
this.xsltParameter['unixTimestamp'] = now.unix();
|
||||||
|
|
||||||
// set OAI base url
|
// set OAI base url
|
||||||
const baseDomain = process.env.BASE_DOMAIN || 'localhost';
|
const baseDomain = process.env.OAI_BASE_DOMAIN || 'localhost';
|
||||||
this.xsltParameter['baseURL'] = baseDomain + '/oai';
|
this.xsltParameter['baseURL'] = baseDomain + '/oai';
|
||||||
this.xsltParameter['repURL'] = request.protocol() + '://' + request.hostname();
|
this.xsltParameter['repURL'] = request.protocol() + '://' + request.hostname();
|
||||||
this.xsltParameter['downloadLink'] = request.protocol() + '://' + request.hostname() + '/file/download/';
|
this.xsltParameter['downloadLink'] = request.protocol() + '://' + request.hostname() + '/file/download/';
|
||||||
|
@ -130,17 +159,15 @@ export default class OaiController {
|
||||||
this.handleIdentify();
|
this.handleIdentify();
|
||||||
} else if (verb === 'ListMetadataFormats') {
|
} else if (verb === 'ListMetadataFormats') {
|
||||||
this.handleListMetadataFormats();
|
this.handleListMetadataFormats();
|
||||||
}
|
} else if (verb == 'GetRecord') {
|
||||||
// else if (verb == "GetRecord") {
|
await this.handleGetRecord(oaiRequest);
|
||||||
// await this.handleGetRecord(oaiRequest);
|
} else if (verb == 'ListRecords') {
|
||||||
// } else if (verb == "ListRecords") {
|
await this.handleListRecords(oaiRequest);
|
||||||
// await this.handleListRecords(oaiRequest);
|
} else if (verb == 'ListIdentifiers') {
|
||||||
// } else if (verb == "ListIdentifiers") {
|
await this.handleListIdentifiers(oaiRequest);
|
||||||
// await this.handleListIdentifiers(oaiRequest);
|
} else if (verb == 'ListSets') {
|
||||||
// } else if (verb == "ListSets") {
|
await this.handleListSets();
|
||||||
// await this.handleListSets();
|
} else {
|
||||||
// }
|
|
||||||
else {
|
|
||||||
this.handleIllegalVerb();
|
this.handleIllegalVerb();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -182,6 +209,434 @@ export default class OaiController {
|
||||||
this.xml.root().ele('Datasets');
|
this.xml.root().ele('Datasets');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async handleListSets() {
|
||||||
|
const repIdentifier = 'tethys.at';
|
||||||
|
this.xsltParameter['repIdentifier'] = repIdentifier;
|
||||||
|
const datasetElement = this.xml.root().ele('Datasets');
|
||||||
|
|
||||||
|
const sets: { [key: string]: string } = {
|
||||||
|
'open_access': 'Set for open access licenses',
|
||||||
|
'openaire_data': "OpenAIRE",
|
||||||
|
'doc-type:ResearchData': 'Set for document type ResearchData',
|
||||||
|
...(await this.getSetsForDatasetTypes()),
|
||||||
|
...(await this.getSetsForCollections()),
|
||||||
|
// ... await this.getSetsForProjects(),
|
||||||
|
} as Dictionary;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(sets)) {
|
||||||
|
const setElement = datasetElement.ele('Rdr_Sets');
|
||||||
|
setElement.att('Type', key);
|
||||||
|
setElement.att('TypeName', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async handleGetRecord(oaiRequest: Dictionary) {
|
||||||
|
const repIdentifier = 'tethys.at';
|
||||||
|
this.xsltParameter['repIdentifier'] = repIdentifier;
|
||||||
|
|
||||||
|
const dataId = this.validateAndGetIdentifier(oaiRequest);
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('publish_id', dataId)
|
||||||
|
.preload('xmlCache')
|
||||||
|
.preload('collections', (builder) => {
|
||||||
|
builder.preload('collectionRole');
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!dataset || !dataset.publish_id) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'The value of the identifier argument is unknown or illegal in this repository.',
|
||||||
|
OaiErrorCodes.IDDOESNOTEXIST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadataPrefix = this.validateAndGetMetadataPrefix(oaiRequest);
|
||||||
|
this.xsltParameter['oai_metadataPrefix'] = metadataPrefix;
|
||||||
|
// do not deliver datasets which are restricted by document state defined in deliveringStates
|
||||||
|
this.validateDatasetState(dataset);
|
||||||
|
|
||||||
|
// add xml elements
|
||||||
|
const datasetNode = this.xml.root().ele('Datasets');
|
||||||
|
await this.createXmlRecord(dataset, datasetNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async handleListIdentifiers(oaiRequest: Dictionary) {
|
||||||
|
!this.tokenWorker.isConnected && (await this.tokenWorker.connect());
|
||||||
|
|
||||||
|
const maxIdentifier: number = config.get('oai.max.listidentifiers', 100);
|
||||||
|
await this.handleLists(oaiRequest, maxIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async handleListRecords(oaiRequest: Dictionary) {
|
||||||
|
!this.tokenWorker.isConnected && (await this.tokenWorker.connect());
|
||||||
|
|
||||||
|
const maxRecords: number = config.get('oai.max.listrecords', 100);
|
||||||
|
await this.handleLists(oaiRequest, maxRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleLists(oaiRequest: Dictionary, maxRecords: number) {
|
||||||
|
maxRecords = maxRecords || 100;
|
||||||
|
const repIdentifier = 'tethys.at';
|
||||||
|
this.xsltParameter['repIdentifier'] = repIdentifier;
|
||||||
|
const datasetNode = this.xml.root().ele('Datasets');
|
||||||
|
|
||||||
|
// list initialisation
|
||||||
|
const numWrapper: ListParameter = {
|
||||||
|
cursor: 0,
|
||||||
|
totalIds: 0,
|
||||||
|
start: maxRecords + 1,
|
||||||
|
reldocIds: [],
|
||||||
|
metadataPrefix: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// resumptionToken is defined
|
||||||
|
if ('resumptionToken' in oaiRequest) {
|
||||||
|
await this.handleResumptionToken(oaiRequest, maxRecords, numWrapper);
|
||||||
|
} else {
|
||||||
|
// no resumptionToken is given
|
||||||
|
await this.handleNoResumptionToken(oaiRequest, numWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handling of document ids
|
||||||
|
const restIds = numWrapper.reldocIds as number[];
|
||||||
|
const workIds = restIds.splice(0, maxRecords) as number[]; // array_splice(restIds, 0, maxRecords);
|
||||||
|
|
||||||
|
// no records returned
|
||||||
|
if (workIds.length == 0) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'The combination of the given values results in an empty list.',
|
||||||
|
OaiErrorCodes.NORECORDSMATCH,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const datasets: Dataset[] = await Dataset.query()
|
||||||
|
.whereIn('publish_id', workIds)
|
||||||
|
.preload('xmlCache')
|
||||||
|
.preload('collections', (builder) => {
|
||||||
|
builder.preload('collectionRole');
|
||||||
|
})
|
||||||
|
.orderBy('publish_id');
|
||||||
|
|
||||||
|
for (const dataset of datasets) {
|
||||||
|
await this.createXmlRecord(dataset, datasetNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the further Ids in a resumption-file
|
||||||
|
const countRestIds = restIds.length; //84
|
||||||
|
if (countRestIds > 0) {
|
||||||
|
const token = new ResumptionToken();
|
||||||
|
token.startPosition = numWrapper.start; //101
|
||||||
|
token.totalIds = numWrapper.totalIds; //184
|
||||||
|
token.documentIds = restIds; //101 -184
|
||||||
|
token.metadataPrefix = numWrapper.metadataPrefix;
|
||||||
|
|
||||||
|
// $tokenWorker->storeResumptionToken($token);
|
||||||
|
const res: string = await this.tokenWorker.set(token);
|
||||||
|
|
||||||
|
// set parameters for the resumptionToken-node
|
||||||
|
// const res = token.ResumptionId;
|
||||||
|
this.setParamResumption(res, numWrapper.cursor, numWrapper.totalIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleResumptionToken(oaiRequest: Dictionary, maxRecords: number, numWrapper: ListParameter) {
|
||||||
|
const resParam = oaiRequest['resumptionToken']; //e.g. "158886496600000"
|
||||||
|
const token = await this.tokenWorker.get(resParam);
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new OaiModelException(StatusCodes.INTERNAL_SERVER_ERROR, 'cache is outdated.', OaiErrorCodes.BADRESUMPTIONTOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
numWrapper.cursor = token.startPosition - 1; //startet dann bei Index 10
|
||||||
|
numWrapper.start = token.startPosition + maxRecords;
|
||||||
|
numWrapper.totalIds = token.totalIds;
|
||||||
|
numWrapper.reldocIds = token.documentIds;
|
||||||
|
numWrapper.metadataPrefix = token.metadataPrefix;
|
||||||
|
|
||||||
|
this.xsltParameter['oai_metadataPrefix'] = numWrapper.metadataPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleNoResumptionToken(oaiRequest: Dictionary, numWrapper: ListParameter) {
|
||||||
|
// no resumptionToken is given
|
||||||
|
if ('metadataPrefix' in oaiRequest) {
|
||||||
|
numWrapper.metadataPrefix = oaiRequest['metadataPrefix'];
|
||||||
|
} else {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'The prefix of the metadata argument is unknown.',
|
||||||
|
OaiErrorCodes.BADARGUMENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.xsltParameter['oai_metadataPrefix'] = numWrapper.metadataPrefix;
|
||||||
|
|
||||||
|
let finder: ModelQueryBuilderContract<typeof Dataset, Dataset> = Dataset.query();
|
||||||
|
// add server state restrictions
|
||||||
|
finder.whereIn('server_state', this.deliveringDocumentStates);
|
||||||
|
if ('set' in oaiRequest) {
|
||||||
|
const set = oaiRequest['set'] as string;
|
||||||
|
const setArray = set.split(':');
|
||||||
|
|
||||||
|
if (setArray[0] == 'data-type') {
|
||||||
|
if (setArray.length == 2 && setArray[1]) {
|
||||||
|
finder.where('type', setArray[1]);
|
||||||
|
}
|
||||||
|
} else if (setArray[0] == 'open_access') {
|
||||||
|
const openAccessLicences = ['CC-BY-4.0', 'CC-BY-SA-4.0'];
|
||||||
|
finder.andWhereHas('licenses', (query) => {
|
||||||
|
query.whereIn('name', openAccessLicences);
|
||||||
|
});
|
||||||
|
} else if (setArray[0] == 'ddc') {
|
||||||
|
if (setArray.length == 2 && setArray[1] != '') {
|
||||||
|
finder.andWhereHas('collections', (query) => {
|
||||||
|
query.where('number', setArray[1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const timeZone = "Europe/Vienna"; // Canonical time zone name
|
||||||
|
// &from=2020-09-03&until2020-09-03
|
||||||
|
// &from=2020-09-11&until=2021-05-11
|
||||||
|
if ('from' in oaiRequest && 'until' in oaiRequest) {
|
||||||
|
const from = oaiRequest['from'] as string;
|
||||||
|
let fromDate = dayjs(from); //.tz(timeZone);
|
||||||
|
const until = oaiRequest['until'] as string;
|
||||||
|
let untilDate = dayjs(until); //.tz(timeZone);
|
||||||
|
if (!fromDate.isValid() || !untilDate.isValid()) {
|
||||||
|
throw new OaiModelException(StatusCodes.INTERNAL_SERVER_ERROR, 'Date Parameter is not valid.', OaiErrorCodes.BADARGUMENT);
|
||||||
|
}
|
||||||
|
fromDate = dayjs.tz(from, 'Europe/Vienna');
|
||||||
|
untilDate = dayjs.tz(until, 'Europe/Vienna');
|
||||||
|
|
||||||
|
if (from.length != until.length) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'The request has different granularities for the from and until parameters.',
|
||||||
|
OaiErrorCodes.BADARGUMENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fromDate.hour() == 0 && (fromDate = fromDate.startOf('day'));
|
||||||
|
untilDate.hour() == 0 && (untilDate = untilDate.endOf('day'));
|
||||||
|
|
||||||
|
finder.whereBetween('server_date_published', [fromDate.format('YYYY-MM-DD HH:mm:ss'), untilDate.format('YYYY-MM-DD HH:mm:ss')]);
|
||||||
|
} else if ('from' in oaiRequest && !('until' in oaiRequest)) {
|
||||||
|
const from = oaiRequest['from'] as string;
|
||||||
|
let fromDate = dayjs(from);
|
||||||
|
if (!fromDate.isValid()) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'From date parameter is not valid.',
|
||||||
|
OaiErrorCodes.BADARGUMENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fromDate = dayjs.tz(from, 'Europe/Vienna');
|
||||||
|
fromDate.hour() == 0 && (fromDate = fromDate.startOf('day'));
|
||||||
|
|
||||||
|
const now = dayjs();
|
||||||
|
if (fromDate.isAfter(now)) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'Given from date is greater than now. The given values results in an empty list.',
|
||||||
|
OaiErrorCodes.NORECORDSMATCH,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finder.andWhere('server_date_published', '>=', fromDate.format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
}
|
||||||
|
} else if (!('from' in oaiRequest) && 'until' in oaiRequest) {
|
||||||
|
const until = oaiRequest['until'] as string;
|
||||||
|
let untilDate = dayjs(until);
|
||||||
|
if (!untilDate.isValid()) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'Until date parameter is not valid.',
|
||||||
|
OaiErrorCodes.BADARGUMENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
untilDate = dayjs.tz(until, 'Europe/Vienna');
|
||||||
|
untilDate.hour() == 0 && (untilDate = untilDate.endOf('day'));
|
||||||
|
|
||||||
|
const firstPublishedDataset: Dataset = (await Dataset.earliestPublicationDate()) as Dataset;
|
||||||
|
const earliestPublicationDate = dayjs(firstPublishedDataset.server_date_published.toISO()); //format("YYYY-MM-DDThh:mm:ss[Z]"));
|
||||||
|
if (earliestPublicationDate.isAfter(untilDate)) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`earliestDatestamp is greater than given until date.
|
||||||
|
The given values results in an empty list.`,
|
||||||
|
OaiErrorCodes.NORECORDSMATCH,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finder.andWhere('server_date_published', '<=', untilDate.format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let reldocIdsDocs = await finder.select('publish_id').orderBy('publish_id');
|
||||||
|
numWrapper.reldocIds = reldocIdsDocs.map((dat) => dat.publish_id);
|
||||||
|
numWrapper.totalIds = numWrapper.reldocIds.length; //212
|
||||||
|
}
|
||||||
|
|
||||||
|
private setParamResumption(res: string, cursor: number, totalIds: number) {
|
||||||
|
const tomorrow = dayjs().add(1, 'day').format('YYYY-MM-DDThh:mm:ss[Z]');
|
||||||
|
this.xsltParameter['dateDelete'] = tomorrow;
|
||||||
|
this.xsltParameter['res'] = res;
|
||||||
|
this.xsltParameter['cursor'] = cursor;
|
||||||
|
this.xsltParameter['totalIds'] = totalIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateAndGetIdentifier(oaiRequest: Dictionary): number {
|
||||||
|
// Identifier references metadata Urn, not plain Id!
|
||||||
|
// Currently implemented as 'oai:foo.bar.de:{docId}' or 'urn:nbn...-123'
|
||||||
|
if (!('identifier' in oaiRequest)) {
|
||||||
|
throw new BadOaiModelException('The prefix of the identifier argument is unknown.');
|
||||||
|
}
|
||||||
|
const dataId = Number(this.getDocumentIdByIdentifier(oaiRequest.identifier));
|
||||||
|
if (isNaN(dataId)) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'The value of the identifier argument is illegal in this repository.',
|
||||||
|
OaiErrorCodes.BADARGUMENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return dataId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateAndGetMetadataPrefix(oaiRequest: Dictionary): string {
|
||||||
|
let metadataPrefix = '';
|
||||||
|
if ('metadataPrefix' in oaiRequest) {
|
||||||
|
metadataPrefix = oaiRequest['metadataPrefix'];
|
||||||
|
} else {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'The prefix of the metadata argument is unknown.',
|
||||||
|
OaiErrorCodes.BADARGUMENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return metadataPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateDatasetState(dataset: Dataset): void {
|
||||||
|
if (dataset.server_state == null || !this.deliveringDocumentStates.includes(dataset.server_state)) {
|
||||||
|
throw new OaiModelException(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'Document is not available for OAI export!',
|
||||||
|
OaiErrorCodes.NORECORDSMATCH,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createXmlRecord(dataset: Dataset, datasetNode: XMLBuilder) {
|
||||||
|
const domNode = await this.getDatasetXmlDomNode(dataset);
|
||||||
|
|
||||||
|
if (domNode) {
|
||||||
|
// add frontdoor url and data-type
|
||||||
|
dataset.publish_id && this.addLandingPageAttribute(domNode, dataset.publish_id.toString());
|
||||||
|
this.addSpecInformation(domNode, 'data-type:' + dataset.type);
|
||||||
|
|
||||||
|
if (dataset.collections) {
|
||||||
|
for (const coll of dataset.collections) {
|
||||||
|
const collRole = coll.collectionRole;
|
||||||
|
this.addSpecInformation(domNode, collRole.oai_name + ':' + coll.number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datasetNode.import(domNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDatasetXmlDomNode(dataset: Dataset) {
|
||||||
|
const xmlModel = new XmlModel(dataset);
|
||||||
|
// xmlModel.setModel(dataset);
|
||||||
|
xmlModel.excludeEmptyFields();
|
||||||
|
xmlModel.caching = true;
|
||||||
|
// const cache = dataset.xmlCache ? dataset.xmlCache : null;
|
||||||
|
// dataset.load('xmlCache');
|
||||||
|
if (dataset.xmlCache) {
|
||||||
|
xmlModel.xmlCache = dataset.xmlCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return cache.getDomDocument();
|
||||||
|
const domDocument: XMLBuilder | null = await xmlModel.getDomDocument();
|
||||||
|
return domDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addSpecInformation(domNode: XMLBuilder, information: string) {
|
||||||
|
domNode.ele('SetSpec').att('Value', information);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addLandingPageAttribute(domNode: XMLBuilder, dataid: string) {
|
||||||
|
const baseDomain = process.env.OAI_BASE_DOMAIN || 'localhost';
|
||||||
|
const url = 'https://' + getDomain(baseDomain) + '/dataset/' + dataid;
|
||||||
|
// add attribute du dataset xml element
|
||||||
|
domNode.att('landingpage', url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDocumentIdByIdentifier(oaiIdentifier: string): string {
|
||||||
|
const identifierParts: string[] = oaiIdentifier.split(':'); // explode(":", $oaiIdentifier);
|
||||||
|
const dataId: string = identifierParts[2];
|
||||||
|
// switch (identifierParts[0]) {
|
||||||
|
// case 'oai':
|
||||||
|
// if (isset($identifierParts[2])) {
|
||||||
|
// $dataId = $identifierParts[2];
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// throw new OaiModelException(
|
||||||
|
// 'The prefix of the identifier argument is unknown.',
|
||||||
|
// OaiModelError::BADARGUMENT
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (empty($dataId) or !preg_match('/^\d+$/', $dataId)) {
|
||||||
|
// throw new OaiModelException(
|
||||||
|
// 'The value of the identifier argument is unknown or illegal in this repository.',
|
||||||
|
// OaiModelError::IDDOESNOTEXIST
|
||||||
|
// );
|
||||||
|
|
||||||
|
return dataId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getSetsForCollections(): Promise<Dictionary> {
|
||||||
|
const sets: { [key: string]: string } = {} as Dictionary;
|
||||||
|
|
||||||
|
const collections = await Collection.query()
|
||||||
|
.select('name', 'number', 'role_id')
|
||||||
|
.whereHas('collectionRole', (query) => {
|
||||||
|
query.where('visible_oai', true);
|
||||||
|
})
|
||||||
|
.preload('collectionRole');
|
||||||
|
|
||||||
|
collections.forEach((collection) => {
|
||||||
|
// if collection has a collection role (classification like ddc):
|
||||||
|
if (collection.number) {
|
||||||
|
// collection.load('collectionRole');
|
||||||
|
const setSpec = collection.collectionRole?.oai_name + ':' + collection.number;
|
||||||
|
sets[setSpec] = `Set ${collection.number} '${collection.name}'`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getSetsForDatasetTypes(): Promise<Dictionary> {
|
||||||
|
const sets: { [key: string]: string } = {} as Dictionary;
|
||||||
|
|
||||||
|
const datasets: Array<Dataset> = await Dataset.query().select('type').where('server_state', 'published');
|
||||||
|
|
||||||
|
datasets.forEach((dataset) => {
|
||||||
|
if (dataset.type && false == preg_match(this.sampleRegEx, dataset.type)) {
|
||||||
|
const msg = `Invalid SetSpec (data-type='${dataset.type}').
|
||||||
|
Allowed characters are [${this.sampleRegEx}].`;
|
||||||
|
// Log::error("OAI-PMH: $msg");
|
||||||
|
logger.error(`OAI-PMH: ${msg}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const setSpec = 'data-type:' + dataset.type;
|
||||||
|
sets[setSpec] = `Set for document type '${dataset.type}'`;
|
||||||
|
});
|
||||||
|
return sets;
|
||||||
|
}
|
||||||
|
|
||||||
private handleIllegalVerb() {
|
private handleIllegalVerb() {
|
||||||
this.xsltParameter['oai_error_code'] = 'badVerb';
|
this.xsltParameter['oai_error_code'] = 'badVerb';
|
||||||
this.xsltParameter['oai_error_message'] = 'The verb provided in the request is illegal.';
|
this.xsltParameter['oai_error_message'] = 'The verb provided in the request is illegal.';
|
||||||
|
|
310
app/Controllers/Http/Reviewer/DatasetController.ts
Normal file
310
app/Controllers/Http/Reviewer/DatasetController.ts
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import User from '#models/user';
|
||||||
|
import Dataset from '#models/dataset';
|
||||||
|
import Field from '#app/Library/Field';
|
||||||
|
import BaseModel from '#models/base_model';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
||||||
|
import vine from '@vinejs/vine';
|
||||||
|
import mail from '@adonisjs/mail/services/main';
|
||||||
|
import logger from '@adonisjs/core/services/logger';
|
||||||
|
import { validate } from 'deep-email-validator';
|
||||||
|
|
||||||
|
interface Dictionary {
|
||||||
|
[index: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DatasetsController {
|
||||||
|
public async index({ auth, request, inertia }: HttpContext) {
|
||||||
|
const user = (await User.find(auth.user?.id)) as User;
|
||||||
|
const page = request.input('page', 1);
|
||||||
|
let datasets: ModelQueryBuilderContract<typeof Dataset, Dataset> = Dataset.query();
|
||||||
|
|
||||||
|
// if (request.input('search')) {
|
||||||
|
// // users = users.whereRaw('name like %?%', [request.input('search')])
|
||||||
|
// const searchTerm = request.input('search');
|
||||||
|
// datasets.where('name', 'ilike', `%${searchTerm}%`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (request.input('sort')) {
|
||||||
|
type SortOrder = 'asc' | 'desc' | undefined;
|
||||||
|
let attribute = request.input('sort');
|
||||||
|
let sortOrder: SortOrder = 'asc';
|
||||||
|
|
||||||
|
if (attribute.substr(0, 1) === '-') {
|
||||||
|
sortOrder = 'desc';
|
||||||
|
// attribute = substr(attribute, 1);
|
||||||
|
attribute = attribute.substr(1);
|
||||||
|
}
|
||||||
|
datasets.orderBy(attribute, sortOrder);
|
||||||
|
} else {
|
||||||
|
// users.orderBy('created_at', 'desc');
|
||||||
|
datasets.orderBy('id', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// const users = await User.query().orderBy('login').paginate(page, limit);
|
||||||
|
const myDatasets = await datasets
|
||||||
|
.where('server_state', 'approved')
|
||||||
|
.where('reviewer_id', user.id)
|
||||||
|
|
||||||
|
.preload('titles')
|
||||||
|
.preload('user', (query) => query.select('id', 'login'))
|
||||||
|
.preload('editor', (query) => query.select('id', 'login'))
|
||||||
|
.paginate(page, 10);
|
||||||
|
|
||||||
|
return inertia.render('Reviewer/Dataset/Index', {
|
||||||
|
datasets: myDatasets.serialize(),
|
||||||
|
filters: request.all(),
|
||||||
|
can: {
|
||||||
|
review: await auth.user?.can(['dataset-review']),
|
||||||
|
reject: await auth.user?.can(['dataset-review-reject']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async review({ request, inertia, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
// .preload('titles')
|
||||||
|
// .preload('descriptions')
|
||||||
|
.preload('user', (builder) => {
|
||||||
|
builder.select('id', 'login');
|
||||||
|
})
|
||||||
|
.firstOrFail();
|
||||||
|
|
||||||
|
const validStates = ['approved'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// session.flash('errors', 'Invalid server state!');
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be reviewed. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.toRoute('reviewer.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldnames: Array<string> = await dataset.describe();
|
||||||
|
const fields: Dictionary = {};
|
||||||
|
for (const fieldName of fieldnames) {
|
||||||
|
const field: Field = dataset.getField(fieldName) as Field;
|
||||||
|
const modelClass = field.getValueModelClass();
|
||||||
|
let fieldValues = field.getValue();
|
||||||
|
let value = '';
|
||||||
|
|
||||||
|
if (fieldValues === null || fieldValues == undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelClass === null) {
|
||||||
|
if (typeof fieldValues === 'number') {
|
||||||
|
// If the field values are a number, use them as is
|
||||||
|
value = fieldValues.toString();
|
||||||
|
} else {
|
||||||
|
// If the field values are not a number, use the replace() function to remove non-printable characters
|
||||||
|
value = fieldValues.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD ');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!Array.isArray(fieldValues)) {
|
||||||
|
fieldValues = [fieldValues];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const fieldValue of fieldValues) {
|
||||||
|
if (fieldValue === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (modelClass.prototype instanceof BaseModel) {
|
||||||
|
// this.mapModelAttributes(fieldValue, childNode);
|
||||||
|
value = '<ul>';
|
||||||
|
Object.keys(fieldValue).forEach((prop) => {
|
||||||
|
let modelValue = fieldValue[prop];
|
||||||
|
// console.log(`${prop}: ${value}`);
|
||||||
|
if (modelValue != null) {
|
||||||
|
if (modelValue instanceof DateTime) {
|
||||||
|
modelValue = modelValue.toFormat('yyyy-MM-dd HH:mm:ss').trim();
|
||||||
|
} else {
|
||||||
|
modelValue = modelValue.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace invalid XML-1.0-Characters by UTF-8 replacement character.
|
||||||
|
modelValue = modelValue.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD ');
|
||||||
|
|
||||||
|
value = value + '<li>' + prop + ' : ' + modelValue + '</li>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
value = value + '</ul>';
|
||||||
|
} else if (modelClass instanceof DateTime) {
|
||||||
|
// console.log('Value is a luxon date');
|
||||||
|
// this.mapDateAttributes(fieldValue, childNode);
|
||||||
|
value = value + ' Year ' + modelClass.year.toString();
|
||||||
|
value = value + ' Month ' + modelClass.month.toString();
|
||||||
|
value = value + ' Day ' + modelClass.day.toString();
|
||||||
|
value = value + ' Hour ' + modelClass.hour.toString();
|
||||||
|
value = value + ' Minute ' + modelClass.minute.toString();
|
||||||
|
value = value + ' Second ' + modelClass.second.toString();
|
||||||
|
value = value + ' UnixTimestamp ' + modelClass.toUnixInteger().toString();
|
||||||
|
let zoneName = modelClass.zoneName ? modelClass.zoneName : '';
|
||||||
|
value = value + ' Timezone ' + zoneName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != '') {
|
||||||
|
fields[fieldName] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inertia.render('Reviewer/Dataset/Review', {
|
||||||
|
dataset,
|
||||||
|
fields: fields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reviewUpdate({ request, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
// const { id } = params;
|
||||||
|
const dataset = await Dataset.findOrFail(id);
|
||||||
|
|
||||||
|
const validStates = ['approved'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// throw new Error('Invalid server state!');
|
||||||
|
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be reviewed. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.toRoute('reviewer.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset.server_state = 'reviewed';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// await dataset.related('editor').associate(user); // speichert schon ab
|
||||||
|
await dataset.save();
|
||||||
|
return response.toRoute('reviewer.dataset.list').flash('message', `You have successfully reviewed dataset ${dataset.id}!`);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle any errors
|
||||||
|
console.error(error);
|
||||||
|
return response.status(500).json({ error: 'An error occurred while reviewing the data.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reject({ request, inertia, response }: HttpContext) {
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
// .preload('titles')
|
||||||
|
// .preload('descriptions')
|
||||||
|
.preload('editor', (builder) => {
|
||||||
|
builder.select('id', 'login', 'email');
|
||||||
|
})
|
||||||
|
.firstOrFail();
|
||||||
|
|
||||||
|
const validStates = ['approved'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// session.flash('errors', 'Invalid server state!');
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
'warning',
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.toRoute('reviewer.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
return inertia.render('Reviewer/Dataset/Reject', {
|
||||||
|
dataset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async rejectUpdate({ request, response, auth }: HttpContext) {
|
||||||
|
const authUser = auth.user!;
|
||||||
|
|
||||||
|
const id = request.param('id');
|
||||||
|
const dataset = await Dataset.query()
|
||||||
|
.where('id', id)
|
||||||
|
.preload('editor', (builder) => {
|
||||||
|
builder.select('id', 'login', 'email');
|
||||||
|
})
|
||||||
|
.firstOrFail();
|
||||||
|
|
||||||
|
// const newSchema = schema.create({
|
||||||
|
// server_state: schema.string({ trim: true }),
|
||||||
|
// reject_reviewer_note: schema.string({ trim: true }, [rules.minLength(10), rules.maxLength(500)]),
|
||||||
|
// });
|
||||||
|
const newSchema = vine.object({
|
||||||
|
server_state: vine.string().trim(),
|
||||||
|
reject_reviewer_note: vine.string().trim().minLength(10).maxLength(500),
|
||||||
|
send_mail: vine.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// await request.validate({ schema: newSchema });
|
||||||
|
const validator = vine.compile(newSchema);
|
||||||
|
await request.validateUsing(validator);
|
||||||
|
} catch (error) {
|
||||||
|
// return response.badRequest(error.messages);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validStates = ['approved'];
|
||||||
|
if (!validStates.includes(dataset.server_state)) {
|
||||||
|
// throw new Error('Invalid server state!');
|
||||||
|
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
|
||||||
|
return response
|
||||||
|
.flash(
|
||||||
|
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
|
||||||
|
'warning',
|
||||||
|
)
|
||||||
|
.redirect()
|
||||||
|
.toRoute('reviewer.dataset.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataset.server_state = 'reviewed';
|
||||||
|
dataset.server_state = 'rejected_reviewer';
|
||||||
|
const rejectReviewerNote = request.input('reject_reviewer_note', '');
|
||||||
|
dataset.reject_reviewer_note = rejectReviewerNote;
|
||||||
|
|
||||||
|
// add logic for sending reject message
|
||||||
|
const sendMail = request.input('send_email', false);
|
||||||
|
// const validRecipientEmail = await this.checkEmailDomain('arno.kaimbacher@outlook.at');
|
||||||
|
const validationResult = await validate({
|
||||||
|
email: dataset.editor.email,
|
||||||
|
validateSMTP: false,
|
||||||
|
});
|
||||||
|
const validRecipientEmail: boolean = validationResult.valid;
|
||||||
|
let emailStatusMessage = '';
|
||||||
|
|
||||||
|
if (sendMail == true) {
|
||||||
|
if (dataset.user.email && validRecipientEmail) {
|
||||||
|
try {
|
||||||
|
await mail.send((message) => {
|
||||||
|
message.to(dataset.editor.email).subject('Dataset Rejection Notification').html(`
|
||||||
|
<p>Dear editor ${dataset.editor.login},</p>
|
||||||
|
<p>Your approved dataset with ID ${dataset.id} has been rejected.</p>
|
||||||
|
<p>Reason for rejection: ${rejectReviewerNote}</p>
|
||||||
|
<p>Best regards,<br>Your Tethys reviewer: ${authUser.login}</p>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
emailStatusMessage = ` A rejection email was successfully sent to ${dataset.editor.email}.`;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return response
|
||||||
|
.flash('Dataset has not been rejected due to an email error: ' + error.message, 'error')
|
||||||
|
.toRoute('reviewer.dataset.list');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emailStatusMessage = ` However, the email could not be sent because the editor's email address (${dataset.editor.email}) is not valid.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dataset.save();
|
||||||
|
|
||||||
|
return response
|
||||||
|
.toRoute('reviewer.dataset.list')
|
||||||
|
.flash(`You have rejected dataset ${dataset.id}! to editor ${dataset.editor.login}`, 'message');
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
// import User from 'App/Models/User';
|
// import User from 'App/Models/User';
|
||||||
// import Dataset from 'App/Models/Dataset';
|
// import Dataset from 'App/Models/Dataset';
|
||||||
// import License from 'App/Models/License';
|
// import License from 'App/Models/License';
|
||||||
|
@ -10,12 +10,11 @@ import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
// import Collection from 'App/Models/Collection';
|
// import Collection from 'App/Models/Collection';
|
||||||
// import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
// import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
||||||
// import dayjs from 'dayjs';
|
// import dayjs from 'dayjs';
|
||||||
import Person from 'App/Models/Person';
|
import Person from '#models/person';
|
||||||
|
import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
|
||||||
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
|
|
||||||
|
|
||||||
export default class PersonController {
|
export default class PersonController {
|
||||||
public async index({ auth, request, inertia }: HttpContextContract) {
|
public async index({ auth, request, inertia }: HttpContext) {
|
||||||
// const user = (await User.find(auth.user?.id)) as User;
|
// const user = (await User.find(auth.user?.id)) as User;
|
||||||
const page = request.input('page', 1);
|
const page = request.input('page', 1);
|
||||||
let persons: ModelQueryBuilderContract<typeof Person, Person> = Person.query();
|
let persons: ModelQueryBuilderContract<typeof Person, Person> = Person.query();
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { Response } from '@adonisjs/http-server/build/src/Response';
|
|
||||||
import { ServerResponse, IncomingMessage } from 'http';
|
|
||||||
import { RouterContract } from '@ioc:Adonis/Core/Route';
|
|
||||||
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
|
|
||||||
import { ResponseConfig, ResponseContract } from '@ioc:Adonis/Core/Response';
|
|
||||||
|
|
||||||
class FlashResponse extends Response implements ResponseContract {
|
|
||||||
protected static macros = {};
|
|
||||||
protected static getters = {};
|
|
||||||
constructor(
|
|
||||||
public request: IncomingMessage,
|
|
||||||
public response: ServerResponse,
|
|
||||||
flashEncryption: EncryptionContract,
|
|
||||||
flashConfig: ResponseConfig,
|
|
||||||
flashRouter: RouterContract,
|
|
||||||
) {
|
|
||||||
super(request, response, flashEncryption, flashConfig, flashRouter);
|
|
||||||
}
|
|
||||||
public nonce: string;
|
|
||||||
|
|
||||||
public flash(key: string, message: any): this {
|
|
||||||
// Store the flash message in the session
|
|
||||||
this.ctx?.session.flash(key, message);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toRoute(route: string): this {
|
|
||||||
// Redirect to the specified route
|
|
||||||
super.redirect().toRoute(route);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FlashResponse;
|
|
93
app/Library/Doi/DoiClient.ts
Normal file
93
app/Library/Doi/DoiClient.ts
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// import { Client } from 'guzzle';
|
||||||
|
// import { Log } from '@adonisjs/core/build/standalone';
|
||||||
|
// import { DoiInterface } from './interfaces/DoiInterface';
|
||||||
|
import DoiClientContract from '#app/Library/Doi/DoiClientContract';
|
||||||
|
import DoiClientException from '#app/exceptions/DoiClientException';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
import logger from '@adonisjs/core/services/logger';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export class DoiClient implements DoiClientContract {
|
||||||
|
public username: string;
|
||||||
|
public password: string;
|
||||||
|
public serviceUrl: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// const datacite_environment = process.env.DATACITE_ENVIRONMENT || 'debug';
|
||||||
|
this.username = process.env.DATACITE_USERNAME || '';
|
||||||
|
this.password = process.env.DATACITE_PASSWORD || '';
|
||||||
|
this.serviceUrl = process.env.DATACITE_SERVICE_URL || '';
|
||||||
|
// this.prefix = process.env.DATACITE_PREFIX || '';
|
||||||
|
// this.base_domain = process.env.BASE_DOMAIN || '';
|
||||||
|
|
||||||
|
if (this.username === '' || this.password === '' || this.serviceUrl === '') {
|
||||||
|
const message = 'issing configuration settings to properly initialize DOI client';
|
||||||
|
logger.error(message);
|
||||||
|
throw new DoiClientException(StatusCodes.BAD_REQUEST, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DOI with the given identifier
|
||||||
|
*
|
||||||
|
* @param doiValue The desired DOI identifier e.g. '10.5072/tethys.999',
|
||||||
|
* @param xmlMeta
|
||||||
|
* @param landingPageUrl e.g. https://www.tethys.at/dataset/1
|
||||||
|
*
|
||||||
|
* @return Promise<AxiosResponse<any>> The http response in the form of a axios response
|
||||||
|
*/
|
||||||
|
public async registerDoi(doiValue: string, xmlMeta: string, landingPageUrl: string): Promise<AxiosResponse<any>> {
|
||||||
|
//step 1: register metadata via xml upload
|
||||||
|
// state draft
|
||||||
|
// let response;
|
||||||
|
// let url = `${this.serviceUrl}/metadata/${doiValue}`; //https://mds.test.datacite.org/metadata/10.21388/tethys.213
|
||||||
|
const auth = {
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
};
|
||||||
|
let headers = {
|
||||||
|
'Content-Type': 'application/xml;charset=UTF-8',
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const metadataResponse = await axios.default.put(`${this.serviceUrl}/metadata/${doiValue}`, xmlMeta, { auth, headers });
|
||||||
|
|
||||||
|
// Response Codes
|
||||||
|
// 201 Created: operation successful
|
||||||
|
// 401 Unauthorised: no login
|
||||||
|
// 403 Forbidden: login problem, quota exceeded
|
||||||
|
// 415 Wrong Content Type : Not including content type in the header.
|
||||||
|
// 422 Unprocessable Entity : invalid XML
|
||||||
|
// let test = metadataResponse.data; // 'OK (10.21388/TETHYS.213)'
|
||||||
|
if (metadataResponse.status !== 201) {
|
||||||
|
const message = `Unexpected DataCite MDS response code ${metadataResponse.status}`;
|
||||||
|
logger.error(message);
|
||||||
|
throw new DoiClientException(metadataResponse.status, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doiResponse = await axios.default.put(`${this.serviceUrl}/doi/${doiValue}`, `doi=${doiValue}\nurl=${landingPageUrl}`, {
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Response Codes
|
||||||
|
// 201 Created: operation successful
|
||||||
|
// 400 Bad Request: request body must be exactly two lines: DOI and URL; wrong domain, wrong prefix;
|
||||||
|
// 401 Unauthorised: no login
|
||||||
|
// 403 Forbidden: login problem, quota exceeded
|
||||||
|
// 412 Precondition failed: metadata must be uploaded first.
|
||||||
|
if (doiResponse.status !== 201) {
|
||||||
|
const message = `Unexpected DataCite MDS response code ${doiResponse.status}`;
|
||||||
|
logger.error(message);
|
||||||
|
throw new DoiClientException(doiResponse.status, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return doiResponse;
|
||||||
|
} catch (error) {
|
||||||
|
// const message = `request for registering DOI failed with ${error.message}`;
|
||||||
|
// Handle the error, log it, or rethrow as needed
|
||||||
|
logger.error(error.message);
|
||||||
|
throw new DoiClientException(error.response.status, error.response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
app/Library/Doi/DoiClientContract.ts
Normal file
13
app/Library/Doi/DoiClientContract.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// import ResumptionToken from './ResumptionToken';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
export default interface DoiClientContract {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
serviceUrl: string;
|
||||||
|
// prefix: string;
|
||||||
|
// base_domain: string;
|
||||||
|
registerDoi(doiValue: string, xmlMeta: string, landingPageUrl: string): Promise<AxiosResponse<any>>;
|
||||||
|
// get(key: string): Promise<ResumptionToken | null>;
|
||||||
|
// set(token: ResumptionToken): Promise<string>;
|
||||||
|
}
|
51
app/Library/Oai/ResumptionToken.ts
Normal file
51
app/Library/Oai/ResumptionToken.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
export default class ResumptionToken {
|
||||||
|
private _documentIds: number[] = [];
|
||||||
|
private _metadataPrefix = '';
|
||||||
|
private _resumptionId = '';
|
||||||
|
private _startPosition = 0;
|
||||||
|
private _totalIds = 0;
|
||||||
|
|
||||||
|
get key(): string {
|
||||||
|
return this.metadataPrefix + this.startPosition + this.totalIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
get documentIds(): number[] {
|
||||||
|
return this._documentIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentIds(idsToStore: number | number[]) {
|
||||||
|
this._documentIds = Array.isArray(idsToStore) ? idsToStore : [idsToStore];
|
||||||
|
}
|
||||||
|
|
||||||
|
get metadataPrefix(): string {
|
||||||
|
return this._metadataPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
set metadataPrefix(value: string) {
|
||||||
|
this._metadataPrefix = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get resumptionId(): string {
|
||||||
|
return this._resumptionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
set resumptionId(resumptionId: string) {
|
||||||
|
this._resumptionId = resumptionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
get startPosition(): number {
|
||||||
|
return this._startPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
set startPosition(startPosition: number) {
|
||||||
|
this._startPosition = startPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalIds(): number {
|
||||||
|
return this._totalIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
set totalIds(totalIds: number) {
|
||||||
|
this._totalIds = totalIds;
|
||||||
|
}
|
||||||
|
}
|
11
app/Library/Oai/TokenWorkerContract.ts
Normal file
11
app/Library/Oai/TokenWorkerContract.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import ResumptionToken from './ResumptionToken.js';
|
||||||
|
|
||||||
|
export default abstract class TokenWorkerContract {
|
||||||
|
abstract ttl: number;
|
||||||
|
abstract isConnected: boolean;
|
||||||
|
abstract connect(): void;
|
||||||
|
abstract close(): void;
|
||||||
|
abstract get(key: string): Promise<ResumptionToken | null>;
|
||||||
|
abstract set(token: ResumptionToken): Promise<string>;
|
||||||
|
}
|
||||||
|
|
95
app/Library/Oai/TokenWorkerSerice.ts
Normal file
95
app/Library/Oai/TokenWorkerSerice.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import ResumptionToken from './ResumptionToken.js';
|
||||||
|
import { createClient, RedisClientType } from 'redis';
|
||||||
|
import InternalServerErrorException from '#app/exceptions/InternalServerException';
|
||||||
|
import { sprintf } from 'sprintf-js';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import TokenWorkerContract from './TokenWorkerContract.js';
|
||||||
|
|
||||||
|
export default class TokenWorkerService implements TokenWorkerContract {
|
||||||
|
protected filePrefix = 'rs_';
|
||||||
|
protected fileExtension = 'txt';
|
||||||
|
|
||||||
|
private cache: RedisClientType;
|
||||||
|
public ttl: number;
|
||||||
|
private url: string;
|
||||||
|
private connected = false;
|
||||||
|
|
||||||
|
constructor(ttl: number) {
|
||||||
|
this.ttl = ttl; // time to live
|
||||||
|
this.url = process.env.REDIS_URL || 'redis://127.0.0.1:6379';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connect() {
|
||||||
|
this.cache = createClient({ url: this.url });
|
||||||
|
this.cache.on('error', (err) => {
|
||||||
|
this.connected = false;
|
||||||
|
console.log('[Redis] Redis Client Error: ', err);
|
||||||
|
});
|
||||||
|
this.cache.on('connect', () => {
|
||||||
|
this.connected = true;
|
||||||
|
});
|
||||||
|
await this.cache.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isConnected(): boolean {
|
||||||
|
return this.connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async has(key: string): Promise<boolean> {
|
||||||
|
const result = await this.cache.get(key);
|
||||||
|
return result !== undefined && result !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async set(token: ResumptionToken): Promise<string> {
|
||||||
|
const uniqueName = await this.generateUniqueName();
|
||||||
|
|
||||||
|
const serialToken = JSON.stringify(token);
|
||||||
|
await this.cache.setEx(uniqueName, this.ttl, serialToken);
|
||||||
|
return uniqueName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generateUniqueName(): Promise<string> {
|
||||||
|
let fc = 0;
|
||||||
|
const uniqueId = dayjs().unix().toString();
|
||||||
|
let uniqueName: string;
|
||||||
|
let cacheKeyExists: boolean;
|
||||||
|
do {
|
||||||
|
// format values
|
||||||
|
// %s - String
|
||||||
|
// %d - Signed decimal number (negative, zero or positive)
|
||||||
|
// [0-9] (Specifies the minimum width held of to the variable value)
|
||||||
|
uniqueName = sprintf('%s%05d', uniqueId, fc++);
|
||||||
|
cacheKeyExists = await this.has(uniqueName);
|
||||||
|
} while (cacheKeyExists);
|
||||||
|
return uniqueName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(key: string): Promise<ResumptionToken | null> {
|
||||||
|
if (!this.cache) {
|
||||||
|
throw new InternalServerErrorException('Dataset is not available for OAI export!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.cache.get(key);
|
||||||
|
return result ? this.parseToken(result) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseToken(result: string): ResumptionToken {
|
||||||
|
const rToken: ResumptionToken = new ResumptionToken();
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
Object.assign(rToken, parsed);
|
||||||
|
return rToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public del(key: string) {
|
||||||
|
this.cache.del(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public flush() {
|
||||||
|
this.cache.flushAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close() {
|
||||||
|
await this.cache.disconnect();
|
||||||
|
this.connected = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
|
||||||
import { create } from 'xmlbuilder2';
|
import { create } from 'xmlbuilder2';
|
||||||
import Dataset from 'App/Models/Dataset';
|
import Dataset from '#models/dataset';
|
||||||
import Field from './Field';
|
import Field from './Field.js';
|
||||||
import BaseModel from 'App/Models/BaseModel';
|
import BaseModel from '#models/base_model';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
export default class Strategy {
|
export default class Strategy {
|
||||||
|
@ -10,7 +10,7 @@ export default class Strategy {
|
||||||
private config;
|
private config;
|
||||||
private xml: XMLBuilder;
|
private xml: XMLBuilder;
|
||||||
|
|
||||||
constructor(config) {
|
constructor(config: any) {
|
||||||
this.version = 1.0;
|
this.version = 1.0;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,11 @@ export default class Strategy {
|
||||||
|
|
||||||
for (const fieldname of fieldsDiff) {
|
for (const fieldname of fieldsDiff) {
|
||||||
const field = model.getField(fieldname);
|
const field = model.getField(fieldname);
|
||||||
this.mapField(field, modelNode);
|
this.mapField(field as Field, modelNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapField(field, modelNode: XMLBuilder) {
|
private mapField(field: Field, modelNode: XMLBuilder) {
|
||||||
const modelClass = field.getValueModelClass();
|
const modelClass = field.getValueModelClass();
|
||||||
let fieldValues = field.getValue();
|
let fieldValues = field.getValue();
|
||||||
|
|
||||||
|
@ -107,10 +107,10 @@ export default class Strategy {
|
||||||
childNode.att('Timezone', zoneName);
|
childNode.att('Timezone', zoneName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapModelAttributes(myObject, childNode: XMLBuilder) {
|
private mapModelAttributes(myObject: any, childNode: XMLBuilder) {
|
||||||
Object.keys(myObject).forEach((prop) => {
|
Object.keys(myObject).forEach((prop) => {
|
||||||
let value = myObject[prop];
|
let value = myObject[prop];
|
||||||
console.log(`${prop}: ${value}`);
|
// console.log(`${prop}: ${value}`);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
if (value instanceof DateTime) {
|
if (value instanceof DateTime) {
|
||||||
value = value.toFormat('yyyy-MM-dd HH:mm:ss').trim();
|
value = value.toFormat('yyyy-MM-dd HH:mm:ss').trim();
|
||||||
|
@ -161,7 +161,7 @@ export default class Strategy {
|
||||||
return fieldValues?.toString().trim();
|
return fieldValues?.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private createModelNode(model) {
|
private createModelNode(model: Dataset) {
|
||||||
const className = 'Rdr_' + model.constructor.name.split('\\').pop(); //Rdr_Dataset
|
const className = 'Rdr_' + model.constructor.name.split('\\').pop(); //Rdr_Dataset
|
||||||
// return dom.createElement(className);
|
// return dom.createElement(className);
|
||||||
return this.xml.root().ele(className);
|
return this.xml.root().ele(className);
|
||||||
|
|
197
app/Library/Utils/Index.ts
Normal file
197
app/Library/Utils/Index.ts
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
import Dataset from '#models/dataset';
|
||||||
|
import { Client } from '@opensearch-project/opensearch';
|
||||||
|
import { create } from 'xmlbuilder2';
|
||||||
|
import SaxonJS from 'saxon-js';
|
||||||
|
import XmlModel from '#app/Library/XmlModel';
|
||||||
|
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
|
||||||
|
import logger from '@adonisjs/core/services/logger';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
// import Config from '@ioc:Adonis/Core/Config';
|
||||||
|
import { getDomain } from '#app/utils/utility-functions';
|
||||||
|
|
||||||
|
// const opensearchNode = process.env.OPENSEARCH_HOST || 'localhost';
|
||||||
|
// const client = new Client({ node: `http://${opensearchNode}` }); // replace with your OpenSearch endpoint
|
||||||
|
interface XslTParameter {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
// opensearchNode: process.env.OPENSEARCH_HOST || 'localhost',
|
||||||
|
client: new Client({ node: `http://${process.env.OPENSEARCH_HOST || 'localhost'}` }), // replace with your OpenSearch endpoint
|
||||||
|
|
||||||
|
async getDoiRegisterString(dataset: Dataset): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const proc = readFileSync('public/assets2/doi_datacite.sef.json');
|
||||||
|
const xsltParameter: XslTParameter = {};
|
||||||
|
let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
||||||
|
const datasetNode = xml.root().ele('Dataset');
|
||||||
|
await createXmlRecord(dataset, datasetNode);
|
||||||
|
const xmlString = xml.end({ prettyPrint: false });
|
||||||
|
|
||||||
|
// set timestamp
|
||||||
|
const date = DateTime.now();
|
||||||
|
const unixTimestamp = date.toUnixInteger();
|
||||||
|
xsltParameter['unixTimestamp'] = unixTimestamp;
|
||||||
|
|
||||||
|
// set prefix
|
||||||
|
let prefix = '';
|
||||||
|
// let base_domain = '';
|
||||||
|
// const datacite_environment = process.env.DATACITE_ENVIRONMENT || 'debug';
|
||||||
|
// if (datacite_environment === 'debug') {
|
||||||
|
// prefix = process.env.DATACITE_TEST_PREFIX || '';
|
||||||
|
// base_domain = process.env.TEST_BASE_DOMAIN || '';
|
||||||
|
// } else if (datacite_environment === 'production') {
|
||||||
|
// prefix = process.env.DATACITE_PREFIX || '';
|
||||||
|
// base_domain = process.env.BASE_DOMAIN || '';
|
||||||
|
// }
|
||||||
|
prefix = process.env.DATACITE_PREFIX || '';
|
||||||
|
xsltParameter['prefix'] = prefix;
|
||||||
|
|
||||||
|
const repIdentifier = 'tethys';
|
||||||
|
xsltParameter['repIdentifier'] = repIdentifier;
|
||||||
|
|
||||||
|
let xmlOutput; // = xmlString;
|
||||||
|
try {
|
||||||
|
const result = await SaxonJS.transform({
|
||||||
|
// stylesheetFileName: `${config.TMP_BASE_DIR}/data-quality/rules/iati.sef.json`,
|
||||||
|
stylesheetText: proc,
|
||||||
|
destination: 'serialized',
|
||||||
|
// sourceFileName: sourceFile,
|
||||||
|
sourceText: xmlString,
|
||||||
|
stylesheetParams: xsltParameter,
|
||||||
|
// logLevel: 10,
|
||||||
|
});
|
||||||
|
xmlOutput = result.principalResult;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('An error occurred while creating the user', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmlOutput;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`An error occurred while indexing datsaet with publish_id ${dataset.publish_id}.`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async indexDocument(dataset: Dataset, index_name: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const proc = readFileSync('public/assets2/solr.sef.json');
|
||||||
|
const doc: string = await this.getTransformedString(dataset, proc);
|
||||||
|
|
||||||
|
let document = JSON.parse(doc);
|
||||||
|
await this.client.index({
|
||||||
|
id: dataset.publish_id?.toString(),
|
||||||
|
index: index_name,
|
||||||
|
body: document,
|
||||||
|
refresh: true,
|
||||||
|
});
|
||||||
|
logger.info(`dataset with publish_id ${dataset.publish_id} successfully indexed`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`An error occurred while indexing datsaet with publish_id ${dataset.publish_id}.`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTransformedString(dataset: Dataset, proc: Buffer): Promise<string> {
|
||||||
|
let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
||||||
|
const datasetNode = xml.root().ele('Dataset');
|
||||||
|
await createXmlRecord(dataset, datasetNode);
|
||||||
|
const xmlString = xml.end({ prettyPrint: false });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await SaxonJS.transform({
|
||||||
|
stylesheetText: proc,
|
||||||
|
destination: 'serialized',
|
||||||
|
sourceText: xmlString,
|
||||||
|
});
|
||||||
|
return result.principalResult;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`An error occurred while creating the user, error: ${error.message},`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Return the default global focus trap stack
|
||||||
|
*
|
||||||
|
* @return {import('focus-trap').FocusTrap[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// export const indexDocument = async (dataset: Dataset, index_name: string, proc: Buffer): Promise<void> => {
|
||||||
|
// try {
|
||||||
|
// const doc = await getJsonString(dataset, proc);
|
||||||
|
|
||||||
|
// let document = JSON.parse(doc);
|
||||||
|
// await client.index({
|
||||||
|
// id: dataset.publish_id?.toString(),
|
||||||
|
// index: index_name,
|
||||||
|
// body: document,
|
||||||
|
// refresh: true,
|
||||||
|
// });
|
||||||
|
// Logger.info(`dataset with publish_id ${dataset.publish_id} successfully indexed`);
|
||||||
|
// } catch (error) {
|
||||||
|
// Logger.error(`An error occurred while indexing datsaet with publish_id ${dataset.publish_id}.`);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const getJsonString = async (dataset, proc): Promise<string> => {
|
||||||
|
// let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
||||||
|
// const datasetNode = xml.root().ele('Dataset');
|
||||||
|
// await createXmlRecord(dataset, datasetNode);
|
||||||
|
// const xmlString = xml.end({ prettyPrint: false });
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const result = await transform({
|
||||||
|
// stylesheetText: proc,
|
||||||
|
// destination: 'serialized',
|
||||||
|
// sourceText: xmlString,
|
||||||
|
// });
|
||||||
|
// return result.principalResult;
|
||||||
|
// } catch (error) {
|
||||||
|
// Logger.error(`An error occurred while creating the user, error: ${error.message},`);
|
||||||
|
// return '';
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const createXmlRecord = async (dataset: Dataset, datasetNode: XMLBuilder): Promise<void> => {
|
||||||
|
const domNode = await getDatasetXmlDomNode(dataset);
|
||||||
|
if (domNode) {
|
||||||
|
// add frontdoor url and data-type
|
||||||
|
dataset.publish_id && addLandingPageAttribute(domNode, dataset.publish_id.toString());
|
||||||
|
addSpecInformation(domNode, 'data-type:' + dataset.type);
|
||||||
|
if (dataset.collections) {
|
||||||
|
for (const coll of dataset.collections) {
|
||||||
|
const collRole = coll.collectionRole;
|
||||||
|
addSpecInformation(domNode, collRole.oai_name + ':' + coll.number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datasetNode.import(domNode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDatasetXmlDomNode = async (dataset: Dataset): Promise<XMLBuilder | null> => {
|
||||||
|
const xmlModel = new XmlModel(dataset);
|
||||||
|
// xmlModel.setModel(dataset);
|
||||||
|
xmlModel.excludeEmptyFields();
|
||||||
|
xmlModel.caching = true;
|
||||||
|
// const cache = dataset.xmlCache ? dataset.xmlCache : null;
|
||||||
|
// dataset.load('xmlCache');
|
||||||
|
await dataset.load('xmlCache');
|
||||||
|
if (dataset.xmlCache) {
|
||||||
|
xmlModel.xmlCache = dataset.xmlCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return cache.getDomDocument();
|
||||||
|
const domDocument: XMLBuilder | null = await xmlModel.getDomDocument();
|
||||||
|
return domDocument;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addLandingPageAttribute = (domNode: XMLBuilder, dataid: string) => {
|
||||||
|
const baseDomain = process.env.OAI_BASE_DOMAIN || 'localhost';
|
||||||
|
const url = 'https://' + getDomain(baseDomain) + '/dataset/' + dataid;
|
||||||
|
// add attribute du dataset xml element
|
||||||
|
domNode.att('landingpage', url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSpecInformation= (domNode: XMLBuilder, information: string) => {
|
||||||
|
domNode.ele('SetSpec').att('Value', information);
|
||||||
|
};
|
|
@ -1,8 +1,9 @@
|
||||||
import DocumentXmlCache from 'App/Models/DocumentXmlCache';
|
import DocumentXmlCache from '#models/DocumentXmlCache';
|
||||||
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
|
||||||
import Dataset from 'App/Models/Dataset';
|
import Dataset from '#models/dataset';
|
||||||
import Strategy from './Strategy';
|
import Strategy from './Strategy.js';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { builder } from 'xmlbuilder2';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the description of the interface
|
* This is the description of the interface
|
||||||
|
@ -84,10 +85,21 @@ export default class XmlModel {
|
||||||
this.cache = this.cache || new DocumentXmlCache();
|
this.cache = this.cache || new DocumentXmlCache();
|
||||||
this.cache.document_id = dataset.id;
|
this.cache.document_id = dataset.id;
|
||||||
this.cache.xml_version = 1; // (int)$this->strategy->getVersion();
|
this.cache.xml_version = 1; // (int)$this->strategy->getVersion();
|
||||||
// this.cache.server_date_modified = dataset.server_date_modified.toFormat("yyyy-MM-dd HH:mm:ss");
|
this.cache.server_date_modified = dataset.server_date_modified.toFormat('yyyy-MM-dd HH:mm:ss');
|
||||||
this.cache.xml_data = domDocument.end();
|
this.cache.xml_data = domDocument.end();
|
||||||
await this.cache.save();
|
await this.cache.save();
|
||||||
}
|
}
|
||||||
|
const node = domDocument.find(
|
||||||
|
(n) => {
|
||||||
|
const test = n.node.nodeName == 'Rdr_Dataset';
|
||||||
|
return test;
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?.node;
|
||||||
|
if (node != undefined) {
|
||||||
|
domDocument = builder({ version: '1.0', encoding: 'UTF-8', standalone: true }, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return domDocument;
|
return domDocument;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import {
|
|
||||||
column,
|
|
||||||
hasMany,
|
|
||||||
HasMany,
|
|
||||||
belongsTo,
|
|
||||||
BelongsTo,
|
|
||||||
// manyToMany,
|
|
||||||
// ManyToMany,
|
|
||||||
SnakeCaseNamingStrategy,
|
|
||||||
} from '@ioc:Adonis/Lucid/Orm';
|
|
||||||
import HashValue from './HashValue';
|
|
||||||
import Dataset from './Dataset';
|
|
||||||
import BaseModel from './BaseModel';
|
|
||||||
|
|
||||||
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 document_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: number;
|
|
||||||
|
|
||||||
@column()
|
|
||||||
public visibleInOai: boolean;
|
|
||||||
|
|
||||||
@column()
|
|
||||||
public visibleInFrontdoor: boolean;
|
|
||||||
|
|
||||||
@column()
|
|
||||||
public sortOrder: number;
|
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true })
|
|
||||||
public createdAt: DateTime;
|
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
||||||
public updatedAt: DateTime;
|
|
||||||
|
|
||||||
// public function dataset()
|
|
||||||
// {
|
|
||||||
// return $this->belongsTo(Dataset::class, 'document_id', 'id');
|
|
||||||
// }
|
|
||||||
@belongsTo(() => Dataset, {
|
|
||||||
foreignKey: 'document_id',
|
|
||||||
})
|
|
||||||
public dataset: BelongsTo<typeof Dataset>;
|
|
||||||
|
|
||||||
@hasMany(() => HashValue, {
|
|
||||||
foreignKey: 'file_id',
|
|
||||||
})
|
|
||||||
public hashvalues: HasMany<typeof HashValue>;
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import { column, beforeSave, manyToMany, ManyToMany, hasMany, HasMany } 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';
|
|
||||||
import Dataset from './Dataset';
|
|
||||||
import BaseModel from './BaseModel';
|
|
||||||
|
|
||||||
// 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>;
|
|
||||||
|
|
||||||
@hasMany(() => Dataset, {
|
|
||||||
foreignKey: 'account_id',
|
|
||||||
})
|
|
||||||
public datasets: HasMany<typeof Dataset>;
|
|
||||||
|
|
||||||
// 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;
|
|
|
@ -1,19 +0,0 @@
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
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 = {};
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
|
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { TitleTypes, DescriptionTypes, RelationTypes, ReferenceIdentifierTypes, ContributorTypes } from 'Contracts/enums';
|
|
||||||
|
|
||||||
export default class CreateDatasetValidator {
|
|
||||||
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({
|
|
||||||
// first step
|
|
||||||
language: schema.string({ trim: true }, [
|
|
||||||
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
|
|
||||||
]),
|
|
||||||
licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
|
|
||||||
rights: schema.string([rules.equalTo('true')]),
|
|
||||||
// second step
|
|
||||||
type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
|
|
||||||
creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
|
|
||||||
titles: schema.array([rules.minLength(1)]).members(
|
|
||||||
schema.object().members({
|
|
||||||
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
|
|
||||||
type: schema.enum(Object.values(TitleTypes)),
|
|
||||||
language: schema.string({ trim: true }, [
|
|
||||||
rules.minLength(2),
|
|
||||||
rules.maxLength(255),
|
|
||||||
rules.translatedLanguage('/language', 'type'),
|
|
||||||
]),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
descriptions: schema.array([rules.minLength(1)]).members(
|
|
||||||
schema.object().members({
|
|
||||||
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
|
|
||||||
type: schema.enum(Object.values(DescriptionTypes)),
|
|
||||||
language: schema.string({ trim: true }, [
|
|
||||||
rules.minLength(2),
|
|
||||||
rules.maxLength(255),
|
|
||||||
rules.translatedLanguage('/language', 'type'),
|
|
||||||
]),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })),
|
|
||||||
contributors: schema.array.optional().members(
|
|
||||||
schema.object().members({
|
|
||||||
email: schema.string({ trim: true }),
|
|
||||||
pivot_contributor_type: schema.enum(Object.keys(ContributorTypes)),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
// third step
|
|
||||||
project_id: schema.number.optional(),
|
|
||||||
embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
|
|
||||||
coverage: schema.object().members({
|
|
||||||
x_min: schema.number(),
|
|
||||||
x_max: schema.number(),
|
|
||||||
y_min: schema.number(),
|
|
||||||
y_max: schema.number(),
|
|
||||||
elevation_absolut: schema.number.optional(),
|
|
||||||
elevation_min: schema.number.optional([rules.requiredIfExists('elevation_max')]),
|
|
||||||
elevation_max: schema.number.optional([rules.requiredIfExists('elevation_min')]),
|
|
||||||
depth_absolut: schema.number.optional(),
|
|
||||||
depth_min: schema.number.optional([rules.requiredIfExists('depth_max')]),
|
|
||||||
depth_max: schema.number.optional([rules.requiredIfExists('depth_min')]),
|
|
||||||
}),
|
|
||||||
references: schema.array.optional([rules.uniqueArray('value')]).members(
|
|
||||||
schema.object().members({
|
|
||||||
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
|
|
||||||
type: schema.enum(Object.values(ReferenceIdentifierTypes)),
|
|
||||||
relation: schema.enum(Object.values(RelationTypes)),
|
|
||||||
label: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
subjects: schema.array([rules.minLength(3), rules.uniqueArray('value')]).members(
|
|
||||||
schema.object().members({
|
|
||||||
value: schema.string({ trim: true }, [
|
|
||||||
rules.minLength(3),
|
|
||||||
rules.maxLength(255),
|
|
||||||
// rules.unique({ table: 'dataset_subjects', column: 'value' }),
|
|
||||||
]),
|
|
||||||
// type: schema.enum(Object.values(TitleTypes)),
|
|
||||||
language: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
// file: schema.file({
|
|
||||||
// size: '100mb',
|
|
||||||
// extnames: ['jpg', 'gif', 'png'],
|
|
||||||
// }),
|
|
||||||
files: schema.array([rules.minLength(1)]).members(
|
|
||||||
schema.file({
|
|
||||||
size: '100mb',
|
|
||||||
extnames: ['jpg', 'gif', 'png', 'tif', 'pdf'],
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
// upload: schema.object().members({
|
|
||||||
// label: schema.string({ trim: true }, [rules.maxLength(255)]),
|
|
||||||
|
|
||||||
// // label: schema.string({ trim: true }, [
|
|
||||||
// // // rules.minLength(3),
|
|
||||||
// // // rules.maxLength(255),
|
|
||||||
// // ]),
|
|
||||||
// }),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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',
|
|
||||||
'licenses.minLength': 'at least {{ options.minLength }} permission must be defined',
|
|
||||||
'licenses.*.number': 'Define roles as valid numbers',
|
|
||||||
'rights.equalTo': 'you must agree to continue',
|
|
||||||
|
|
||||||
'titles.0.value.minLength': 'Main Title must be at least {{ options.minLength }} characters long',
|
|
||||||
'titles.0.value.required': 'Main Title is required',
|
|
||||||
'titles.*.value.required': 'Additional title is required, if defined',
|
|
||||||
'titles.*.type.required': 'Additional title type is required',
|
|
||||||
'titles.*.language.required': 'Additional title language is required',
|
|
||||||
'titles.*.language.translatedLanguage': 'The language of the translated title must be different from the language of the dataset',
|
|
||||||
|
|
||||||
'descriptions.0.value.minLength': 'Main Abstract must be at least {{ options.minLength }} characters long',
|
|
||||||
'descriptions.0.value.required': 'Main Abstract is required',
|
|
||||||
'descriptions.*.value.required': 'Additional description is required, if defined',
|
|
||||||
'descriptions.*.type.required': 'Additional description type is required',
|
|
||||||
'descriptions.*.language.required': 'Additional description language is required',
|
|
||||||
'descriptions.*.language.translatedLanguage':
|
|
||||||
'The language of the translated description must be different from the language of the dataset',
|
|
||||||
|
|
||||||
'authors.minLength': 'at least {{ options.minLength }} author must be defined',
|
|
||||||
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
|
|
||||||
|
|
||||||
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
|
|
||||||
|
|
||||||
'subjects.minLength': 'at least {{ options.minLength }} keywords must be defined',
|
|
||||||
'subjects.uniqueArray': 'The {{ options.array }} array must have unique values based on the {{ options.field }} attribute.',
|
|
||||||
'subjects.*.value.required': 'keyword value is required',
|
|
||||||
'subjects.*.value.minLength': 'keyword value must be at least {{ options.minLength }} characters long',
|
|
||||||
'subjects.*.type.required': 'keyword type is required',
|
|
||||||
'subjects.*.language.required': 'language of keyword is required',
|
|
||||||
|
|
||||||
'references.*.value.required': 'Additional reference value is required, if defined',
|
|
||||||
'references.*.type.required': 'Additional reference identifier type is required',
|
|
||||||
'references.*.relation.required': 'Additional reference relation type is required',
|
|
||||||
'references.*.label.required': 'Additional reference label is required',
|
|
||||||
|
|
||||||
'files.minLength': 'At least {{ options.minLength }} file upload is required.',
|
|
||||||
'files.*.size': 'file size is to big',
|
|
||||||
'files.extnames': 'file extension is not supported',
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
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',
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
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',
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
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',
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
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',
|
|
||||||
};
|
|
||||||
}
|
|
12
app/exceptions/DoiClientException.ts
Normal file
12
app/exceptions/DoiClientException.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class DoiClientException extends Error {
|
||||||
|
public status: number;
|
||||||
|
public message: string;
|
||||||
|
|
||||||
|
constructor(status: number, message: string) {
|
||||||
|
super(message);
|
||||||
|
this.status = status;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DoiClientException;
|
|
@ -1,5 +1,5 @@
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
import HTTPException from './HttpException';
|
import HTTPException from './HttpException.js';
|
||||||
|
|
||||||
class InternalServerErrorException extends HTTPException {
|
class InternalServerErrorException extends HTTPException {
|
||||||
constructor(message?: string) {
|
constructor(message?: string) {
|
|
@ -1,4 +1,5 @@
|
||||||
import { Exception } from '@adonisjs/core/build/standalone';
|
import { Exception } from "@adonisjs/core/exceptions";
|
||||||
|
import { HttpContext } from "@adonisjs/core/http";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -23,21 +24,21 @@ export default class InvalidCredentialException extends Exception {
|
||||||
* Unable to find user
|
* Unable to find user
|
||||||
*/
|
*/
|
||||||
public static invalidUid() {
|
public static invalidUid() {
|
||||||
const error = new this('User not found', 400, 'E_INVALID_AUTH_UID');
|
const error = new this('User not found', {status: 400, code: 'E_INVALID_AUTH_UID'});
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Invalid user password
|
* Invalid user password
|
||||||
*/
|
*/
|
||||||
public static invalidPassword() {
|
public static invalidPassword() {
|
||||||
const error = new this('Password mis-match', 400, 'E_INVALID_AUTH_PASSWORD');
|
const error = new this('Password mis-match', {status: 400, code: 'E_INVALID_AUTH_PASSWORD'});
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flash error message and redirect the user back
|
* Flash error message and redirect the user back
|
||||||
*/
|
*/
|
||||||
private respondWithRedirect(error, ctx) {
|
private respondWithRedirect(error: any, ctx: HttpContext) {
|
||||||
// if (!ctx.session) {
|
// if (!ctx.session) {
|
||||||
// return ctx.response.status(this.status).send(this.responseText);
|
// return ctx.response.status(this.status).send(this.responseText);
|
||||||
// }
|
// }
|
||||||
|
@ -59,7 +60,7 @@ export default class InvalidCredentialException extends Exception {
|
||||||
* Handle this exception by itself
|
* Handle this exception by itself
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public handle(error, ctx) {
|
public handle(error: any, ctx: HttpContext) {
|
||||||
// return response.status(403).view.render("errors/unauthorized", {
|
// return response.status(403).view.render("errors/unauthorized", {
|
||||||
// error: error,
|
// error: error,
|
||||||
// });
|
// });
|
|
@ -1,6 +1,6 @@
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
// import HTTPException from './HttpException';
|
// import HTTPException from './HttpException';
|
||||||
import { OaiErrorCodes } from './OaiErrorCodes';
|
import { OaiErrorCodes } from './OaiErrorCodes.js';
|
||||||
|
|
||||||
export class ErrorCode {
|
export class ErrorCode {
|
||||||
public static readonly Unauthenticated = 'Unauthenticated';
|
public static readonly Unauthenticated = 'Unauthenticated';
|
125
app/exceptions/handler.ts
Normal file
125
app/exceptions/handler.ts
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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 app from '@adonisjs/core/services/app';
|
||||||
|
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http';
|
||||||
|
// import logger from '@adonisjs/core/services/logger';
|
||||||
|
import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http';
|
||||||
|
|
||||||
|
export default class HttpExceptionHandler extends ExceptionHandler {
|
||||||
|
/**
|
||||||
|
* In debug mode, the exception handler will display verbose errors
|
||||||
|
* with pretty printed stack traces.
|
||||||
|
*/
|
||||||
|
protected debug = !app.inProduction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status pages are used to display a custom HTML pages for certain error
|
||||||
|
* codes. You might want to enable them in production only, but feel
|
||||||
|
* free to enable them in development as well.
|
||||||
|
*/
|
||||||
|
protected renderStatusPages = true; //app.inProduction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status pages is a collection of error code range and a callback
|
||||||
|
* to return the HTML contents to send as a response.
|
||||||
|
*/
|
||||||
|
// protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
|
||||||
|
// '401..403': (error, { view }) => {
|
||||||
|
// return view.render('./errors/unauthorized', { error });
|
||||||
|
// },
|
||||||
|
// '404': (error, { view }) => {
|
||||||
|
// return view.render('./errors/not-found', { error });
|
||||||
|
// },
|
||||||
|
// '500..599': (error, { view }) => {
|
||||||
|
// return view.render('./errors/server-error', { error });
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
|
||||||
|
'404': (error, { inertia }) => {
|
||||||
|
return inertia.render('Errors/ServerError', {
|
||||||
|
error: error.message,
|
||||||
|
code: error.status,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'401..403': async (error, { inertia }) => {
|
||||||
|
// session.flash('errors', error.message);
|
||||||
|
return inertia.render('Errors/ServerError', {
|
||||||
|
error: error.message,
|
||||||
|
code: error.status,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'500..599': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// constructor() {
|
||||||
|
// super(logger);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public async handle(error: any, ctx: HttpContext) {
|
||||||
|
const { response, request, session } = 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
|
||||||
|
// let test = response.getStatus(); //200
|
||||||
|
// let header = request.header('X-Inertia'); // true
|
||||||
|
// if (request.header('X-Inertia') && [500, 503, 404, 403, 401, 200].includes(response.getStatus())) {
|
||||||
|
if (request.header('X-Inertia') && [422].includes(error.status)) {
|
||||||
|
// session.flash('errors', error.messages.errors);
|
||||||
|
session.flash('errors', error.messages);
|
||||||
|
return response.redirect().back();
|
||||||
|
// return inertia.render('errors/server_error', {
|
||||||
|
// return inertia.render('errors/server_error', {
|
||||||
|
// // status: response.getStatus(),
|
||||||
|
// error: error,
|
||||||
|
// });
|
||||||
|
// ->toResponse($request)
|
||||||
|
// ->setStatusCode($response->status());
|
||||||
|
}
|
||||||
|
// Dynamically change the error templates based on the absence of X-Inertia header
|
||||||
|
// if (!ctx.request.header('X-Inertia')) {
|
||||||
|
// this.statusPages = {
|
||||||
|
// '401..403': (error, { view }) => view.render('./errors/unauthorized', { error }),
|
||||||
|
// '404': (error, { view }) => view.render('./errors/not-found', { error }),
|
||||||
|
// '500..599': (error, { view }) => view.render('./errors/server-error', { error }),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward rest of the exceptions to the parent class
|
||||||
|
*/
|
||||||
|
return super.handle(error, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method is used to report error to the logging service or
|
||||||
|
* the a third party error monitoring service.
|
||||||
|
*
|
||||||
|
* @note You should not attempt to send a response from this method.
|
||||||
|
*/
|
||||||
|
async report(error: unknown, ctx: HttpContext) {
|
||||||
|
return super.report(error, ctx);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContext } from '@adonisjs/core/http';
|
||||||
import Config from '@ioc:Adonis/Core/Config';
|
// import Config from '@ioc:Adonis/Core/Config';
|
||||||
import Database from '@ioc:Adonis/Lucid/Database';
|
import config from '@adonisjs/core/services/config'
|
||||||
import User from 'App/Models/User';
|
import db from '@adonisjs/lucid/services/db';
|
||||||
|
import User from '#models/user';
|
||||||
// import { Exception } from '@adonisjs/core/build/standalone'
|
// import { Exception } from '@adonisjs/core/build/standalone'
|
||||||
|
|
||||||
const roleTable = Config.get('rolePermission.role_table', 'roles');
|
const roleTable = config.get('rolePermission.role_table', 'roles');
|
||||||
const userRoleTable = Config.get('rolePermission.user_role_table', 'user_roles');
|
const userRoleTable = config.get('rolePermission.user_role_table', 'user_roles');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Role authentication to check if user has any of the specified roles
|
* Role authentication to check if user has any of the specified roles
|
||||||
|
@ -16,7 +17,7 @@ export default class Is {
|
||||||
/**
|
/**
|
||||||
* Handle request
|
* Handle request
|
||||||
*/
|
*/
|
||||||
public async handle({ auth, response }: HttpContextContract, next: () => Promise<void>, roleNames: string[]) {
|
public async handle({ auth, response }: HttpContext, next: () => Promise<void>, roleNames: string[]) {
|
||||||
/**
|
/**
|
||||||
* Check if user is logged-in or not.
|
* Check if user is logged-in or not.
|
||||||
*/
|
*/
|
||||||
|
@ -33,7 +34,8 @@ export default class Is {
|
||||||
// 401,
|
// 401,
|
||||||
// "E_INVALID_AUTH_UID");
|
// "E_INVALID_AUTH_UID");
|
||||||
}
|
}
|
||||||
await next();
|
// await next();
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkHasRoles(user: User, roleNames: Array<string>): Promise<boolean> {
|
private async checkHasRoles(user: User, roleNames: Array<string>): Promise<boolean> {
|
||||||
|
@ -46,7 +48,7 @@ export default class Is {
|
||||||
0: {
|
0: {
|
||||||
0: { roleCount },
|
0: { roleCount },
|
||||||
},
|
},
|
||||||
} = await Database.rawQuery(
|
} = await db.rawQuery(
|
||||||
'SELECT count(`ur`.`id`) as roleCount FROM ' +
|
'SELECT count(`ur`.`id`) as roleCount FROM ' +
|
||||||
userRoleTable +
|
userRoleTable +
|
||||||
' ur INNER JOIN ' +
|
' ur INNER JOIN ' +
|
25
app/middleware/auth_middleware.ts
Normal file
25
app/middleware/auth_middleware.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http'
|
||||||
|
import type { NextFn } from '@adonisjs/core/types/http'
|
||||||
|
import type { Authenticators } from '@adonisjs/auth/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth middleware is used authenticate HTTP requests and deny
|
||||||
|
* access to unauthenticated users.
|
||||||
|
*/
|
||||||
|
export default class AuthMiddleware {
|
||||||
|
/**
|
||||||
|
* The URL to redirect to, when authentication fails
|
||||||
|
*/
|
||||||
|
redirectTo = '/app/login'
|
||||||
|
|
||||||
|
async handle(
|
||||||
|
ctx: HttpContext,
|
||||||
|
next: NextFn,
|
||||||
|
options: {
|
||||||
|
guards?: (keyof Authenticators)[]
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo })
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,14 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContext } from '@adonisjs/core/http';
|
||||||
import Config from '@ioc:Adonis/Core/Config';
|
// import Config from '@ioc:Adonis/Core/Config';
|
||||||
import Database from '@ioc:Adonis/Lucid/Database';
|
import config from '@adonisjs/core/services/config';
|
||||||
import User from 'App/Models/User';
|
import db from '@adonisjs/lucid/services/db';
|
||||||
import { Exception } from '@adonisjs/core/build/standalone';
|
import User from '#models/user';
|
||||||
|
import { Exception } from '@adonisjs/core/exceptions';
|
||||||
|
|
||||||
const permissionTable = Config.get('rolePermission.permission_table', 'permissions');
|
const permissionTable = config.get('rolePermission.permission_table', 'permissions');
|
||||||
const rolePermissionTable = Config.get('rolePermission.role_permission_table', 'role_has_permissions');
|
const rolePermissionTable = config.get('rolePermission.role_permission_table', 'role_has_permissions');
|
||||||
const roleTable = Config.get('rolePermission.role_table', 'roles');
|
const roleTable = config.get('rolePermission.role_table', 'roles');
|
||||||
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
|
const userRoleTable = config.get('rolePermission.user_role_table', 'link_accounts_roles');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permission authentication to check if user has any of the specified permissions
|
* Permission authentication to check if user has any of the specified permissions
|
||||||
|
@ -18,7 +19,7 @@ export default class Can {
|
||||||
/**
|
/**
|
||||||
* Handle request
|
* Handle request
|
||||||
*/
|
*/
|
||||||
public async handle({ auth, response }: HttpContextContract, next: () => Promise<void>, permissionNames: string[]) {
|
public async handle({ auth, response }: HttpContext, next: () => Promise<void>, permissionNames: string[]) {
|
||||||
/**
|
/**
|
||||||
* Check if user is logged-in
|
* Check if user is logged-in
|
||||||
*/
|
*/
|
||||||
|
@ -31,9 +32,10 @@ export default class Can {
|
||||||
// return response.unauthorized({
|
// return response.unauthorized({
|
||||||
// error: `Doesn't have required role(s): ${permissionNames.join(',')}`,
|
// error: `Doesn't have required role(s): ${permissionNames.join(',')}`,
|
||||||
// });
|
// });
|
||||||
throw new Exception(`Doesn't have required permission(s): ${permissionNames.join(',')}`, 401);
|
throw new Exception(`Doesn't have required permission(s): ${permissionNames.join(',')}`, { status: 401 });
|
||||||
}
|
}
|
||||||
await next();
|
// await next();
|
||||||
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkHasPermissions(user: User, permissionNames: Array<string>): Promise<boolean> {
|
private async checkHasPermissions(user: User, permissionNames: Array<string>): Promise<boolean> {
|
||||||
|
@ -66,7 +68,7 @@ export default class Can {
|
||||||
rows: {
|
rows: {
|
||||||
0: { permissioncount },
|
0: { permissioncount },
|
||||||
},
|
},
|
||||||
} = await Database.rawQuery(
|
} = await db.rawQuery(
|
||||||
'SELECT count("p"."name") as permissionCount FROM ' +
|
'SELECT count("p"."name") as permissionCount FROM ' +
|
||||||
roleTable +
|
roleTable +
|
||||||
' r INNER JOIN ' +
|
' r INNER JOIN ' +
|
19
app/middleware/container_bindings_middleware.ts
Normal file
19
app/middleware/container_bindings_middleware.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Logger } from '@adonisjs/core/logger';
|
||||||
|
import { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import { NextFn } from '@adonisjs/core/types/http';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The container bindings middleware binds classes to their request
|
||||||
|
* specific value using the container resolver.
|
||||||
|
*
|
||||||
|
* - We bind "HttpContext" class to the "ctx" object
|
||||||
|
* - And bind "Logger" class to the "ctx.logger" object
|
||||||
|
*/
|
||||||
|
export default class ContainerBindingsMiddleware {
|
||||||
|
handle(ctx: HttpContext, next: NextFn) {
|
||||||
|
ctx.containerResolver.bindValue(HttpContext, ctx);
|
||||||
|
ctx.containerResolver.bindValue(Logger, ctx.logger);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
27
app/middleware/guest_middleware.ts
Normal file
27
app/middleware/guest_middleware.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import type { NextFn } from '@adonisjs/core/types/http';
|
||||||
|
import type { Authenticators } from '@adonisjs/auth/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guest middleware is used to deny access to routes that should
|
||||||
|
* be accessed by unauthenticated users.
|
||||||
|
*
|
||||||
|
* For example, the login page should not be accessible if the user
|
||||||
|
* is already logged-in
|
||||||
|
*/
|
||||||
|
export default class GuestMiddleware {
|
||||||
|
/**
|
||||||
|
* The URL to redirect to when user is logged-in
|
||||||
|
*/
|
||||||
|
redirectTo = '/';
|
||||||
|
|
||||||
|
async handle(ctx: HttpContext, next: NextFn, options: { guards?: (keyof Authenticators)[] } = {}) {
|
||||||
|
for (let guard of options.guards || [ctx.auth.defaultGuard]) {
|
||||||
|
if (await ctx.auth.use(guard).check()) {
|
||||||
|
return ctx.response.redirect(this.redirectTo, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
43
app/middleware/normalize_newlines_middleware.ts
Normal file
43
app/middleware/normalize_newlines_middleware.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* This middleware class normalizes newlines in the request input data by replacing
|
||||||
|
* all occurrences of '\r\n' with '\n' recursively for strings, arrays, and objects.
|
||||||
|
*/
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import type { NextFn } from '@adonisjs/core/types/http';
|
||||||
|
|
||||||
|
export default class NormalizeNewlinesMiddleware {
|
||||||
|
async handle(ctx: HttpContext, next: NextFn) {
|
||||||
|
// Function to recursively normalize newlines
|
||||||
|
const normalizeNewlines = (input: any): any => {
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
return input.replace(/\r\n/g, '\n');
|
||||||
|
} else if (Array.isArray(input)) {
|
||||||
|
return input.map((item) => normalizeNewlines(item));
|
||||||
|
} else if (typeof input === 'object' && input !== null) {
|
||||||
|
for (const key in input) {
|
||||||
|
input[key] = normalizeNewlines(input[key]);
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Middleware logic goes here (before the next call)
|
||||||
|
*/
|
||||||
|
// console.log(ctx)
|
||||||
|
// Get all request input
|
||||||
|
const input = ctx.request.all();
|
||||||
|
|
||||||
|
// Normalize newlines in text inputs
|
||||||
|
const normalizedInput = normalizeNewlines(input);
|
||||||
|
|
||||||
|
// Replace request input with normalized data
|
||||||
|
ctx.request.updateBody(normalizedInput);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call next method in the pipeline and return its output
|
||||||
|
*/
|
||||||
|
const output = await next();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,25 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
import Database from '@ioc:Adonis/Lucid/Database';
|
import db from '@adonisjs/lucid/services/db';
|
||||||
import Config from '@ioc:Adonis/Core/Config';
|
import config from '@adonisjs/core/services/config';
|
||||||
import User from 'app/Models/User';
|
import User from '#models/user';
|
||||||
import { Exception } from '@adonisjs/core/build/standalone';
|
import { Exception } from '@adonisjs/core/exceptions';
|
||||||
|
|
||||||
const roleTable = Config.get('rolePermission.role_table', 'roles');
|
// const roleTable = Config.get('rolePermission.role_table', 'roles');
|
||||||
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
|
const roleTable = config.get('rolePermission.role_table', 'roles');
|
||||||
|
// const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
|
||||||
|
const userRoleTable = config.get('rolePermission.user_role_table', 'user_roles');
|
||||||
|
|
||||||
// node ace make:middleware role
|
// node ace make:middleware role
|
||||||
export default class Role {
|
export default class Role {
|
||||||
// .middleware(['auth', 'role:admin,moderator'])
|
// .middleware(['auth', 'role:admin,moderator'])
|
||||||
public async handle({ auth, response }: HttpContextContract, next: () => Promise<void>, userRoles: string[]) {
|
public async handle({ auth, response }: HttpContext, next: () => Promise<void>, userRoles: string[]) {
|
||||||
// Check if user is logged-in or not.
|
// Check if user is logged-in or not.
|
||||||
// let expression = "";
|
// let expression = "";
|
||||||
// if (Array.isArray(args)) {
|
// if (Array.isArray(args)) {
|
||||||
// expression = args.join(" || ");
|
// expression = args.join(" || ");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let user = await auth.user;
|
let user = auth.user as User;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return response.unauthorized({ error: 'Must be logged in' });
|
return response.unauthorized({ error: 'Must be logged in' });
|
||||||
}
|
}
|
||||||
|
@ -28,7 +30,7 @@ export default class Role {
|
||||||
// error: `Doesn't have required role(s): ${userRoles.join(',')}`,
|
// error: `Doesn't have required role(s): ${userRoles.join(',')}`,
|
||||||
// // error: `Doesn't have required role(s)`,
|
// // error: `Doesn't have required role(s)`,
|
||||||
// });
|
// });
|
||||||
throw new Exception(`Doesn't have required role(s): ${userRoles.join(',')}`, 401);
|
throw new Exception(`Doesn't have required role(s): ${userRoles.join(',')}`, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// code for middleware goes here. ABOVE THE NEXT CALL
|
// code for middleware goes here. ABOVE THE NEXT CALL
|
||||||
|
@ -62,7 +64,7 @@ export default class Role {
|
||||||
rows: {
|
rows: {
|
||||||
0: { rolecount },
|
0: { rolecount },
|
||||||
},
|
},
|
||||||
} = await Database.rawQuery(
|
} = await db.rawQuery(
|
||||||
'SELECT count("r"."id") as roleCount FROM ' +
|
'SELECT count("r"."id") as roleCount FROM ' +
|
||||||
roleTable +
|
roleTable +
|
||||||
' r INNER JOIN ' +
|
' r INNER JOIN ' +
|
|
@ -1,4 +1,4 @@
|
||||||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Silent auth middleware can be used as a global middleware to silent check
|
* Silent auth middleware can be used as a global middleware to silent check
|
||||||
|
@ -10,7 +10,7 @@ export default class SilentAuthMiddleware {
|
||||||
/**
|
/**
|
||||||
* Handle request
|
* Handle request
|
||||||
*/
|
*/
|
||||||
public async handle({ auth }: HttpContextContract, next: () => Promise<void>) {
|
public async handle({ auth }: HttpContext, next: () => Promise<void>) {
|
||||||
/**
|
/**
|
||||||
* Check if user is logged-in or not. If yes, then `ctx.auth.user` will be
|
* 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.
|
* set to the instance of the currently logged in user.
|
47
app/middleware/stardust_middleware.ts
Normal file
47
app/middleware/stardust_middleware.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import type { NextFn } from '@adonisjs/core/types/http';
|
||||||
|
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
function myFunction(): boolean;
|
||||||
|
var myVariable: number;
|
||||||
|
|
||||||
|
interface StardustData {
|
||||||
|
pathname?: string;
|
||||||
|
namedRoutes?: Record<string, string>;
|
||||||
|
}
|
||||||
|
var stardust: StardustData;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {}
|
||||||
|
export default class StardustMiddleware {
|
||||||
|
async handle(ctx: HttpContext, next: NextFn): Promise<void> {
|
||||||
|
/**
|
||||||
|
* Middleware logic goes here (before the next call)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check if the request is an API request
|
||||||
|
if (!ctx.request.url().startsWith('/api')) {
|
||||||
|
// Middleware logic for non-API requests
|
||||||
|
const { pathname } = new URL(ctx.request.completeUrl()); // '/', '/app/login'
|
||||||
|
globalThis.myFunction = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
globalThis.myVariable = 1;
|
||||||
|
|
||||||
|
globalThis.stardust = {
|
||||||
|
...globalThis.stardust,
|
||||||
|
pathname,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call next method in the pipeline and return its output
|
||||||
|
*/
|
||||||
|
await next();
|
||||||
|
} else {
|
||||||
|
// Skip middleware for API requests
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { column, BaseModel, SnakeCaseNamingStrategy, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm';
|
import { column, BaseModel, SnakeCaseNamingStrategy, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import { builder, create } from 'xmlbuilder2';
|
import { builder, create } from 'xmlbuilder2';
|
||||||
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
|
||||||
import Database from '@ioc:Adonis/Lucid/Database';
|
import db from '@adonisjs/lucid/services/db';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class DocumentXmlCache extends BaseModel {
|
export default class DocumentXmlCache extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -87,7 +87,7 @@ export default class DocumentXmlCache extends BaseModel {
|
||||||
// Assuming 'DocumentXmlCache' has a table with a 'server_date_modified' column in your database
|
// Assuming 'DocumentXmlCache' has a table with a 'server_date_modified' column in your database
|
||||||
public static async hasValidEntry(datasetId: number, datasetServerDateModified: DateTime): Promise<boolean> {
|
public static async hasValidEntry(datasetId: number, datasetServerDateModified: DateTime): Promise<boolean> {
|
||||||
const serverDateModifiedString: string = datasetServerDateModified.toFormat('yyyy-MM-dd HH:mm:ss'); // Convert DateTime to ISO string
|
const serverDateModifiedString: string = datasetServerDateModified.toFormat('yyyy-MM-dd HH:mm:ss'); // Convert DateTime to ISO string
|
||||||
const query = Database.from(this.table)
|
const query = db.from(this.table)
|
||||||
.where('document_id', datasetId)
|
.where('document_id', datasetId)
|
||||||
.where('server_date_modified', '>=', serverDateModifiedString) // Check if server_date_modified is newer or equal
|
.where('server_date_modified', '>=', serverDateModifiedString) // Check if server_date_modified is newer or equal
|
||||||
.first();
|
.first();
|
33
app/models/appconfig.ts
Normal file
33
app/models/appconfig.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import BaseModel from './base_model.js';
|
||||||
|
import { column } from '@adonisjs/lucid/orm';
|
||||||
|
|
||||||
|
export default class AppConfig extends BaseModel {
|
||||||
|
public static table = 'appconfigs'; // Specify the table name if it differs from the model name
|
||||||
|
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public appid: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public configkey: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public configvalue: string | null;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public type: number;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public lazy: number;
|
||||||
|
|
||||||
|
// async function setConfig(key: string, value: string) {
|
||||||
|
// await this.updateOrCreate({ key }, { value })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async function getConfig(key: string) {
|
||||||
|
// const config = await this.findBy('key', key)
|
||||||
|
// return config ? config.value : null
|
||||||
|
// }
|
||||||
|
}
|
51
app/models/backup_code.ts
Normal file
51
app/models/backup_code.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import BaseModel from './base_model.js';
|
||||||
|
import { column, SnakeCaseNamingStrategy, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
|
import User from './user.js';
|
||||||
|
import type { BelongsTo } from '@adonisjs/lucid/types/relations';
|
||||||
|
import db from '@adonisjs/lucid/services/db';
|
||||||
|
import hash from '@adonisjs/core/services/hash';
|
||||||
|
|
||||||
|
export default class BackupCode extends BaseModel {
|
||||||
|
public static table = 'backupcodes';
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public user_id: number;
|
||||||
|
|
||||||
|
@column({
|
||||||
|
// serializeAs: null,
|
||||||
|
// consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '{}') : null),
|
||||||
|
// prepare: (value: string) => encryption.encrypt(JSON.stringify(value)),
|
||||||
|
})
|
||||||
|
public code: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public used: boolean;
|
||||||
|
|
||||||
|
@belongsTo(() => User, {
|
||||||
|
foreignKey: 'user_id',
|
||||||
|
})
|
||||||
|
public user: BelongsTo<typeof User>;
|
||||||
|
|
||||||
|
// public static async getBackupCodes(user: User): Promise<BackupCode[]> {
|
||||||
|
// return await db.from(this.table).select('id', 'user_id', 'code', 'used').where('user_id', user.id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public static async deleteCodes(user: User): Promise<void> {
|
||||||
|
await db.from(this.table).where('user_id', user.id).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async deleteCodesByUserId(uid: string): Promise<void> {
|
||||||
|
await db.from(this.table).where('user_id', uid).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to verify password
|
||||||
|
public async verifyCode(plainCode: string) {
|
||||||
|
return await hash.verify(this.code, plainCode);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseModel as LucidBaseModel } from '@ioc:Adonis/Lucid/Orm';
|
import { BaseModel as LucidBaseModel } from '@adonisjs/lucid/orm';
|
||||||
// import { ManyToManyQueryClient } from '@ioc:Adonis/Lucid/Orm';
|
// import { ManyToManyQueryClient } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
|
||||||
// export class CustomManyToManyQueryClient extends ManyToManyQueryClient {
|
// export class CustomManyToManyQueryClient extends ManyToManyQueryClient {
|
||||||
|
@ -13,7 +13,6 @@ import { BaseModel as LucidBaseModel } from '@ioc:Adonis/Lucid/Orm';
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to find if value is a valid Object or
|
* Helper to find if value is a valid Object or
|
||||||
* not
|
* not
|
||||||
|
@ -22,7 +21,7 @@ export function isObject(value: any): boolean {
|
||||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BaseModel extends LucidBaseModel {
|
export default class BaseModel extends LucidBaseModel {
|
||||||
/**
|
/**
|
||||||
* When `fill` method is called, then we may have a situation where it
|
* When `fill` method is called, then we may have a situation where it
|
||||||
* removed the values which exists in `original` and hence the dirty
|
* removed the values which exists in `original` and hence the dirty
|
||||||
|
@ -30,6 +29,8 @@ export default class BaseModel extends LucidBaseModel {
|
||||||
*/
|
*/
|
||||||
// private fillInvoked: boolean = false;
|
// private fillInvoked: boolean = false;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
public static fillable: string[] = [];
|
public static fillable: string[] = [];
|
||||||
|
|
||||||
public fill(attributes: any, allowExtraProperties: boolean = false): this {
|
public fill(attributes: any, allowExtraProperties: boolean = false): this {
|
||||||
|
@ -48,7 +49,7 @@ export default class BaseModel extends LucidBaseModel {
|
||||||
// this.fillInvoked = true;
|
// this.fillInvoked = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge bulk attributes with existing attributes.
|
* Merge bulk attributes with existing attributes.
|
||||||
*
|
*
|
||||||
|
@ -117,9 +118,10 @@ export default class BaseModel extends LucidBaseModel {
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export class DatasetRelatedBaseModel extends LucidBaseModel {
|
// export class DatasetRelatedBaseModel extends LucidBaseModel {
|
||||||
// public dataset: BelongsTo<typeof Dataset>;
|
// public dataset: BelongsTo<typeof Dataset>;
|
||||||
// }
|
// }
|
|
@ -1,6 +1,9 @@
|
||||||
import { column, SnakeCaseNamingStrategy, manyToMany, ManyToMany } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy, manyToMany, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import CollectionRole from './collection_role.js';
|
||||||
|
import type { ManyToMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Collection extends BaseModel {
|
export default class Collection extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -43,4 +46,9 @@ export default class Collection extends BaseModel {
|
||||||
pivotTable: 'link_documents_collections',
|
pivotTable: 'link_documents_collections',
|
||||||
})
|
})
|
||||||
public datasets: ManyToMany<typeof Dataset>;
|
public datasets: ManyToMany<typeof Dataset>;
|
||||||
|
|
||||||
|
@belongsTo(() => CollectionRole, {
|
||||||
|
foreignKey: 'role_id',
|
||||||
|
})
|
||||||
|
public collectionRole: BelongsTo<typeof CollectionRole>;
|
||||||
}
|
}
|
39
app/models/collection_role.ts
Normal file
39
app/models/collection_role.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { column, SnakeCaseNamingStrategy, hasMany } from '@adonisjs/lucid/orm';
|
||||||
|
import BaseModel from './base_model.js';
|
||||||
|
import Collection from './collection.js';
|
||||||
|
import type { HasMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
|
export default class CollectionRole extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'id';
|
||||||
|
public static table = 'collections_roles';
|
||||||
|
public static fillable: string[] = ['name', 'oai_name', 'visible'];
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public oai_name?: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public position: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public visible: boolean;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public visible_frontdoor: boolean;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public visible_oai: boolean;
|
||||||
|
|
||||||
|
@hasMany(() => Collection, {
|
||||||
|
foreignKey: 'role_id',
|
||||||
|
})
|
||||||
|
public collections: HasMany<typeof Collection>;
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
import { column, SnakeCaseNamingStrategy, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Coverage extends BaseModel {
|
export default class Coverage extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
|
@ -2,31 +2,31 @@ import {
|
||||||
column,
|
column,
|
||||||
SnakeCaseNamingStrategy,
|
SnakeCaseNamingStrategy,
|
||||||
manyToMany,
|
manyToMany,
|
||||||
ManyToMany,
|
|
||||||
belongsTo,
|
belongsTo,
|
||||||
BelongsTo,
|
|
||||||
hasMany,
|
hasMany,
|
||||||
HasMany,
|
|
||||||
computed,
|
computed,
|
||||||
hasOne,
|
hasOne
|
||||||
HasOne,
|
} from '@adonisjs/lucid/orm';
|
||||||
} from '@ioc:Adonis/Lucid/Orm';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Person from './Person';
|
import Person from './person.js';
|
||||||
import User from './User';
|
import User from './user.js';
|
||||||
import Title from './Title';
|
import Title from './title.js';
|
||||||
import Description from './Description';
|
import Description from './description.js';
|
||||||
import License from './License';
|
import License from './license.js';
|
||||||
import Subject from './Subject';
|
import Subject from './subject.js';
|
||||||
import File from './File';
|
import File from './file.js';
|
||||||
import Coverage from './Coverage';
|
import Coverage from './coverage.js';
|
||||||
import DatasetReference from './DatasetReference';
|
import DatasetReference from './dataset_reference.js';
|
||||||
import Collection from './Collection';
|
import Collection from './collection.js';
|
||||||
import DatasetIdentifier from './DatasetIdentifier';
|
import DatasetIdentifier from './dataset_identifier.js';
|
||||||
import Project from './Project';
|
import Project from './project.js';
|
||||||
import DocumentXmlCache from './DocumentXmlCache';
|
import DocumentXmlCache from './DocumentXmlCache.js';
|
||||||
import DatasetExtension from 'App/Models/Traits/DatasetExtension'; // Adjust the import path
|
import DatasetExtension from '#models/traits/dataset_extension';
|
||||||
|
import type { ManyToMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
import type { HasMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
import type { HasOne } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Dataset extends DatasetExtension {
|
export default class Dataset extends DatasetExtension {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -55,7 +55,7 @@ export default class Dataset extends DatasetExtension {
|
||||||
@column({})
|
@column({})
|
||||||
public language: string;
|
public language: string;
|
||||||
|
|
||||||
@column({})
|
@column({columnName: 'publish_id'})
|
||||||
public publish_id: number | null = null;
|
public publish_id: number | null = null;
|
||||||
|
|
||||||
@column({})
|
@column({})
|
||||||
|
@ -95,7 +95,14 @@ export default class Dataset extends DatasetExtension {
|
||||||
})
|
})
|
||||||
public created_at: DateTime;
|
public created_at: DateTime;
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true, autoUpdate: true, columnName: 'server_date_modified' })
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
autoUpdate: true,
|
||||||
|
columnName: 'server_date_modified',
|
||||||
|
})
|
||||||
public server_date_modified: DateTime;
|
public server_date_modified: DateTime;
|
||||||
|
|
||||||
@manyToMany(() => Person, {
|
@manyToMany(() => Person, {
|
||||||
|
@ -188,6 +195,15 @@ export default class Dataset extends DatasetExtension {
|
||||||
return mainTitle ? mainTitle.value : null;
|
return mainTitle ? mainTitle.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'main_abstract',
|
||||||
|
})
|
||||||
|
public get mainAbstract() {
|
||||||
|
// return `${this.firstName} ${this.lastName}`;
|
||||||
|
const mainTitle = this.descriptions?.find((desc) => desc.type === 'Abstract');
|
||||||
|
return mainTitle ? mainTitle.value : null;
|
||||||
|
}
|
||||||
|
|
||||||
@manyToMany(() => Person, {
|
@manyToMany(() => Person, {
|
||||||
pivotForeignKey: 'document_id',
|
pivotForeignKey: 'document_id',
|
||||||
pivotRelatedForeignKey: 'person_id',
|
pivotRelatedForeignKey: 'person_id',
|
||||||
|
@ -203,7 +219,7 @@ export default class Dataset extends DatasetExtension {
|
||||||
pivotForeignKey: 'document_id',
|
pivotForeignKey: 'document_id',
|
||||||
pivotRelatedForeignKey: 'person_id',
|
pivotRelatedForeignKey: 'person_id',
|
||||||
pivotTable: 'link_documents_persons',
|
pivotTable: 'link_documents_persons',
|
||||||
pivotColumns: ['role', 'sort_order', 'allow_email_contact'],
|
pivotColumns: ['role', 'sort_order', 'allow_email_contact', 'contributor_type'],
|
||||||
onQuery(query) {
|
onQuery(query) {
|
||||||
query.wherePivot('role', 'contributor');
|
query.wherePivot('role', 'contributor');
|
||||||
},
|
},
|
||||||
|
@ -214,4 +230,44 @@ export default class Dataset extends DatasetExtension {
|
||||||
foreignKey: 'document_id',
|
foreignKey: 'document_id',
|
||||||
})
|
})
|
||||||
public xmlCache: HasOne<typeof DocumentXmlCache>;
|
public xmlCache: HasOne<typeof DocumentXmlCache>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account that the dataset belongs to
|
||||||
|
*/
|
||||||
|
@belongsTo(() => User, {
|
||||||
|
foreignKey: 'editor_id',
|
||||||
|
})
|
||||||
|
public editor: BelongsTo<typeof User>;
|
||||||
|
|
||||||
|
@belongsTo(() => User, {
|
||||||
|
foreignKey: 'reviewer_id',
|
||||||
|
})
|
||||||
|
public reviewer: BelongsTo<typeof User>;
|
||||||
|
|
||||||
|
static async earliestPublicationDate(): Promise<Dataset | null> {
|
||||||
|
const serverState = 'published';
|
||||||
|
|
||||||
|
const model = await this.query().where('server_state', serverState).orderBy('server_date_published', 'asc').first();
|
||||||
|
|
||||||
|
return model || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getMax (column: string) {
|
||||||
|
let dataset = await this.query().max(column + ' as max_publish_id').firstOrFail();
|
||||||
|
return dataset.$extras.max_publish_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'remaining_time',
|
||||||
|
})
|
||||||
|
public get remainingTime() {
|
||||||
|
const dateFuture = this.server_date_modified.plus({ days: 14 });
|
||||||
|
if (this.server_state === 'approved') {
|
||||||
|
const now = DateTime.now();
|
||||||
|
let duration = dateFuture.diff(now, ['days', 'hours', 'months']).toObject();
|
||||||
|
return duration.days;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
import { column, SnakeCaseNamingStrategy, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class DatasetIdentifier extends BaseModel {
|
export default class DatasetIdentifier extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -20,6 +21,9 @@ export default class DatasetIdentifier extends BaseModel {
|
||||||
@column({})
|
@column({})
|
||||||
public type: string;
|
public type: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public status: string;
|
||||||
|
|
||||||
@column({})
|
@column({})
|
||||||
public value: string;
|
public value: string;
|
||||||
|
|
||||||
|
@ -38,4 +42,9 @@ export default class DatasetIdentifier extends BaseModel {
|
||||||
foreignKey: 'dataset_id',
|
foreignKey: 'dataset_id',
|
||||||
})
|
})
|
||||||
public dataset: BelongsTo<typeof Dataset>;
|
public dataset: BelongsTo<typeof Dataset>;
|
||||||
|
|
||||||
|
// // Specify the relationships to touch when this model is updated
|
||||||
|
// public static get touches() {
|
||||||
|
// return ['dataset'];
|
||||||
|
// }
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
import { column, SnakeCaseNamingStrategy, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class DatasetReference extends BaseModel {
|
export default class DatasetReference extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
|
@ -1,6 +1,7 @@
|
||||||
import { column, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm';
|
import { column, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Description extends BaseModel {
|
export default class Description extends BaseModel {
|
||||||
public static primaryKey = 'id';
|
public static primaryKey = 'id';
|
||||||
|
@ -9,6 +10,11 @@ export default class Description extends BaseModel {
|
||||||
public static timestamps = false;
|
public static timestamps = false;
|
||||||
public static fillable: string[] = ['value', 'type', 'language'];
|
public static fillable: string[] = ['value', 'type', 'language'];
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
@column({})
|
@column({})
|
||||||
public document_id: number;
|
public document_id: number;
|
||||||
|
|
184
app/models/file.ts
Normal file
184
app/models/file.ts
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { column, hasMany, belongsTo, SnakeCaseNamingStrategy, computed } from '@adonisjs/lucid/orm';
|
||||||
|
import HashValue from './hash_value.js';
|
||||||
|
import Dataset from './dataset.js';
|
||||||
|
import BaseModel from './base_model.js';
|
||||||
|
// import { Buffer } from 'buffer';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
// import Drive from '@ioc:Adonis/Core/Drive';
|
||||||
|
// import Drive from '@adonisjs/drive';
|
||||||
|
import drive from '#services/drive';
|
||||||
|
|
||||||
|
import type { HasMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
// import { TransactionClientContract } from "@adonisjs/lucid/database";
|
||||||
|
import { TransactionClientContract } from '@adonisjs/lucid/types/database';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default class File extends BaseModel {
|
||||||
|
// private readonly _data: Uint8Array;
|
||||||
|
// private readonly _type: string;
|
||||||
|
// private readonly _size: number;
|
||||||
|
|
||||||
|
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 document_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: number;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public visibleInOai: boolean;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public visibleInFrontdoor: boolean;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public sortOrder: number;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true })
|
||||||
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||||
|
public updatedAt: DateTime;
|
||||||
|
|
||||||
|
// public function dataset()
|
||||||
|
// {
|
||||||
|
// return $this->belongsTo(Dataset::class, 'document_id', 'id');
|
||||||
|
// }
|
||||||
|
@belongsTo(() => Dataset, {
|
||||||
|
foreignKey: 'document_id',
|
||||||
|
})
|
||||||
|
public dataset: BelongsTo<typeof Dataset>;
|
||||||
|
|
||||||
|
@hasMany(() => HashValue, {
|
||||||
|
foreignKey: 'file_id',
|
||||||
|
})
|
||||||
|
public hashvalues: HasMany<typeof HashValue>;
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'filePath',
|
||||||
|
})
|
||||||
|
public get filePath() {
|
||||||
|
return `/storage/app/public/${this.pathName}`;
|
||||||
|
// const mainTitle = this.titles?.find((title) => title.type === 'Main');
|
||||||
|
// return mainTitle ? mainTitle.value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'size',
|
||||||
|
})
|
||||||
|
public get size() {
|
||||||
|
return this.fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'type',
|
||||||
|
})
|
||||||
|
public get type() {
|
||||||
|
return this.mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'name',
|
||||||
|
})
|
||||||
|
get name(): string {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'lastModified',
|
||||||
|
})
|
||||||
|
get lastModified(): number {
|
||||||
|
return this.updatedAt.toUnixInteger(); //.toFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly webkitRelativePath: string = '';
|
||||||
|
|
||||||
|
@computed({
|
||||||
|
serializeAs: 'fileData',
|
||||||
|
})
|
||||||
|
public get fileData(): string {
|
||||||
|
try {
|
||||||
|
const fileContent: Buffer = fs.readFileSync(this.filePath);
|
||||||
|
// Create a Blob from the file content
|
||||||
|
// const blob = new Blob([fileContent], { type: this.type }); // Adjust
|
||||||
|
// let fileSrc = URL.createObjectURL(blob);
|
||||||
|
// return fileSrc;
|
||||||
|
|
||||||
|
// create a JSON string that contains the data in the property "blob"
|
||||||
|
const json = JSON.stringify({ blob: fileContent.toString('base64') });
|
||||||
|
return json;
|
||||||
|
} catch (err) {
|
||||||
|
// console.error(`Error reading file: ${err}`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createHashValues(trx?: TransactionClientContract) {
|
||||||
|
const hashtypes: string[] = ['md5', 'sha512'];
|
||||||
|
|
||||||
|
for (const type of hashtypes) {
|
||||||
|
const hash = new HashValue();
|
||||||
|
hash.type = type;
|
||||||
|
const hashString = await this._checksumFile(this.filePath, type); // Assuming getRealHash is a method in the same model
|
||||||
|
hash.value = hashString;
|
||||||
|
|
||||||
|
// https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289
|
||||||
|
const file: File = this;
|
||||||
|
if (trx) {
|
||||||
|
await file.useTransaction(trx).related('hashvalues').save(hash); // Save the hash value to the database
|
||||||
|
} else {
|
||||||
|
await file.related('hashvalues').save(hash); // Save the hash value to the database
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete() {
|
||||||
|
if (this.pathName) {
|
||||||
|
// Delete file from additional storage
|
||||||
|
await drive.delete(this.pathName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the original delete method of the BaseModel to remove the record from the database
|
||||||
|
await super.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _checksumFile(path: string, 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')));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
import { column, BaseModel, belongsTo, BelongsTo, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm';
|
import { column, BaseModel, belongsTo, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
import File from './File';
|
import File from './file.js';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class HashValue extends BaseModel {
|
export default class HashValue extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
public static primaryKey = 'file_id, type';
|
// public static primaryKey = 'file_id,type';
|
||||||
public static table = 'file_hashvalues';
|
public static table = 'file_hashvalues';
|
||||||
|
|
||||||
// static get primaryKey () {
|
// static get primaryKey () {
|
||||||
|
@ -20,10 +21,10 @@ export default class HashValue extends BaseModel {
|
||||||
// public id: number;
|
// public id: number;
|
||||||
|
|
||||||
// Foreign key is still on the same model
|
// Foreign key is still on the same model
|
||||||
@column({})
|
@column({ isPrimary: true })
|
||||||
public file_id: number;
|
public file_id: number;
|
||||||
|
|
||||||
@column({})
|
@column({ isPrimary: true })
|
||||||
public type: string;
|
public type: string;
|
||||||
|
|
||||||
@column()
|
@column()
|
|
@ -1,5 +1,5 @@
|
||||||
import { column, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
// import { DateTime } from 'luxon';
|
// import { DateTime } from 'luxon';
|
||||||
|
|
||||||
export default class Language extends BaseModel {
|
export default class Language extends BaseModel {
|
|
@ -1,5 +1,5 @@
|
||||||
import { column, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
|
||||||
export default class License extends BaseModel {
|
export default class License extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
40
app/models/mime_type.ts
Normal file
40
app/models/mime_type.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
|
import BaseModel from './base_model.js';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
export default class MimeType extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static primaryKey = 'id';
|
||||||
|
public static table = 'mime_types';
|
||||||
|
public static fillable: string[] = ['name', 'file_extension', 'enabled'];
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public file_extension: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public enabled: boolean;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
autoCreate: true,
|
||||||
|
})
|
||||||
|
public created_at: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
autoCreate: true,
|
||||||
|
autoUpdate: true,
|
||||||
|
})
|
||||||
|
public updated_at: DateTime;
|
||||||
|
|
||||||
|
// @hasMany(() => Collection, {
|
||||||
|
// foreignKey: 'role_id',
|
||||||
|
// })
|
||||||
|
// public collections: HasMany<typeof Collection>;
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import { column, manyToMany, ManyToMany, SnakeCaseNamingStrategy, beforeUpdate, beforeCreate } from '@ioc:Adonis/Lucid/Orm';
|
import { column, manyToMany, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Role from 'App/Models/Role';
|
import Role from '#models/role';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import type { ManyToMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Permission extends BaseModel {
|
export default class Permission extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -44,12 +45,12 @@ export default class Permission extends BaseModel {
|
||||||
})
|
})
|
||||||
public updated_at: DateTime;
|
public updated_at: DateTime;
|
||||||
|
|
||||||
@beforeCreate()
|
// @beforeCreate()
|
||||||
@beforeUpdate()
|
// @beforeUpdate()
|
||||||
public static async resetDate(role) {
|
// public static async resetDate(role) {
|
||||||
role.created_at = this.formatDateTime(role.created_at);
|
// role.created_at = this.formatDateTime(role.created_at);
|
||||||
role.updated_at = this.formatDateTime(role.updated_at);
|
// role.updated_at = this.formatDateTime(role.updated_at);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// public static boot() {
|
// public static boot() {
|
||||||
// super.boot()
|
// super.boot()
|
||||||
|
@ -64,22 +65,22 @@ export default class Permission extends BaseModel {
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private static formatDateTime(datetime) {
|
// private static formatDateTime(datetime) {
|
||||||
let value = new Date(datetime);
|
// let value = new Date(datetime);
|
||||||
return datetime
|
// return datetime
|
||||||
? value.getFullYear() +
|
// ? value.getFullYear() +
|
||||||
'-' +
|
// '-' +
|
||||||
(value.getMonth() + 1) +
|
// (value.getMonth() + 1) +
|
||||||
'-' +
|
// '-' +
|
||||||
value.getDate() +
|
// value.getDate() +
|
||||||
' ' +
|
// ' ' +
|
||||||
value.getHours() +
|
// value.getHours() +
|
||||||
':' +
|
// ':' +
|
||||||
value.getMinutes() +
|
// value.getMinutes() +
|
||||||
':' +
|
// ':' +
|
||||||
value.getSeconds()
|
// value.getSeconds()
|
||||||
: datetime;
|
// : datetime;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// @belongsTo(() => Role)
|
// @belongsTo(() => Role)
|
||||||
// public role: BelongsTo<typeof Role>;
|
// public role: BelongsTo<typeof Role>;
|
|
@ -1,8 +1,9 @@
|
||||||
import { column, SnakeCaseNamingStrategy, computed, manyToMany, ManyToMany } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy, computed, manyToMany } from '@adonisjs/lucid/orm';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import type { ManyToMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Person extends BaseModel {
|
export default class Person extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -69,6 +70,12 @@ export default class Person extends BaseModel {
|
||||||
return stock;
|
return stock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed()
|
||||||
|
public get pivot_contributor_type() {
|
||||||
|
const contributor_type = this.$extras.pivot_contributor_type; //my pivot column name was "stock"
|
||||||
|
return contributor_type;
|
||||||
|
}
|
||||||
|
|
||||||
@manyToMany(() => Dataset, {
|
@manyToMany(() => Dataset, {
|
||||||
pivotForeignKey: 'person_id',
|
pivotForeignKey: 'person_id',
|
||||||
pivotRelatedForeignKey: 'document_id',
|
pivotRelatedForeignKey: 'document_id',
|
|
@ -1,6 +1,6 @@
|
||||||
import { column, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
|
||||||
export default class Project extends BaseModel {
|
export default class Project extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
|
@ -1,10 +1,11 @@
|
||||||
import { column, SnakeCaseNamingStrategy, manyToMany, ManyToMany, beforeCreate, beforeUpdate } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy, manyToMany, beforeCreate, beforeUpdate } from '@adonisjs/lucid/orm';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
// import moment from 'moment';
|
// import moment from 'moment';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import User from './User';
|
import User from './user.js';
|
||||||
import Permission from 'App/Models/Permission';
|
import Permission from '#models/permission';
|
||||||
|
import type { ManyToMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Role extends BaseModel {
|
export default class Role extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -46,7 +47,7 @@ export default class Role extends BaseModel {
|
||||||
|
|
||||||
@beforeCreate()
|
@beforeCreate()
|
||||||
@beforeUpdate()
|
@beforeUpdate()
|
||||||
public static async resetDate(role) {
|
public static async resetDate(role: Role) {
|
||||||
role.created_at = this.formatDateTime(role.created_at);
|
role.created_at = this.formatDateTime(role.created_at);
|
||||||
role.updated_at = this.formatDateTime(role.updated_at);
|
role.updated_at = this.formatDateTime(role.updated_at);
|
||||||
}
|
}
|
||||||
|
@ -64,7 +65,7 @@ export default class Role extends BaseModel {
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private static formatDateTime(datetime) {
|
private static formatDateTime(datetime: any) {
|
||||||
let value = new Date(datetime);
|
let value = new Date(datetime);
|
||||||
return datetime
|
return datetime
|
||||||
? value.getFullYear() +
|
? value.getFullYear() +
|
|
@ -1,9 +1,10 @@
|
||||||
import { column, SnakeCaseNamingStrategy, manyToMany, ManyToMany, beforeCreate, beforeUpdate } from '@ioc:Adonis/Lucid/Orm';
|
import { column, SnakeCaseNamingStrategy, manyToMany, computed} from '@adonisjs/lucid/orm';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
|
import type { ManyToMany } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
export default class Subject extends BaseModel {
|
export default class Subject extends BaseModel {
|
||||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
@ -44,28 +45,33 @@ export default class Subject extends BaseModel {
|
||||||
})
|
})
|
||||||
public updated_at: DateTime;
|
public updated_at: DateTime;
|
||||||
|
|
||||||
@beforeCreate()
|
// @beforeCreate()
|
||||||
@beforeUpdate()
|
// @beforeUpdate()
|
||||||
public static async resetDate(role) {
|
// public static async resetDate(role) {
|
||||||
role.created_at = this.formatDateTime(role.created_at);
|
// role.created_at = this.formatDateTime(role.created_at);
|
||||||
role.updated_at = this.formatDateTime(role.updated_at);
|
// role.updated_at = this.formatDateTime(role.updated_at);
|
||||||
}
|
// }
|
||||||
|
|
||||||
private static formatDateTime(datetime) {
|
// private static formatDateTime(datetime) {
|
||||||
let value = new Date(datetime);
|
// let value = new Date(datetime);
|
||||||
return datetime
|
// return datetime
|
||||||
? value.getFullYear() +
|
// ? value.getFullYear() +
|
||||||
'-' +
|
// '-' +
|
||||||
(value.getMonth() + 1) +
|
// (value.getMonth() + 1) +
|
||||||
'-' +
|
// '-' +
|
||||||
value.getDate() +
|
// value.getDate() +
|
||||||
' ' +
|
// ' ' +
|
||||||
value.getHours() +
|
// value.getHours() +
|
||||||
':' +
|
// ':' +
|
||||||
value.getMinutes() +
|
// value.getMinutes() +
|
||||||
':' +
|
// ':' +
|
||||||
value.getSeconds()
|
// value.getSeconds()
|
||||||
: datetime;
|
// : datetime;
|
||||||
|
// }
|
||||||
|
@computed()
|
||||||
|
public get dataset_count() : number{
|
||||||
|
const count = this.$extras.datasets_count; //my pivot column name was "stock"
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@manyToMany(() => Dataset, {
|
@manyToMany(() => Dataset, {
|
|
@ -1,6 +1,8 @@
|
||||||
import { column, belongsTo, BelongsTo } from '@ioc:Adonis/Lucid/Orm';
|
import { column, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
import Dataset from './Dataset';
|
import Dataset from './dataset.js';
|
||||||
import BaseModel from './BaseModel';
|
import BaseModel from './base_model.js';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
// import { DatasetRelatedBaseModel } from './BaseModel';
|
// import { DatasetRelatedBaseModel } from './BaseModel';
|
||||||
|
|
||||||
export default class Title extends BaseModel {
|
export default class Title extends BaseModel {
|
||||||
|
@ -10,6 +12,11 @@ export default class Title extends BaseModel {
|
||||||
public static timestamps = false;
|
public static timestamps = false;
|
||||||
public static fillable: string[] = ['value', 'type', 'language'];
|
public static fillable: string[] = ['value', 'type', 'language'];
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
@column({})
|
@column({})
|
||||||
public document_id: number;
|
public document_id: number;
|
||||||
|
|
65
app/models/totp_secret.ts
Normal file
65
app/models/totp_secret.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import { column, BaseModel, SnakeCaseNamingStrategy, belongsTo } from '@adonisjs/lucid/orm';
|
||||||
|
import User from './user.js';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
// import Encryption from '@ioc:Adonis/Core/Encryption';
|
||||||
|
import encryption from '@adonisjs/core/services/encryption';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
|
export default class TotpSecret extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static table = 'totp_secrets';
|
||||||
|
// public static fillable: string[] = ['value', 'label', 'type', 'relation'];
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public user_id: number;
|
||||||
|
|
||||||
|
// @column()
|
||||||
|
// public twoFactorSecret: string;
|
||||||
|
@column({
|
||||||
|
serializeAs: null,
|
||||||
|
consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '{}') : null),
|
||||||
|
prepare: (value: string) => encryption.encrypt(JSON.stringify(value)),
|
||||||
|
})
|
||||||
|
public twoFactorSecret?: string | null;
|
||||||
|
|
||||||
|
// serializeAs: null removes the model properties from the serialized output.
|
||||||
|
@column({
|
||||||
|
serializeAs: null,
|
||||||
|
consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '[]') : []),
|
||||||
|
prepare: (value: string[]) => encryption.encrypt(JSON.stringify(value)),
|
||||||
|
})
|
||||||
|
public twoFactorRecoveryCodes?: string[] | null;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public state: number;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
@belongsTo(() => User, {
|
||||||
|
foreignKey: 'user_id',
|
||||||
|
})
|
||||||
|
public user: BelongsTo<typeof User>;
|
||||||
|
|
||||||
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
import Title from 'App/Models/Title';
|
import Title from '#models/title';
|
||||||
import Description from 'App/Models/Description';
|
import Description from '#models/description';
|
||||||
import License from 'App/Models/License';
|
import License from '#models/license';
|
||||||
import Person from 'App/Models/Person';
|
import Person from '#models/person';
|
||||||
import DatasetReference from 'App/Models/DatasetReference';
|
import DatasetReference from '#models/dataset_reference';
|
||||||
import DatasetIdentifier from 'App/Models/DatasetIdentifier';
|
import DatasetIdentifier from '#models/dataset_identifier';
|
||||||
import Subject from 'App/Models/Subject';
|
import Subject from '#models/subject';
|
||||||
import File from 'App/Models/File';
|
import File from '#models/file';
|
||||||
import Coverage from 'App/Models/Coverage';
|
import Coverage from '#models/coverage';
|
||||||
import Collection from 'App/Models/Collection';
|
import Collection from '#models/collection';
|
||||||
import { BaseModel as LucidBaseModel } from '@ioc:Adonis/Lucid/Orm';
|
import { BaseModel as LucidBaseModel } from '@adonisjs/lucid/orm';
|
||||||
import Field from 'App/Library/Field';
|
import Field from '#app/Library/Field';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
// @StaticImplements<LucidModel>()
|
// @StaticImplements<LucidModel>()
|
||||||
|
@ -30,13 +30,13 @@ export type DatasetRelatedModel =
|
||||||
| typeof DatasetIdentifier
|
| typeof DatasetIdentifier
|
||||||
| typeof File;
|
| typeof File;
|
||||||
|
|
||||||
|
|
||||||
export default abstract class DatasetExtension extends LucidBaseModel {
|
export default abstract class DatasetExtension extends LucidBaseModel {
|
||||||
public abstract id;
|
public abstract id: number;
|
||||||
public externalFields: Record<string, any> = this.getExternalFields();
|
public externalFields: Record<string, any> = this.getExternalFields();
|
||||||
// which fields shoud#t be published
|
// which fields shoud#t be published
|
||||||
protected internalFields: Record<string, any> = {};
|
protected internalFields: Record<string, any> = {};
|
||||||
protected fields: Record<string, any> = {};
|
protected fields: Record<string, any> = {};
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
private getExternalFields(): Record<string, any> {
|
private getExternalFields(): Record<string, any> {
|
||||||
// External fields definition
|
// External fields definition
|
||||||
|
@ -83,7 +83,7 @@ export default abstract class DatasetExtension extends LucidBaseModel {
|
||||||
sort_order: 'sort_order',
|
sort_order: 'sort_order',
|
||||||
allow_email_contact: 'allow_email_contact',
|
allow_email_contact: 'allow_email_contact',
|
||||||
},
|
},
|
||||||
relation: 'persons',
|
relation: 'contributors',
|
||||||
fetch: 'eager',
|
fetch: 'eager',
|
||||||
},
|
},
|
||||||
Reference: {
|
Reference: {
|
||||||
|
@ -160,7 +160,7 @@ export default abstract class DatasetExtension extends LucidBaseModel {
|
||||||
|
|
||||||
// // Initialize available date fields and set up date validator
|
// // Initialize available date fields and set up date validator
|
||||||
// // if the particular field is present
|
// // if the particular field is present
|
||||||
let dateFields = new Array<string>('EmbargoDate', 'CreatedAt', 'ServerDatePublished', 'ServerDateDeleted');
|
let dateFields = new Array<string>('EmbargoDate', 'CreatedAt', 'ServerDateModified', 'ServerDatePublished', 'ServerDateDeleted');
|
||||||
dateFields.forEach((fieldname) => {
|
dateFields.forEach((fieldname) => {
|
||||||
let dateField = this.getField(fieldname);
|
let dateField = this.getField(fieldname);
|
||||||
dateField instanceof Field && dateField.setValueModelClass(DateTime.now());
|
dateField instanceof Field && dateField.setValueModelClass(DateTime.now());
|
||||||
|
@ -323,7 +323,7 @@ export default abstract class DatasetExtension extends LucidBaseModel {
|
||||||
private convertColumnToFieldname(columnName: string): string {
|
private convertColumnToFieldname(columnName: string): string {
|
||||||
return columnName
|
return columnName
|
||||||
.split(/[-_]/)
|
.split(/[-_]/)
|
||||||
.map((word) => (word.charAt(0).toUpperCase() + word.slice(1)))
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
.join('');
|
.join('');
|
||||||
}
|
}
|
||||||
}
|
}
|
180
app/models/user.ts
Normal file
180
app/models/user.ts
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid';
|
||||||
|
import { column, manyToMany, hasMany, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
|
import hash from '@adonisjs/core/services/hash';
|
||||||
|
import Role from './role.js';
|
||||||
|
import db from '@adonisjs/lucid/services/db';
|
||||||
|
import config from '@adonisjs/core/services/config';
|
||||||
|
import Dataset from './dataset.js';
|
||||||
|
import BaseModel from './base_model.js';
|
||||||
|
// import Encryption from '@ioc:Adonis/Core/Encryption';
|
||||||
|
import encryption from '@adonisjs/core/services/encryption';
|
||||||
|
import { TotpState } from '#contracts/enums';
|
||||||
|
import type { ManyToMany } from '@adonisjs/lucid/types/relations';
|
||||||
|
import type { HasMany } from '@adonisjs/lucid/types/relations';
|
||||||
|
import { compose } from '@adonisjs/core/helpers';
|
||||||
|
import BackupCode from './backup_code.js';
|
||||||
|
|
||||||
|
const AuthFinder = withAuthFinder(() => hash.use('laravel'), {
|
||||||
|
uids: ['email'],
|
||||||
|
passwordColumnName: 'password',
|
||||||
|
});
|
||||||
|
|
||||||
|
// import TotpSecret from './TotpSecret';
|
||||||
|
|
||||||
|
// 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 compose(BaseModel, AuthFinder) {
|
||||||
|
// export default class User extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static table = 'accounts';
|
||||||
|
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public login: string;
|
||||||
|
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public firstName: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public lastName: 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;
|
||||||
|
|
||||||
|
// serializeAs: null removes the model properties from the serialized output.
|
||||||
|
@column({
|
||||||
|
serializeAs: null,
|
||||||
|
consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '{}') : null),
|
||||||
|
prepare: (value: string) => encryption.encrypt(JSON.stringify(value)),
|
||||||
|
})
|
||||||
|
public twoFactorSecret?: string | null;
|
||||||
|
|
||||||
|
// serializeAs: null removes the model properties from the serialized output.
|
||||||
|
@column({
|
||||||
|
serializeAs: null,
|
||||||
|
consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '[]') : []),
|
||||||
|
prepare: (value: string[]) => encryption.encrypt(JSON.stringify(value)),
|
||||||
|
})
|
||||||
|
public twoFactorRecoveryCodes?: string[] | null;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public state: number;
|
||||||
|
|
||||||
|
// @hasOne(() => TotpSecret, {
|
||||||
|
// foreignKey: 'user_id',
|
||||||
|
// })
|
||||||
|
// public totp_secret: HasOne<typeof TotpSecret>;
|
||||||
|
|
||||||
|
// @beforeSave()
|
||||||
|
// public static async hashPassword(user: User) {
|
||||||
|
// if (user.$dirty.password) {
|
||||||
|
// user.password = await hash.use('laravel').make(user.password);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public get isTwoFactorEnabled(): boolean {
|
||||||
|
return Boolean(this?.twoFactorSecret && this.state == TotpState.STATE_ENABLED);
|
||||||
|
// return Boolean(this.totp_secret?.twoFactorSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@manyToMany(() => Role, {
|
||||||
|
pivotForeignKey: 'account_id',
|
||||||
|
pivotRelatedForeignKey: 'role_id',
|
||||||
|
pivotTable: 'link_accounts_roles',
|
||||||
|
})
|
||||||
|
public roles: ManyToMany<typeof Role>;
|
||||||
|
|
||||||
|
@hasMany(() => Dataset, {
|
||||||
|
foreignKey: 'account_id',
|
||||||
|
})
|
||||||
|
public datasets: HasMany<typeof Dataset>;
|
||||||
|
|
||||||
|
@hasMany(() => BackupCode, {
|
||||||
|
foreignKey: 'user_id',
|
||||||
|
})
|
||||||
|
public backupcodes: HasMany<typeof BackupCode>;
|
||||||
|
|
||||||
|
public async getBackupCodes(this: User): Promise<BackupCode[]> {
|
||||||
|
const test = await this.related('backupcodes').query();
|
||||||
|
// return test.map((role) => role.code);
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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> {
|
||||||
|
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');
|
||||||
|
|
||||||
|
let permissionPlaceHolder = '(';
|
||||||
|
let placeholders = new Array(permissionNames.length).fill('?');
|
||||||
|
permissionPlaceHolder += placeholders.join(',');
|
||||||
|
permissionPlaceHolder += ')';
|
||||||
|
|
||||||
|
let {
|
||||||
|
rows: {
|
||||||
|
0: { permissioncount },
|
||||||
|
},
|
||||||
|
} = await db.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;
|
|
@ -1,8 +1,10 @@
|
||||||
import { column, BaseModel, belongsTo, BelongsTo, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm';
|
import { column, BaseModel, belongsTo, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||||
|
|
||||||
import User from 'App/Models/User';
|
import User from '#models/user';
|
||||||
import Role from 'App/Models/Role';
|
import Role from '#models/role';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||||
|
|
||||||
// import moment from 'moment'
|
// import moment from 'moment'
|
||||||
|
|
||||||
export default class UserRole extends BaseModel {
|
export default class UserRole extends BaseModel {
|
||||||
|
@ -49,7 +51,7 @@ export default class UserRole extends BaseModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static formatDateTime(datetime) {
|
private static formatDateTime(datetime: any) {
|
||||||
let value = new Date(datetime);
|
let value = new Date(datetime);
|
||||||
return datetime
|
return datetime
|
||||||
? value.getFullYear() +
|
? value.getFullYear() +
|
128
app/services/TwoFactorAuthProvider.ts
Normal file
128
app/services/TwoFactorAuthProvider.ts
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
// import Config from '@ioc:Adonis/Core/Config';
|
||||||
|
// import config from '@adonisjs/core/services/config'
|
||||||
|
import env from '#start/env';
|
||||||
|
import User from '#models/user';
|
||||||
|
import { generateSecret, verifyToken } from 'node-2fa/dist/index.js';
|
||||||
|
// import cryptoRandomString from 'crypto-random-string';
|
||||||
|
import QRCode from 'qrcode';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { TotpState } from '#contracts/enums';
|
||||||
|
|
||||||
|
// npm install node-2fa --save
|
||||||
|
// npm install crypto-random-string --save
|
||||||
|
// import { cryptoRandomStringAsync } from 'crypto-random-string/index';
|
||||||
|
// npm install qrcode --save
|
||||||
|
// npm i --save-dev @types/qrcode
|
||||||
|
|
||||||
|
class TwoFactorAuthProvider {
|
||||||
|
private issuer: string = env.get('APP_NAME') || 'TethysCloud';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generateSecret will generate a user-specific 32-character secret.
|
||||||
|
* We’re providing the name of the app and the user’s email as parameters for the function.
|
||||||
|
* This secret key will be used to verify whether the token provided by the user during authentication is valid or not.
|
||||||
|
*
|
||||||
|
* Return the default global focus trap stack *
|
||||||
|
* @param {User} user user for the secrect
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
public generateSecret(user: User) {
|
||||||
|
const secret = generateSecret({
|
||||||
|
name: this.issuer,
|
||||||
|
account: user.email,
|
||||||
|
});
|
||||||
|
return secret.secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We also generated recovery codes which can be used in case we’re unable to retrieve tokens from 2FA applications.
|
||||||
|
* We assign the user a list of recovery codes and each code can be used only once during the authentication process.
|
||||||
|
* The recovery codes are random strings generated using the cryptoRandomString library.
|
||||||
|
*
|
||||||
|
* Return recovery codes
|
||||||
|
* @return {string[]}
|
||||||
|
*/
|
||||||
|
public generateRecoveryCodes(): string[] {
|
||||||
|
const recoveryCodeLimit: number = 8;
|
||||||
|
const codes: string[] = [];
|
||||||
|
for (let i = 0; i < recoveryCodeLimit; i++) {
|
||||||
|
const recoveryCode: string = `${this.secureRandomString()}-${this.secureRandomString()}`;
|
||||||
|
codes.push(recoveryCode);
|
||||||
|
}
|
||||||
|
return codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private secureRandomString() {
|
||||||
|
// return await cryptoRandomString.async({ length: 10, type: 'hex' });
|
||||||
|
return this.generateRandomString(10, 'hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateRandomString(length: number, type: 'hex' | 'base64' | 'numeric' = 'hex'): string {
|
||||||
|
const byteLength = Math.ceil(length * 0.5); // For hex encoding, each byte generates 2 characters
|
||||||
|
const randomBytes = crypto.randomBytes(byteLength);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'hex':
|
||||||
|
return randomBytes.toString('hex').slice(0, length);
|
||||||
|
case 'base64':
|
||||||
|
return randomBytes.toString('base64').slice(0, length);
|
||||||
|
case 'numeric':
|
||||||
|
return randomBytes
|
||||||
|
.toString('hex')
|
||||||
|
.replace(/[a-fA-F]/g, '') // Remove non-numeric characters
|
||||||
|
.slice(0, length);
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid type specified');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public async generateQrCode(user: User) : Promise<{svg: string; url: string; secret: string; }> {
|
||||||
|
// const issuer = encodeURIComponent(this.issuer); // 'TethysCloud'
|
||||||
|
// // const userName = encodeURIComponent(user.email); // 'rrqx9472%40tethys.at'
|
||||||
|
// const label = `${this.issuer}:${user.email}`;
|
||||||
|
|
||||||
|
// const algorithm = encodeURIComponent("SHA256");
|
||||||
|
// const query = `?secret=${user.twoFactorSecret}&issuer=${issuer}&algorithm=${algorithm}&digits=6`; // '?secret=FEYCLOSO627CB7SMLX6QQ7BP75L7SJ54&issuer=TethysCloud'
|
||||||
|
// const url = `otpauth://totp/${label}${query}`; // 'otpauth://totp/rrqx9472%40tethys.at?secret=FEYCLOSO627CB7SMLX6QQ7BP75L7SJ54&issuer=TethysCloud'
|
||||||
|
// const svg = await QRCode.toDataURL(url);
|
||||||
|
// const secret = user.twoFactorSecret as string;
|
||||||
|
// return { svg, url, secret };
|
||||||
|
// }
|
||||||
|
|
||||||
|
public async generateQrCode(user: User, twoFactorSecret?: string): Promise<{ svg: string; url: string; secret: string }> {
|
||||||
|
const issuer = encodeURIComponent(this.issuer); // 'TethysCloud'
|
||||||
|
// const userName = encodeURIComponent(user.email); // 'rrqx9472%40tethys.at'
|
||||||
|
const label = `${this.issuer}:${user.email}`;
|
||||||
|
|
||||||
|
// const algorithm = encodeURIComponent('SHA256');
|
||||||
|
const secret = twoFactorSecret ? twoFactorSecret : (user.twoFactorSecret as string);
|
||||||
|
const query = `?secret=${secret}&issuer=${issuer}&digits=6`; // '?secret=FEYCLOSO627CB7SMLX6QQ7BP75L7SJ54&issuer=TethysCloud'
|
||||||
|
|
||||||
|
const url = `otpauth://totp/${label}${query}`; // 'otpauth://totp/rrqx9472%40tethys.at?secret=FEYCLOSO627CB7SMLX6QQ7BP75L7SJ54&issuer=TethysCloud'
|
||||||
|
const svg = await QRCode.toDataURL(url);
|
||||||
|
|
||||||
|
return { svg, url, secret };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async enable(user: User, token: string): Promise<boolean> {
|
||||||
|
const isValid = verifyToken(user.twoFactorSecret as string, token, 1);
|
||||||
|
if (!isValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
user.state = TotpState.STATE_ENABLED;
|
||||||
|
if (await user.save()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validate(user: User, token: string): Promise<boolean> {
|
||||||
|
const isValid = verifyToken(user.twoFactorSecret as string, token, 1);
|
||||||
|
if (isValid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new TwoFactorAuthProvider();
|
136
app/services/backup_code_storage.ts
Normal file
136
app/services/backup_code_storage.ts
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
import User from '#models/user';
|
||||||
|
import BackupCode from '#models/backup_code';
|
||||||
|
import hash from '@adonisjs/core/services/hash';
|
||||||
|
|
||||||
|
export interface ISecureRandom {
|
||||||
|
CHAR_UPPER: string;
|
||||||
|
CHAR_LOWER: string;
|
||||||
|
CHAR_DIGITS: string;
|
||||||
|
CHAR_SYMBOLS: string;
|
||||||
|
CHAR_ALPHANUMERIC: string;
|
||||||
|
CHAR_HUMAN_READABLE: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random string of specified length.
|
||||||
|
* @param int $length The length of the generated string
|
||||||
|
* @param string $characters An optional list of characters to use if no character list is
|
||||||
|
* specified all valid base64 characters are used.
|
||||||
|
* @return string
|
||||||
|
* @since 8.0.0
|
||||||
|
*/
|
||||||
|
generate(length: number, characters?: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SecureRandom implements ISecureRandom {
|
||||||
|
CHAR_UPPER: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
CHAR_LOWER: string = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
CHAR_DIGITS: string = '0123456789';
|
||||||
|
CHAR_SYMBOLS: string = '!"#$%&\\\'()*+,-./:;<=>?@[]^_`{|}~';
|
||||||
|
CHAR_ALPHANUMERIC: string = this.CHAR_UPPER + this.CHAR_LOWER + this.CHAR_DIGITS;
|
||||||
|
CHAR_HUMAN_READABLE: string = 'abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789';
|
||||||
|
|
||||||
|
public generate(length: number, characters: string = this.CHAR_ALPHANUMERIC): string {
|
||||||
|
if (length <= 0) {
|
||||||
|
throw new Error('Invalid length specified: ' + length + ' must be bigger than 0');
|
||||||
|
}
|
||||||
|
const maxCharIndex: number = characters.length - 1;
|
||||||
|
let randomString: string = '';
|
||||||
|
while (length > 0) {
|
||||||
|
const randomNumber: number = Math.floor(Math.random() * (maxCharIndex + 1));
|
||||||
|
randomString += characters[randomNumber];
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupCodeStorage {
|
||||||
|
private static CODE_LENGTH: number = 16;
|
||||||
|
// private mapper: BackupCodeMapper;
|
||||||
|
// private hasher: IHasher;
|
||||||
|
private random: ISecureRandom;
|
||||||
|
// private eventDispatcher: IEventDispatcher;
|
||||||
|
|
||||||
|
// constructor(mapper: BackupCodeMapper, random: ISecureRandom, hasher: IHasher, eventDispatcher: IEventDispatcher) {
|
||||||
|
// this.mapper = mapper;
|
||||||
|
// this.hasher = hasher;
|
||||||
|
// this.random = random;
|
||||||
|
// this.eventDispatcher = eventDispatcher;
|
||||||
|
// }
|
||||||
|
constructor(random: ISecureRandom) {
|
||||||
|
// this.mapper = mapper;
|
||||||
|
// this.hasher = hasher;
|
||||||
|
this.random = random;
|
||||||
|
// this.eventDispatcher = eventDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createCodes(user: User, number: number = 10): Promise<string[]> {
|
||||||
|
let results: string[] = [];
|
||||||
|
// this.mapper.deleteCodes(user);
|
||||||
|
await BackupCode.deleteCodes(user);
|
||||||
|
// user.twoFactorRecoveryCodes = [""];
|
||||||
|
|
||||||
|
// const uid = user.getUID();
|
||||||
|
for (let i = 1; i <= Math.min(number, 20); i++) {
|
||||||
|
const code = this.random.generate(BackupCodeStorage.CODE_LENGTH, this.random.CHAR_HUMAN_READABLE);
|
||||||
|
// const code = crypto
|
||||||
|
// .randomBytes(Math.ceil(BackupCodeStorage.CODE_LENGTH / 2))
|
||||||
|
// .toString('hex')
|
||||||
|
// .slice(0, BackupCodeStorage.CODE_LENGTH);
|
||||||
|
const dbCode = new BackupCode();
|
||||||
|
// dbCode.setUserId(uid);
|
||||||
|
|
||||||
|
// dbCode.setCode(this.hasher.hash(code));
|
||||||
|
dbCode.code = await hash.make(code);
|
||||||
|
// dbCode.setUsed(0);
|
||||||
|
dbCode.used = false;
|
||||||
|
// this.mapper.insert(dbCode);
|
||||||
|
// await dbCode.save();
|
||||||
|
await dbCode.related('user').associate(user); // speichert schon ab
|
||||||
|
results.push(code);
|
||||||
|
}
|
||||||
|
// this.eventDispatcher.dispatchTyped(new CodesGenerated(user));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async hasBackupCodes(user: User): Promise<boolean> {
|
||||||
|
const codes = await user.getBackupCodes();
|
||||||
|
return codes.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getBackupCodesState(user: User) {
|
||||||
|
// const codes = this.mapper.getBackupCodes(user);
|
||||||
|
// const codes = await user.related('backupcodes').query().exec();
|
||||||
|
const codes: BackupCode[] = await user.getBackupCodes();
|
||||||
|
const total = codes.length;
|
||||||
|
let used: number = 0;
|
||||||
|
codes.forEach((code) => {
|
||||||
|
if (code.used === true) {
|
||||||
|
used++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
enabled: total > 0,
|
||||||
|
total: total,
|
||||||
|
used: used,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// public validateCode(user: User, code: string): boolean {
|
||||||
|
// const dbCodes = await user.getBackupCodes();
|
||||||
|
// for (const dbCode of dbCodes) {
|
||||||
|
// if (parseInt(dbCode.getUsed()) === 0 && this.hasher.verify(code, dbCode.getCode())) {
|
||||||
|
// dbCode.setUsed(1);
|
||||||
|
// this.mapper.update(dbCode);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public deleteCodes(user: User): void {
|
||||||
|
// this.mapper.deleteCodes(user);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BackupCodeStorage;
|
13
app/services/drive.ts
Normal file
13
app/services/drive.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// import app from './app.js';
|
||||||
|
import DriveManager from "#providers/drive/src/drive_manager";
|
||||||
|
import app from "@adonisjs/core/services/app";
|
||||||
|
|
||||||
|
let drive: DriveManager;
|
||||||
|
/**
|
||||||
|
* Returns a singleton instance of the router class from
|
||||||
|
* the container
|
||||||
|
*/
|
||||||
|
await app.booted(async () => {
|
||||||
|
drive = await app.container.make(DriveManager);
|
||||||
|
});
|
||||||
|
export { drive as default };
|
26
app/utils/utility-functions.ts
Normal file
26
app/utils/utility-functions.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
export function sum(a: number, b: number): number {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDomain(host: string): string {
|
||||||
|
// $myhost = strtolower(trim($host));
|
||||||
|
let myHost: string = host.trim().toLocaleLowerCase();
|
||||||
|
// $count = substr_count($myhost, '.');
|
||||||
|
const count: number = myHost.split(',').length - 1;
|
||||||
|
|
||||||
|
if (count == 2) {
|
||||||
|
const words = myHost.split('.');
|
||||||
|
if (words[1].length > 3) {
|
||||||
|
myHost = myHost.split('.', 2)[1];
|
||||||
|
}
|
||||||
|
} else if (count > 2) {
|
||||||
|
myHost = getDomain(myHost.split('.', 2)[1]);
|
||||||
|
}
|
||||||
|
myHost = myHost.replace(new RegExp(/^.*:\/\//i, 'g'), '');
|
||||||
|
return myHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function preg_match(regex: RegExp, str: string) {
|
||||||
|
const result: boolean = regex.test(str);
|
||||||
|
return result;
|
||||||
|
}
|
20
app/validators/auth.ts
Normal file
20
app/validators/auth.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import vine from '@vinejs/vine';
|
||||||
|
|
||||||
|
// public schema = schema.create({
|
||||||
|
// email: schema.string({ trim: true }, [
|
||||||
|
// rules.email(),
|
||||||
|
// // rules.unique({ table: 'accounts', column: 'email' })
|
||||||
|
// ]),
|
||||||
|
// password: schema.string({}, [rules.minLength(6)]),
|
||||||
|
// });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the role's creation action
|
||||||
|
* node ace make:validator role
|
||||||
|
*/
|
||||||
|
export const authValidator = vine.compile(
|
||||||
|
vine.object({
|
||||||
|
email: vine.string().maxLength(255).email().normalizeEmail(),
|
||||||
|
password: vine.string().trim().minLength(6),
|
||||||
|
}),
|
||||||
|
);
|
341
app/validators/dataset.ts
Normal file
341
app/validators/dataset.ts
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
||||||
|
import { TitleTypes, DescriptionTypes, ContributorTypes, ReferenceIdentifierTypes, RelationTypes } from '#contracts/enums';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import MimeType from '#models/mime_type';
|
||||||
|
|
||||||
|
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
||||||
|
const extensions = enabledExtensions
|
||||||
|
.map((extension) => {
|
||||||
|
return extension.file_extension.split('|');
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the dataset's creation action
|
||||||
|
* node ace make:validator dataset
|
||||||
|
*/
|
||||||
|
export const createDatasetValidator = vine.compile(
|
||||||
|
vine.object({
|
||||||
|
// first step
|
||||||
|
language: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^[a-zA-Z0-9]+$/),
|
||||||
|
licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
|
||||||
|
rights: vine.string().in(['true']),
|
||||||
|
// second step
|
||||||
|
type: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
creating_corporation: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
titles: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
type: vine.enum(Object.values(TitleTypes)),
|
||||||
|
language: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.minLength(2)
|
||||||
|
.maxLength(255)
|
||||||
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(1),
|
||||||
|
descriptions: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(2500),
|
||||||
|
type: vine.enum(Object.values(DescriptionTypes)),
|
||||||
|
language: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.minLength(2)
|
||||||
|
.maxLength(255)
|
||||||
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(1),
|
||||||
|
authors: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(1)
|
||||||
|
.distinct('email'),
|
||||||
|
contributors: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.distinct('email')
|
||||||
|
.optional(),
|
||||||
|
// third step
|
||||||
|
project_id: vine.number().optional(),
|
||||||
|
// embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
|
||||||
|
embargo_date: vine
|
||||||
|
.date({
|
||||||
|
formats: ['YYYY-MM-DD'],
|
||||||
|
})
|
||||||
|
.afterOrEqual((_field) => {
|
||||||
|
return dayjs().add(10, 'day').format('YYYY-MM-DD');
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
coverage: vine.object({
|
||||||
|
x_min: vine.number(),
|
||||||
|
x_max: vine.number(),
|
||||||
|
y_min: vine.number(),
|
||||||
|
y_max: vine.number(),
|
||||||
|
elevation_absolut: vine.number().positive().optional(),
|
||||||
|
elevation_min: vine.number().positive().optional().requiredIfExists('elevation_max'),
|
||||||
|
elevation_max: vine.number().positive().optional().requiredIfExists('elevation_min'),
|
||||||
|
// type: vine.enum(Object.values(DescriptionTypes)),
|
||||||
|
depth_absolut: vine.number().negative().optional(),
|
||||||
|
depth_min: vine.number().negative().optional().requiredIfExists('depth_max'),
|
||||||
|
depth_max: vine.number().negative().optional().requiredIfExists('depth_min'),
|
||||||
|
}),
|
||||||
|
references: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
|
||||||
|
relation: vine.enum(Object.values(RelationTypes)),
|
||||||
|
label: vine.string().trim().minLength(2).maxLength(255),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
subjects: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
// pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||||
|
language: vine.string().trim().minLength(2).maxLength(255),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(3)
|
||||||
|
.distinct('value'),
|
||||||
|
// last step
|
||||||
|
files: vine
|
||||||
|
.array(
|
||||||
|
vine
|
||||||
|
.myfile({
|
||||||
|
size: '512mb',
|
||||||
|
extnames: extensions,
|
||||||
|
})
|
||||||
|
.filenameLength({ clientNameSizeLimit: 100 })
|
||||||
|
.fileScan({ removeInfected: true }),
|
||||||
|
)
|
||||||
|
.minLength(1),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the dataset's update action
|
||||||
|
*/
|
||||||
|
export const updateDatasetValidator = vine.compile(
|
||||||
|
vine.object({
|
||||||
|
// first step
|
||||||
|
language: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^[a-zA-Z0-9]+$/),
|
||||||
|
licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
|
||||||
|
rights: vine.string().in(['true']),
|
||||||
|
// second step
|
||||||
|
type: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
creating_corporation: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
titles: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
type: vine.enum(Object.values(TitleTypes)),
|
||||||
|
language: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.minLength(2)
|
||||||
|
.maxLength(255)
|
||||||
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(1),
|
||||||
|
descriptions: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(2500),
|
||||||
|
type: vine.enum(Object.values(DescriptionTypes)),
|
||||||
|
language: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.minLength(2)
|
||||||
|
.maxLength(255)
|
||||||
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(1),
|
||||||
|
authors: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(1)
|
||||||
|
.distinct('email'),
|
||||||
|
contributors: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
email: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.maxLength(255)
|
||||||
|
.email()
|
||||||
|
.normalizeEmail()
|
||||||
|
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||||
|
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.distinct('email')
|
||||||
|
.optional(),
|
||||||
|
// third step
|
||||||
|
project_id: vine.number().optional(),
|
||||||
|
// embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
|
||||||
|
embargo_date: vine
|
||||||
|
.date({
|
||||||
|
formats: ['YYYY-MM-DD'],
|
||||||
|
})
|
||||||
|
.afterOrEqual((_field) => {
|
||||||
|
return dayjs().add(10, 'day').format('YYYY-MM-DD');
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
coverage: vine.object({
|
||||||
|
x_min: vine.number(),
|
||||||
|
x_max: vine.number(),
|
||||||
|
y_min: vine.number(),
|
||||||
|
y_max: vine.number(),
|
||||||
|
elevation_absolut: vine.number().positive().optional(),
|
||||||
|
elevation_min: vine.number().positive().optional().requiredIfExists('elevation_max'),
|
||||||
|
elevation_max: vine.number().positive().optional().requiredIfExists('elevation_min'),
|
||||||
|
// type: vine.enum(Object.values(DescriptionTypes)),
|
||||||
|
depth_absolut: vine.number().negative().optional(),
|
||||||
|
depth_min: vine.number().negative().optional().requiredIfExists('depth_max'),
|
||||||
|
depth_max: vine.number().negative().optional().requiredIfExists('depth_min'),
|
||||||
|
}),
|
||||||
|
references: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
|
||||||
|
relation: vine.enum(Object.values(RelationTypes)),
|
||||||
|
label: vine.string().trim().minLength(2).maxLength(255),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
subjects: vine
|
||||||
|
.array(
|
||||||
|
vine.object({
|
||||||
|
value: vine.string().trim().minLength(3).maxLength(255),
|
||||||
|
// pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||||
|
language: vine.string().trim().minLength(2).maxLength(255),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.minLength(3)
|
||||||
|
.distinct('value'),
|
||||||
|
// last step
|
||||||
|
files: vine.array(
|
||||||
|
vine.myfile({
|
||||||
|
size: '512mb',
|
||||||
|
extnames: extensions,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
// .minLength(1),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// files: schema.array([rules.minLength(1)]).members(
|
||||||
|
// schema.file({
|
||||||
|
// size: '512mb',
|
||||||
|
// extnames: ['jpg', 'gif', 'png', 'tif', 'pdf', 'zip', 'fgb', 'nc', 'qml', 'ovr', 'gpkg', 'gml', 'gpx', 'kml', 'kmz', 'json'],
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
|
||||||
|
let messagesProvider = new SimpleMessagesProvider({
|
||||||
|
'minLength': '{{ field }} must be at least {{ min }} characters long',
|
||||||
|
'maxLength': '{{ field }} must be less then {{ max }} characters long',
|
||||||
|
'required': '{{ field }} is required',
|
||||||
|
'unique': '{{ field }} must be unique, and this value is already taken',
|
||||||
|
// 'confirmed': '{{ field }} is not correct',
|
||||||
|
'licenses.minLength': 'at least {{ min }} permission must be defined',
|
||||||
|
'licenses.*.number': 'Define roles as valid numbers',
|
||||||
|
'rights.in': 'you must agree to continue',
|
||||||
|
|
||||||
|
'titles.0.value.minLength': 'Main Title must be at least {{ min }} characters long',
|
||||||
|
'titles.0.value.maxLength': 'Main Title must be less than {{ max }} characters long',
|
||||||
|
'titles.0.value.required': 'Main Title is required',
|
||||||
|
'titles.*.value.required': 'Additional title is required, if defined',
|
||||||
|
'titles.*.type.required': 'Additional title type is required',
|
||||||
|
'titles.*.language.required': 'Additional title language is required',
|
||||||
|
'titles.*.language.translatedLanguage': 'The language of the translated title must be different from the language of the dataset',
|
||||||
|
|
||||||
|
'descriptions.0.value.minLength': 'Main Abstract must be at least {{ min }} characters long',
|
||||||
|
'descriptions.0.value.maxLength': 'Main Abstract must be less than {{ max }} characters long',
|
||||||
|
'descriptions.0.value.required': 'Main Abstract is required',
|
||||||
|
'descriptions.*.value.required': 'Additional description is required, if defined',
|
||||||
|
'descriptions.*.type.required': 'Additional description type is required',
|
||||||
|
'descriptions.*.language.required': 'Additional description language is required',
|
||||||
|
'descriptions.*.language.translatedLanguage':
|
||||||
|
'The language of the translated description must be different from the language of the dataset',
|
||||||
|
|
||||||
|
'authors.array.minLength': 'at least {{ min }} author must be defined',
|
||||||
|
'authors.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
|
||||||
|
'authors.*.email.isUnique': 'the email of the new creator already exists in the database',
|
||||||
|
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
|
||||||
|
'contributors.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
|
||||||
|
|
||||||
|
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
|
||||||
|
|
||||||
|
'subjects.array.minLength': 'at least {{ min }} keywords must be defined',
|
||||||
|
'subjects.*.value.required': 'keyword value is required',
|
||||||
|
'subjects.*.value.minLength': 'keyword value must be at least {{ min }} characters long',
|
||||||
|
'subjects.*.type.required': 'keyword type is required',
|
||||||
|
'subjects.*.language.required': 'language of keyword is required',
|
||||||
|
'subjects.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
|
||||||
|
|
||||||
|
'references.*.value.required': 'Additional reference value is required, if defined',
|
||||||
|
'references.*.type.required': 'Additional reference identifier type is required',
|
||||||
|
'references.*.relation.required': 'Additional reference relation type is required',
|
||||||
|
'references.*.label.required': 'Additional reference label is required',
|
||||||
|
|
||||||
|
'files.array.minLength': 'At least {{ min }} file upload is required.',
|
||||||
|
'files.*.size': 'file size is to big',
|
||||||
|
'files.*.extnames': 'file extension is not supported',
|
||||||
|
});
|
||||||
|
|
||||||
|
createDatasetValidator.messagesProvider = messagesProvider;
|
||||||
|
updateDatasetValidator.messagesProvider = messagesProvider;
|
||||||
|
// export default createDatasetValidator;
|
64
app/validators/role.ts
Normal file
64
app/validators/role.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the role's creation action
|
||||||
|
* node ace make:validator role
|
||||||
|
*/
|
||||||
|
export const createRoleValidator = vine.compile(
|
||||||
|
vine.object({
|
||||||
|
name: vine
|
||||||
|
.string()
|
||||||
|
.isUnique({ table: 'roles', column: 'name' })
|
||||||
|
.trim()
|
||||||
|
.minLength(3)
|
||||||
|
.maxLength(255)
|
||||||
|
.regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
|
||||||
|
display_name: vine
|
||||||
|
.string()
|
||||||
|
.isUnique({ table: 'roles', column: 'display_name' })
|
||||||
|
.trim()
|
||||||
|
.minLength(3)
|
||||||
|
.maxLength(255)
|
||||||
|
.regex(/^[a-zA-Z0-9]+$/),
|
||||||
|
description: vine.string().trim().escape().minLength(3).maxLength(255).optional(),
|
||||||
|
permissions: vine.array(vine.number()).minLength(1), // define at least one permission for the new role
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateRoleValidator = vine.withMetaData<{ roleId: number }>().compile(
|
||||||
|
vine.object({
|
||||||
|
name: vine
|
||||||
|
.string()
|
||||||
|
// .unique(async (db, value, field) => {
|
||||||
|
// const result = await db.from('roles').select('id').whereNot('id', field.meta.roleId).where('name', value).first();
|
||||||
|
// return result.length ? false : true;
|
||||||
|
// })
|
||||||
|
.isUnique({
|
||||||
|
table: 'roles',
|
||||||
|
column: 'name',
|
||||||
|
whereNot: (field) => field.meta.roleId,
|
||||||
|
})
|
||||||
|
.trim()
|
||||||
|
.minLength(3)
|
||||||
|
.maxLength(255),
|
||||||
|
|
||||||
|
description: vine.string().trim().escape().minLength(3).maxLength(255).optional(),
|
||||||
|
permissions: vine.array(vine.number()).minLength(1), // define at least one permission for the new role
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let messagesProvider = new SimpleMessagesProvider({
|
||||||
|
// Applicable for all fields
|
||||||
|
'required': 'The {{ field }} field is required',
|
||||||
|
'unique': '{{ field }} must be unique, and this value is already taken',
|
||||||
|
'string': 'The value of {{ field }} field must be a string',
|
||||||
|
'email': 'The value is not a valid email address',
|
||||||
|
|
||||||
|
// 'contacts.0.email.required': 'The primary email of the contact is required',
|
||||||
|
// 'contacts.*.email.required': 'Contact email is required',
|
||||||
|
'permissions.minLength': 'at least {{min }} permission must be defined',
|
||||||
|
'permissions.*.number': 'Define permissions as valid numbers',
|
||||||
|
});
|
||||||
|
|
||||||
|
createRoleValidator.messagesProvider = messagesProvider;
|
||||||
|
updateRoleValidator.messagesProvider = messagesProvider;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user