Compare commits

...
Sign in to create a new pull request.

7 commits

Author SHA1 Message Date
a41b091214 feat: Adjust z-index values for map components, enhance ISBN validation message, and add dynamic placeholders for reference inputs, add additional mimetypes
All checks were successful
CI / container-job (push) Successful in 38s
2025-02-17 16:08:36 +01:00
a3031169ca feat: Add alternate mimetype support, enhance validation for alternate mimetypes, and improve script loading performance
All checks were successful
CI / container-job (push) Successful in 36s
- mime_type.ts: Added a new column `public alternate_mimetype: string;`
- MimetypeController.ts: Extended validation and storage logic to accommodate the new `alternate_mimetype` attribute
- adonisrc.ts: Integrated new validation rule to validate user-provided mimetypes
- vite.ts: Set `defer: true` for script attributes to improve loading performance
- update_1_to_mime_types.ts: Added migration for the new `alternate_mimetype` column in the database
- UI improvements: Updated components such as AsideMenuLayer.vue, FormCheckRadioGroup.vue, MimeTypeInput.vue, NavBar.vue (lime-green background), NavBarMenu.vue, SectionBannerStarOnGitea.vue, Admin/mimetype/Create.vue, Admin/mimetype/Delete.vue, Admin/mimetype/Index.vue
- allowed_extensions_mimetype.ts: Enhanced rule to also check for alternate mimetypes
- referenceValidation.ts: Improved validation to allow only ISBNs with a '-' delimiter
- package-lock.json: Updated npm dependencie
2025-02-13 15:49:09 +01:00
4c5a8f5a42 feat: update to vite.js, Refactor configuration files, remove unused assets, and clean up commented code:
All checks were successful
CI / container-job (push) Successful in 43s
- ace.js: use ts-node-maintained
- adonisrc.ts: load vite_provider, sett assetBundler to false, addd hooks property
- Dockerfile: change to node version 22
- package.json: remove babel depencies; add @swc/wasm, add vitejs/plugin-vue, add hot-hook, add vite,  update eslint-config-prettier, tailwindcss, ts-node-maintained
- new vite.config.js and config/vite.ts
- inertia.js
- improved own vinejs_provider.ts
- adapted app.css needed for vitejs
- adapted app.ts: new resolve method neede for vitejs
relocated resources/js/logo.svg
- remove Buffer import into FileUpload.vue
- Create.vue: improved submit needed for @inertiajs/vue3 form helper
- Edit.vue: mproved submit needed for @inertiajs/vue3 form helper
- kernel.ts: load vite_middleware
- formated rotes.ts file
- rewritten allowed_extensions_mimetypes.ts file (removed typescript errors)
2025-02-07 10:14:57 +01:00
8d47a58d29 feat: Update .gitignore and refine TypeScript configuration; clean up commented code and enhance dataset validation; npm updates
Some checks failed
CI / container-job (push) Failing after 35s
- Updated .gitignore to include new patterns
- Refined TypeScript configuration for better performance and readability
- Cleaned up commented code in several files
- Enhanced dataset validation logic
- Updated npm dependencies to the latest versions
2025-01-29 11:26:21 +01:00
a5e0a36327 feat: Update CI workflow for reference validation tests and add environment variable configurations
All checks were successful
CI / container-job (push) Successful in 41s
2025-01-27 12:20:49 +01:00
c0496be51b - workflow adaptions for new tests
Some checks failed
CI / test (push) Failing after 1m12s
2025-01-24 17:45:58 +01:00
2c4f51be68 feat: Enhance reference validation and add support for Handle URLs
Some checks failed
CI Pipeline / japa-tests (push) Failing after 51s
- Updated reference validation to handle various identifier types including DOI, ISBN, ISSN, URN, and Handle.
- Improved regex patterns for DOI and Handle validation to correctly extract and validate identifiers from URLs.
- Added asynchronous checks to verify the existence of DOI and Handle URLs.
- Added asynchronous checks to verify the existence of  ISBNs
- Included detailed comments explaining the regex patterns and validation logic.
- Adjusted the validation logic to handle any URL prefix for Handle identifiers.
- Ensured that the Handle format `handle/20.500.12854/36478` is correctly validated.
- Updated the CI workflow to trigger on push and pull request events.
2025-01-24 17:11:10 +01:00
77 changed files with 3493 additions and 9364 deletions

View file

@ -17,4 +17,6 @@ REDIS_PORT=6379
REDIS_PASSWORD=
SMTP_HOST=
SMTP_PORT=
RESEND_API_KEY=
RESEND_API_KEY=
OPENSEARCH_HOST=http://localhost
OPENSEARCH_CORE=tethys-records

View file

@ -0,0 +1,78 @@
# This is a Gitea Actions workflow configuration file for running CI tests on the `feat/checkReferenceType` branch.
# The workflow is named "CI" and runs on the latest Ubuntu environment using a Node.js 20 Docker container.
# It sets up a PostgreSQL service with specified environment variables and health checks.
# The workflow includes the following steps:
# 1. Checkout the repository using the actions/checkout@v3 action.
# 2. Install Node.js dependencies using `npm ci`.
# 3. Create a `.env.test` file by copying from `.env.example`.
# 4. Set up environment variables in the `.env.test` file, including database connection details and other app-specific settings.
# 5. Run functional tests using the `node ace test functional --groups "ReferenceValidation"` command.
name: CI
run-name: Running tests for checkReferenceType branch
on:
push:
branches:
- feat/checkReferenceType
jobs:
container-job:
runs-on: ubuntu-latest
# Docker Hub image that `container-job` executes in
container: node:20-bullseye
services:
# Label used to access the service container
postgres:
image: postgres:latest
env:
POSTGRES_USER: alice
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_DB: tethys_dev
# ports:
# - 5432:5432
options: |
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout repository
uses: actions/checkout@v3
# - name: Set up Node.js
# uses: actions/setup-node@v2
# with:
# node-version: '20'
- name: Install dependencies
run: npm ci
- name: Create .env.test file
run: cp .env.example .env.test
- name: Set up environment variables
run: |
echo "DB_CONNECTION=pg" >> .env.test
echo "PG_HOST=postgres" >> .env.test
echo "PG_PORT=5432" >> .env.test
echo "PG_USER=alice" >> .env.test
echo "PG_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}" >> .env.test
echo "PG_DB_NAME=tethys_dev" >> .env.test
echo "NODE_ENV=test" >> .env.test
echo "ASSETS_DRIVER=fake" >> .env.test
echo "SESSION_DRIVER=memory" >> .env.test
echo "HASH_DRIVER=bcrypt" >> .env.test
echo "HOST=127.0.0.1" >> .env.test
echo "PORT=3333" >> .env.test
echo "APP_NAME=TethysCloud" >> .env.test
echo "APP_URL=http://${HOST}:${PORT}" >> .env.test
echo "CACHE_VIEWS=false" >> .env.test
echo "APP_KEY=pfi5N2ACN4tMJ5d8d8BPHfh3FEuvleej" >> .env.test
echo "DRIVE_DISK=local" >> .env.test
echo "OAI_LIST_SIZE=200" >> .env.test
echo "OPENSEARCH_HOST=${{ secrets.OPENSEARCH_HOST }}" >> .env.test
echo "OPENSEARCH_CORE=tethys-records" >> .env.test
- name: Run tests
run: node ace test functional --groups "ReferenceValidation"

View file

@ -4,7 +4,13 @@
name: CI Pipeline
run-name: ${{ github.actor }} is running CI pipeline
# trigger build when pushing, or when creating a pull request
on: [push, pull_request]
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
# Label of the container job
@ -12,7 +18,7 @@ jobs:
# run build on latest ubuntu
runs-on: ubuntu-latest
container: node:18-bullseye
container: node:20-bullseye
services:
mydb:
@ -70,6 +76,7 @@ jobs:
&& echo "CACHE_VIEWS=false" >> .env.test
&& echo "APP_KEY=pfi5N2ACN4tMJ5d8d8BPHfh3FEuvleej" >> .env.test
&& echo "DRIVE_DISK=local" >> .env.test
&& echo "OAI_LIST_SIZE=200" >> .env.test
# finally run the tests
# - run: npm test
@ -95,3 +102,4 @@ jobs:
# uses: coverallsapp/github-action@master
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ coverage
tmp
docker-compose.yml
.env.test
public/assets

View file

@ -1,7 +1,7 @@
################## First Stage - Creating base #########################
# Created a variable to hold our node base image
ARG NODE_IMAGE=node:20-bookworm-slim
ARG NODE_IMAGE=node:22-bookworm-slim
FROM $NODE_IMAGE AS base
# Install dumb-init and ClamAV, and perform ClamAV database update

7
ace.js
View file

@ -15,10 +15,11 @@
/**
* Register hook to process TypeScript files using ts-node
*/
import { register } from 'node:module'
register('ts-node/esm', import.meta.url)
// import { register } from 'node:module';
// register('ts-node/esm', import.meta.url);
import 'ts-node-maintained/register/esm';
/**
* Import ace console entrypoint
*/
await import('./bin/console.js')
await import('./bin/console.js');

View file

@ -1,7 +1,7 @@
import { defineConfig } from '@adonisjs/core/app'
import { defineConfig } from '@adonisjs/core/app';
export default defineConfig({
/*
/*
|--------------------------------------------------------------------------
| Commands
|--------------------------------------------------------------------------
@ -10,12 +10,11 @@ export default defineConfig({
| will be scanned automatically from the "./commands" directory.
*/
commands: [
() => import('@adonisjs/core/commands'),
() => import('@adonisjs/lucid/commands'),
() => import('@adonisjs/mail/commands')
],
/*
commands: [
() => import('@adonisjs/core/commands'),
() => import('@adonisjs/lucid/commands'),
() => import('@adonisjs/mail/commands')],
/*
|--------------------------------------------------------------------------
| Preloads
|--------------------------------------------------------------------------
@ -23,19 +22,21 @@ export default defineConfig({
| 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'),
() => import('#start/rules/allowed_extensions_mimetypes'),
() => import('#start/rules/dependent_array_min_length')
],
/*
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'),
() => import('#start/rules/allowed_extensions_mimetypes'),
() => import('#start/rules/dependent_array_min_length'),
() => import('#start/rules/referenceValidation'),
() => import('#start/rules/valid_mimetype'),
],
/*
|--------------------------------------------------------------------------
| Service providers
|--------------------------------------------------------------------------
@ -44,48 +45,48 @@ export default defineConfig({
| 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,
},
],
/*
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('@adonisjs/vite/vite_provider'),
],
metaFiles: [
{
pattern: 'public/**',
reloadServer: false,
},
{
pattern: 'resources/views/**/*.edge',
reloadServer: false,
},
],
/*
|--------------------------------------------------------------------------
| Tests
|--------------------------------------------------------------------------
@ -94,22 +95,24 @@ export default defineConfig({
| 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,
},
})
tests: {
suites: [
{
files: ['tests/unit/**/*.spec(.ts|.js)'],
name: 'unit',
timeout: 2000,
},
{
files: ['tests/functional/**/*.spec(.ts|.js)'],
name: 'functional',
timeout: 30000,
},
],
forceExit: false,
},
assetsBundler: false,
hooks: {
onBuildStarting: [() => import('@adonisjs/vite/build_hook')],
},
// assetsBundler: false
});

View file

@ -25,6 +25,7 @@ export default class MimetypeController {
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
alternate_mimetype: vine.array(vine.string().isValidMimetype()).distinct().optional(), // define alias mimetypes
enabled: vine.boolean(),
});
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
@ -32,18 +33,22 @@ export default class MimetypeController {
// 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);
validator.messagesProvider = new SimpleMessagesProvider(this.messages);
await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
}
const input = request.only(['name', 'enabled', 'file_extension']);
const input = request.only(['name', 'enabled', 'file_extension', 'alternate_mimetype']);
// 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('|');
}
// Concatenate the alias_mimetype array into a string with '|' as the separator
if (Array.isArray(input.alternate_mimetype)) {
input.alternate_mimetype = input.alternate_mimetype.join('|');
}
await MimeType.create(input);
// if (request.input('roles')) {
// const roles: Array<number> = request.input('roles');

View file

@ -276,7 +276,7 @@ export default class DatasetsController {
validateSMTP: false,
});
const validRecipientEmail: boolean = validationResult.valid;
let emailStatusMessage = '';
// let emailStatusMessage = '';
if (sendMail == true) {
if (dataset.editor.email && validRecipientEmail) {
@ -289,7 +289,7 @@ export default class DatasetsController {
<p>Best regards,<br>Your Tethys reviewer: ${authUser.login}</p>
`);
});
emailStatusMessage = ` A rejection email was successfully sent to ${dataset.editor.email}.`;
// emailStatusMessage = ` A rejection email was successfully sent to ${dataset.editor.email}.`;
} catch (error) {
logger.error(error);
return response
@ -297,7 +297,7 @@ export default class DatasetsController {
.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.`;
// emailStatusMessage = ` However, the email could not be sent because the editor's email address (${dataset.editor.email}) is not valid.`;
}
}

View file

@ -363,7 +363,8 @@ export default class DatasetController {
references: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
// value: vine.string().trim().minLength(3).maxLength(255),
value: vine.string().trim().minLength(3).maxLength(255).validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
relation: vine.enum(Object.values(RelationTypes)),
label: vine.string().trim().minLength(2).maxLength(255),
@ -1192,7 +1193,7 @@ export default class DatasetController {
throw error;
} else if (error instanceof Exception) {
// General exception handling
return response.flash('errors', { error: error.message }).redirect().back();
return response.flash('errors', error.message).redirect().back();
} else {
session.flash({ error: 'An error occurred while deleting the dataset.' });
return response.redirect().back();

View file

@ -6,7 +6,7 @@ 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';
import { default as axios } from 'axios';
export class DoiClient implements DoiClientContract {
public username: string;
@ -50,7 +50,7 @@ export class DoiClient implements DoiClientContract {
'Content-Type': 'application/xml;charset=UTF-8',
};
try {
const metadataResponse = await axios.default.put(`${this.serviceUrl}/metadata/${doiValue}`, xmlMeta, { auth, headers });
const metadataResponse = await axios.put(`${this.serviceUrl}/metadata/${doiValue}`, xmlMeta, { auth, headers });
// Response Codes
// 201 Created: operation successful
@ -65,7 +65,7 @@ export class DoiClient implements DoiClientContract {
throw new DoiClientException(metadataResponse.status, message);
}
const doiResponse = await axios.default.put(`${this.serviceUrl}/doi/${doiValue}`, `doi=${doiValue}\nurl=${landingPageUrl}`, {
const doiResponse = await axios.put(`${this.serviceUrl}/doi/${doiValue}`, `doi=${doiValue}\nurl=${landingPageUrl}`, {
auth,
headers,
});

View file

@ -3,7 +3,6 @@ import { column, hasMany, belongsTo, SnakeCaseNamingStrategy, computed } from '@
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';

View file

@ -16,9 +16,14 @@ export default class MimeType extends BaseModel {
@column({})
public name: string;
// 1 : n file_extensions are separated by '|' in the database
@column({})
public file_extension: string;
// 1 : n alternate_mimetype are separated by '|' in the database
@column({})
public alternate_mimetype: string;
@column({})
public enabled: boolean;

View file

@ -1,6 +1,7 @@
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();
@ -125,7 +126,7 @@ export const createDatasetValidator = vine.compile(
references: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
value: vine.string().trim().minLength(3).maxLength(255).validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
relation: vine.enum(Object.values(RelationTypes)),
label: vine.string().trim().minLength(2).maxLength(255),
@ -272,7 +273,7 @@ export const updateDatasetValidator = vine.compile(
references: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
value: vine.string().trim().minLength(3).maxLength(255).validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
relation: vine.enum(Object.values(RelationTypes)),
label: vine.string().trim().minLength(2).maxLength(255),

View file

@ -142,7 +142,7 @@ export class VanillaErrorReporter implements ErrorReporterContract {
// }
this.hasErrors = true;
var test = field.getFieldPath();
// var test = field.getFieldPath();
// this.errors.push(error);
// if (this.errors[error.field]) {

View file

@ -47,7 +47,7 @@ const databaseConfig = defineConfig({
migrations: {
naturalSort: true,
},
healthCheck: false,
// healthCheck: false,
debug: false,
pool: { min: 1, max: 100 },
},

View file

@ -1,7 +1,8 @@
import { defineConfig } from '@adonisjs/inertia';
import type { HttpContext } from '@adonisjs/core/http';
import type { InferSharedProps } from '@adonisjs/inertia/types'
export default defineConfig({
const inertiaConfig = defineConfig({
/**
* Path to the Edge view that will be used as the root view for Inertia responses
*/
@ -52,6 +53,12 @@ export default defineConfig({
},
});
export default inertiaConfig
declare module '@adonisjs/inertia/types' {
export interface SharedProps extends InferSharedProps<typeof inertiaConfig> {}
}
// import { InertiaConfig } from '@ioc:EidelLev/Inertia';
// /*

View file

@ -12,7 +12,7 @@ const mailConfig = defineConfig({
mailers: {
smtp: transports.smtp({
socketTimeout: 5000,// Overall timeout (5 seconds)
// socketTimeout: 5000,// Overall timeout (5 seconds)
host: env.get('SMTP_HOST', ''),
port: env.get('SMTP_PORT'),
secure: false,
@ -30,10 +30,10 @@ const mailConfig = defineConfig({
}, */
}),
resend: transports.resend({
key: env.get('RESEND_API_KEY'),
baseUrl: 'https://api.resend.com',
}),
// resend: transports.resend({
// key: env.get('RESEND_API_KEY'),
// baseUrl: 'https://api.resend.com',
// }),
},
});

View file

@ -6,7 +6,7 @@
*/
import env from '#start/env';
import app from '@adonisjs/core/services/app';
// import app from '@adonisjs/core/services/app';
import { defineConfig, stores } from '@adonisjs/session';
const sessionConfig = defineConfig({

32
config/vite.ts Normal file
View file

@ -0,0 +1,32 @@
import { defineConfig } from '@adonisjs/vite';
const viteBackendConfig = defineConfig({
/**
* The output of vite will be written inside this
* directory. The path should be relative from
* the application root.
*/
buildDirectory: 'public/assets',
/**
* The path to the manifest file generated by the
* "vite build" command.
*/
manifestFile: 'public/assets/.vite/manifest.json',
/**
* Feel free to change the value of the "assetsUrl" to
* point to a CDN in production.
*/
assetsUrl: '/assets',
/**
* Add defer attribute to scripts for better performance.
*/
scriptAttributes: {
defer: true,
},
});
export default viteBackendConfig;

View file

@ -0,0 +1,18 @@
import { BaseSchema } from "@adonisjs/lucid/schema";
export default class AddAlternateMimetypeToMimeTypes extends BaseSchema {
protected tableName = 'mime_types';
public async up () {
this.schema.alterTable(this.tableName, (table) => {
table.string('alternate_mimetype').nullable();
});
}
public async down () {
this.schema.alterTable(this.tableName, (table) => {
table.dropColumn('alternate_mimetype');
});
}
}
// ALTER TABLE "mime_types" ADD COLUMN "alternate_mimetype" VARCHAR(255) NULL

10056
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,8 @@
"private": true,
"scripts": {
"type-check": "tsc --noEmit",
"dev": "node ace serve --watch",
"dev": "node ace serve",
"devInspect": "node ace serve --watch --node-args='--inspect'",
"compress:xslt": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/datasetxml2oai-pmh.xslt -export:public/assets2/datasetxml2oai.sef.json -t -nogo '-ns:##html5'",
"compress:solr": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/solr.xslt -export:public/assets2/solr.sef.json -t -nogo '-ns:##html5'",
"compress:doi": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/doi_datacite.xslt -export:public/assets2/doi_datacite.sef.json -t -nogo '-ns:##html5'",
@ -15,59 +16,57 @@
"format-check": "prettier --check ./**/*.{ts,js}",
"test": "node ace test"
},
"eslintIgnore": [
"build"
],
"eslintConfig": {
"ignorePatterns": [
"build"
]
},
"alias": {
"vue": "./node_modules/vue/dist/vue.esm-bundler.js"
},
"devDependencies": {
"@adonisjs/assembler": "^7.1.1",
"@adonisjs/tsconfig": "^1.2.1",
"@babel/core": "^7.20.12",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.20.13",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@japa/api-client": "^2.0.3",
"@japa/assert": "^3.0.0",
"@japa/plugin-adonisjs": "^3.0.0",
"@japa/runner": "^3.1.1",
"@adonisjs/tsconfig": "^1.4.0",
"@japa/assert": "^4.0.1",
"@japa/plugin-adonisjs": "^4.0.0",
"@japa/runner": "^4.2.0",
"@mdi/js": "^7.1.96",
"@poppinss/utils": "^6.7.2",
"@swc/core": "^1.4.2",
"@symfony/webpack-encore": "^5.0.1",
"@swc/wasm": "^1.10.14",
"@tailwindcss/forms": "^0.5.2",
"@types/bcryptjs": "^2.4.6",
"@types/clamscan": "^2.0.4",
"@types/escape-html": "^1.0.4",
"@types/fs-extra": "^11.0.4",
"@types/leaflet": "^1.9.3",
"@types/luxon": "^3.4.2",
"@types/node": "^22.5.5",
"@types/node": "^22.10.2",
"@types/proxy-addr": "^2.0.0",
"@types/qrcode": "^1.5.5",
"@types/source-map-support": "^0.5.6",
"@types/sprintf-js": "^1.1.4",
"@types/supertest": "^6.0.2",
"@vitejs/plugin-vue": "^5.2.1",
"autoprefixer": "^10.4.13",
"babel-preset-typescript-vue3": "^2.0.17",
"chart.js": "^4.2.0",
"dotenv-webpack": "^8.0.1",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.0.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-adonis": "^2.1.1",
"eslint-plugin-prettier": "^5.0.0-alpha.2",
"hot-hook": "^0.4.0",
"numeral": "^2.0.6",
"pinia": "^2.0.30",
"pino-pretty": "^11.2.2",
"pino-pretty": "^13.0.0",
"postcss-loader": "^8.1.1",
"prettier": "^3.0.0",
"prettier": "^3.4.2",
"supertest": "^6.3.3",
"tailwindcss": "^3.2.4",
"tailwindcss": "^3.4.17",
"ts-loader": "^9.4.2",
"ts-node": "^10.9.2",
"typescript": "^5.1.3",
"ts-node-maintained": "^10.9.5",
"typescript": "~5.7",
"vite": "^6.0.11",
"vue": "^3.4.26",
"vue-facing-decorator": "^3.0.0",
"vue-loader": "^17.0.1",
@ -75,30 +74,31 @@
"xslt3": "^2.5.0"
},
"dependencies": {
"@adonisjs/auth": "^9.1.1",
"@adonisjs/core": "^6.3.1",
"@adonisjs/auth": "^9.2.4",
"@adonisjs/core": "^6.17.0",
"@adonisjs/cors": "^2.2.1",
"@adonisjs/drive": "^2.3.0",
"@adonisjs/encore": "^1.0.0",
"@adonisjs/inertia": "^1.0.0-7",
"@adonisjs/lucid": "^21.1.0",
"@adonisjs/drive": "^3.2.0",
"@adonisjs/inertia": "^2.1.3",
"@adonisjs/lucid": "^21.5.1",
"@adonisjs/mail": "^9.2.2",
"@adonisjs/redis": "^9.1.0",
"@adonisjs/session": "^7.1.1",
"@adonisjs/session": "^7.5.0",
"@adonisjs/shield": "^8.1.1",
"@adonisjs/static": "^1.1.1",
"@adonisjs/vite": "^4.0.0",
"@eidellev/adonis-stardust": "^3.0.0",
"@fontsource/archivo-black": "^5.0.1",
"@fontsource/inter": "^5.0.1",
"@inertiajs/inertia": "^0.11.1",
"@inertiajs/vue3": "^1.0.0",
"@opensearch-project/opensearch": "^2.4.0",
"@inertiajs/vue3": "^2.0.3",
"@opensearch-project/opensearch": "^3.2.0",
"@phc/format": "^1.0.0",
"@vinejs/vine": "^2.0.0",
"@poppinss/manager": "^5.0.2",
"@vinejs/vine": "^3.0.0",
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"bcryptjs": "^2.4.3",
"clamscan": "^2.1.2",
"crypto": "^1.0.1",
"dayjs": "^1.11.7",
"deep-email-validator": "^0.1.21",
"edge.js": "^6.0.1",
@ -121,6 +121,12 @@
"vuedraggable": "^4.1.0",
"xmlbuilder2": "^3.1.1"
},
"hotHook": {
"boundaries": [
"./app/Controllers/**/*.ts",
"./app/middleware/*.ts"
]
},
"type": "module",
"imports": {
"#controllers/*": "./app/Controllers/*.js",

View file

@ -1,7 +1,10 @@
module.exports = {
plugins: {
// 'postcss-import': {},
'tailwindcss/nesting': {},
// 'postcss-nesting': {},
'tailwindcss/nesting': {},
// "@tailwindcss/postcss": {},
// tailwindcss: {},
tailwindcss: {},
autoprefixer: {},
},

View file

@ -69,7 +69,7 @@ export default class MailProvider {
const mailConfigProvider = this.app.config.get('mail');
const config = await configProvider.resolve<any>(this.app, mailConfigProvider);
const iwas = await config.mailers.smtp();
await config.mailers.smtp();
// iwas.config.host = 'hhhost';
// this.app.config.set('mail.mailers.smtp.host', 'xhost');
// const iwas = await config.mailers.smtp();

View file

@ -63,6 +63,15 @@ export default class QueryBuilderProvider {
public register() {
// Register your own bindings
// const ModelQueryBuilder = this.app.container.bind('@adonisjs/lucid/orm/ModelQueryBuilder');
// ModelQueryBuilder.macro('whereTrue', function (columnName: string) {
// return this.where(columnName, true);
// });
// ModelQueryBuilder.macro('whereFalse', function (columnName: string) {
// return this.where(columnName, false);
// });
}
public async boot() {
@ -73,15 +82,14 @@ export default class QueryBuilderProvider {
// let rolesPluck = {};
let rolesPluck: { [key: number]: any } = {};
const result = await this.exec();
result.forEach((user, index) => {
let idc;
result.forEach((user: { [key: string]: any }, index: number) => {
let idc: number;
if (!id) {
idc = index;
} else {
idc = user[id];
}
const value = user[valueColumn];
// rolesPluck[idc] = user.name;
const value: any = user[valueColumn];
rolesPluck[idc] = value;
});
return rolesPluck;

View file

@ -4,14 +4,15 @@
|--------------------------------------------------------------------------
|*/
import type { ApplicationService } from '@adonisjs/core/types';
import vine, { BaseLiteralType, Vine } from '@vinejs/vine';
import type { Validation, FieldContext, FieldOptions } from '@vinejs/vine/types';
import vine, { symbols, BaseLiteralType, Vine } from '@vinejs/vine';
import type { FieldContext, FieldOptions } from '@vinejs/vine/types';
// import type { MultipartFile, FileValidationOptions } from '@adonisjs/bodyparser/types';
import type { MultipartFile } from '@adonisjs/core/bodyparser';
import type { FileValidationOptions } from '@adonisjs/core/types/bodyparser';
import { Request, RequestValidator } from '@adonisjs/core/http';
import MimeType from '#models/mime_type';
/**
* Validation options accepted by the "file" rule
*/
@ -28,8 +29,7 @@ declare module '@vinejs/vine' {
* Extend HTTP request class
*/
declare module '@adonisjs/core/http' {
interface Request extends RequestValidator {
}
interface Request extends RequestValidator {}
}
/**
@ -48,7 +48,7 @@ export async function getEnabledExtensions() {
.flat();
return extensions;
};
}
/**
* VineJS validation rule that validates the file to be an
* instance of BodyParser MultipartFile class.
@ -82,8 +82,8 @@ const isMultipartFile = vine.createRule(async (file: MultipartFile | unknown, op
if (validatedFile.allowedExtensions === undefined && validationOptions.extnames) {
validatedFile.allowedExtensions = await getEnabledExtensions();
}
/**
* wieder löschen
/**
* wieder löschen
* Set extensions when it's defined in the options and missing
* on the file instance
*/
@ -102,7 +102,20 @@ const isMultipartFile = vine.createRule(async (file: MultipartFile | unknown, op
});
});
const MULTIPART_FILE: typeof symbols.SUBTYPE = symbols.SUBTYPE;
export class VineMultipartFile extends BaseLiteralType<MultipartFile, MultipartFile, MultipartFile> {
[MULTIPART_FILE]: string;
// constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions) {
// super(options, [isMultipartFile(validationOptions || {})]);
// this.validationOptions = validationOptions;
// this.#private = true;
// }
// clone(): this {
// return new VineMultipartFile(this.validationOptions, this.cloneOptions()) as this;
// }
// #private;
// constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions, validations?: Validation<any>[]);
// clone(): this;
@ -111,14 +124,16 @@ export class VineMultipartFile extends BaseLiteralType<MultipartFile, MultipartF
// extnames: (18) ['gpkg', 'htm', 'html', 'csv', 'txt', 'asc', 'c', 'cc', 'h', 'srt', 'tiff', 'pdf', 'png', 'zip', 'jpg', 'jpeg', 'jpe', 'xlsx']
// size: '512mb'
public constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions, validations?: Validation<any>[]) {
// public constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions, validations?: Validation<any>[]) {
public constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions) {
// super(options, validations);
super(options, [isMultipartFile(validationOptions || {})]);
this.validationOptions = validationOptions;
}
public clone(): any {
return new VineMultipartFile(this.validationOptions, this.cloneOptions(), this.cloneValidations());
// return new VineMultipartFile(this.validationOptions, this.cloneOptions(), this.cloneValidations());
return new VineMultipartFile(this.validationOptions, this.cloneOptions());
}
}
@ -152,10 +167,12 @@ export default class VinejsProvider {
* The validate method can be used to validate the request
* data for the current request using VineJS validators
*/
Request.macro('validateUsing', function (...args) {
return new RequestValidator(this.ctx).validateUsing(...args);
});
Request.macro('validateUsing', function (this: Request, ...args) {
if (!this.ctx) {
throw new Error('HttpContext is not available');
}
return new RequestValidator(this.ctx).validateUsing(...args);
});
}
/**

View file

@ -1,12 +0,0 @@
{
"entrypoints": {
"app": {
"css": [
"http://localhost:8080/assets/app.css"
],
"js": [
"http://localhost:8080/assets/app.js"
]
}
}
}

View file

@ -1,103 +0,0 @@
{
"assets/app.css": "http://localhost:8080/assets/app.css",
"assets/app.js": "http://localhost:8080/assets/app.js",
"assets/resources_js_apps_settings_l18n_de_js.js": "http://localhost:8080/assets/resources_js_apps_settings_l18n_de_js.js",
"assets/resources_js_apps_settings_l18n_en_js.js": "http://localhost:8080/assets/resources_js_apps_settings_l18n_en_js.js",
"assets/resources_js_Pages_Admin_License_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_License_Index_vue.js",
"assets/resources_js_Pages_Admin_Mimetype_Create_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Mimetype_Create_vue.js",
"assets/resources_js_Pages_Admin_Mimetype_Delete_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Mimetype_Delete_vue.js",
"assets/resources_js_Pages_Admin_Mimetype_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Mimetype_Index_vue.js",
"assets/resources_js_Pages_Admin_Permission_Create_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Permission_Create_vue.js",
"assets/resources_js_Pages_Admin_Permission_Edit_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Permission_Edit_vue.js",
"assets/resources_js_Pages_Admin_Permission_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Permission_Index_vue.js",
"assets/resources_js_Pages_Admin_Permission_Show_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Permission_Show_vue.js",
"assets/resources_js_Pages_Admin_Role_Create_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Role_Create_vue.js",
"assets/resources_js_Pages_Admin_Role_Edit_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Role_Edit_vue.js",
"assets/resources_js_Pages_Admin_Role_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Role_Index_vue.js",
"assets/resources_js_Pages_Admin_Role_Show_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Role_Show_vue.js",
"assets/resources_js_Pages_Admin_Settings_vue-resources_js_utils_toast_css.css": "http://localhost:8080/assets/resources_js_Pages_Admin_Settings_vue-resources_js_utils_toast_css.css",
"assets/resources_js_Pages_Admin_Settings_vue-resources_js_utils_toast_css.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Settings_vue-resources_js_utils_toast_css.js",
"assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.css": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.css",
"assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Create_vue-resources_js_Components_SimplePasswordMeter_password-f3312a.js",
"assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.css": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.css",
"assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Edit_vue-resources_js_Components_SimplePasswordMeter_password-m-6dc207.js",
"assets/resources_js_Pages_Admin_User_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Index_vue.js",
"assets/resources_js_Pages_Admin_User_Show_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_User_Show_vue.js",
"assets/resources_js_Pages_App_vue.js": "http://localhost:8080/assets/resources_js_Pages_App_vue.js",
"assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.css": "http://localhost:8080/assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.css",
"assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.js": "http://localhost:8080/assets/resources_js_Pages_Auth_AccountInfo_vue-resources_js_utils_toast_css-resources_js_Components_-06c7b5.js",
"assets/resources_js_Pages_Auth_Login_vue.js": "http://localhost:8080/assets/resources_js_Pages_Auth_Login_vue.js",
"assets/resources_js_Pages_Auth_Register_vue.js": "http://localhost:8080/assets/resources_js_Pages_Auth_Register_vue.js",
"assets/resources_js_Pages_Dashboard_vue.js": "http://localhost:8080/assets/resources_js_Pages_Dashboard_vue.js",
"assets/resources_js_Pages_Editor_Dataset_Approve_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Approve_vue.js",
"assets/resources_js_Pages_Editor_Dataset_Doi_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Doi_vue.js",
"assets/resources_js_Pages_Editor_Dataset_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Index_vue.js",
"assets/resources_js_Pages_Editor_Dataset_Publish_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Publish_vue.js",
"assets/resources_js_Pages_Editor_Dataset_Receive_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Receive_vue.js",
"assets/resources_js_Pages_Editor_Dataset_Reject_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Reject_vue.js",
"assets/resources_js_Pages_Error_vue.js": "http://localhost:8080/assets/resources_js_Pages_Error_vue.js",
"assets/resources_js_Pages_Errors_ServerError_vue.js": "http://localhost:8080/assets/resources_js_Pages_Errors_ServerError_vue.js",
"assets/resources_js_Pages_Errors_not_found_vue.js": "http://localhost:8080/assets/resources_js_Pages_Errors_not_found_vue.js",
"assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.css": "http://localhost:8080/assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.css",
"assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.js": "http://localhost:8080/assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.js",
"assets/resources_js_Pages_ProfileView_vue.js": "http://localhost:8080/assets/resources_js_Pages_ProfileView_vue.js",
"assets/resources_js_Pages_Reviewer_Dataset_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Index_vue.js",
"assets/resources_js_Pages_Reviewer_Dataset_Reject_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Reject_vue.js",
"assets/resources_js_Pages_Reviewer_Dataset_Review_vue.js": "http://localhost:8080/assets/resources_js_Pages_Reviewer_Dataset_Review_vue.js",
"assets/resources_js_Pages_Submitter_Dataset_Category_vue.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Category_vue.css",
"assets/resources_js_Pages_Submitter_Dataset_Category_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Category_vue.js",
"assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.css",
"assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Create_vue-resources_js_utils_toast_css-resources_js_Com-03a898.js",
"assets/resources_js_Pages_Submitter_Dataset_Delete_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Delete_vue.js",
"assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.css",
"assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Edit_vue-resources_js_utils_toast_css-resources_js_Compo-a37b65.js",
"assets/resources_js_Pages_Submitter_Dataset_Index_vue.css": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Index_vue.css",
"assets/resources_js_Pages_Submitter_Dataset_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Index_vue.js",
"assets/resources_js_Pages_Submitter_Dataset_Release_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Release_vue.js",
"assets/resources_js_Pages_Submitter_Person_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Person_Index_vue.js",
"assets/resources_js_Pages_register-view_register-view-component_vue.js": "http://localhost:8080/assets/resources_js_Pages_register-view_register-view-component_vue.js",
"assets/vendors-node_modules_mdi_js_mdi_js-node_modules_vue-loader_dist_exportHelper_js.js": "http://localhost:8080/assets/vendors-node_modules_mdi_js_mdi_js-node_modules_vue-loader_dist_exportHelper_js.js",
"assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_js.js": "http://localhost:8080/assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_js.js",
"assets/vendors-node_modules_vue-facing-decorator_dist_esm_utils_js.js": "http://localhost:8080/assets/vendors-node_modules_vue-facing-decorator_dist_esm_utils_js.js",
"assets/vendors-node_modules_toastify-js_src_toastify_js.js": "http://localhost:8080/assets/vendors-node_modules_toastify-js_src_toastify_js.js",
"assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_control_Control_Att-adabdc.js": "http://localhost:8080/assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_control_Control_Att-adabdc.js",
"assets/vendors-node_modules_buffer_index_js-node_modules_vuedraggable_dist_vuedraggable_umd_js.js": "http://localhost:8080/assets/vendors-node_modules_buffer_index_js-node_modules_vuedraggable_dist_vuedraggable_umd_js.js",
"assets/vendors-node_modules_mime_dist_src_index_js.js": "http://localhost:8080/assets/vendors-node_modules_mime_dist_src_index_js.js",
"assets/vendors-node_modules_numeral_numeral_js-node_modules_chart_js_dist_chart_js.js": "http://localhost:8080/assets/vendors-node_modules_numeral_numeral_js-node_modules_chart_js_dist_chart_js.js",
"assets/resources_js_Components_BaseButton_vue.js": "http://localhost:8080/assets/resources_js_Components_BaseButton_vue.js",
"assets/resources_js_Stores_main_ts-resources_js_Components_BaseDivider_vue-resources_js_Components_C-b45805.js": "http://localhost:8080/assets/resources_js_Stores_main_ts-resources_js_Components_BaseDivider_vue-resources_js_Components_C-b45805.js",
"assets/resources_js_Layouts_LayoutAuthenticated_vue.css": "http://localhost:8080/assets/resources_js_Layouts_LayoutAuthenticated_vue.css",
"assets/resources_js_Layouts_LayoutAuthenticated_vue.js": "http://localhost:8080/assets/resources_js_Layouts_LayoutAuthenticated_vue.js",
"assets/resources_js_Components_BaseButtons_vue-resources_js_Components_FormControl_vue-resources_js_-d830d6.js": "http://localhost:8080/assets/resources_js_Components_BaseButtons_vue-resources_js_Components_FormControl_vue-resources_js_-d830d6.js",
"assets/resources_js_Components_Admin_Pagination_vue-resources_js_Components_BaseButtons_vue-resource-6f3a70.js": "http://localhost:8080/assets/resources_js_Components_Admin_Pagination_vue-resources_js_Components_BaseButtons_vue-resource-6f3a70.js",
"assets/resources_js_utils_toast_ts-resources_js_Components_NotificationBar_vue.js": "http://localhost:8080/assets/resources_js_utils_toast_ts-resources_js_Components_NotificationBar_vue.js",
"assets/resources_js_Components_Map_draw_component_vue-resources_js_Components_Map_zoom_component_vue-058bcc.js": "http://localhost:8080/assets/resources_js_Components_Map_draw_component_vue-resources_js_Components_Map_zoom_component_vue-058bcc.js",
"assets/resources_js_Components_SectionMain_vue-resources_js_Components_SectionTitleLineWithButton_vu-764dfe.js": "http://localhost:8080/assets/resources_js_Components_SectionMain_vue-resources_js_Components_SectionTitleLineWithButton_vu-764dfe.js",
"assets/resources_js_Components_BaseButtons_vue-resources_js_Components_NotificationBar_vue-resources-7e06d8.js": "http://localhost:8080/assets/resources_js_Components_BaseButtons_vue-resources_js_Components_NotificationBar_vue-resources-7e06d8.js",
"assets/resources_js_Components_Admin_Sort_vue-resources_js_Components_SectionTitleLineWithButton_vue.js": "http://localhost:8080/assets/resources_js_Components_Admin_Sort_vue-resources_js_Components_SectionTitleLineWithButton_vue.js",
"assets/resources_js_Components_CardBoxModal_vue.js": "http://localhost:8080/assets/resources_js_Components_CardBoxModal_vue.js",
"assets/resources_js_Components_FileUpload_vue-resources_js_Components_FormCheckRadioGroup_vue-resour-25e686.js": "http://localhost:8080/assets/resources_js_Components_FileUpload_vue-resources_js_Components_FormCheckRadioGroup_vue-resour-25e686.js",
"assets/fonts/inter-latin-ext-400-normal.woff": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.1c20f7dc.woff",
"assets/fonts/inter-latin-400-normal.woff": "http://localhost:8080/assets/fonts/inter-latin-400-normal.b0c8fe9d.woff",
"assets/fonts/inter-latin-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.3d10c85f.woff2",
"assets/fonts/inter-latin-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-latin-400-normal.9698cc7d.woff2",
"assets/fonts/archivo-black-latin-400-normal.woff2": "http://localhost:8080/assets/fonts/archivo-black-latin-400-normal.fc847a1f.woff2",
"assets/fonts/archivo-black-latin-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/archivo-black-latin-ext-400-normal.21761451.woff2",
"assets/fonts/inter-cyrillic-ext-400-normal.woff": "http://localhost:8080/assets/fonts/inter-cyrillic-ext-400-normal.e8945162.woff",
"assets/fonts/archivo-black-latin-400-normal.woff": "http://localhost:8080/assets/fonts/archivo-black-latin-400-normal.58a301a6.woff",
"assets/fonts/inter-cyrillic-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-cyrillic-ext-400-normal.fd1478dc.woff2",
"assets/fonts/inter-cyrillic-400-normal.woff": "http://localhost:8080/assets/fonts/inter-cyrillic-400-normal.e2841352.woff",
"assets/fonts/inter-greek-400-normal.woff": "http://localhost:8080/assets/fonts/inter-greek-400-normal.a42da273.woff",
"assets/fonts/archivo-black-latin-ext-400-normal.woff": "http://localhost:8080/assets/fonts/archivo-black-latin-ext-400-normal.5ab5ba92.woff",
"assets/fonts/inter-greek-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-greek-400-normal.a8de720a.woff2",
"assets/fonts/inter-cyrillic-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-cyrillic-400-normal.cb04b2ee.woff2",
"assets/fonts/inter-greek-ext-400-normal.woff": "http://localhost:8080/assets/fonts/inter-greek-ext-400-normal.b9e1e894.woff",
"assets/fonts/inter-vietnamese-400-normal.woff": "http://localhost:8080/assets/fonts/inter-vietnamese-400-normal.96f8adc7.woff",
"assets/fonts/inter-greek-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-greek-ext-400-normal.f2fa0d9e.woff2",
"assets/fonts/inter-vietnamese-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-vietnamese-400-normal.44c9df13.woff2",
"assets/images/marker-icon.png": "http://localhost:8080/assets/images/marker-icon.2b3e1faf.png",
"assets/images/layers-2x.png": "http://localhost:8080/assets/images/layers-2x.8f2c4d11.png",
"assets/images/layers.png": "http://localhost:8080/assets/images/layers.416d9136.png",
"assets/images/Close.svg": "http://localhost:8080/assets/images/Close.e4887675.svg",
"assets/vendors-node_modules_vue-facing-decorator_dist_esm_index_js-node_modules_vue-facing-decorator-818045.js": "http://localhost:8080/assets/vendors-node_modules_vue-facing-decorator_dist_esm_index_js-node_modules_vue-facing-decorator-818045.js"
}

View file

@ -1,19 +1,20 @@
/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap'); */
/* @import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,600,700'); */
@tailwind base;
@tailwind components;
@tailwind utilities;
@import '_checkbox-radio-switch.css';
@import '_progress.css';
@import '_scrollbars.css';
@import '_table.css';
@import '~leaflet/dist/leaflet.css';
/* @import '~leaflet/dist/leaflet.css'; */
@import '~/leaflet/dist/leaflet.css';
@import '@fontsource/inter/index.css';
@import '@fontsource/archivo-black/index.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-main-background: #ffffff;
--color-main-background-rgb: 255,255,255;

View file

@ -36,13 +36,24 @@ const logoutItemClick = async () => {
await router.post(stardust.route('logout'));
};
const menuClick = (event, item) => {
interface MenuItem {
name: string;
label: string;
icon: string;
color: string;
link: string;
}
const menuClick = (event: Event, item: MenuItem) => {
emit('menu-click', event, item);
};
</script>
<template>
<aside id="aside" class="lg:py-2 lg:pl-2 w-60 fixed flex z-40 top-0 h-screen transition-position overflow-hidden">
<aside
id="aside"
class="lg:pb-2 lg:pl-2 w-60 fixed flex z-40 top-0 lg:top-16 h-screen lg:h-[calc(100vh-64px)] transition-position overflow-hidden"
>
<div :class="styleStore.asideStyle" class="lg:rounded-xl flex-1 flex flex-col overflow-hidden dark:bg-slate-900">
<div :class="styleStore.asideBrandStyle" class="flex flex-row h-14 items-center justify-between dark:bg-slate-900">
<div class="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">

View file

@ -198,7 +198,7 @@ import DeleteIcon from '@/Components/Icons/Delete.vue';
import RefreshIcon from '@/Components/Icons/Refresh.vue';
// import { Page, PageProps, Errors, ErrorBag } from '@inertiajs/inertia';
import Draggable from 'vuedraggable';
import { Buffer } from 'buffer';
// import { Buffer } from 'buffer';
import { TethysFile } from '@/Dataset';
// lastModified: 1691759507591
@ -445,18 +445,19 @@ class FileUploadComponent extends Vue {
let localUrl: string = '';
if (file instanceof File) {
localUrl = URL.createObjectURL(file as Blob);
} else if (file.fileData) {
// const blob = new Blob([file.fileData]);
// localUrl = URL.createObjectURL(blob);
const parsed = JSON.parse(file.fileData);
file.fileData = '';
// retrieve the original buffer of data
const buff = Buffer.from(parsed.blob, 'base64');
const blob = new Blob([buff], { type: 'application/octet-stream' });
// file.blob = blob;
localUrl = URL.createObjectURL(blob);
file.fileSrc = localUrl;
}
}
// else if (file.fileData) {
// // const blob = new Blob([file.fileData]);
// // localUrl = URL.createObjectURL(blob);
// const parsed = JSON.parse(file.fileData);
// file.fileData = '';
// // retrieve the original buffer of data
// const buff = Buffer.from(parsed.blob, 'base64');
// const blob = new Blob([buff], { type: 'application/octet-stream' });
// // file.blob = blob;
// localUrl = URL.createObjectURL(blob);
// file.fileSrc = localUrl;
// }
// setTimeout(() => {
// URL.revokeObjectURL(localUrl);

View file

@ -1,10 +1,22 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import FormCheckRadio from '@/Components/FormCheckRadio.vue';
import BaseButton from '@/Components/BaseButton.vue';
import FormControl from '@/Components/FormControl.vue';
import { mdiPlusCircle } from '@mdi/js';
const props = defineProps({
options: {
type: Object,
default: () => {},
default: () => { },
},
allowManualAdding: {
type: Boolean,
default: false,
},
manualAddingPlaceholder: {
type: String,
default: 'Add manually',
required: false,
},
name: {
type: String,
@ -55,6 +67,29 @@ const computedValue = computed({
const hasIdAttribute = (obj: any): obj is { id: any } => {
return typeof obj === 'object' && 'id' in obj;
};
const newOption = ref<string>('');
const addOption = () => {
if (newOption.value && !props.options[newOption.value]) {
props.options[newOption.value] = newOption.value;
newOption.value = '';
}
};
const inputElClass = computed(() => {
const base = [
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
'dark:placeholder-gray-400',
'h-12',
'border',
'bg-transparent'
// props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
];
// if (props.icon) {
// base.push('pl-10');
// }
return base;
});
</script>
<template>
@ -63,15 +98,17 @@ const hasIdAttribute = (obj: any): obj is { id: any } => {
<!-- :label="value" -->
<!-- :input-value="value.id"
:label="value.name" -->
<FormCheckRadio
v-for="(value, key) in options"
:key="key"
v-model="computedValue"
:type="type"
:name="name"
:input-value="key"
:label="value"
:class="componentClass"
/>
<div v-if="allowManualAdding && type === 'checkbox'" class="flex items-center mt-2 mb-2 relative">
<input v-model="newOption" :placeholder="manualAddingPlaceholder" :class="inputElClass"
@keydown.prevent.enter="addOption" />
<svg v-show="newOption.length >= 2" @click.prevent="addOption" xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 absolute right-2 top-1/2 transform -translate-y-1/2 cursor-pointer text-gray-500"
viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-6H5v-2h6V5h2v6h6v2h-6v6z" />
</svg>
</div>
<FormCheckRadio v-for="(value, key) in options" :key="key" v-model="computedValue" :type="type" :name="name"
:input-value="key" :label="value" :class="componentClass" />
</div>
</template>

View file

@ -268,7 +268,7 @@ export default class DrawControlComponent extends Vue {
position: absolute;
left: 10px;
top: 100px;
z-index: 999;
z-index: 40;
}
.btn-group-vertical button {

View file

@ -372,6 +372,9 @@ export default class MapComponent extends Vue {
margin-top: 0.5em;
}
.leaflet-container .leaflet-pane {
z-index: 30!important;
}
/* .leaflet-pane {
z-index: 30;
} */

View file

@ -100,7 +100,7 @@ export default class ZoomControlComponent extends Vue {
position: absolute;
left: 10px;
top: 10px;
z-index: 999;
z-index: 40;
}
.btn-group-vertical button {

View file

@ -0,0 +1,138 @@
<template>
<div class="relative mb-4">
<!-- <label for="mimetype-input" class="block text-sm font-medium text-gray-700">Search for Mimetypes</label> -->
<input id="mimetype-input" v-model="newExtension" type="text" placeholder="Enter Mimetype Name"
class="block w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-blue-500 focus:border-blue-500"
:class="inputElClass" @input="handleInputChange" @keydown.down="onArrowDown" @keydown.up="onArrowUp"
@keydown.prevent.enter="onEnter">
</input>
<svg class="w-4 h-4 absolute left-2.5 top-3.5" v-show="newExtension.length < 2"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<svg class="w-4 h-4 absolute left-2.5 top-3.5" v-show="newExtension.length >= 2"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
@click="clearInput">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
<ul v-if="showDropdown && filteredMimetypes.length"
class="bg-white dark:bg-slate-800 w-full mt-2 max-h-28 overflow-y-auto scroll-smooth">
<li v-for="(mimeType, index) in filteredMimetypes" :key="index" @click="selectResult(mimeType)"
class="pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-blue-500 hover:text-white"
:class="{
'bg-blue-500 text-white': selectedIndex === index,
'bg-white text-gray-900': selectedIndex !== index
}" :ref="(el) => {
if (ul) {
ul[index] = el as HTMLLIElement;
}
}">
<svg class="absolute w-4 h-4 left-2 top-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
<span>{{ mimeType }}</span>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { ref, Ref, watch, computed } from 'vue';
// import mime from 'mime';
const emit = defineEmits(['onSelectResult', 'onClearInput'])
const props = defineProps({
borderless: Boolean,
transparent: Boolean,
mimeTypes: {
type: Array as () => string[],
required: true
},
// form: Object,
// isValidMimeType: Function,
});
const newExtension = ref('');
const showDropdown = ref(false);
const filteredMimetypes = ref<string[]>([]);
const selectedIndex: Ref<number> = ref(0);
const ul: Ref<HTMLLIElement[] | null> = ref<HTMLLIElement[]>([]);
watch(selectedIndex, (selectedIndex: number) => {
if (selectedIndex != null && ul.value != null) {
const currentElement: HTMLLIElement = ul.value[selectedIndex];
currentElement &&
currentElement?.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}
});
const inputElClass = computed(() => {
const base = [
'block p-2.5 w-full z-20 text-sm text-gray-900 bg-gray-50 rounded-r-lg',
'dark:placeholder-gray-400 dark:text-white dark:focus:border-blue-500',
'h-12',
props.borderless ? 'border-0' : 'border',
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
];
base.push('pl-10');
return base;
});
const handleInputChange = (e: Event) => {
const target = <HTMLInputElement>e.target;
newExtension.value = target.value;
if (newExtension.value.length >= 2) {
showDropdown.value = true;
filteredMimetypes.value = props.mimeTypes.filter(mimeType =>
mimeType.toLowerCase().includes(newExtension.value.toLowerCase())
);
} else {
showDropdown.value = false;
}
};
const selectResult = (mimeType: string) => {
showDropdown.value = false;
newExtension.value = '';
selectedIndex.value = -1;
emit('onSelectResult', mimeType);
};
const clearInput = () => {
newExtension.value = '';
showDropdown.value = false;
// props.form.name = '';
// props.resetFileExtensions();
emit('onClearInput');
};
const onArrowDown = () => {
if (filteredMimetypes.value.length > 0) {
selectedIndex.value = selectedIndex.value === filteredMimetypes.value.length - 1 ? 0 : selectedIndex.value + 1;
}
};
const onArrowUp = () => {
if (filteredMimetypes.value.length > 0) {
selectedIndex.value = selectedIndex.value == 0 || selectedIndex.value == -1 ? filteredMimetypes.value.length - 1 : selectedIndex.value - 1;
}
};
const onEnter = () => {
if (Array.isArray(filteredMimetypes.value) && filteredMimetypes.value.length && selectedIndex.value !== -1 && selectedIndex.value < filteredMimetypes.value.length) {
const mimeType = filteredMimetypes.value[selectedIndex.value];
selectResult(mimeType);
}
};
</script>

View file

@ -72,7 +72,7 @@ const menuNavBarToggle = () => {
const menuOpenLg = () => {
layoutStore.isAsideLgActive = true;
};
const userHasRoles = (roleNames): boolean => {
const userHasRoles = (roleNames: Array<string>): boolean => {
return user.value.roles.some(role => roleNames.includes(role.name));
};
@ -95,7 +95,7 @@ const showAbout = async () => {
</script>
<template>
<nav class="text-base top-0 left-0 right-0 fixed bg-gray-50 h-14 z-40 w-screen transition-position lg:w-auto dark:bg-slate-800"
<nav class="text-base top-0 left-0 right-0 fixed bg-lime h-14 z-50 w-screen transition-position lg:w-auto dark:bg-slate-800"
:class="{ 'xl:pl-60': props.showBurger == true }">
<FirstrunWizard ref="about"></FirstrunWizard>
<div class="flex lg:items-stretch" :class="containerMaxW">
@ -122,10 +122,10 @@ const showAbout = async () => {
<BaseIcon :path="isMenuNavBarActive ? mdiClose : mdiDotsVertical" size="24" />
</NavBarItem>
</div>
<div class="absolute w-screen top-14 left-0 bg-gray-50 shadow lg:w-auto lg:items-stretch lg:flex lg:grow lg:static lg:border-b-0 lg:overflow-visible lg:shadow-none dark:bg-slate-800"
<div class="fixed w-screen top-14 left-0 shadow lg:w-auto lg:items-stretch lg:flex lg:grow lg:static lg:border-b-0 lg:overflow-visible lg:shadow-none dark:bg-slate-800"
:class="[isMenuNavBarActive ? 'block' : 'hidden']">
<div
class="max-h-screen-menu overflow-y-auto lg:overflow-visible lg:flex lg:items-stretch lg:justify-end lg:ml-auto">
class="bg-white lg:bg-lime dark:bg-transparent max-h-screen-menu overflow-y-auto lg:overflow-visible lg:flex lg:items-stretch lg:justify-end lg:ml-auto">
<!-- help menu -->
<NavBarMenu>
@ -186,7 +186,7 @@ const showAbout = async () => {
<NavBarItem is-desktop-icon-only @click.prevent="toggleLightDark">
<NavBarItemLabel v-bind:icon="mdiThemeLightDark" label="Light/Dark" is-desktop-icon-only />
</NavBarItem>
<NavBarItem href="https://gitea.geologie.ac.at/geolba/tethys" target="_blank" is-desktop-icon-only>
<NavBarItem href="https://gitea.geosphere.at/geolba/tethys.backend" target="_blank" is-desktop-icon-only>
<NavBarItemLabel v-bind:icon="mdiGithub" label="GitHub" is-desktop-icon-only />
</NavBarItem>
<NavBarItem is-desktop-icon-only @click="showAbout">

View file

@ -1,4 +1,4 @@
<script setup>
<script lang="ts" setup>
import { StyleService } from '@/Stores/style.service';
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
import { mdiChevronUp, mdiChevronDown } from '@mdi/js';
@ -15,10 +15,10 @@ const toggle = () => {
isDropdownActive.value = !isDropdownActive.value;
};
const root = ref(null);
const root = ref(NavBarItem);
const forceClose = (event) => {
if (!root.value.$el.contains(event.target)) {
const forceClose = (event: MouseEvent) => {
if (!root.value?.$el.contains(event.target)) {
isDropdownActive.value = false;
}
};

View file

@ -5,9 +5,9 @@ import SectionBanner from '@/Components/SectionBanner.vue';
</script>
<template>
<SectionBanner bg="greenBlue">
<h1 class="text-3xl text-white mb-6">Like the project? Please star on <b>Gitea</b>!</h1>
<h1 class="text-3xl text-white mb-6">Like the project? Please star on <b>GeoSphere Git Repository</b>!</h1>
<div>
<BaseButton href="https://gitea.geologie.ac.at/geolba/tethys" :icon="mdiGithub" label="Gitea" target="_blank" rounded-full />
<BaseButton href="https://gitea.geosphere.at/geolba/tethys.backend" :icon="mdiGithub" label="Forgejo" target="_blank" rounded-full />
</div>
</SectionBanner>
</template>

View file

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref, watch, computed, Ref, reactive } from 'vue';
import { ref, reactive } from 'vue';
import { Head, useForm } from '@inertiajs/vue3';
import { mdiAccountKey, mdiArrowLeftBoldOutline } from '@mdi/js';
import { mdiAccountKey, mdiArrowLeftBoldOutline, mdiTrashCan, mdiImageText, mdiPlus } from '@mdi/js';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
@ -10,115 +10,97 @@ import BaseDivider from '@/Components/BaseDivider.vue';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
import mime from 'mime';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import standardTypes from 'mime/types/standard.js';
import otherTypes from 'mime/types/other.js';
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
import MimetypeInput from '@/Components/MimetypeInput.vue';
const props = defineProps({
permissions: {
type: Object,
default: () => ({}),
},
defineProps({
borderless: Boolean,
transparent: Boolean,
ctrlKFocus: Boolean,
});
const customTypes: { [key: string]: string[] } = {
'application/vnd.opengeospatial.geopackage+sqlite3': ['gpkg'],
'text/plain': ['txt', 'asc', 'c', 'cc', 'h', 'srt'],
};
const isReadOnly: boolean = true;
const standardMimeTypes = Object.keys(standardTypes);
const otherMimeTypes = Object.keys(otherTypes);
const mimeTypes = [...standardMimeTypes, ...otherMimeTypes];
// const standardMimeTypes = Object.keys(standardTypes);
// const otherMimeTypes = Object.keys(otherTypes);
// const customMimeTypes = Object.keys(customTypes);
const mimeTypesMap = new Map<string, string[]>();
Object.entries(standardTypes).forEach(([mimeType, extensions]) => {
mimeTypesMap.set(mimeType, extensions);
});
Object.entries(otherTypes).forEach(([mimeType, extensions]) => {
mimeTypesMap.set(mimeType, extensions);
});
Object.entries(customTypes).forEach(([mimeType, extensions]) => {
mimeTypesMap.set(mimeType, extensions);
});
const mimeTypes = Array.from(mimeTypesMap.keys());
const file_extensions = reactive<Record<string, string>>({});
interface FormData {
name: string;
file_extension: string[];
alternate_mimetype: string[];
enabled: boolean;
[key: string]: string | string[] | boolean;
}
const form = useForm<FormData>({
name: '',
file_extension: [],
alternate_mimetype: [],
enabled: true,
});
const filteredMimetypes = ref<string[]>([]); // Stores the filtered MIME types for the dropdown
const showDropdown = ref(false); // Controls the visibility of the autocomplete dropdown
const selectedIndex: Ref<number> = ref(0); // Track selected MIME type in the dropdown
const ul: Ref<HTMLLIElement[] | null> = ref<HTMLLIElement[]>([]);
const newExtension: Ref = ref(''); //reactive([] as Array<string>);
const mimetypeError = ref<string | null>(null);
watch(selectedIndex, (selectedIndex: number) => {
if (selectedIndex != null && ul.value != null) {
const currentElement: HTMLLIElement = ul.value[selectedIndex];
currentElement &&
currentElement?.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}
});
const addAlternateMimetype = () => {
form.alternate_mimetype.push("");
};
const removeAliasMimetype = (index: number) => {
form.alternate_mimetype.splice(index, 1);
};
// Function to reset the object
function resetFileExtensions() {
// Reset to an empty object
Object.keys(file_extensions).forEach(key => {
delete file_extensions[key];
});
}
const inputElClass = computed(() => {
const base = [
'block p-2.5 w-full z-20 text-sm text-gray-900 bg-gray-50 rounded-r-lg',
'dark:placeholder-gray-400 dark:text-white dark:focus:border-blue-500',
'h-12',
props.borderless ? 'border-0' : 'border',
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
];
// if (props.icon) {
base.push('pl-10');
// }
return base;
});
// Check if the MIME type is valid
const isValidMimeType = (mimeType: string): boolean => {
let extensions = mime.getExtension(mimeType)
let extensions = mimeTypesMap.get(mimeType) || null;
return extensions !== null;
};
async function handleInputChange(e: Event) {
const target = <HTMLInputElement>e.target;
newExtension.value = target.value;
const clearInput = () => {
// newExtension.value = '';
// showDropdown.value = false;
form.name = '';
resetFileExtensions();
};
if (newExtension.value.length >= 2) {
showDropdown.value = true;
filteredMimetypes.value = mimeTypes.filter(mimeType =>
mimeType.toLowerCase().includes(newExtension.value.toLowerCase())
);
} else {
// data.results = [];
showDropdown.value = false;
}
}
// Handle MIME type selection from the dropdown
const selectResult = (mimeType: string) => {
form.name = mimeType;
// file_extensions.values = [];
resetFileExtensions();
showDropdown.value = false;
newExtension.value = ''; // Reset the input
selectedIndex.value = -1;
// showDropdown.value = false;
// newExtension.value = '';
// selectedIndex.value = -1;
if (form.name && isValidMimeType(form.name)) {
const extensions = mime.getAllExtensions(form.name) as Set<string>;
// Iterate over each extension and set both key and value to the extension
Array.from(extensions).forEach(extension => {
// const extensions = mime.getAllExtensions(form.name) as Set<string>;
const extensions = mimeTypesMap.get(mimeType);
extensions?.forEach(extension => {
file_extensions[extension] = extension;
});
} else {
@ -126,61 +108,14 @@ const selectResult = (mimeType: string) => {
}
};
function onArrowDown() {
if (filteredMimetypes.value.length > 0) {
selectedIndex.value = selectedIndex.value === filteredMimetypes.value.length - 1 ? 0 : selectedIndex.value + 1;
// const currentElement: HTMLLIElement = ul.value[selectedIndex.value];
}
}
function onArrowUp() {
if (filteredMimetypes.value.length > 0) {
selectedIndex.value = selectedIndex.value == 0 || selectedIndex.value == -1 ? filteredMimetypes.value.length - 1 : selectedIndex.value - 1;
}
}
function onEnter() {
if (Array.isArray(filteredMimetypes.value) && filteredMimetypes.value.length && selectedIndex.value !== -1 && selectedIndex.value < filteredMimetypes.value.length) {
const mimeType = filteredMimetypes.value[selectedIndex.value];
// this.$emit('person', person);
form.name = mimeType;
// reset form file extensions
// file_extensions.values = [];
resetFileExtensions();
showDropdown.value = false;
newExtension.value = ''; // Reset the input
selectedIndex.value = -1;
if (form.name) {
// clear all loaded file extensions
// file_extensions.values = [];
resetFileExtensions();
if (isValidMimeType(form.name)) {
let extensions = mime.getAllExtensions(form.name) as Set<string>;
// Convert the Set to an array of objects
// Convert the Set to an object
Array.from(extensions).forEach(extension => {
file_extensions[extension] = extension;
});
// file_extensions.push(...formattedExtensions);
} else {
mimetypeError.value = 'Invalid MIME type.';
}
}
}
}
// Handle form submission
const submit = async () => {
if (isValidForm()) {
await form.post(stardust.route('settings.mimetype.store'), {
preserveScroll: true,
});
}
};
// Form validation before submission
const isValidForm = (): boolean => {
if (!form.name) {
form.errors.name = 'Name is required.';
@ -190,6 +125,7 @@ const isValidForm = (): boolean => {
}
if (!form.file_extension.length) {
form.errors.file_extension = 'At least one file extension is required.';
return false;
}
return true;
@ -205,59 +141,15 @@ const isValidForm = (): boolean => {
<BaseButton :route-name="stardust.route('settings.mimetype.index')" :icon="mdiArrowLeftBoldOutline"
label="Back" color="white" rounded-full small />
</SectionTitleLineWithButton>
<!-- <CardBox form @submit.prevent="form.post(stardust.route('role.store'))"> -->
<CardBox form @submit.prevent="submit()">
<!-- MIME Type Input Field with Autocomplete -->
<div class="relative mb-4">
<input v-model="newExtension" type="text" placeholder="Enter Mimetype Name"
class="block w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-blue-500 focus:border-blue-500"
:class="inputElClass" @input="handleInputChange" @keydown.down="onArrowDown"
@keydown.up="onArrowUp" @keydown.prevent.enter="onEnter">
</input>
<svg class="w-4 h-4 absolute left-2.5 top-3.5" v-show="newExtension.length < 2"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<svg class="w-4 h-4 absolute left-2.5 top-3.5" v-show="newExtension.length >= 2"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" @click="() => {
newExtension = '';
showDropdown = false;
form.name = '';
resetFileExtensions();
}
">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
<ul v-if="showDropdown && filteredMimetypes.length"
class="bg-white dark:bg-slate-800 w-full mt-2 max-h-28 overflow-y-auto scroll-smooth">
<li v-for="(mimeType, index) in filteredMimetypes" :key="index" @click="selectResult(mimeType)"
class="pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-blue-500 hover:text-white"
:class="{
'bg-blue-500 text-white': selectedIndex === index,
'bg-white text-gray-900': selectedIndex !== index
}" :ref="(el) => {
if (ul) {
ul[index] = el as HTMLLIElement;
}
}">
<svg class="absolute w-4 h-4 left-2 top-2" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
<span>{{ mimeType }}</span>
</li>
</ul>
</div>
<CardBox form>
<MimetypeInput @on-select-result="selectResult" @on-clear-input="clearInput" :transparent="transparent"
:borderless="borderless" :mimeTypes="mimeTypes" :isValidMimeType="isValidMimeType" />
<div v-if="mimetypeError" class="text-red-400 text-sm mt-1">
{{ mimetypeError }}
</div>
<BaseDivider v-if="form.name" />
<FormField v-if="form.name" label="Mimetype Name" :class="{ 'text-red-400': form.errors.name }">
<FormControl v-model="form.name" name="display_name" :error="form.errors.name"
:is-read-only=isReadOnly>
@ -266,34 +158,79 @@ const isValidForm = (): boolean => {
</div>
</FormControl>
</FormField>
<FormField v-if="form.name" help="Activate mimetype immediately?" wrap-body
<!-- <FormField v-if="form.name" help="Activate mimetype immediately?" wrap-body
class="mt-8 w-full mx-2 flex-1 flex-col">
<label for="rights" class="checkbox mr-6 mb-3 last:mr-0">
<input type="checkbox" id="rights" required v-model="form.enabled" />
<span class="check" />
<a class="pl-2" target="_blank">Enable mimetype immediately </a>
</label>
</FormField>
<FormField label="Extensions" wrap-body>
</FormField> -->
<FormField v-if="Object.keys(file_extensions).length > 0" label="File Extensions" wrap-body>
<!-- <div class="flex items-center mt-2">
<FormControl v-model="newExtension" placeholder="Enter file extension" class="mr-2" />
<BaseButton color="info" @click="addFileExtension" label="Add" />
</div> -->
<FormCheckRadioGroup v-model="form.file_extension" :options="file_extensions" name="file_extensions"
is-column />
is-column allow-manual-adding manual-adding-placeholder="Enter additional file extension manually" />
</FormField>
<div class="text-red-400 text-sm"
v-if="form.errors.file_extension && Array.isArray(form.errors.file_extension)">
{{ form.errors.file_extension.join(', ') }}
<div class="text-red-400 text-sm" v-if="form.errors.file_extension">
{{ form.errors.file_extension }}
</div>
<BaseDivider />
<BaseDivider v-if="Object.keys(file_extensions).length > 0" />
<CardBox v-if="form.name" class="mb-6 shadow" has-table :icon="mdiImageText" title="Alternate Mimetypes"
:header-icon="mdiPlus" @header-icon-click="addAlternateMimetype">
<div v-if="form.alternate_mimetype.length === 0" class="text-center py-4">
<p class="text-gray-600">No alternate mimetypes added yet.</p>
<p class="text-gray-400">
Click the plus icon above to add a new alternate mimetype.
<br>
An alternate mimetype is needed to ensure compatibility across different systems and
software.
For example, the GeoPackage standard mimetype is
'application/vnd.opengeospatial.geopackage+sqlite3', but most software stores it as
'application/x-sqlite3'. Therefore, 'application/x-sqlite3' must be added as an alternate
mimetype.
</p>
</div>
<table class="table-fixed border-green-900" v-if="form.alternate_mimetype.length > 0">
<thead>
<tr>
<th class="w-10/12">Alias Mimetype</th>
<th class="w-2/12"></th>
</tr>
</thead>
<tbody>
<tr v-for="(alias, index) in form.alternate_mimetype" :key="index">
<td>
<FormControl required v-model="form.alternate_mimetype[index]"
placeholder="[alternate mimetype]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`alternate_mimetype.${index}`])">
{{ form.errors[`alternate_mimetype.${index}`]?.join(', ') }}
</div>
</FormControl>
</td>
<td class="before:hidden lg:w-1 whitespace-nowrap">
<BaseButton color="danger" :icon="mdiTrashCan" small
@click.prevent="removeAliasMimetype(index)" />
</td>
</tr>
</tbody>
</table>
<div class="text-red-400 text-sm"
v-if="form.errors.alternate_mimetype && Array.isArray(form.errors.alternate_mimetype)">
{{ form.errors.alternate_mimetype.join(', ') }}
</div>
</CardBox>
<template #footer>
<BaseButtons>
<BaseButton type="submit" color="info" label="Create" :class="{ 'opacity-25': form.processing }"
:disabled="form.processing" />
:disabled="form.processing" @click.prevent="submit()" />
</BaseButtons>
</template>
</CardBox>

View file

@ -32,7 +32,7 @@ const form = useForm({
// isPreferationRequired: false,
});
const handleSubmit = async (e) => {
const handleSubmit = async (e: Event) => {
e.preventDefault();
await form.delete(stardust.route('settings.mimetype.deleteStore', [props.mimetype.id]));
};

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { Head, usePage } from '@inertiajs/vue3';
import { mdiAccountKey, mdiSquareEditOutline, mdiAlertBoxOutline, mdiPlus, mdiTrashCan } from '@mdi/js';
import { mdiAccountKey, mdiSquareEditOutline, mdiAlertBoxOutline, mdiPlus, mdiTrashCan, mdiCheckCircle, mdiCloseCircle } from '@mdi/js';
import { computed, ComputedRef } from 'vue';
import type { PropType } from "vue";
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
@ -16,6 +16,7 @@ interface MimeType {
id: number;
name: string;
file_extension: string;
alternate_mimetype: string;
enabled: boolean;
}
@ -54,24 +55,41 @@ const flash: ComputedRef<any> = computed(() => {
{{ flash.message }}
</NotificationBar>
<CardBox class="mb-6" has-table>
<table>
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th v-if="can.edit">Actions</th>
<th class="px-4 py-2 text-left text-sm font-medium uppercase tracking-wider">Mimetype</th>
<th class="px-4 py-2 text-left text-sm font-medium uppercase tracking-wider">Alternate Mime Types</th>
<th v-if="can.edit" class="px-4 py-2 text-left text-sm font-medium uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody>
<tbody class="divide-y divide-gray-200">
<tr v-for="mimetype in mimetypes" :key="mimetype.id">
<td data-label="Name">
{{ mimetype.name }} ({{ mimetype.file_extension }})
<td class="px-4 py-2 whitespace-nowrap text-left text-sm" data-label="Name">
<span class="flex items-center">
<svg viewBox="0 0 24 24" v-if="mimetype.enabled" :class="{'text-green-500': mimetype.enabled}" class="w-4 h-4 mr-2">
<path fill="currentColor" :d="mdiCheckCircle" />
</svg>
<svg v-else viewBox="0 0 24 24" :class="{'text-red-500': !mimetype.enabled}" class="w-4 h-4 mr-2">
<path fill="currentColor" :d="mdiCloseCircle" />
</svg>
<br>
<span class="truncate block max-w-xs">{{ mimetype.name }}</span>
</span>
<ul class="list-none pl-0">
<li v-for="ext in mimetype.file_extension?.split('|')" :key="ext" class="flex items-center truncate block max-w-xs">- .{{ ext }}</li>
</ul>
</td>
<td data-label="Status">
<td class="px-4 py-2 whitespace-nowrap text-left text-sm" data-label="Alternate Mime Types">
<ul class="list-none pl-0">
<li v-for="alt in mimetype.alternate_mimetype?.split('|')" :key="alt" class="flex items-center truncate block max-w-xs">- {{ alt }}</li>
</ul>
</td>
<!-- <td class="px-4 py-2 whitespace-nowrap" data-label="Status">
<template v-if="mimetype.enabled">Active</template>
<template v-else>Inactive</template>
</td>
<td v-if="can.edit" class="before:hidden lg:w-1 whitespace-nowrap">
</td> -->
<td v-if="can.edit" class="before:hidden lg:w-1 whitespace-nowrap px-4 py-2 whitespace-nowrap text-left text-sm">
<BaseButtons type="justify-start lg:justify-end" no-wrap>
<BaseButton v-if="mimetype.enabled"
:route-name="stardust.route('settings.mimetype.down', [mimetype.id])"

View file

@ -28,7 +28,7 @@ const form = useForm({
});
const submit = async () => {
await form.post(stardust.route('settings.role.store'), form);
await form.post(stardust.route('settings.role.store'));
};
</script>

View file

@ -1,5 +1,5 @@
<script setup>
import { Head, Link, useForm } from '@inertiajs/vue3';
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
import { mdiAccountKey, mdiArrowLeftBoldOutline, mdiFormTextarea } from '@mdi/js';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
@ -29,15 +29,15 @@ const props = defineProps({
});
const form = useForm({
_method: 'put',
name: props.role.name,
description: props.role.description,
permissions: props.roleHasPermissions,
});
const submit = async () => {
// await Inertia.post(stardust.route('user.store'), form);
await form.put(stardust.route('settings.role.update', [props.role.id]), form);
// await Inertia.post(stardust.route('user.store'), form); old
await form.put(stardust.route('settings.role.update', [props.role.id]));
// await router.put(stardust.route('settings.role.update', [props.role.id]), form);
};
</script>
@ -58,7 +58,7 @@ const submit = async () => {
<!-- <CardBox form @submit.prevent="form.put(stardust.route('role.update', [props.role.id]))"> -->
<CardBox form @submit.prevent="submit()">
<FormField label="Name" :class="{ 'text-red-400': form.errors.name }">
<FormControl v-model="form.name" type="text" placeholder="Enter Name" required :error="form.errors.name" is-read-only="true">
<FormControl v-model="form.name" type="text" placeholder="Enter Name" required :error="form.errors.name" :is-read-only=true>
<div class="text-red-400 text-sm" v-if="form.errors.name">
{{ form.errors.name }}
</div>

View file

@ -6,7 +6,7 @@
<!-- <SectionFullScreen v-slot="{ cardClass }" :bg="'greenBlue'"> -->
<SectionFullScreen v-slot="{ cardClass }">
<a class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
<img src="/logo.svg" class="h-10 mr-4" alt="Windster Logo" />
<img src="../../logo.svg" class="h-10 mr-4" alt="Windster Logo" />
<!-- <span class="self-center text-2xl font-bold whitespace-nowrap">Tethys</span> -->
</a>

View file

@ -20,7 +20,7 @@
import AuthLayout from '@/Layouts/Auth.vue';
import { reactive } from 'vue';
import { useForm } from '@inertiajs/vue3';
import { Inertia } from '@inertiajs/inertia';
// import { Inertia } from '@inertiajs/inertia';
// import { NButton, NInput } from 'naive-ui';
// import { useForm } from '@inertiajs/inertia-vue3'
import FormInput from '@/Components/FormInput.vue';
@ -45,7 +45,7 @@ export default {
});
const submit = async () => {
await Inertia.post('/app/register', form);
// await Inertia.post('/app/register', form);
};
return { form, submit };

View file

@ -69,10 +69,10 @@ const datasets = computed(() => mainService.datasets);
<SectionMain>
<SectionTitleLineWithButton v-bind:icon="mdiChartTimelineVariant" title="Overview" main>
<BaseButton
href="https://gitea.geologie.ac.at/geolba/tethys"
href="https://gitea.geosphere.at/geolba/tethys.backend"
target="_blank"
:icon="mdiGithub"
label="Star on Gitea"
label="Star on GeoSphere Forgejo"
color="contrast"
rounded-full
small

View file

@ -4,10 +4,10 @@
<h1 class="text-xl font-bold">SKOS Browser</h1>
<div class="flex space-x-2">
<button @click="updateApp" title="Update the application">
<img src="/Resources/Images/refresh.png" alt="Update" class="w-4 h-4" />
<!-- <img src="/Resources/Images/refresh.png" alt="Update" class="w-4 h-4" /> -->
</button>
<button @click="showInfo" title="Info">
<img src="/Resources/Images/info.png" alt="Info" class="w-4 h-4" />
<!-- <img src="/Resources/Images/info.png" alt="Info" class="w-4 h-4" /> -->
</button>
</div>
</header>

View file

@ -45,7 +45,7 @@ import { LayerOptions } from '@/Components/Map/LayerOptions';
import TableKeywords from '@/Components/TableKeywords.vue';
import NotificationBar from '@/Components/NotificationBar.vue';
import FileUploadComponent from '@/Components/FileUpload.vue';
import Person from '#models/person';
import type Person from '#models/person';
const props = defineProps({
licenses: {
@ -96,6 +96,27 @@ const flash: ComputedRef<any> = computed(() => {
return usePage().props.flash;
});
// Computed property to determine the placeholder based on the selected option
const getPlaceholder = computed(() => (type: string) => {
switch (type) {
case 'DOI':
return 'https://doi.org/10.24341/tethys.236';
case 'Handle':
return '20.500.12345/67890';
case 'ISBN':
return '978-3-85316-076-3';
case 'ISSN':
return '1234-5678';
case 'URL':
return 'https://example.com';
case 'URN':
return 'urn:nbn:de:1234-5678';
default:
return '[VALUE]';
}
});
const mainService = MainService();
// let serrors = reactive([]);
@ -1050,7 +1071,7 @@ Removes a selected keyword
<!-- <input name="Reference Value" class="form-control"
placeholder="[VALUE]" v-model="item.value" /> -->
<FormControl required v-model="item.value" :type="'text'"
placeholder="[VALUE]" :errors="form.errors.embargo_date">
:placeholder="getPlaceholder(form.references[index].type)" :errors="form.errors.embargo_date">
<div class="text-red-400 text-sm"
v-if="form.errors[`references.${index}.value`] && Array.isArray(form.errors[`references.${index}.value`])">
{{ form.errors[`references.${index}.value`].join(', ') }}

View file

@ -9,8 +9,8 @@ import { StyleService } from '@/Stores/style.service';
import { LayoutService } from '@/Stores/layout';
import { LocaleStore } from '@/Stores/locale';
import { darkModeKey, styleKey } from '@/config';
// import type { DefineComponent } from 'vue';
// import { resolvePageComponent } from '@adonisjs/inertia/helpers';
import type { DefineComponent } from 'vue';
import { resolvePageComponent } from '@adonisjs/inertia/helpers';
const pinia = createPinia();
// import i18n from './i18n';
import { EmitterPlugin } from '@/EmitterDirective';
@ -37,32 +37,19 @@ createInertiaApp({
// color: '#4B5563',
color: '#22C55E',
},
// Webpack
// resolve: (name) => require(`./Pages/${name}`),
// resolve: (name) => require(`./Pages/${name}.vue`),
// add default layout
// resolve: (name) => {
// const page = require(`./Pages/${name}.vue`).default;
// Webpack
// resolve: async (name: string) => {
// // Dynamically import the Vue component using import
// const { default: page } = await import(`./Pages/${name}.vue`);
// // const page = require(`./Pages/${name}.vue`).default;
// // if (!page.layout) {
// // page.layout = DefaultLayout;
// // }
// return page;
// },
resolve: async (name: string) => {
// Dynamically import the Vue component using import
const { default: page } = await import(`./Pages/${name}.vue`);
// const page = require(`./Pages/${name}.vue`).default;
// if (!page.layout) {
// page.layout = DefaultLayout;
// }
return page;
resolve: (name) => {
return resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob<DefineComponent>('./Pages/**/*.vue'));
},
// resolve: (name) => {
// return resolvePageComponent(
// `./Pages/${name}.vue`,
// import.meta.glob<DefineComponent>('./pages/**/*.vue'),
// )
// },
setup({ el, App, props, plugin }) {
const app = createApp({ render: () => h(App, props) })
@ -72,11 +59,16 @@ createInertiaApp({
.use(EmitterPlugin);
// .component('inertia-link', Link)
// Listen for navigation event to handle layout changes
// window.addEventListener('inertia:navigate', () => {
// layoutService.isAsideMobileExpanded = false;
// layoutService.isAsideLgActive = false;
// });
asyncPlugin.install('settings').then(() => {
app.mount(el);
});
},
});
const styleService = StyleService(pinia);

183
resources/js/logo.svg Normal file
View file

@ -0,0 +1,183 @@
<!-- <svg width="60" height="59" viewBox="0 0 60 59" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M22.0604 27.0103C29.7333 23.0893 35.8474 16.6142 39.6254 8.51905C40.1123 7.47585 39.871 6.25371 39.3648 5.21976C38.1966 2.83369 37.9273 0.35731 31.5792 1.07481L29.0769 1.19405C30.0128 1.19405 29.5449 1.19405 29.0769 1.19405C26.2213 9.83921 20.8705 16.9667 13.0175 20.9078C7.54419 23.7048 3.49869 28.7902 1.59493 34.8927L1.08063 36.5412C1.02718 36.7125 0.953783 36.7439 1.03271 36.905V36.905C1.69551 38.2585 2.41237 37.8338 1.24774 36.8773C-0.0162787 35.8391 4.54649 41.9692 8.20235 42.3859C8.98846 42.4755 9.62624 41.8869 9.99718 41.188C12.8897 35.7384 16.5983 29.7497 22.0604 27.0103Z"
fill="#3798A6" />
<path
d="M54.293 18.2545C52.9177 18.0829 51.6326 19.0064 50.8501 20.1503C46.5712 26.4051 40.9276 31.4619 34.0992 34.9362C28.0309 37.9875 23.3905 43.7085 21.2488 50.5738V50.5738C20.8823 51.9051 21.321 53.3053 22.0817 54.4577C22.8541 55.6279 23.2013 56.5055 25.0387 57.2056C25.2331 57.2797 25.4363 57.33 25.6412 57.3659L29.6561 58.0696C30.1014 58.1477 30.5803 57.9029 30.6385 57.4546V57.4546C30.6522 57.3488 30.6656 57.26 30.6995 57.1589C33.5732 48.5843 39.3809 41.6511 47.1875 37.7332C52.0449 35.251 56.5674 29.4173 58.7712 23.8703C59.2774 22.5963 58.9189 21.1675 57.9822 20.1666V20.1666C57.743 19.9111 57.4687 19.6866 57.1678 19.5079C55.698 18.635 54.964 18.3382 54.293 18.2545Z"
fill="#3798A6" />
<path
d="M17.8462 46.7817C18.4399 48.0437 20.2086 47.5179 20.8231 46.2658C23.9715 39.8511 29.07 34.8495 35.2979 31.6915C41.4799 28.5111 46.6705 23.683 50.3399 17.7735C50.6236 17.3166 50.7683 16.7849 50.7594 16.2472C50.658 10.1604 49.3958 9.67645 47.3183 8.9339C45.5442 8.29976 43.3135 8.84337 42.332 10.4516C37.9465 17.6372 31.7406 23.4608 24.3513 27.2417C19.6664 29.5615 15.8593 33.5228 13.4571 38.3374C13.2609 38.7306 13.1639 39.1681 13.1742 39.6074C13.3408 46.715 16.2215 43.2109 17.84 46.768C17.8439 46.7766 17.8422 46.773 17.8462 46.7817V46.7817Z"
fill="#3798A6" />
</svg> -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 626.4 224.7" style="enable-background:new 0 0 626.4 224.7;" xml:space="preserve">
<style type="text/css">
.st0 {
fill: #336699;
}
.st1 {
fill: #00FFFF;
}
.st2 {
fill: #30D5C8;
}
.st3 {
opacity: 0.8;
fill: #336699;
}
.st4 {
opacity: 0.5;
fill: #336699;
}
.st5 {
fill: #393939;
}
</style>
<g>
<g>
<path class="st0"
d="M215.3,69.1l-4.4,19.2h-47.1l-4.2,18h43.7l-4.5,19.2h-43.6l-5.9,25.4h47L192,170h-73.1l23.3-100.9H215.3z" />
<path class="st0" d="M311.6,88.6h-31.4L261.4,170h-26.1L254,88.6h-31.5l4.5-19.5h89L311.6,88.6z" />
<path class="st0" d="M422.1,69.1L398.9,170h-26.1l10.3-44.6h-38.3L334.4,170h-26.1l23.3-100.9h26.1l-8.6,37.1h38.3l8.6-37.1H422.1
z" />
<path class="st0"
d="M536.7,69.1l-51.4,61.9l-9.1,39h-26.2l8.8-37.8l-23.2-63.1h27.9l13,39.6l30.2-39.6H536.7z" />
<path class="st0" d="M563.8,171.8c-8.6,0-16.3-0.7-23.1-2.2c-6.8-1.5-12.6-3.3-17.5-5.6l5.2-24.2h2.8c4.7,4.2,10.1,7.5,16.4,9.9
c6.3,2.3,13,3.5,20.3,3.5c7.4,0,12.9-1,16.3-3c3.5-2,5.2-4.9,5.2-8.6c0-1.4-0.3-2.6-0.8-3.6c-0.6-1-1.7-2-3.3-3
c-1.6-1-3.9-2-6.7-2.9c-2.8-1-6.5-2.1-10.9-3.4c-4.9-1.4-9.3-2.8-13.2-4.4c-3.9-1.6-7.2-3.5-10-5.6c-2.8-2.2-4.8-4.8-6.3-7.7
c-1.4-2.9-2.1-6.3-2.1-10.4c0-10.1,4.5-18.2,13.4-24.2c8.9-6,21.1-9,36.6-9c7.5,0,14.5,0.6,21.1,1.8c6.6,1.2,12.2,2.9,16.8,4.9
l-4.9,23.2h-2.8c-3.5-3.2-8.2-5.9-13.9-8.1c-5.8-2.2-12.1-3.3-18.9-3.3c-6.7,0-11.9,1-15.5,2.9c-3.7,1.9-5.5,4.6-5.5,8
c0,1.6,0.3,2.9,0.8,3.9c0.5,1,1.6,2.1,3.3,3c1.4,0.9,3.7,1.9,6.8,2.9c3.1,1.1,6.8,2.2,11,3.3c11.3,3,19.4,6.6,24.2,10.6
s7.2,9.6,7.2,16.5c0,5.8-1.3,11-3.9,15.4c-2.6,4.4-6.2,8-10.8,10.8c-4.8,2.9-10.3,5.1-16.5,6.4
C578.5,171.1,571.6,171.8,563.8,171.8z" />
</g>
<path class="st1" d="M539.2,37.1" />
<path class="st1" d="M539.2,37.1" />
<polygon class="st2" points="24,213.8 57.7,213.8 98.2,44.6 12.5,45.1 3.7,81 56.7,80.6 " />
<polygon class="st0" points="72.1,170.5 112.4,2.8 199.8,2.8 192.4,39.2 137.1,39.2 105.8,170.1 " />
<polygon class="st0" points="207,2.6 225.9,2.6 218.6,39 199.9,39 " />
<polygon class="st3" points="233.2,2.9 252.1,2.9 244.7,39.4 226.1,39.4 " />
<polygon class="st4" points="259.2,2.9 278.1,2.9 270.8,39.4 252.1,39.4 " />
<g>
<path class="st5" d="M97.4,213.7h-4.7l-8-12.1h-5.8l-2.8,12.1H72l7-30.2h8.3c1.9,0,3.4,0.1,4.5,0.4c1.2,0.2,2.2,0.7,3,1.3
c0.8,0.6,1.4,1.2,1.8,2c0.4,0.8,0.7,1.8,0.7,2.9c0,2.5-0.8,4.7-2.4,6.6c-1.6,1.9-3.7,3.2-6.2,4L97.4,213.7z M92.9,191
c0-0.7-0.1-1.3-0.3-1.8c-0.2-0.5-0.6-0.9-1-1.3c-0.5-0.4-1.2-0.7-1.9-0.9c-0.7-0.2-1.7-0.2-2.8-0.2h-4.6l-2.7,11.6h4.3
c1.3,0,2.5-0.1,3.5-0.4c1-0.2,1.9-0.7,2.7-1.3c0.9-0.7,1.6-1.5,2.1-2.5C92.6,193.2,92.9,192.1,92.9,191z" />
<path class="st5" d="M111.5,214.2c-3.2,0-5.6-0.7-7.4-2.2c-1.8-1.5-2.7-3.6-2.7-6.5c0-4.2,1.3-7.7,4-10.7c2.6-3,5.9-4.5,9.8-4.5
c2.6,0,4.6,0.6,5.9,1.9c1.4,1.3,2.1,3.1,2.1,5.4c0,0.4-0.1,1-0.2,1.9c-0.1,0.9-0.3,1.9-0.6,3.1h-16.8c-0.1,0.4-0.1,0.8-0.2,1.2
c0,0.4-0.1,0.7-0.1,1.1c0,1.9,0.6,3.4,1.8,4.5c1.2,1.1,2.8,1.6,5,1.6c1.5,0,3-0.3,4.6-0.9s2.9-1.2,4-2h0.2l-0.8,4.1
c-0.7,0.2-1.3,0.5-1.8,0.7c-0.5,0.2-1.2,0.4-2.1,0.6c-0.8,0.2-1.6,0.4-2.2,0.5C113.3,214.2,112.5,214.2,111.5,214.2z M119.2,199.9
c0.1-0.4,0.1-0.7,0.1-1c0-0.3,0-0.6,0-0.9c0-1.4-0.4-2.6-1.2-3.4c-0.8-0.8-2.1-1.2-3.8-1.2c-1.9,0-3.6,0.6-5.1,1.8
c-1.5,1.2-2.5,2.8-3,4.7H119.2z" />
<path class="st5" d="M132.8,214.3c-1.7,0-3.2-0.2-4.6-0.6s-2.5-0.9-3.4-1.4l0.9-4.1h0.2c0.3,0.2,0.7,0.5,1.1,0.9
c0.5,0.3,1,0.7,1.7,1c0.6,0.3,1.4,0.6,2.2,0.8c0.8,0.2,1.7,0.3,2.6,0.3c1.9,0,3.4-0.4,4.5-1.1c1.1-0.7,1.6-1.7,1.6-3.1
c0-0.7-0.3-1.3-0.8-1.7c-0.6-0.4-1.4-0.7-2.4-0.9c-0.5-0.1-1.2-0.3-1.9-0.4c-0.7-0.1-1.5-0.3-2.3-0.5c-1.6-0.4-2.8-1.1-3.5-1.9
c-0.8-0.8-1.1-1.9-1.1-3.1c0-1.1,0.2-2,0.7-3c0.5-0.9,1.1-1.8,2.1-2.5c0.9-0.7,2-1.3,3.3-1.8c1.3-0.5,2.8-0.7,4.5-0.7
c1.4,0,2.7,0.2,4.1,0.5c1.4,0.3,2.5,0.8,3.3,1.3l-0.8,3.9h-0.2c-0.2-0.2-0.5-0.4-1-0.7c-0.4-0.3-1-0.6-1.7-0.9
c-0.6-0.3-1.3-0.5-2.1-0.7c-0.8-0.2-1.6-0.3-2.4-0.3c-1.7,0-3.1,0.4-4.1,1.1s-1.6,1.7-1.6,2.9c0,0.7,0.3,1.2,0.8,1.7
c0.5,0.5,1.3,0.8,2.4,1.1c0.7,0.2,1.4,0.3,2.1,0.5c0.7,0.1,1.4,0.3,2.1,0.5c1.6,0.4,2.8,1,3.6,1.8s1.2,1.9,1.2,3.1
c0,1-0.2,2.1-0.7,3.1c-0.5,1-1.2,1.9-2.2,2.6c-1,0.8-2.1,1.4-3.5,1.8C136,214,134.5,214.3,132.8,214.3z" />
<path class="st5" d="M158.2,214.2c-3.2,0-5.6-0.7-7.4-2.2c-1.8-1.5-2.7-3.6-2.7-6.5c0-4.2,1.3-7.7,4-10.7c2.6-3,5.9-4.5,9.8-4.5
c2.6,0,4.6,0.6,5.9,1.9c1.4,1.3,2.1,3.1,2.1,5.4c0,0.4-0.1,1-0.2,1.9c-0.1,0.9-0.3,1.9-0.6,3.1h-16.8c-0.1,0.4-0.1,0.8-0.2,1.2
c0,0.4-0.1,0.7-0.1,1.1c0,1.9,0.6,3.4,1.8,4.5c1.2,1.1,2.8,1.6,5,1.6c1.5,0,3-0.3,4.6-0.9c1.6-0.6,2.9-1.2,4-2h0.2l-0.8,4.1
c-0.7,0.2-1.3,0.5-1.8,0.7c-0.5,0.2-1.2,0.4-2.1,0.6c-0.8,0.2-1.6,0.4-2.2,0.5C160,214.2,159.2,214.2,158.2,214.2z M165.9,199.9
c0.1-0.4,0.1-0.7,0.1-1c0-0.3,0-0.6,0-0.9c0-1.4-0.4-2.6-1.2-3.4c-0.8-0.8-2.1-1.2-3.8-1.2c-1.9,0-3.6,0.6-5.1,1.8
c-1.5,1.2-2.5,2.8-3,4.7H165.9z" />
<path class="st5"
d="M186.8,211.3c-0.4,0.2-0.9,0.5-1.5,0.9c-0.6,0.4-1.3,0.7-1.9,1c-0.7,0.3-1.5,0.6-2.3,0.8
c-0.9,0.2-1.8,0.3-3,0.3c-1.8,0-3.3-0.5-4.4-1.6c-1.1-1-1.7-2.4-1.7-4.1c0-1.8,0.4-3.3,1.2-4.6c0.8-1.2,2-2.2,3.5-3
c1.5-0.8,3.4-1.3,5.6-1.7c2.2-0.3,4.6-0.6,7.4-0.6c0.1-0.4,0.2-0.7,0.2-1c0.1-0.3,0.1-0.6,0.1-0.9c0-0.6-0.1-1.2-0.4-1.6
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.4-1.7-0.5c-0.6-0.1-1.3-0.1-2.1-0.1c-1.2,0-2.5,0.2-4,0.6c-1.5,0.4-2.7,0.7-3.6,1.1H177
l0.8-3.8c0.8-0.2,1.9-0.4,3.4-0.7c1.5-0.3,2.9-0.4,4.3-0.4c2.8,0,5,0.4,6.4,1.3c1.4,0.9,2.1,2.3,2.1,4.2c0,0.4,0,0.8-0.1,1.2
s-0.1,0.8-0.2,1.2l-3.6,15.4h-3.8L186.8,211.3z M189.1,201.8c-2.1,0.1-4,0.2-5.6,0.5c-1.6,0.2-3,0.6-4,1c-1.1,0.4-1.9,1.1-2.5,1.8
c-0.6,0.8-0.9,1.7-0.9,2.9c0,1,0.3,1.7,1,2.2c0.7,0.5,1.7,0.8,3.1,0.8c1.2,0,2.5-0.3,3.8-0.8c1.3-0.5,2.5-1.2,3.6-2L189.1,201.8z" />
<path class="st5" d="M215.9,195.1h-0.2c-0.5-0.1-1-0.2-1.5-0.3c-0.5-0.1-1-0.1-1.7-0.1c-1.3,0-2.5,0.3-3.8,0.9
c-1.3,0.6-2.5,1.3-3.6,2.1l-3.7,16.1h-3.9l5.2-22.7h3.9l-0.8,3.3c1.8-1.3,3.3-2.1,4.6-2.6c1.3-0.5,2.5-0.7,3.6-0.7
c0.7,0,1.2,0,1.4,0.1c0.3,0,0.7,0.1,1.3,0.2L215.9,195.1z" />
<path class="st5" d="M225.2,214.2c-1.4,0-2.7-0.2-3.8-0.5c-1.1-0.3-2.1-0.9-2.9-1.6c-0.8-0.7-1.4-1.6-1.9-2.7
c-0.4-1.1-0.7-2.3-0.7-3.7c0-2.1,0.3-4.1,1-5.9c0.7-1.8,1.6-3.5,2.8-4.9c1.2-1.4,2.6-2.4,4.4-3.2c1.7-0.8,3.6-1.2,5.6-1.2
c1.3,0,2.6,0.2,3.8,0.5c1.2,0.4,2.2,0.8,3.1,1.3l-0.8,4.1h-0.2c-0.3-0.2-0.6-0.5-1-0.8c-0.4-0.3-0.9-0.6-1.4-0.9
c-0.6-0.3-1.2-0.5-1.9-0.7c-0.7-0.2-1.5-0.3-2.3-0.3c-2.6,0-4.8,1.1-6.5,3.3c-1.7,2.2-2.5,4.9-2.5,8.1c0,1.9,0.5,3.4,1.5,4.4
c1,1,2.4,1.5,4.2,1.5c0.9,0,1.7-0.1,2.6-0.4c0.9-0.2,1.6-0.5,2.2-0.8c0.7-0.3,1.3-0.6,1.9-1c0.6-0.3,1-0.6,1.2-0.8h0.2l-0.8,4.2
c-1.2,0.5-2.4,1-3.8,1.4C227.9,214,226.5,214.2,225.2,214.2z" />
<path class="st5" d="M259.9,196.2c0,0.3,0,0.8-0.1,1.3c-0.1,0.6-0.1,1-0.3,1.5l-3.4,14.7h-3.8l3-12.9c0.2-0.7,0.3-1.3,0.4-1.9
c0.1-0.5,0.1-1.1,0.1-1.6c0-1.1-0.3-2-0.9-2.6c-0.6-0.6-1.6-0.9-3.1-0.9c-1,0-2.2,0.3-3.4,0.9c-1.2,0.6-2.5,1.2-3.7,2l-3.9,16.9
h-3.8l7.3-31.6h3.8l-2.7,11.4c1.5-1,2.8-1.8,4.1-2.3c1.3-0.5,2.6-0.8,3.9-0.8c2,0,3.5,0.5,4.6,1.5
C259.4,192.9,259.9,194.3,259.9,196.2z" />
<path class="st5" d="M308.1,195.1c0,3.3-0.8,6.4-2.5,9.4c-1.7,3-4,5.2-6.9,6.8c-1.8,1-3.6,1.6-5.5,1.9c-1.9,0.3-4,0.5-6.4,0.5
h-8.2l7-30.2h7.1c2.2,0,4.2,0.2,6,0.5c1.8,0.3,3.5,1,5,2c1.4,1,2.5,2.2,3.3,3.8C307.7,191.2,308.1,193,308.1,195.1z M303.8,195.4
c0-1.5-0.3-2.9-0.8-4c-0.6-1.1-1.4-2-2.5-2.8c-1.1-0.7-2.3-1.2-3.5-1.5c-1.3-0.2-2.8-0.4-4.8-0.4h-3.4l-5.5,23.6h4.2
c1.9,0,3.7-0.2,5.2-0.5c1.5-0.3,3-0.9,4.3-1.6c2.2-1.2,3.9-3,5-5.4C303.3,200.6,303.8,198.1,303.8,195.4z" />
<path class="st5"
d="M324.7,211.3c-0.4,0.2-0.9,0.5-1.5,0.9c-0.6,0.4-1.3,0.7-1.9,1c-0.7,0.3-1.5,0.6-2.3,0.8
c-0.9,0.2-1.8,0.3-3,0.3c-1.8,0-3.3-0.5-4.4-1.6c-1.1-1-1.7-2.4-1.7-4.1c0-1.8,0.4-3.3,1.2-4.6c0.8-1.2,2-2.2,3.5-3
c1.5-0.8,3.4-1.3,5.6-1.7c2.2-0.3,4.6-0.6,7.4-0.6c0.1-0.4,0.2-0.7,0.2-1c0.1-0.3,0.1-0.6,0.1-0.9c0-0.6-0.1-1.2-0.4-1.6
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.4-1.7-0.5c-0.6-0.1-1.3-0.1-2.1-0.1c-1.2,0-2.5,0.2-4,0.6c-1.5,0.4-2.7,0.7-3.6,1.1h-0.2
l0.8-3.8c0.8-0.2,1.9-0.4,3.4-0.7c1.5-0.3,2.9-0.4,4.3-0.4c2.8,0,5,0.4,6.4,1.3c1.4,0.9,2.1,2.3,2.1,4.2c0,0.4,0,0.8-0.1,1.2
s-0.1,0.8-0.2,1.2l-3.6,15.4h-3.8L324.7,211.3z M327,201.8c-2.1,0.1-4,0.2-5.6,0.5c-1.6,0.2-3,0.6-4,1c-1.1,0.4-1.9,1.1-2.5,1.8
c-0.6,0.8-0.9,1.7-0.9,2.9c0,1,0.3,1.7,1,2.2c0.7,0.5,1.7,0.8,3.1,0.8c1.2,0,2.5-0.3,3.8-0.8c1.3-0.5,2.5-1.2,3.6-2L327,201.8z" />
<path class="st5" d="M352.5,191l-0.7,3.1h-7.9l-2.4,10.5c-0.1,0.5-0.3,1.1-0.4,1.8c-0.1,0.7-0.2,1.2-0.2,1.6c0,1,0.3,1.7,0.8,2.2
c0.5,0.5,1.4,0.7,2.8,0.7c0.6,0,1.2-0.1,2-0.3c0.8-0.2,1.3-0.3,1.6-0.4h0.2l-0.7,3.3c-0.8,0.2-1.6,0.3-2.4,0.5
c-0.9,0.1-1.6,0.2-2.3,0.2c-1.9,0-3.3-0.4-4.4-1.2c-1-0.8-1.5-2.1-1.5-3.9c0-0.4,0-0.9,0.1-1.3c0.1-0.4,0.1-0.9,0.3-1.5l2.8-12.2
h-2.6l0.7-3.1h2.6l1.5-6.5h3.9l-1.5,6.5H352.5z" />
<path class="st5"
d="M366.3,211.3c-0.4,0.2-0.9,0.5-1.5,0.9c-0.6,0.4-1.3,0.7-1.9,1c-0.7,0.3-1.5,0.6-2.3,0.8
c-0.9,0.2-1.8,0.3-3,0.3c-1.8,0-3.3-0.5-4.4-1.6c-1.1-1-1.7-2.4-1.7-4.1c0-1.8,0.4-3.3,1.2-4.6c0.8-1.2,2-2.2,3.5-3
c1.5-0.8,3.4-1.3,5.6-1.7c2.2-0.3,4.6-0.6,7.4-0.6c0.1-0.4,0.2-0.7,0.2-1c0.1-0.3,0.1-0.6,0.1-0.9c0-0.6-0.1-1.2-0.4-1.6
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.4-1.7-0.5c-0.6-0.1-1.3-0.1-2.1-0.1c-1.2,0-2.5,0.2-4,0.6c-1.5,0.4-2.7,0.7-3.6,1.1h-0.2
l0.8-3.8c0.8-0.2,1.9-0.4,3.4-0.7c1.5-0.3,2.9-0.4,4.3-0.4c2.8,0,5,0.4,6.4,1.3c1.4,0.9,2.1,2.3,2.1,4.2c0,0.4,0,0.8-0.1,1.2
s-0.1,0.8-0.2,1.2l-3.6,15.4h-3.8L366.3,211.3z M368.5,201.8c-2.1,0.1-4,0.2-5.6,0.5c-1.6,0.2-3,0.6-4,1c-1.1,0.4-1.9,1.1-2.5,1.8
c-0.6,0.8-0.9,1.7-0.9,2.9c0,1,0.3,1.7,1,2.2c0.7,0.5,1.7,0.8,3.1,0.8c1.2,0,2.5-0.3,3.8-0.8c1.3-0.5,2.5-1.2,3.6-2L368.5,201.8z" />
<path class="st5" d="M417.4,213.7h-4.7l-8-12.1h-5.8l-2.8,12.1H392l7-30.2h8.3c1.9,0,3.4,0.1,4.5,0.4c1.2,0.2,2.2,0.7,3,1.3
c0.8,0.6,1.4,1.2,1.8,2c0.4,0.8,0.7,1.8,0.7,2.9c0,2.5-0.8,4.7-2.4,6.6c-1.6,1.9-3.7,3.2-6.2,4L417.4,213.7z M412.8,191
c0-0.7-0.1-1.3-0.3-1.8c-0.2-0.5-0.6-0.9-1-1.3c-0.5-0.4-1.2-0.7-1.9-0.9c-0.7-0.2-1.7-0.2-2.8-0.2h-4.6l-2.7,11.6h4.3
c1.3,0,2.5-0.1,3.5-0.4c1-0.2,1.9-0.7,2.7-1.3c0.9-0.7,1.6-1.5,2.1-2.5C412.6,193.2,412.8,192.1,412.8,191z" />
<path class="st5" d="M431.5,214.2c-3.2,0-5.6-0.7-7.4-2.2c-1.8-1.5-2.7-3.6-2.7-6.5c0-4.2,1.3-7.7,4-10.7s5.9-4.5,9.8-4.5
c2.6,0,4.6,0.6,5.9,1.9c1.4,1.3,2.1,3.1,2.1,5.4c0,0.4-0.1,1-0.2,1.9c-0.1,0.9-0.3,1.9-0.6,3.1h-16.8c-0.1,0.4-0.1,0.8-0.2,1.2
c0,0.4-0.1,0.7-0.1,1.1c0,1.9,0.6,3.4,1.8,4.5c1.2,1.1,2.8,1.6,5,1.6c1.5,0,3-0.3,4.6-0.9c1.6-0.6,2.9-1.2,4-2h0.2l-0.8,4.1
c-0.7,0.2-1.3,0.5-1.8,0.7s-1.2,0.4-2.1,0.6c-0.8,0.2-1.6,0.4-2.2,0.5C433.3,214.2,432.5,214.2,431.5,214.2z M439.2,199.9
c0.1-0.4,0.1-0.7,0.1-1c0-0.3,0-0.6,0-0.9c0-1.4-0.4-2.6-1.2-3.4c-0.8-0.8-2.1-1.2-3.8-1.2c-1.9,0-3.6,0.6-5.1,1.8
c-1.5,1.2-2.5,2.8-3,4.7H439.2z" />
<path class="st5" d="M468.8,198.2c0,2.2-0.4,4.4-1.1,6.3c-0.7,2-1.6,3.7-2.8,5c-1.2,1.4-2.5,2.5-4.1,3.4c-1.6,0.8-3.2,1.2-5,1.2
c-1.2,0-2.4-0.1-3.4-0.4s-2-0.7-2.8-1.2l-2.2,9.5h-3.8l7.2-31h3.8l-0.6,2.4c1.3-0.9,2.5-1.6,3.7-2.2c1.2-0.6,2.6-0.8,4.1-0.8
c2.2,0,3.9,0.7,5.1,2.1C468.2,193.8,468.8,195.7,468.8,198.2z M464.8,198.9c0-1.6-0.4-2.9-1.1-3.7c-0.7-0.9-1.8-1.3-3.4-1.3
c-1.1,0-2.3,0.3-3.5,0.8c-1.2,0.6-2.3,1.2-3.4,1.9l-3,12.9c0.9,0.5,1.7,0.8,2.5,1.1c0.8,0.2,1.8,0.3,2.9,0.3c1.4,0,2.7-0.3,3.8-1
s2.1-1.6,2.8-2.6c0.8-1.1,1.4-2.4,1.7-3.8C464.6,202,464.8,200.5,464.8,198.9z" />
<path class="st5" d="M494.1,199.1c0,2-0.3,4-0.9,5.8c-0.6,1.9-1.5,3.5-2.7,4.9c-1.2,1.4-2.6,2.6-4.1,3.4c-1.6,0.8-3.4,1.2-5.4,1.2
c-2.7,0-4.8-0.8-6.3-2.3c-1.5-1.5-2.3-3.7-2.3-6.4c0-2,0.3-4,0.9-5.8c0.6-1.8,1.5-3.5,2.7-4.9c1.1-1.4,2.5-2.5,4.2-3.3
c1.6-0.8,3.4-1.2,5.4-1.2c2.6,0,4.7,0.7,6.3,2.2S494.1,196.3,494.1,199.1z M487.8,207.6c0.7-1.1,1.3-2.3,1.7-3.8s0.6-2.9,0.6-4.5
c0-1.9-0.5-3.3-1.4-4.3c-0.9-1-2.2-1.5-3.9-1.5c-1.3,0-2.5,0.3-3.6,0.9c-1,0.6-2,1.5-2.7,2.6c-0.7,1.1-1.3,2.3-1.7,3.8
c-0.4,1.4-0.6,2.9-0.6,4.5c0,1.9,0.5,3.3,1.4,4.3c0.9,1,2.2,1.5,3.9,1.5c1.3,0,2.5-0.3,3.6-0.9
C486.2,209.6,487.1,208.7,487.8,207.6z" />
<path class="st5" d="M504.2,214.3c-1.7,0-3.2-0.2-4.6-0.6c-1.3-0.4-2.5-0.9-3.4-1.4l0.9-4.1h0.2c0.3,0.2,0.7,0.5,1.1,0.9
c0.5,0.3,1,0.7,1.7,1c0.6,0.3,1.4,0.6,2.2,0.8c0.8,0.2,1.7,0.3,2.6,0.3c1.9,0,3.4-0.4,4.5-1.1s1.6-1.7,1.6-3.1
c0-0.7-0.3-1.3-0.8-1.7c-0.6-0.4-1.4-0.7-2.4-0.9c-0.5-0.1-1.2-0.3-1.9-0.4c-0.7-0.1-1.5-0.3-2.3-0.5c-1.6-0.4-2.8-1.1-3.5-1.9
c-0.8-0.8-1.1-1.9-1.1-3.1c0-1.1,0.2-2,0.7-3c0.5-0.9,1.1-1.8,2.1-2.5c0.9-0.7,2-1.3,3.3-1.8c1.3-0.5,2.8-0.7,4.5-0.7
c1.4,0,2.7,0.2,4.1,0.5c1.4,0.3,2.5,0.8,3.3,1.3l-0.8,3.9H516c-0.2-0.2-0.5-0.4-1-0.7c-0.4-0.3-1-0.6-1.7-0.9
c-0.6-0.3-1.3-0.5-2.1-0.7c-0.8-0.2-1.6-0.3-2.4-0.3c-1.7,0-3.1,0.4-4.1,1.1c-1.1,0.7-1.6,1.7-1.6,2.9c0,0.7,0.3,1.2,0.8,1.7
c0.5,0.5,1.3,0.8,2.4,1.1c0.7,0.2,1.4,0.3,2.1,0.5c0.7,0.1,1.4,0.3,2.1,0.5c1.6,0.4,2.8,1,3.6,1.8s1.2,1.9,1.2,3.1
c0,1-0.2,2.1-0.7,3.1c-0.5,1-1.2,1.9-2.2,2.6c-1,0.8-2.1,1.4-3.5,1.8C507.4,214,505.9,214.3,504.2,214.3z" />
<path class="st5" d="M528,191l-5.3,22.7h-3.8l5.3-22.7H528z M530.1,183.3l-0.9,4h-4.3l0.9-4H530.1z" />
<path class="st5" d="M547.4,191l-0.7,3.1h-7.9l-2.4,10.5c-0.1,0.5-0.3,1.1-0.4,1.8c-0.1,0.7-0.2,1.2-0.2,1.6c0,1,0.3,1.7,0.8,2.2
c0.5,0.5,1.4,0.7,2.8,0.7c0.6,0,1.2-0.1,2-0.3c0.8-0.2,1.3-0.3,1.6-0.4h0.2l-0.7,3.3c-0.8,0.2-1.6,0.3-2.4,0.5
c-0.9,0.1-1.6,0.2-2.3,0.2c-1.9,0-3.3-0.4-4.4-1.2c-1-0.8-1.5-2.1-1.5-3.9c0-0.4,0-0.9,0.1-1.3c0.1-0.4,0.1-0.9,0.3-1.5l2.8-12.2
h-2.6l0.7-3.1h2.6l1.5-6.5h3.9l-1.5,6.5H547.4z" />
<path class="st5" d="M569.2,199.1c0,2-0.3,4-0.9,5.8c-0.6,1.9-1.5,3.5-2.7,4.9c-1.2,1.4-2.6,2.6-4.1,3.4c-1.5,0.8-3.4,1.2-5.4,1.2
c-2.7,0-4.8-0.8-6.3-2.3c-1.5-1.5-2.3-3.7-2.3-6.4c0-2,0.3-4,0.9-5.8c0.6-1.8,1.5-3.5,2.7-4.9c1.1-1.4,2.5-2.5,4.2-3.3
c1.6-0.8,3.4-1.2,5.4-1.2c2.6,0,4.7,0.7,6.3,2.2C568.4,194.1,569.2,196.3,569.2,199.1z M563,207.6c0.7-1.1,1.3-2.3,1.7-3.8
c0.4-1.4,0.6-2.9,0.6-4.5c0-1.9-0.5-3.3-1.4-4.3c-0.9-1-2.2-1.5-3.9-1.5c-1.3,0-2.5,0.3-3.6,0.9c-1,0.6-2,1.5-2.7,2.6
c-0.7,1.1-1.3,2.3-1.7,3.8c-0.4,1.4-0.6,2.9-0.6,4.5c0,1.9,0.5,3.3,1.4,4.3c0.9,1,2.2,1.5,3.9,1.5c1.3,0,2.5-0.3,3.6-0.9
C561.3,209.6,562.2,208.7,563,207.6z" />
<path class="st5" d="M590.6,195.1h-0.2c-0.5-0.1-1.1-0.2-1.5-0.3c-0.5-0.1-1-0.1-1.7-0.1c-1.3,0-2.5,0.3-3.8,0.9
c-1.3,0.6-2.5,1.3-3.6,2.1l-3.7,16.1h-3.9l5.2-22.7h3.9l-0.8,3.3c1.8-1.3,3.3-2.1,4.6-2.6c1.3-0.5,2.5-0.7,3.6-0.7
c0.7,0,1.2,0,1.4,0.1c0.3,0,0.7,0.1,1.3,0.2L590.6,195.1z" />
<path class="st5" d="M594.1,222.1h-4.2l6.6-9.7l-4.1-21.3h4l3.2,16.9l10.9-16.9h4.2L594.1,222.1z" />
</g>
</g>
</svg>

View file

@ -15,5 +15,5 @@
},
},
"include": ["./**/*.ts", "./**/*.vue"],
"exclude": ["./utils/*.js"],
"exclude": ["./utils/*.js", "./utils/Timer.js", "./utils/focusTrap.js"],
}

View file

@ -1,16 +1,15 @@
/**
* Get the first day of the week
*
* @return {number}
*/
export function getFirstDay(): number {
if (typeof window.firstDay === 'undefined') {
console.warn('No firstDay found')
return 1
}
if (typeof window.firstDay === 'undefined') {
console.warn('No firstDay found');
return 1;
}
return window.firstDay
return window.firstDay;
}
/**
@ -19,20 +18,12 @@ export function getFirstDay(): number {
* @return {string[]}
*/
export function getDayNames(): string[] {
if (typeof window.dayNames === 'undefined') {
console.warn('No dayNames found')
return [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
]
}
if (typeof window.dayNames === 'undefined') {
console.warn('No dayNames found');
return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
}
return window.dayNames
return window.dayNames;
}
/**
@ -41,12 +32,12 @@ export function getDayNames(): string[] {
* @return {string[]}
*/
export function getDayNamesShort(): string[] {
if (typeof window.dayNamesShort === 'undefined') {
console.warn('No dayNamesShort found')
return ['Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.']
}
if (typeof window.dayNamesShort === 'undefined') {
console.warn('No dayNamesShort found');
return ['Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.'];
}
return window.dayNamesShort
return window.dayNamesShort;
}
/**
@ -55,12 +46,12 @@ export function getDayNamesShort(): string[] {
* @return {string[]}
*/
export function getDayNamesMin(): string[] {
if (typeof window.dayNamesMin === 'undefined') {
console.warn('No dayNamesMin found')
return ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
}
if (typeof window.dayNamesMin === 'undefined') {
console.warn('No dayNamesMin found');
return ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
}
return window.dayNamesMin
return window.dayNamesMin;
}
/**
@ -69,25 +60,12 @@ export function getDayNamesMin(): string[] {
* @return {string[]}
*/
export function getMonthNames(): string[] {
if (typeof window.monthNames === 'undefined') {
console.warn('No monthNames found')
return [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
]
}
if (typeof window.monthNames === 'undefined') {
console.warn('No monthNames found');
return ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
}
return window.monthNames
return window.monthNames;
}
/**
@ -96,23 +74,10 @@ export function getMonthNames(): string[] {
* @return {string[]}
*/
export function getMonthNamesShort(): string[] {
if (typeof window.monthNamesShort === 'undefined') {
console.warn('No monthNamesShort found')
return [
'Jan.',
'Feb.',
'Mar.',
'Apr.',
'May.',
'Jun.',
'Jul.',
'Aug.',
'Sep.',
'Oct.',
'Nov.',
'Dec.',
]
}
if (typeof window.monthNamesShort === 'undefined') {
console.warn('No monthNamesShort found');
return ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
}
return window.monthNamesShort
return window.monthNamesShort;
}

View file

@ -1,8 +1,8 @@
import type { Translations } from './registry';
import { getLanguage, getBrowserLocale } from './locale';
import { getAppTranslations, hasAppTranslations, registerAppTranslations, unregisterAppTranslations } from './registry';
import { getLanguage } from './locale';
import { getAppTranslations, registerAppTranslations, unregisterAppTranslations } from './registry';
// import { generateFilePath } from '@nextcloud/router';
import axios from 'axios';
// import axios from 'axios';
// import DOMPurify from 'dompurify';
import escapeHTML from 'escape-html';
@ -206,7 +206,7 @@ export async function loadTranslations(appName: string) {
try {
// Making a GET request using Axios
// const response = await axios.get(url);
const response = await import(`@/apps/settings/l18n/${locale}`);
const response = await import(`@/apps/settings/l18n/${locale}.js`);
// Check if the response data contains translations
const bundle = response.default;
if (typeof bundle.translations === 'object') {

View file

@ -22,6 +22,7 @@
<input type="hidden" id="initial-state-settings-cronErrors" value="IiI=">
<input type="hidden" id="initial-state-settings-cliBasedCronPossible" value="dHJ1ZQ==">
<input type="hidden" id="initial-state-settings-cliBasedCronUser" value="Ind3dy1kYXRhIg==">
@vite(['resources/js/app.ts'])
@routes('test')
</head>
@ -29,8 +30,8 @@
<body>
@inertia({ as: 'div', class: 'h-full' })
@entryPointStyles('app')
@entryPointScripts('app')
{{-- @entryPointStyles('app')
@entryPointScripts('app') --}}
</body>
</html>

View file

@ -11,39 +11,37 @@
| and hence do not rename or move this file to a different location.
|
*/
import { Env } from "@adonisjs/core/env"
import { Env } from '@adonisjs/core/env';
export default await Env.create(new URL("../", import.meta.url), {
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
CACHE_VIEWS: Env.schema.boolean(),
SESSION_DRIVER: Env.schema.enum(["cookie", "memory"] as const),
export default await Env.create(new URL('../', import.meta.url), {
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
CACHE_VIEWS: Env.schema.boolean(),
SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const),
DRIVE_DISK: Env.schema.enum(['local'] as const),
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
DB_CONNECTION: Env.schema.string(),
PG_HOST: Env.schema.string({ format: 'host' }),
PG_PORT: Env.schema.number(),
PG_USER: Env.schema.string(),
PG_PASSWORD: Env.schema.string.optional(),
PG_DB_NAME: Env.schema.string(),
DRIVE_DISK: Env.schema.enum(['local'] as const),
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
DB_CONNECTION: Env.schema.string(),
PG_HOST: Env.schema.string({ format: 'host' }),
PG_PORT: Env.schema.number(),
PG_USER: Env.schema.string(),
PG_PASSWORD: Env.schema.string.optional(),
PG_DB_NAME: Env.schema.string(),
REDIS_HOST: Env.schema.string({ format: 'host' }),
REDIS_PORT: Env.schema.number(),
REDIS_HOST: Env.schema.string({ format: 'host' }),
REDIS_PORT: Env.schema.number(),
HASH_DRIVER: Env.schema.enum(["scrypt", "argon", "bcrypt", "laravel", undefined] as const),
OAI_LIST_SIZE: Env.schema.number(),
HASH_DRIVER: Env.schema.enum(['scrypt', 'argon', 'bcrypt', 'laravel', undefined] as const),
OAI_LIST_SIZE: Env.schema.number(),
/*
/*
|----------------------------------------------------------
| Variables for configuring the mail package
|----------------------------------------------------------
*/
SMTP_HOST: Env.schema.string.optional(),
SMTP_PORT: Env.schema.string.optional(),
RESEND_API_KEY: Env.schema.string.optional()
})
SMTP_HOST: Env.schema.string.optional(),
SMTP_PORT: Env.schema.string.optional(),
RESEND_API_KEY: Env.schema.string.optional(),
});

View file

@ -24,8 +24,8 @@ server.use([
() => import('@adonisjs/static/static_middleware'),
// () => import('@adonisjs/cors/cors_middleware'),
() => import('@adonisjs/inertia/inertia_middleware'),
() => import('@adonisjs/vite/vite_middleware'),
]);
/**
* The router middleware stack runs middleware on all the HTTP
* requests with a registered route.

View file

@ -41,150 +41,361 @@ import db from '@adonisjs/lucid/services/db'; // Import the DB service
router.get('/health', ({ response }: HttpContext) => response.noContent());
// OAI routes
router.group(() => {
router.get('/oai', [OaiController, 'index']).as('get');
router.post('/oai', [OaiController, 'index']).as('post');
}).as('oai');
router
.group(() => {
router.get('/oai', [OaiController, 'index']).as('get');
router.post('/oai', [OaiController, 'index']).as('post');
})
.as('oai');
// Welcome route
router.get('/welcome', async ({ view }: HttpContext) => {
return view.render('welcome');
}).as('welcome');
router
.get('/welcome', async ({ view }: HttpContext) => {
return view.render('welcome');
})
.as('welcome');
// Dashboard route
router.get('/', async ({ response }: HttpContext) => {
return response.redirect().toRoute('apps.dashboard');
}).as('dashboard');
router
.get('/', async ({ response }: HttpContext) => {
return response.redirect().toRoute('apps.dashboard');
})
.as('dashboard');
// Apps group
router.group(() => {
router.get('/dashboard', async ({ inertia }: HttpContext) => {
return inertia.render('Dashboard');
}).as('dashboard');
router
.group(() => {
router
.get('/dashboard', async ({ inertia }: HttpContext) => {
return inertia.render('Dashboard');
})
.as('dashboard');
router.get('/map', async ({ inertia }: HttpContext) => {
return inertia.render('Map');
}).as('map');
router
.get('/map', async ({ inertia }: HttpContext) => {
return inertia.render('Map');
})
.as('map');
router.get('/', async ({ inertia }: HttpContext) => {
const users = await User.query().orderBy('login');
return inertia.render('App', {
testing: 'this is a test',
users: users,
});
}).as('index');
router
.get('/', async ({ inertia }: HttpContext) => {
const users = await User.query().orderBy('login');
return inertia.render('App', {
testing: 'this is a test',
users: users,
});
})
.as('index');
router.get('/register', async ({ inertia }: HttpContext) => {
return inertia.render('register-view/register-view-component');
}).as('register.show');
router
.get('/register', async ({ inertia }: HttpContext) => {
return inertia.render('register-view/register-view-component');
})
.as('register.show');
router.post('/register', async ({ request, response }: HttpContext) => {
const data = await request.validateUsing(authValidator);
return response.redirect().toRoute('app.index');
}).as('register.store');
}).prefix('apps').as('apps').use(middleware.auth());
router
.post('/register', async ({ request, response }: HttpContext) => {
await request.validateUsing(authValidator);
return response.redirect().toRoute('app.index');
})
.as('register.store');
})
.prefix('apps')
.as('apps')
.use(middleware.auth());
// Auth routes
router.get('/app/login', ({ inertia }: HttpContext) => {
return inertia.render('Auth/Login');
}).as('app.login.show');
router
.get('/app/login', ({ inertia }: HttpContext) => {
return inertia.render('Auth/Login');
})
.as('app.login.show');
router.post('/app/login', [AuthController, 'login']).as('login.store');
router.post('/app/twoFactorChallenge', [AuthController, 'twoFactorChallenge']).as('login.twoFactorChallenge');
router.post('/signout', [AuthController, 'logout']).as('logout');
// Administrator routes
router.group(() => {
router.get('/settings', async ({ inertia }: HttpContext) => {
const updatedConfigValue = await db.from('appconfigs')
.select('configvalue')
.where('appid', 'backgroundjob')
.where('configkey', 'lastjob')
.first();
return inertia.render('Admin/Settings', {
lastCron: updatedConfigValue?.configvalue || '',
});
}).as('overview');
router
.group(() => {
router
.get('/settings', async ({ inertia }: HttpContext) => {
const updatedConfigValue = await db
.from('appconfigs')
.select('configvalue')
.where('appid', 'backgroundjob')
.where('configkey', 'lastjob')
.first();
return inertia.render('Admin/Settings', {
lastCron: updatedConfigValue?.configvalue || '',
});
})
.as('overview');
router.post('/mail/store', [MailSettingsController, 'setMailSettings']).as('mail.store').use(middleware.can(['user-create']));
router.post('/mail/send', [MailSettingsController, 'sendTestMail']).as('mail.send').use(middleware.can(['user-create']));
router
.post('/mail/store', [MailSettingsController, 'setMailSettings'])
.as('mail.store')
.use(middleware.can(['user-create']));
router
.post('/mail/send', [MailSettingsController, 'sendTestMail'])
.as('mail.send')
.use(middleware.can(['user-create']));
// User routes
router.get('/user', [AdminuserController, 'index']).as('user.index').use(middleware.can(['user-list']));
router.get('/user/create', [AdminuserController, 'create']).as('user.create').use(middleware.can(['user-create']));
router.post('/user/store', [AdminuserController, 'store']).as('user.store').use(middleware.can(['user-create']));
router.get('/user/:id', [AdminuserController, 'show']).as('user.show').where('id', router.matchers.number());
router.get('/user/:id/edit', [AdminuserController, 'edit']).as('user.edit').where('id', router.matchers.number()).use(middleware.can(['user-edit']));
router.put('/user/:id/update', [AdminuserController, 'update']).as('user.update').where('id', router.matchers.number()).use(middleware.can(['user-edit']));
router.delete('/user/:id', [AdminuserController, 'destroy']).as('user.destroy').where('id', router.matchers.number()).use(middleware.can(['user-delete']));
// User routes
router
.get('/user', [AdminuserController, 'index'])
.as('user.index')
.use(middleware.can(['user-list']));
router
.get('/user/create', [AdminuserController, 'create'])
.as('user.create')
.use(middleware.can(['user-create']));
router
.post('/user/store', [AdminuserController, 'store'])
.as('user.store')
.use(middleware.can(['user-create']));
router.get('/user/:id', [AdminuserController, 'show']).as('user.show').where('id', router.matchers.number());
router
.get('/user/:id/edit', [AdminuserController, 'edit'])
.as('user.edit')
.where('id', router.matchers.number())
.use(middleware.can(['user-edit']));
router
.put('/user/:id/update', [AdminuserController, 'update'])
.as('user.update')
.where('id', router.matchers.number())
.use(middleware.can(['user-edit']));
router
.delete('/user/:id', [AdminuserController, 'destroy'])
.as('user.destroy')
.where('id', router.matchers.number())
.use(middleware.can(['user-delete']));
// Role routes
router.get('/role', [RoleController, 'index']).as('role.index').use(middleware.can(['user-list']));
router.get('/role/create', [RoleController, 'create']).as('role.create').use(middleware.can(['user-create']));
router.post('/role/store', [RoleController, 'store']).as('role.store').use(middleware.can(['user-create']));
router.get('/role/:id', [RoleController, 'show']).as('role.show').where('id', router.matchers.number());
router.get('/role/:id/edit', [RoleController, 'edit']).as('role.edit').where('id', router.matchers.number()).use(middleware.can(['user-edit']));
router.put('/role/:id/update', [RoleController, 'update']).as('role.update').where('id', router.matchers.number()).use(middleware.can(['user-edit']));
// Role routes
router
.get('/role', [RoleController, 'index'])
.as('role.index')
.use(middleware.can(['user-list']));
router
.get('/role/create', [RoleController, 'create'])
.as('role.create')
.use(middleware.can(['user-create']));
router
.post('/role/store', [RoleController, 'store'])
.as('role.store')
.use(middleware.can(['user-create']));
router.get('/role/:id', [RoleController, 'show']).as('role.show').where('id', router.matchers.number());
router
.get('/role/:id/edit', [RoleController, 'edit'])
.as('role.edit')
.where('id', router.matchers.number())
.use(middleware.can(['user-edit']));
router
.put('/role/:id/update', [RoleController, 'update'])
.as('role.update')
.where('id', router.matchers.number())
.use(middleware.can(['user-edit']));
// License routes
router.get('/license', [LicenseController, 'index']).as('license.index');
router.get('/license/:id/down', [LicenseController, 'down']).as('license.down').where('id', router.matchers.number()).use(middleware.can(['settings']));
router.get('/license/:id/up', [LicenseController, 'up']).as('license.up').where('id', router.matchers.number()).use(middleware.can(['settings']));
// License routes
router.get('/license', [LicenseController, 'index']).as('license.index');
router
.get('/license/:id/down', [LicenseController, 'down'])
.as('license.down')
.where('id', router.matchers.number())
.use(middleware.can(['settings']));
router
.get('/license/:id/up', [LicenseController, 'up'])
.as('license.up')
.where('id', router.matchers.number())
.use(middleware.can(['settings']));
// Mimetype routes
router.get('/mimetype', [MimetypeController, 'index']).as('mimetype.index');
router.get('/mimetype/create', [MimetypeController, 'create']).as('mimetype.create').use(middleware.can(['settings']));
router.post('/mimetype/store', [MimetypeController, 'store']).as('mimetype.store').use(middleware.can(['settings']));
router.get('/mimetype/:id/down', [MimetypeController, 'down']).as('mimetype.down').where('id', router.matchers.number()).use(middleware.can(['settings']));
router.get('/mimetype/:id/up', [MimetypeController, 'up']).as('mimetype.up').where('id', router.matchers.number()).use(middleware.can(['settings']));
router.get('/mimetype/:id/delete', [MimetypeController, 'delete']).as('mimetype.delete').use([middleware.auth(), middleware.can(['dataset-delete'])]);
router.delete('/mimetype/:id/deleteStore', [MimetypeController, 'deleteStore']).as('mimetype.deleteStore').use([middleware.auth(), middleware.can(['settings'])]);
}).prefix('admin').as('settings').use([middleware.auth(), middleware.is(['administrator', 'moderator'])]);
// Mimetype routes
router.get('/mimetype', [MimetypeController, 'index']).as('mimetype.index');
router
.get('/mimetype/create', [MimetypeController, 'create'])
.as('mimetype.create')
.use(middleware.can(['settings']));
router
.post('/mimetype/store', [MimetypeController, 'store'])
.as('mimetype.store')
.use(middleware.can(['settings']));
router
.get('/mimetype/:id/down', [MimetypeController, 'down'])
.as('mimetype.down')
.where('id', router.matchers.number())
.use(middleware.can(['settings']));
router
.get('/mimetype/:id/up', [MimetypeController, 'up'])
.as('mimetype.up')
.where('id', router.matchers.number())
.use(middleware.can(['settings']));
router
.get('/mimetype/:id/delete', [MimetypeController, 'delete'])
.as('mimetype.delete')
.use([middleware.auth(), middleware.can(['dataset-delete'])]);
router
.delete('/mimetype/:id/deleteStore', [MimetypeController, 'deleteStore'])
.as('mimetype.deleteStore')
.use([middleware.auth(), middleware.can(['settings'])]);
})
.prefix('admin')
.as('settings')
.use([middleware.auth(), middleware.is(['administrator', 'moderator'])]);
router.get('/settings/user/security', [UserController, 'accountInfo']).as('settings.user').use(middleware.auth());
router.post('/settings/user/store', [UserController, 'accountInfoStore']).as('account.password.store').use(middleware.auth());
// Submitter routes
router.group(() => {
router.get('/dataset', [DatasetController, 'index']).as('dataset.list').use([middleware.auth(), middleware.can(['dataset-list'])]);
router.get('/dataset/create', [DatasetController, 'create']).as('dataset.create').use([middleware.auth(), middleware.can(['dataset-submit'])]);
router.post('/dataset/first/first-step', [DatasetController, 'firstStep']).as('dataset.first.step').use([middleware.auth(), middleware.can(['dataset-submit'])]);
router.post('/dataset/second/second-step', [DatasetController, 'secondStep']).as('dataset.second.step').use([middleware.auth(), middleware.can(['dataset-submit'])]);
router.post('/dataset/second/third-step', [DatasetController, 'thirdStep']).as('dataset.third.step').use([middleware.auth(), middleware.can(['dataset-submit'])]);
router.post('/dataset/submit', [DatasetController, 'store']).as('dataset.submit').use([middleware.auth(), middleware.can(['dataset-submit'])]);
router.get('/dataset/:id/release', [DatasetController, 'release']).as('dataset.release').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-edit'])]);
router.put('/dataset/:id/releaseupdate', [DatasetController, 'releaseUpdate']).as('dataset.releaseUpdate').use([middleware.auth(), middleware.can(['dataset-edit'])]);
router.get('/dataset/:id/edit', [DatasetController, 'edit']).as('dataset.edit').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-edit'])]);
router.put('/dataset/:id/update', [DatasetController, 'update']).as('dataset.update').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-edit'])]);
router.get('/dataset/:id/delete', [DatasetController, 'delete']).as('dataset.delete').use([middleware.auth(), middleware.can(['dataset-delete'])]);
router.put('/dataset/:id/deleteupdate', [DatasetController, 'deleteUpdate']).as('dataset.deleteUpdate').use([middleware.auth(), middleware.can(['dataset-delete'])]);
router.get('/person', [PersonController, 'index']).as('person.index').use([middleware.auth()]);
router.get('/dataset/categorize', ({ inertia }: HttpContext) => {
return inertia.render('Submitter/Dataset/Category');
});
}).prefix('submitter');
router
.group(() => {
router
.get('/dataset', [DatasetController, 'index'])
.as('dataset.list')
.use([middleware.auth(), middleware.can(['dataset-list'])]);
router
.get('/dataset/create', [DatasetController, 'create'])
.as('dataset.create')
.use([middleware.auth(), middleware.can(['dataset-submit'])]);
router
.post('/dataset/first/first-step', [DatasetController, 'firstStep'])
.as('dataset.first.step')
.use([middleware.auth(), middleware.can(['dataset-submit'])]);
router
.post('/dataset/second/second-step', [DatasetController, 'secondStep'])
.as('dataset.second.step')
.use([middleware.auth(), middleware.can(['dataset-submit'])]);
router
.post('/dataset/second/third-step', [DatasetController, 'thirdStep'])
.as('dataset.third.step')
.use([middleware.auth(), middleware.can(['dataset-submit'])]);
router
.post('/dataset/submit', [DatasetController, 'store'])
.as('dataset.submit')
.use([middleware.auth(), middleware.can(['dataset-submit'])]);
router
.get('/dataset/:id/release', [DatasetController, 'release'])
.as('dataset.release')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-edit'])]);
router
.put('/dataset/:id/releaseupdate', [DatasetController, 'releaseUpdate'])
.as('dataset.releaseUpdate')
.use([middleware.auth(), middleware.can(['dataset-edit'])]);
router
.get('/dataset/:id/edit', [DatasetController, 'edit'])
.as('dataset.edit')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-edit'])]);
router
.put('/dataset/:id/update', [DatasetController, 'update'])
.as('dataset.update')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-edit'])]);
router
.get('/dataset/:id/delete', [DatasetController, 'delete'])
.as('dataset.delete')
.use([middleware.auth(), middleware.can(['dataset-delete'])]);
router
.put('/dataset/:id/deleteupdate', [DatasetController, 'deleteUpdate'])
.as('dataset.deleteUpdate')
.use([middleware.auth(), middleware.can(['dataset-delete'])]);
router.get('/person', [PersonController, 'index']).as('person.index').use([middleware.auth()]);
router.get('/dataset/categorize', ({ inertia }: HttpContext) => {
return inertia.render('Submitter/Dataset/Category');
});
})
.prefix('submitter');
// Editor routes
router.group(() => {
router.get('/dataset', [EditorDatasetController, 'index']).as('editor.dataset.list').use([middleware.auth(), middleware.can(['dataset-editor-list'])]);
router.get('dataset/:id/receive', [EditorDatasetController, 'receive']).as('editor.dataset.receive').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-receive'])]);
router.put('dataset/:id/receive', [EditorDatasetController, 'receiveUpdate']).as('editor.dataset.receiveUpdate').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-receive'])]);
router.get('dataset/:id/approve', [EditorDatasetController, 'approve']).as('editor.dataset.approve').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-approve'])]);
router.put('dataset/:id/approve', [EditorDatasetController, 'approveUpdate']).as('editor.dataset.approveUpdate').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-approve'])]);
router.get('dataset/:id/reject', [EditorDatasetController, 'reject']).as('editor.dataset.reject').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-editor-reject'])]);
router.put('dataset/:id/reject', [EditorDatasetController, 'rejectUpdate']).as('editor.dataset.rejectUpdate').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-editor-reject'])]);
router.get('dataset/:id/publish', [EditorDatasetController, 'publish']).as('editor.dataset.publish').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-publish'])]);
router.put('dataset/:id/publish', [EditorDatasetController, 'publishUpdate']).as('editor.dataset.publishUpdate').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-publish'])]);
router.get('dataset/:id/doi', [EditorDatasetController, 'doiCreate']).as('editor.dataset.doi').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-publish'])]);
router.put('dataset/:publish_id/doi', [EditorDatasetController, 'doiStore']).as('editor.dataset.doiStore').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-publish'])]);
router.put('/dataset/:id/update', [EditorDatasetController, 'update']).as('editor.dataset.update').use([middleware.auth(), middleware.can(['dataset-editor-edit'])]);
}).prefix('editor');
router
.group(() => {
router
.get('/dataset', [EditorDatasetController, 'index'])
.as('editor.dataset.list')
.use([middleware.auth(), middleware.can(['dataset-editor-list'])]);
router
.get('dataset/:id/receive', [EditorDatasetController, 'receive'])
.as('editor.dataset.receive')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-receive'])]);
router
.put('dataset/:id/receive', [EditorDatasetController, 'receiveUpdate'])
.as('editor.dataset.receiveUpdate')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-receive'])]);
router
.get('dataset/:id/approve', [EditorDatasetController, 'approve'])
.as('editor.dataset.approve')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-approve'])]);
router
.put('dataset/:id/approve', [EditorDatasetController, 'approveUpdate'])
.as('editor.dataset.approveUpdate')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-approve'])]);
router
.get('dataset/:id/reject', [EditorDatasetController, 'reject'])
.as('editor.dataset.reject')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-editor-reject'])]);
router
.put('dataset/:id/reject', [EditorDatasetController, 'rejectUpdate'])
.as('editor.dataset.rejectUpdate')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-editor-reject'])]);
router
.get('dataset/:id/publish', [EditorDatasetController, 'publish'])
.as('editor.dataset.publish')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-publish'])]);
router
.put('dataset/:id/publish', [EditorDatasetController, 'publishUpdate'])
.as('editor.dataset.publishUpdate')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-publish'])]);
router
.get('dataset/:id/doi', [EditorDatasetController, 'doiCreate'])
.as('editor.dataset.doi')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-publish'])]);
router
.put('dataset/:publish_id/doi', [EditorDatasetController, 'doiStore'])
.as('editor.dataset.doiStore')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-publish'])]);
router
.put('/dataset/:id/update', [EditorDatasetController, 'update'])
.as('editor.dataset.update')
.use([middleware.auth(), middleware.can(['dataset-editor-edit'])]);
})
.prefix('editor');
// Reviewer routes
router.group(() => {
router.get('/dataset', [ReviewerDatasetController, 'index']).as('reviewer.dataset.list').use([middleware.auth(), middleware.can(['dataset-review-list'])]);
router.get('dataset/:id/review', [ReviewerDatasetController, 'review']).as('reviewer.dataset.review').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-review'])]);
router.put('dataset/:id/review', [ReviewerDatasetController, 'reviewUpdate']).as('reviewer.dataset.reviewUpdate').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-review'])]);
router.get('dataset/:id/reject', [ReviewerDatasetController, 'reject']).as('reviewer.dataset.reject').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-review-reject'])]);
router.put('dataset/:id/reject', [ReviewerDatasetController, 'rejectUpdate']).as('reviewer.dataset.rejectUpdate').where('id', router.matchers.number()).use([middleware.auth(), middleware.can(['dataset-review-reject'])]);
}).prefix('reviewer');
router
.group(() => {
router
.get('/dataset', [ReviewerDatasetController, 'index'])
.as('reviewer.dataset.list')
.use([middleware.auth(), middleware.can(['dataset-review-list'])]);
router
.get('dataset/:id/review', [ReviewerDatasetController, 'review'])
.as('reviewer.dataset.review')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-review'])]);
router
.put('dataset/:id/review', [ReviewerDatasetController, 'reviewUpdate'])
.as('reviewer.dataset.reviewUpdate')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-review'])]);
router
.get('dataset/:id/reject', [ReviewerDatasetController, 'reject'])
.as('reviewer.dataset.reject')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-review-reject'])]);
router
.put('dataset/:id/reject', [ReviewerDatasetController, 'rejectUpdate'])
.as('reviewer.dataset.rejectUpdate')
.where('id', router.matchers.number())
.use([middleware.auth(), middleware.can(['dataset-review-reject'])]);
})
.prefix('reviewer');

View file

@ -6,30 +6,32 @@ import HomeController from '#controllers/Http/Api/HomeController';
import FileController from '#controllers/Http/Api/FileController';
import AvatarController from '#controllers/Http/Api/AvatarController';
import UserController from '#controllers/Http/Api/UserController';
import { middleware } from '../kernel.js'
import { middleware } from '../kernel.js';
// API
router.group(() => {
router
.group(() => {
router.get('authors', [AuthorsController, 'index']).as('author.index');
router.get('datasets', [DatasetController, 'index']).as('dataset.index');
router.get('persons', [AuthorsController, 'persons']).as('author.persons');
router.get('authors', [AuthorsController, "index"]).as('author.index');
router.get('datasets', [DatasetController, "index"]).as('dataset.index');
router.get('persons', [AuthorsController, "persons"]).as('author.persons');
router.get('/dataset', [DatasetController, "findAll"]).as('dataset.findAll');
router.get('/dataset/:publish_id', [DatasetController, "findOne"]).as('dataset.findOne');
router.get('/sitelinks/:year', [HomeController, "findDocumentsPerYear"]);
router.get('/years', [HomeController, "findYears"]);
router.get('/statistic/:year', [HomeController, "findPublicationsPerMonth"]);
router.get('/dataset', [DatasetController, 'findAll']).as('dataset.findAll');
router.get('/dataset/:publish_id', [DatasetController, 'findOne']).as('dataset.findOne');
router.get('/sitelinks/:year', [HomeController, 'findDocumentsPerYear']);
router.get('/years', [HomeController, 'findYears']);
router.get('/statistic/:year', [HomeController, 'findPublicationsPerMonth']);
router.get('/download/:id', [FileController, "findOne"]).as('file.findOne');
router.get('/download/:id', [FileController, 'findOne']).as('file.findOne');
router.get('/avatar/:name/:background?/:textColor?/:size?', [AvatarController, 'generateAvatar']);
router.post('/twofactor_totp/settings/enable/:state/:code?', [UserController, 'enable']).as('apps.twofactor_totp.enable') .use(middleware.auth());
router.post('/twofactor_backupcodes/settings/create', [UserController, 'createCodes']).as('apps.twofactor_backupcodes.create') .use(middleware.auth());
})
router
.post('/twofactor_totp/settings/enable/:state/:code?', [UserController, 'enable'])
.as('apps.twofactor_totp.enable')
.use(middleware.auth());
router
.post('/twofactor_backupcodes/settings/create', [UserController, 'createCodes'])
.as('apps.twofactor_backupcodes.create')
.use(middleware.auth());
})
// .namespace('App/Controllers/Http/Api')
.prefix('api');

View file

@ -9,27 +9,21 @@ import vine from '@vinejs/vine';
// import { VineString } from '@vinejs/vine';
import { VineMultipartFile, isBodyParserFile } from '#providers/vinejs_provider';
import type { MultipartFile } from '@adonisjs/core/bodyparser';
// import db from '@adonisjs/lucid/services/db';
import MimeType from '#models/mime_type';
/**
* Options accepted by the unique rule
*/
// type Options = {
// mainLanguageField: string;
// typeField: string;
// };
type Options = {
// size: string | number;
// extnames: string[];
clientNameSizeLimit: number;
// clientNameSizeLimit?: number;
allowedExtensions: string[];
allowedMimeTypes: string[];
};
async function allowedMimetypeExtensions(file: VineMultipartFile | unknown, options: Options | unknown, field: FieldContext) {
// if (typeof value !== 'string' && typeof value != 'number') {
// return;
// }
// async function allowedMimetypeExtensions(file: VineMultipartFile | unknown, options: Options | unknown, field: FieldContext) {
async function allowedMimetypeExtensions(file: VineMultipartFile | unknown, options: Options, field: FieldContext) {
if (!isBodyParserFile(file)) {
return;
}
@ -38,7 +32,15 @@ async function allowedMimetypeExtensions(file: VineMultipartFile | unknown, opti
const fileExtension = validatedFile?.extname?.toLocaleLowerCase() as string; // Get file extension from the file
// validate if file extension is allowed in combination with mimetype
const mimeRecord = await MimeType.query().select('file_extension').where('name', mimeType).andWhere('enabled', true).first();
let mimeRecord = await MimeType.query().select('file_extension').where('name', mimeType).andWhere('enabled', true).first();
if (!mimeRecord) {
mimeRecord = await MimeType.query()
.select('file_extension')
.whereRaw("? = ANY (string_to_array(alternate_mimetype, '|'))", [mimeType])
.andWhere('enabled', true)
.first();
}
if (!mimeRecord) {
const allowedMimetypes = await MimeType.query().select('name').where('enabled', true);
@ -52,14 +54,19 @@ async function allowedMimetypeExtensions(file: VineMultipartFile | unknown, opti
field,
);
} else {
const allowedExtensions = mimeRecord.file_extension.split('|');
let allowedExtensions: string[] = [];
if (options && options.allowedExtensions) {
allowedExtensions = options.allowedExtensions;
} else {
allowedExtensions = mimeRecord.file_extension.split('|');
}
// Validate if the file's extension is in the allowed extensions
if (!allowedExtensions.includes(fileExtension)) {
//throw new Error(`File extension ${fileExtension} is not allowed for MIME type ${mimeType}`);
field.report(
`File extension ${fileExtension} is not allowed for MIME type ${mimeType}. Allowed extensions are: ${mimeRecord.file_extension}`,
'allowedMimetypeExtensions',
field
'allowedMimetypeExtensions',
field,
);
}
// if (validatedFile.clientName.length > options.clientNameSizeLimit) {

View file

@ -3,37 +3,16 @@
| Preloaded File - node ace make:preload rules/fileScan
|--------------------------------------------------------------------------
|*/
import { FieldContext } from '@vinejs/vine/types';
import vine, { errors } from '@vinejs/vine';
// import { VineString } from '@vinejs/vine';
import { VineMultipartFile, isBodyParserFile } from '#providers/vinejs_provider';
import type { MultipartFile } from '@adonisjs/core/bodyparser';
import ClamScan from 'clamscan';
/**
* Options accepted by the unique rule
*/
// type Options = {
// mainLanguageField: string;
// typeField: string;
// };
type Options = {
// size: string | number;
// extnames: string[];
removeInfected: boolean;
// debugMode?: boolean;
// scanRecursively?: boolean;
host?: string;
port?: number;
// clamdscan: {
// active: boolean;
// host: string;
// port: number;
// multiscan: boolean;
// };
// preference: string;
};
async function fileScan(file: VineMultipartFile | unknown, options: Options, field: FieldContext) {
@ -44,42 +23,47 @@ async function fileScan(file: VineMultipartFile | unknown, options: Options, fie
return;
}
const validatedFile = file as MultipartFile;
try {
await scanFileForViruses(validatedFile.tmpPath, options.host, options.port); //, 'gitea.lan', 3310);
// await this.scanFileForViruses("/tmp/testfile.txt");
await scanFileForViruses(validatedFile.tmpPath, options);
} catch (error) {
// If the file is infected or there's an error scanning the file, throw a validation exception
// throw error;
field.report(`Upload error. Code: ${error.code} message: ${error.messages.uploadError}`, 'fileScan', field);
}
}
async function scanFileForViruses(filePath: string | undefined, host?: string, port?: number): Promise<void> {
// const clamscan = await (new ClamScan().init());
async function scanFileForViruses(filePath: string | undefined, options: Options): Promise<void> {
if (!filePath) {
throw new errors.E_VALIDATION_ERROR({ uploadError: 'File path is undefined!' });
}
const opts: ClamScan.Options = {
removeInfected: true, // If true, removes infected files
debugMode: false, // Whether or not to log info/debug/error msgs to the console
removeInfected: options.removeInfected, // If true, removes infected files
debugMode: false, // If true, deep scan folders recursively
scanRecursively: true, // If true, deep scan folders recursively
clamdscan: {
active: true, // If true, this module will consider using the clamdscan binary
host,
port,
host: options.host,
port: options.port,
multiscan: true, // Scan using all available cores! Yay!
},
preference: 'clamdscan', // If clamdscan is found and active, it will be used by default
};
const clamscan = await new ClamScan().init(opts);
return new Promise(async (resolve, reject) => {
try {
const clamscan = await new ClamScan().init(opts);
// You can re-use the `clamscan` object as many times as you want
// const version = await clamscan.getVersion();
// console.log(`ClamAV Version: ${version}`);
const { file, isInfected, viruses } = await clamscan.isInfected(filePath);
const result = await clamscan.isInfected(filePath);
if (!result || typeof result.isInfected === 'undefined') {
reject(new errors.E_VALIDATION_ERROR({ uploadError: 'Unexpected response from virus scan!' }));
return;
}
const { file, isInfected, viruses } = result;
if (isInfected) {
console.log(`${file} is infected with ${viruses}!`);
// reject(new ValidationException(true, { 'upload error': `File ${file} is infected!` }));
reject(new errors.E_VALIDATION_ERROR({ uploadError: `File ${file} is infected!` }));
console.log(`${file} is infected with ${viruses}!`); // reject(new ValidationException(true, { 'upload error': `File ${file} is infected!` }));
reject(new errors.E_VALIDATION_ERROR({ uploadError: `File ${file} is infected with ${viruses}!` }));
} else {
resolve();
}

View file

@ -0,0 +1,115 @@
import { FieldContext } from '@vinejs/vine/types';
import vine from '@vinejs/vine';
import { VineString } from '@vinejs/vine';
import { default as axios } from 'axios';
import { ReferenceIdentifierTypes } from '#contracts/enums';
type Options = {
typeField: string;
};
// Function to check if DOI exists using the DOI API
async function checkDoiExists(doi: string): Promise<boolean> {
try {
const response = await axios.get(`${doi}`);
return response.status === 200; // If status is 200, DOI is valid
} catch (error) {
return false; // If request fails, DOI does not exist
}
}
// Function to check if ISBN exists using the Open Library API
async function checkIsbnExists(isbn: string): Promise<boolean> {
try {
const response = await axios.get(`https://isbnsearch.org/isbn/${isbn}`);
return response.status === 200 && response.data.includes('ISBN'); // Check if response contains ISBN information
} catch (error) {
return false; // If request fails, ISBN does not exist
}
}
async function validateReference(value: unknown, options: Options, field: FieldContext) {
if (typeof value !== 'string') {
return;
}
const type = field.parent[options.typeField];
if (type === ReferenceIdentifierTypes.URL) {
if (!/^https?:\/\/[^\s$.?#].[^\s]*$/.test(value)) {
field.report('The {{ field }} must be a valid URL', 'validateReference', field);
} else {
try {
const exists = await checkDoiExists(value);
if (!exists) {
field.report('The {{ field }} must be an existing DOI', 'validateReference', field);
}
} catch (error) {
field.report('Error checking DOI existence: ' + error.message, 'validateReference', field);
}
}
}
// Check if the value does not match the DOI pattern
// The regex pattern ^10.\d{4,9}\/[-._;()/:a-zA-Z0-9]+$ is designed to match valid DOI formats.
// - ^10. ensures that the string starts with "10."
// - \d{4,9} matches a sequence of 4 to 9 digits.
// - \/ matches the literal forward slash character.
// - [-._;()/:a-zA-Z0-9]+ matches one or more characters that can include uppercase and lowercase letters, digits, and a set of special characters.
// The i flag at the end of the regex makes the matching case-insensitive, meaning it will match both uppercase and lowercase letters.
// If the value does not match this pattern, the code inside the if block will execute.
else if (type === ReferenceIdentifierTypes.DOI) {
// Extract the DOI from the URL if it starts with 'https://doi.org/'
const doiPattern = /^https:\/\/doi\.org\/(10.\d{4,9}\/[-._;()/:a-zA-Z0-9]+)$/i;
const match = value.match(doiPattern);
const doi = match ? match[1] : value;
// Check if the extracted DOI or the value itself matches the DOI patter
if (!/^10.\d{4,9}\/[-._;()/:a-zA-Z0-9]+$/i.test(doi)) {
field.report('The {{ field }} must be a valid DOI', 'validateReference', field);
} else {
try {
const exists = await checkDoiExists(value);
if (!exists) {
field.report('The {{ field }} must be an existing DOI', 'validateReference', field);
}
} catch (error) {
field.report('Error checking DOI existence: ' + error.message, 'validateReference', field);
}
}
} else if (type === ReferenceIdentifierTypes.ISBN) {
const isbnRegex = /^(?:\d{1,5}-\d{1,7}-\d{1,7}-[\dX]$|97[89]-\d{1,5}-\d{1,7}-\d{1,7}-\d)$/;
if (!isbnRegex.test(value)) {
field.report('Invalid {{ field }}. Expected format: 978-3-16-148410-0 or similar.', 'validateReference', field);
} else {
try {
const exists = await checkIsbnExists(value);
if (!exists) {
field.report('The {{ field }} must be an existing ISBN', 'validateReference', field);
}
} catch (error) {
field.report('Error checking ISBN existence: ' + error.message, 'validateReference', field);
}
}
} else if (type === ReferenceIdentifierTypes.Handle && !/^\d{2,}.\d{4,9}\/[-._;()/:a-zA-Z0-9]+$/.test(value)) {
/// Extract the Handle from the URL if it contains '/handle/'
field.report('The {{ field }} must be a valid Handle', 'validateReference', field);
} else if (
type === ReferenceIdentifierTypes.URN &&
!/^urn:[a-zA-Z0-9][a-zA-Z0-9-]{0,31}:[a-zA-Z0-9()+,\-.:=@;$_!*'%/?#]+$/.test(value)
) {
field.report('The {{ field }} must be a valid URN', 'validateReference', field);
} else if (type === ReferenceIdentifierTypes.ISSN && !/^\d{4}-\d{3}[\dxX]$/.test(value)) {
field.report('The {{ field }} must be a valid ISSN', 'validateReference', field);
}
}
export const validateReferenceRule = vine.createRule(validateReference);
declare module '@vinejs/vine' {
interface VineString {
validateReference(options: Options): this;
}
}
VineString.macro('validateReference', function (this: VineString, options: Options) {
return this.use(validateReferenceRule(options));
});

View file

@ -17,7 +17,7 @@ type Options = {
};
async function translatedLanguage(value: unknown, options: Options, field: FieldContext) {
if (typeof value !== 'string' && typeof value != 'number') {
if (typeof value !== 'string' && typeof value !== 'number') {
return;
}

View file

@ -15,11 +15,11 @@ import { VineString, VineNumber } from '@vinejs/vine';
type Options = {
table: string;
column: string;
whereNot?: ((field: FieldContext) => string);
whereNot?: (field: FieldContext) => string;
};
async function isUnique(value: unknown, options: Options, field: FieldContext) {
if (typeof value !== 'string' && typeof value != 'number') {
if (typeof value !== 'string' && typeof value !== 'number') {
return;
}
@ -37,13 +37,11 @@ async function isUnique(value: unknown, options: Options, field: FieldContext) {
// report that value is NOT unique
field.report('The {{ field }} field is not unique', 'isUnique', field);
// field.report(messages.unique, "isUnique", field);
}
}
}
export const isUniqueRule = vine.createRule(isUnique);
declare module '@vinejs/vine' {
interface VineString {
isUnique(options: Options): this;
@ -58,4 +56,4 @@ VineString.macro('isUnique', function (this: VineString, options: Options) {
});
VineNumber.macro('isUnique', function (this: VineNumber, options: Options) {
return this.use(isUniqueRule(options));
});
});

View file

@ -19,9 +19,8 @@ type Options = {
idField: string;
};
async function isUniquePerson(value: unknown, options: Options, field: FieldContext) {
if (typeof value !== 'string' && typeof value != 'number') {
if (typeof value !== 'string' && typeof value !== 'number') {
return;
}
@ -40,13 +39,11 @@ async function isUniquePerson(value: unknown, options: Options, field: FieldCont
if (result) {
// report that value is NOT unique
field.report('The {{ field }} field is not unique', 'isUnique', field);
}
}
}
export const isUniquePersonRule = vine.createRule(isUniquePerson);
declare module '@vinejs/vine' {
interface VineString {
isUniquePerson(options: Options): this;
@ -61,4 +58,4 @@ VineString.macro('isUniquePerson', function (this: VineString, options: Options)
});
VineNumber.macro('isUniquePerson', function (this: VineNumber, options: Options) {
return this.use(isUniquePersonRule(options));
});
});

View file

@ -0,0 +1,29 @@
import { FieldContext } from '@vinejs/vine/types';
import vine from '@vinejs/vine';
import { VineString } from '@vinejs/vine';
async function isValidMimetype(value: unknown, options: unknown, field: FieldContext) {
if (typeof value !== 'string') {
return;
}
// Regex pattern to match valid mimetypes (e.g., "application/json", "text/html")
const mimetypePattern = /^[a-zA-Z0-9!#$&^_.+-]+\/[a-zA-Z0-9!#$&^_.+-]+$/;
if (!mimetypePattern.test(value)) {
field.report('The given value is not a valid mimetype', 'isValidMimetype', field);
}
}
export const isValidMimetypeRule = vine.createRule(isValidMimetype);
declare module '@vinejs/vine' {
interface VineString {
isValidMimetype(): this;
}
}
VineString.macro('isValidMimetype', function (this: VineString) {
return this.use(isValidMimetypeRule());
});

View file

@ -13,20 +13,18 @@ import vine from '@vinejs/vine';
// import db from '@adonisjs/lucid/services/db';
import { VanillaErrorReporter } from '#validators/vanilla_error_reporter';
// vine.messagesProvider = new SimpleMessagesProvider({
// // Applicable for all fields
// 'required': 'The {{ field }} field is required',
// '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 {{ options.minLength }} permission must be defined',
// 'permissions.*.number': 'Define permissions as valid numbers',
// })
vine.errorReporter = () => new VanillaErrorReporter();
// /**
// * Options accepted by the unique rule

View file

@ -12,10 +12,10 @@ import testUtils from '@adonisjs/core/services/test_utils';
// import { assert, runFailedTests, specReporter, apiClient } from '@japa/preset-adonis';
import { assert } from '@japa/assert';
import { apiClient } from '@japa/api-client';
// import { apiClient } from '@japa/api-client';
import { pluginAdonisJS } from '@japa/plugin-adonisjs';
import app from '@adonisjs/core/services/app';
// import env from '#start/env'
/*
|--------------------------------------------------------------------------
| Japa Plugins
@ -28,7 +28,13 @@ import app from '@adonisjs/core/services/app';
|
*/
// export const plugins: Required<Config>['plugins'] = [assert(), runFailedTests(), apiClient()];
export const plugins: Config['plugins'] = [assert(), apiClient(), pluginAdonisJS(app)];
export const plugins: Config['plugins'] = [
assert(),
// apiClient({
// baseURL: `http://${env.get('HOST')}:${env.get('PORT')}`,
// }),
pluginAdonisJS(app),
];
/*
|--------------------------------------------------------------------------
@ -59,11 +65,18 @@ export const reporters: Required<Config>['reporters'] = {
*/
export const runnerHooks: Pick<Required<Config>, 'setup' | 'teardown'> = {
setup: [
() => {
console.log('running before all the tests');
},
// () => testUtils.ace().loadCommands(),
() => testUtils.db().migrate(),
// () => testUtils.httpServer().start(),
],
teardown: [],
teardown: [
() => {
console.log('running after all the tests');
},
],
};
/*

View file

@ -8,7 +8,7 @@ import User from '#models/user';
import Role from '#models/role';
import Permission from '#models/permission';
import { TestContext } from '@japa/runner/core';
const BASE_URL = `http://${process.env.HOST}:${process.env.PORT}`
const BASE_URL = `http://${process.env.HOST}:${process.env.PORT}`;
test.group('DatasetController', (group) => {
// Write your test here
@ -24,8 +24,6 @@ test.group('DatasetController', (group) => {
// server = await supertest(BASE_URL);
// });
test('should render dataset release page', async ({ assert }: TestContext) => {
var testAgent = supertest(BASE_URL);
@ -35,19 +33,18 @@ test.group('DatasetController', (group) => {
email: 'alice@email.com',
password: 'password',
});
const role = await Role.create({
name: 'administrator',
display_name: 'admin',
description: 'User has access to all system functionality'
description: 'User has access to all system functionality',
});
await user.related('roles').attach([role.id]);
const permission = await Permission.create({
name: 'dataset-edit',
display_name: 'edit dataset',
description: 'allow role to edit datasets'
description: 'allow role to edit datasets',
});
await role.related('permissions').attach([permission.id]);

View file

@ -1,8 +1,8 @@
import { test } from '@japa/runner';
// import { test } from '@japa/runner';
test('display welcome page', async ({ client }) => {
const response = await client.get('/welcome');
// test('display welcome page', async ({ client }) => {
// const response = await client.get('/welcome');
response.assertStatus(200);
response.assertTextIncludes('<h1 class="title"> It Works! </h1>');
});
// response.assertStatus(200);
// response.assertTextIncludes('<h1 class="title"> It Works! </h1>');
// });

View file

@ -0,0 +1,215 @@
import { test } from '@japa/runner';
import { ReferenceIdentifierTypes } from '#contracts/enums';
import vine from '@vinejs/vine';
// node ace test functional --groups "ReferenceValidation"
test.group('ReferenceValidation', () => {
test('validate valid DOI', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: 'https://doi.org/10.24341/tethys.236',
type: ReferenceIdentifierTypes.DOI,
};
try {
const payload = await validator.validate(data);
assert.deepEqual(payload, data);
} catch {
assert.isTrue(false);
}
});
test('validate invalid DOI', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: 'https://doi.org/invalid-doi',
type: ReferenceIdentifierTypes.DOI,
};
let payload = {};
try {
payload = await validator.validate(data);
} catch {
assert.notDeepEqual(payload, data);
}
});
test('validate valid Handle', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: '20.5000/abc123',
type: ReferenceIdentifierTypes.Handle,
};
try {
const payload = await validator.validate(data);
assert.deepEqual(payload, data);
} catch {
assert.isTrue(false);
}
});
test('validate valid ISBN', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
// reference: '978-3-85316-090-9',
// reference: '9783853160909',
// reference: '978-3-900312-64-0', // Geologische Karte der Republik Österreich 1 : 50.000
reference: '3-90031-264-8', // Geologische Karte der Republik Österreich 1 : 50.000
type: ReferenceIdentifierTypes.ISBN,
};
try {
const payload = await validator.validate(data);
assert.deepEqual(payload, data);
} catch {
assert.isTrue(false);
}
});
test('validate invalid ISBN', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: 'invalid-isbn',
type: ReferenceIdentifierTypes.ISBN,
};
let payload = {};
try {
payload = await validator.validate(data);
} catch {
assert.notDeepEqual(payload, data);
}
});
test('validate valid URN', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: 'urn:isbn:0451450523',
type: ReferenceIdentifierTypes.URN,
};
try {
const payload = await validator.validate(data);
assert.deepEqual(payload, data);
} catch {
assert.isTrue(false);
}
});
test('validate invalid URN', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: 'invalid-urn',
type: ReferenceIdentifierTypes.URN,
};
let payload = {};
try {
payload = await validator.validate(data);
} catch {
assert.notDeepEqual(payload, data);
}
});
test('validate valid ISSN', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: '1234-567X',
type: ReferenceIdentifierTypes.ISSN,
};
try {
const payload = await validator.validate(data);
assert.deepEqual(payload, data);
} catch {
assert.isTrue(false);
}
});
test('validate invalid ISSN', async ({ assert }) => {
const validator = vine.compile(
vine.object({
reference: vine.string().validateReference({ typeField: 'type' }),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
}),
);
const data = {
reference: 'invalid-issn',
type: ReferenceIdentifierTypes.ISSN,
};
let payload = {};
try {
payload = await validator.validate(data);
} catch {
assert.notDeepEqual(payload, data);
}
});
// test('validate invalid Handle', async ({ assert }) => {
// const validator = vine.compile(
// vine.object({
// reference: vine.string().validateReference({ typeField: 'type' }),
// type: vine.enum(Object.values(ReferenceIdentifierTypes)),
// })
// );
// const data = {
// reference: 'invalid-handle',
// type: ReferenceIdentifierTypes.Handle,
// };
// const result = await validator.validate(data);
// assert.isFalse(result.valid);
// });
// Add more tests for other reference types as needed
});

View file

@ -10,8 +10,8 @@
"node_modules",
"build",
"public",
"./resources/js/**/*.vue",
"./resources/js/**/*.ts"
"./resources/js/**/*",
// "./resources/js/**/*.ts"
],
// "exclude": ["./inertia/**/*"]
"compilerOptions": {
@ -19,9 +19,10 @@
"outDir": "build",
"rootDir": "./",
"sourceMap": true,
"sourceRoot": "./build",
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"target": "esnext",
"target": "esnext", // Update to a version that supports top-level await
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,

78
vite.config.ts Normal file
View file

@ -0,0 +1,78 @@
import { defineConfig } from 'vite';
import adonisjs from '@adonisjs/vite/client';
// import { getDirname } from '@adonisjs/core/helpers';
import vue from '@vitejs/plugin-vue';
import path from 'path';
// import tailwind from '@tailwindcss/postcss';
// import autoprefixer from 'autoprefixer';
// import postcssNesting from 'postcss-nesting';
import inertia from '@adonisjs/inertia/client';
export default defineConfig({
plugins: [
inertia(),
vue(),
adonisjs({
/**
* Entrypoints of your application. Each entrypoint will
* result in a separate bundle.
*/
entrypoints: ['resources/js/app.ts', 'resources/css/app.css'],
/**
* Paths to watch and reload the browser on file change
*/
reload: ['resources/views/**/*.edge'],
}),
],
server: {
port: 5173,
// host: '127.0.0.1'
},
// css: {
// postcss: {
// plugins: [
// postcssNesting(),
// tailwind(),
// autoprefixer(),
// ],
// },
// },
/**
* Define aliases for importing modules from
* your frontend code
*/
resolve: {
alias: {
'@': path.resolve('./resources/js/'),
'~': path.resolve(__dirname, 'node_modules/'),
},
},
// optimizeDeps: {
// esbuildOptions: {
// target: 'esnext'
// },
// include: ['resources/js/**/*.{vue,js,jsx,ts,tsx}'],
// exclude: ['node_modules', 'app'],
// },
build: {
sourcemap: true,
outDir: 'public/assets',
emptyOutDir: true,
manifest: true,
rollupOptions: {
input: 'resources/js/app.ts',
},
},
// build: {
// outDir: 'public/assets',
// emptyOutDir: true,
// manifest: true,
// rollupOptions: {
// input: path.resolve(__dirname, 'resources/js/app.ts'),
// },
// },
});

View file

@ -302,18 +302,18 @@ Encore.addLoader({
// vue$: 'vue/dist/vue.runtime.esm-bundler.js',
// });
Encore.addLoader(babelLoader)
// Encore.enableTypeScriptLoader(config => {
// // Loader-specific options
// config.configFile = 'resources/js/tsconfig.json';
// config.appendTsSuffixTo = [/\.vue$/];
// config.transpileOnly = true;
// config.happyPackMode = false;
// }, {
// // Directly change the exclude rule
// exclude: /node_modules/,
// Encore.addLoader(babelLoader)
Encore.enableTypeScriptLoader(config => {
// Loader-specific options
config.configFile = 'resources/js/tsconfig.json';
config.appendTsSuffixTo = [/\.vue$/];
config.transpileOnly = true;
config.happyPackMode = false;
}, {
// Directly change the exclude rule
exclude: /node_modules/,
// })
})
.addAliases({
'@': join(__dirname, 'resources/js'),
'vue$': 'vue/dist/vue.runtime.esm-bundler.js',