- added LicenseController.ts and MimetypeController for enabling mime_types and licences
Some checks failed
CI Pipeline / japa-tests (push) Failing after 58s

- add new authors and contributors only by unique email addresses
- allow multiple file upload
- added validation rule for validating length of uploaded files
- modified Dockerfile for starting "bin/server.js" instead of *server.js"
- npm updates
This commit is contained in:
Kaimbacher 2024-06-14 12:38:04 +02:00
parent 770e791613
commit ac473b1e72
27 changed files with 1720 additions and 914 deletions

View File

@ -89,4 +89,4 @@ COPY --chown=node:node --from=build /home/node/app/build .
EXPOSE 3333 EXPOSE 3333
ENTRYPOINT ["/home/node/app/docker-entrypoint.sh"] ENTRYPOINT ["/home/node/app/docker-entrypoint.sh"]
# Run the command to start the server using "dumb-init" # Run the command to start the server using "dumb-init"
CMD [ "dumb-init", "node", "server.js" ] CMD [ "dumb-init", "node", "bin/server.js" ]

View File

@ -27,7 +27,9 @@ export default defineConfig({
() => import('./start/kernel.js'), () => import('./start/kernel.js'),
() => import('#start/validator'), () => import('#start/validator'),
() => import('#start/rules/unique'), () => import('#start/rules/unique'),
() => import('#start/rules/translated_language') () => import('#start/rules/translated_language'),
() => import('#start/rules/unique_person'),
() => import('#start/rules/file_length')
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -64,7 +66,8 @@ export default defineConfig({
() => import('#providers/token_worker_provider'), () => import('#providers/token_worker_provider'),
// () => import('#providers/validator_provider'), // () => import('#providers/validator_provider'),
() => import('#providers/drive/provider/drive_provider'), () => import('#providers/drive/provider/drive_provider'),
() => import('@adonisjs/core/providers/vinejs_provider') // () => import('@adonisjs/core/providers/vinejs_provider'),
() => import('#providers/vinejs_provider')
], ],
metaFiles: [ metaFiles: [
{ {

View File

@ -134,7 +134,7 @@ export default class AdminuserController {
// validate update form // validate update form
await request.validateUsing(updateUserValidator, { await request.validateUsing(updateUserValidator, {
meta: { meta: {
userId: user.id, objId: user.id,
}, },
}); });
@ -166,8 +166,8 @@ export default class AdminuserController {
return response.redirect().toRoute('settings.user.index'); return response.redirect().toRoute('settings.user.index');
} }
// private async syncRoles(userId: number, roleIds: Array<number>) { // private async syncRoles(objId: number, roleIds: Array<number>) {
// const user = await User.findOrFail(userId) // const user = await User.findOrFail(objId)
// // const roles: Role[] = await Role.query().whereIn('id', roleIds); // // const roles: Role[] = await Role.query().whereIn('id', roleIds);
// // await user.roles().sync(roles.rows.map(role => role.id)) // // await user.roles().sync(roles.rows.map(role => role.id))

View File

@ -0,0 +1,51 @@
import type { HttpContext } from '@adonisjs/core/http';
import License from '#models/license';
export default class LicenseController {
public async index({ auth, inertia }: HttpContext) {
const direction = 'asc'; // or 'desc'
const licenses = await License.query().orderBy('sort_order', direction).exec();
return inertia.render('Admin/License/Index', {
licenses: licenses,
can: {
edit: await auth.user?.can(['settings']),
},
});
}
public async down({ request, response }: HttpContext) {
const id = request.param('id');
const license = await License.findOrFail(id);
license.active = false;
await license.save();
// session.flash({ message: 'person has been deactivated!' });
return response.flash('person has been deactivated!', 'message').toRoute('settings.license.index')
}
public async up({ request, response }: HttpContext) {
const id = request.param('id');
const license = await License.findOrFail(id);
license.active = true;
await license.save();
// session.flash({ message: 'person has been activated!' });
return response.flash('person has been activated!', 'message').toRoute('settings.license.index');
}
// public async edit({ request, inertia }: HttpContext) {
// const id = request.param('id');
// const license = await License.query().where('id', id).firstOrFail();
// // const permissions = await Permission.query().pluck('name', 'id');
// // // const userHasRoles = user.roles;
// // const rolerHasPermissions = await role.related('permissions').query().orderBy('name').pluck('id');
// return inertia.render('Admin/License/Edit', {
// // permissions: permissions,
// license: license,
// // roleHasPermissions: Object.keys(rolerHasPermissions).map((key) => rolerHasPermissions[key]), //convert object to array with role ids
// });
// }
}

View File

@ -0,0 +1,51 @@
import type { HttpContext } from '@adonisjs/core/http';
import MimeType from '#models/mime_type';
export default class MimetypeController {
public async index({ auth, inertia }: HttpContext) {
const direction = 'asc'; // or 'desc'
const mimetypes = await MimeType.query().orderBy('name', direction).exec();
return inertia.render('Admin/Mimetype/Index', {
mimetypes: mimetypes,
can: {
edit: await auth.user?.can(['settings']),
},
});
}
public async down({ request, response }: HttpContext) {
const id = request.param('id');
const mimetype = await MimeType.findOrFail(id);
mimetype.enabled = false;
await mimetype .save();
// session.flash({ message: 'person has been deactivated!' });
return response.flash('mimetype has been deactivated!', 'message').toRoute('settings.mimetype.index')
}
public async up({ request, response }: HttpContext) {
const id = request.param('id');
const mimetype = await MimeType.findOrFail(id);
mimetype.enabled = true;
await mimetype .save();
// session.flash({ message: 'person has been activated!' });
return response.flash('mimetype has been activated!', 'message').toRoute('settings.mimetype.index');
}
// public async edit({ request, inertia }: HttpContext) {
// const id = request.param('id');
// const license = await License.query().where('id', id).firstOrFail();
// // const permissions = await Permission.query().pluck('name', 'id');
// // // const userHasRoles = user.roles;
// // const rolerHasPermissions = await role.related('permissions').query().orderBy('name').pluck('id');
// return inertia.render('Admin/License/Edit', {
// // permissions: permissions,
// license: license,
// // roleHasPermissions: Object.keys(rolerHasPermissions).map((key) => rolerHasPermissions[key]), //convert object to array with role ids
// });
// }
}

View File

@ -216,18 +216,24 @@ export default class DatasetController {
authors: vine authors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
}), }),
) )
.minLength(1), .minLength(1)
.distinct('email'),
contributors: vine contributors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)), pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}), }),
) )
.optional(), .distinct('email')
.optional(),
project_id: vine.number().optional(), project_id: vine.number().optional(),
}); });
@ -287,18 +293,24 @@ export default class DatasetController {
authors: vine authors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
}), }),
) )
.minLength(1), .minLength(1)
.distinct('email'),
contributors: vine contributors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)), pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}), }),
) )
.optional(), .distinct('email')
.optional(),
// third step // third step
project_id: vine.number().optional(), project_id: vine.number().optional(),
// embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]), // embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
@ -631,7 +643,9 @@ export default class DatasetController {
'The language of the translated description must be different from the language of the dataset', 'The language of the translated description must be different from the language of the dataset',
'authors.array.minLength': 'at least {{ min }} author must be defined', 'authors.array.minLength': 'at least {{ min }} author must be defined',
'authors.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined', 'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
'contributors.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`, 'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,

View File

@ -55,18 +55,24 @@ export const createDatasetValidator = vine.compile(
authors: vine authors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
}), }),
) )
.minLength(1), .minLength(1)
.distinct('email'),
contributors: vine contributors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)), pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}), }),
) )
.optional(), .distinct('email')
.optional(),
// third step // third step
project_id: vine.number().optional(), project_id: vine.number().optional(),
// embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]), // embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
@ -114,10 +120,10 @@ export const createDatasetValidator = vine.compile(
// last step // last step
files: vine files: vine
.array( .array(
vine.file({ vine.myfile({
size: '512mb', size: '512mb',
extnames: extensions, extnames: extensions,
}), }).filenameLength({ clientNameSizeLimit : 100 }),
) )
.minLength(1), .minLength(1),
}), }),
@ -169,17 +175,23 @@ export const updateDatasetValidator = vine.compile(
authors: vine authors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
}), }),
) )
.minLength(1), .minLength(1)
.distinct('email'),
contributors: vine contributors: vine
.array( .array(
vine.object({ vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(), email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
first_name: vine.string().trim().minLength(3).maxLength(255),
last_name: vine.string().trim().minLength(3).maxLength(255),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)), pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}), }),
) )
.distinct('email')
.optional(), .optional(),
// third step // third step
project_id: vine.number().optional(), project_id: vine.number().optional(),
@ -228,7 +240,7 @@ export const updateDatasetValidator = vine.compile(
// last step // last step
files: vine files: vine
.array( .array(
vine.file({ vine.myfile({
size: '512mb', size: '512mb',
extnames: extensions, extnames: extensions,
}), }),
@ -270,7 +282,9 @@ let messagesProvider = new SimpleMessagesProvider({
'The language of the translated description must be different from the language of the dataset', 'The language of the translated description must be different from the language of the dataset',
'authors.array.minLength': 'at least {{ min }} author must be defined', 'authors.array.minLength': 'at least {{ min }} author must be defined',
'authors.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined', 'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
'contributors.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`, 'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,

View File

@ -23,21 +23,21 @@ export const createUserValidator = vine.compile(
* Validates the role's update action * Validates the role's update action
* node ace make:validator user * node ace make:validator user
*/ */
export const updateUserValidator = vine.withMetaData<{ userId: number }>().compile( export const updateUserValidator = vine.withMetaData<{ objId: number }>().compile(
vine.object({ vine.object({
login: vine login: vine
.string() .string()
.trim() .trim()
.minLength(3) .minLength(3)
.maxLength(20) .maxLength(20)
.isUnique({ table: 'accounts', column: 'login', whereNot: (field) => field.meta.userId }) .isUnique({ table: 'accounts', column: 'login', whereNot: (field) => field.meta.objId })
.regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores .regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
email: vine email: vine
.string() .string()
.maxLength(255) .maxLength(255)
.email() .email()
.normalizeEmail() .normalizeEmail()
.isUnique({ table: 'accounts', column: 'email', whereNot: (field) => field.meta.userId }), .isUnique({ table: 'accounts', column: 'email', whereNot: (field) => field.meta.objId }),
password: vine.string().confirmed().trim().minLength(3).maxLength(60), password: vine.string().confirmed().trim().minLength(3).maxLength(60),
roles: vine.array(vine.number()).minLength(1), // define at least one role for the new user roles: vine.array(vine.number()).minLength(1), // define at least one role for the new user
}), }),

View File

@ -13,7 +13,7 @@ import env from '#start/env';
// import { ServerConfig } from "@adonisjs/core/services/server"; // import { ServerConfig } from "@adonisjs/core/services/server";
// import { LoggerConfig } from "@adonisjs/core/types/logger"; // import { LoggerConfig } from "@adonisjs/core/types/logger";
// import { ValidatorConfig } from "@adonisjs/validator/types"; // import { ValidatorConfig } from "@adonisjs/validator/types";
import { defineConfig } from "@adonisjs/core/http"; import { defineConfig } from '@adonisjs/core/http';
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -40,7 +40,7 @@ export const appKey: string = env.get('APP_KEY');
| |
*/ */
export const http = defineConfig({ export const http = defineConfig({
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Allow method spoofing | Allow method spoofing
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -51,16 +51,16 @@ export const http = defineConfig({
| so on. | so on.
| |
*/ */
allowMethodSpoofing: false, allowMethodSpoofing: false,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Subdomain offset | Subdomain offset
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
subdomainOffset: 2, subdomainOffset: 2,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Request Ids | Request Ids
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -69,9 +69,9 @@ export const http = defineConfig({
| HTTP request and set it as `x-request-id` header. | HTTP request and set it as `x-request-id` header.
| |
*/ */
generateRequestId: false, generateRequestId: false,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Trusting proxy servers | Trusting proxy servers
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -80,9 +80,9 @@ export const http = defineConfig({
| headers. | headers.
| |
*/ */
trustProxy: proxyAddr.compile('loopback'), trustProxy: proxyAddr.compile('loopback'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Generating Etag | Generating Etag
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -90,31 +90,30 @@ export const http = defineConfig({
| Whether or not to generate an etag for every response. | Whether or not to generate an etag for every response.
| |
*/ */
etag: false, etag: false,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| JSONP Callback | JSONP Callback
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
jsonpCallbackName: 'callback', jsonpCallbackName: 'callback',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Cookie settings | Cookie settings
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
cookie: { cookie: {
domain: '', domain: '',
path: '/', path: '/',
maxAge: '2h', maxAge: '2h',
httpOnly: true, httpOnly: true,
secure: false, secure: false,
sameSite: false, sameSite: false,
}, },
}); });
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Profiler | Profiler

1670
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
/*
|--------------------------------------------------------------------------
| Provider File - node ace make:provider vinejsProvider
|--------------------------------------------------------------------------
|*/
import type { ApplicationService } from '@adonisjs/core/types';
import vine, { BaseLiteralType, Vine } from '@vinejs/vine';
import type { Validation, 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';
/**
* Validation options accepted by the "file" rule
*/
export type FileRuleValidationOptions = Partial<FileValidationOptions> | ((field: FieldContext) => Partial<FileValidationOptions>);
/**
* Extend VineJS
*/
declare module '@vinejs/vine' {
interface Vine {
myfile(options?: FileRuleValidationOptions): VineMultipartFile;
}
}
/**
* Extend HTTP request class
*/
declare module '@adonisjs/core/http' {
interface Request extends RequestValidator {
}
}
/**
* Checks if the value is an instance of multipart file
* from bodyparser.
*/
export function isBodyParserFile(file: MultipartFile | unknown): boolean {
return !!(file && typeof file === 'object' && 'isMultipartFile' in file);
}
/**
* VineJS validation rule that validates the file to be an
* instance of BodyParser MultipartFile class.
*/
const isMultipartFile = vine.createRule((file: MultipartFile | unknown, options: FileRuleValidationOptions, field: FieldContext) => {
/**
* Report error when value is not a field multipart
* file object
*/
if (!isBodyParserFile(file)) {
field.report('The {{ field }} must be a file', 'file', field);
return;
}
// At this point, you can use type assertion to explicitly tell TypeScript that file is of type MultipartFile
const validatedFile = file as MultipartFile;
const validationOptions = typeof options === 'function' ? options(field) : options;
/**
* Set size when it's defined in the options and missing
* on the file instance
*/
if (validatedFile.sizeLimit === undefined && validationOptions.size) {
validatedFile.sizeLimit = validationOptions.size;
}
/**
* Set extensions when it's defined in the options and missing
* on the file instance
*/
if (validatedFile.allowedExtensions === undefined && validationOptions.extnames) {
validatedFile.allowedExtensions = validationOptions.extnames;
}
/**
* wieder löschen
* Set extensions when it's defined in the options and missing
* on the file instance
*/
// if (file.clientNameSizeLimit === undefined && validationOptions.clientNameSizeLimit) {
// file.clientNameSizeLimit = validationOptions.clientNameSizeLimit;
// }
/**
* Validate file
*/
validatedFile.validate();
/**
* Report errors
*/
validatedFile.errors.forEach((error) => {
field.report(error.message, `file.${error.type}`, field, validationOptions);
});
});
export class VineMultipartFile extends BaseLiteralType<MultipartFile, MultipartFile, MultipartFile> {
// #private;
// constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions, validations?: Validation<any>[]);
// clone(): this;
public validationOptions;
// 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>[]) {
// super(options, validations);
super(options, [isMultipartFile(validationOptions || {})]);
this.validationOptions = validationOptions;
}
public clone(): any {
return new VineMultipartFile(this.validationOptions, this.cloneOptions(), this.cloneValidations());
}
}
export default class VinejsProvider {
protected app: ApplicationService;
constructor(app: ApplicationService) {
this.app = app;
this.app.usingVineJS = true;
}
/**
* Register bindings to the container
*/
register() {}
/**
* The container bindings have booted
*/
boot(): void {
// VineString.macro('translatedLanguage', function (this: VineString, options: Options) {
// return this.use(translatedLanguageRule(options));
// });
Vine.macro('myfile', function (this: Vine, options) {
return new VineMultipartFile(options);
});
/**
* 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);
});
}
/**
* The application has been booted
*/
async start() {}
/**
* The process has been started
*/
async ready() {}
/**
* Preparing to shutdown the app
*/
async shutdown() {}
}

View File

@ -1,6 +1,8 @@
{ {
"assets/app.css": "http://localhost:8080/assets/app.css", "assets/app.css": "http://localhost:8080/assets/app.css",
"assets/app.js": "http://localhost:8080/assets/app.js", "assets/app.js": "http://localhost:8080/assets/app.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_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_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_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_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Admin_Permission_Index_vue.js",
@ -60,6 +62,7 @@
"assets/resources_js_Components_Admin_Pagination_vue-resources_js_Components_BaseButtons_vue-resource-8134e7.js": "http://localhost:8080/assets/resources_js_Components_Admin_Pagination_vue-resources_js_Components_BaseButtons_vue-resource-8134e7.js", "assets/resources_js_Components_Admin_Pagination_vue-resources_js_Components_BaseButtons_vue-resource-8134e7.js": "http://localhost:8080/assets/resources_js_Components_Admin_Pagination_vue-resources_js_Components_BaseButtons_vue-resource-8134e7.js",
"assets/resources_js_Components_Map_draw_component_vue-resources_js_Components_Map_zoom_component_vue-196747.js": "http://localhost:8080/assets/resources_js_Components_Map_draw_component_vue-resources_js_Components_Map_zoom_component_vue-196747.js", "assets/resources_js_Components_Map_draw_component_vue-resources_js_Components_Map_zoom_component_vue-196747.js": "http://localhost:8080/assets/resources_js_Components_Map_draw_component_vue-resources_js_Components_Map_zoom_component_vue-196747.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_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-d5e5fc.js": "http://localhost:8080/assets/resources_js_Components_FileUpload_vue-resources_js_Components_FormCheckRadioGroup_vue-resour-d5e5fc.js", "assets/resources_js_Components_FileUpload_vue-resources_js_Components_FormCheckRadioGroup_vue-resour-d5e5fc.js": "http://localhost:8080/assets/resources_js_Components_FileUpload_vue-resources_js_Components_FormCheckRadioGroup_vue-resour-d5e5fc.js",
"assets/fonts/inter-latin-ext-400-normal.woff": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.40b3b0d5.woff", "assets/fonts/inter-latin-ext-400-normal.woff": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.40b3b0d5.woff",
"assets/fonts/inter-latin-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.0f9e8d4e.woff2", "assets/fonts/inter-latin-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.0f9e8d4e.woff2",

View File

@ -47,7 +47,7 @@
</p> </p>
<!-- <p class="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p> --> <!-- <p class="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p> -->
</div> </div>
<input id="dropzone-file" type="file" class="hidden" @change="onChangeFile" /> <input id="dropzone-file" type="file" class="hidden" @change="onChangeFile" multiple="true"/>
</label> </label>
</header> </header>

View File

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

View File

@ -257,7 +257,7 @@ export default class MapComponent extends Vue {
background: none; background: none;
} }
.leaflet-pane { /* .leaflet-pane {
z-index: 30; z-index: 30;
} } */
</style> </style>

View File

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

View File

@ -22,6 +22,10 @@ const props = defineProps({
type: Array<Person>, type: Array<Person>,
default: () => [], default: () => [],
}, },
relation: {
type: String,
required: true,
},
contributortypes: { contributortypes: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
@ -82,7 +86,7 @@ const pagesList = computed(() => {
return pagesList; return pagesList;
}); });
const removeAuthor = (key) => { const removeAuthor = (key: number) => {
items.value.splice(key, 1); items.value.splice(key, 1);
}; };
@ -131,8 +135,10 @@ const removeAuthor = (key) => {
<!-- <th v-if="checkable" /> --> <!-- <th v-if="checkable" /> -->
<th /> <th />
<th scope="col">Sort</th> <th scope="col">Sort</th>
<th class="hidden lg:table-cell"></th> <th scope="col">Id</th>
<th>Name</th> <!-- <th class="hidden lg:table-cell"></th> -->
<th>First Name</th>
<th>Last Name</th>
<th>Email</th> <th>Email</th>
<th scope="col" v-if="Object.keys(contributortypes).length"> <th scope="col" v-if="Object.keys(contributortypes).length">
<span>Type</span> <span>Type</span>
@ -153,15 +159,56 @@ const removeAuthor = (key) => {
<BaseIcon :path="mdiDragVariant" /> <BaseIcon :path="mdiDragVariant" />
</td> </td>
<td scope="row">{{ index + 1 }}</td> <td scope="row">{{ index + 1 }}</td>
<td data-label="Id">{{ element.id }}</td>
<!-- <TableCheckboxCell v-if="checkable" @checked="checked($event, client)" /> --> <!-- <TableCheckboxCell v-if="checkable" @checked="checked($event, client)" /> -->
<td class="border-b-0 lg:w-6 before:hidden hidden lg:table-cell"> <!-- <td v-if="element.name" class="border-b-0 lg:w-6 before:hidden hidden lg:table-cell">
<UserAvatar :username="element.name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" /> <UserAvatar :username="element.name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
</td> -->
<td data-label="First Name">
<!-- {{ element.first_name }} -->
<FormControl
required
v-model="element.first_name"
type="text" :is-read-only="element.status==true"
placeholder="[FIRST NAME]"
>
<div
class="text-red-400 text-sm"
v-if="errors && Array.isArray(errors[`${relation}.${index}.first_name`])"
>
{{ errors[`${relation}.${index}.first_name`].join(', ') }}
</div>
</FormControl>
</td> </td>
<td data-label="Name"> <td data-label="Last Name">
{{ element.name }} <FormControl
required
v-model="element.last_name"
type="text" :is-read-only="element.status==true"
placeholder="[LAST NAME]"
>
<div
class="text-red-400 text-sm"
v-if="errors && Array.isArray(errors[`${relation}.${index}.last_name`])"
>
{{ errors[`${relation}.${index}.last_name`].join(', ') }}
</div>
</FormControl>
</td> </td>
<td data-label="Email"> <td data-label="Email">
{{ element.email }} <FormControl
required
v-model="element.email"
type="text" :is-read-only="element.status==true"
placeholder="[EMAIL]"
>
<div
class="text-red-400 text-sm"
v-if="errors && Array.isArray(errors[`${relation}.${index}.email`])"
>
{{ errors[`${relation}.${index}.email`].join(', ') }}
</div>
</FormControl>
</td> </td>
<td v-if="Object.keys(contributortypes).length"> <td v-if="Object.keys(contributortypes).length">
<!-- <select type="text" v-model="element.pivot.contributor_type"> <!-- <select type="text" v-model="element.pivot.contributor_type">
@ -173,14 +220,14 @@ const removeAuthor = (key) => {
required required
v-model="element.pivot_contributor_type" v-model="element.pivot_contributor_type"
type="select" type="select"
:options="contributortypes" :options="contributortypes"
placeholder="[relation type]" placeholder="[relation type]"
> >
<div <div
class="text-red-400 text-sm" class="text-red-400 text-sm"
v-if="errors && Array.isArray(errors[`contributors.${index}.pivot_contributor_type`])" v-if="errors && Array.isArray(errors[`${relation}.${index}.pivot_contributor_type`])"
> >
{{ errors[`contributors.${index}.pivot_contributor_type`].join(', ') }} {{ errors[`${relation}.${index}.pivot_contributor_type`].join(', ') }}
</div> </div>
</FormControl> </FormControl>
</td> </td>

View File

@ -130,13 +130,14 @@ export interface Description {
} }
export interface Person { export interface Person {
id: number; id?: number;
name: string; name?: string;
email: string; email: string;
name_type: string; name_type: string;
identifier_orcid: string; identifier_orcid: string;
datasetCount: string; datasetCount?: string;
created_at: string; created_at?: string;
status: boolean;
} }
interface IErrorMessage { interface IErrorMessage {

View File

@ -0,0 +1,116 @@
<script lang="ts" setup>
import { Head, usePage } from '@inertiajs/vue3';
import { mdiAccountKey, mdiSquareEditOutline, mdiAlertBoxOutline } from '@mdi/js';
import { computed, ComputedRef } from 'vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import BaseButton from '@/Components/BaseButton.vue';
import CardBox from '@/Components/CardBox.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import NotificationBar from '@/Components/NotificationBar.vue';
// import Pagination from '@/Components/Admin/Pagination.vue';
// import Sort from '@/Components/Admin/Sort.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
// import CardBoxModal from '@/Components/CardBoxModal.vue';
// const isModalDangerActive = ref(false);
// const deleteId = ref();
defineProps({
licenses: {
type: Object,
default: () => ({}),
},
// filters: {
// type: Object,
// default: () => ({}),
// },
can: {
type: Object,
default: () => ({}),
},
});
const flash: ComputedRef<any> = computed(() => {
// let test = usePage();
// console.log(test);
return usePage().props.flash;
});
</script>
<template>
<LayoutAuthenticated>
<Head title="Licenses" />
<SectionMain>
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Licenses" main>
<!-- <BaseButton
v-if="can.create"
:route-name="stardust.route('settings.role.create')"
:icon="mdiPlus"
label="Add"
color="info"
rounded-full
small
/> -->
</SectionTitleLineWithButton>
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
{{ flash.message }}
</NotificationBar>
<CardBox class="mb-6" has-table>
</CardBox>
<CardBox class="mb-6" has-form-data>
<table>
<thead>
<tr>
<th>
<!-- <Sort label="Name" attribute="name" /> -->
Name
</th>
<th>
<!-- <Sort label="Sort Order" attribute="sort_order" /> -->
Sort Order
</th>
<th v-if="can.edit">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="license in licenses" :key="license.id">
<td data-label="Name">
<!-- <Link
:href="stardust.route('settings.role.show', [role.id])"
class="no-underline hover:underline text-cyan-600 dark:text-cyan-400"
>
{{ license.name }}
</Link> -->
{{ license.name }}
</td>
<td data-label="Description">
{{ license.sort_order }}
</td>
<td v-if="can.edit" class="before:hidden lg:w-1 whitespace-nowrap">
<BaseButtons type="justify-start lg:justify-end" no-wrap>
<BaseButton v-if="license.active"
:route-name="stardust.route('settings.license.down', [license.id])"
color="warning" :icon="mdiSquareEditOutline" label="deactivate" small />
<BaseButton v-else :route-name="stardust.route('settings.license.up', [license.id])"
color="success" :icon="mdiSquareEditOutline" label="activate" small />
</BaseButtons>
</td>
</tr>
</tbody>
</table>
<!-- <div class="py-4">
<Pagination v-bind:data="roles.meta" />
</div> -->
</CardBox>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@ -0,0 +1,118 @@
<script lang="ts" setup>
import { Head, usePage } from '@inertiajs/vue3';
import { mdiAccountKey, mdiSquareEditOutline, mdiAlertBoxOutline } from '@mdi/js';
import { computed, ComputedRef } from 'vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import BaseButton from '@/Components/BaseButton.vue';
import CardBox from '@/Components/CardBox.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import NotificationBar from '@/Components/NotificationBar.vue';
// import Pagination from '@/Components/Admin/Pagination.vue';
// import Sort from '@/Components/Admin/Sort.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
// import CardBoxModal from '@/Components/CardBoxModal.vue';
// const isModalDangerActive = ref(false);
// const deleteId = ref();
defineProps({
mimetypes: {
type: Object,
default: () => ({}),
},
// filters: {
// type: Object,
// default: () => ({}),
// },
can: {
type: Object,
default: () => ({}),
},
});
const flash: ComputedRef<any> = computed(() => {
// let test = usePage();
// console.log(test);
return usePage().props.flash;
});
</script>
<template>
<LayoutAuthenticated>
<Head title="Mime Types" />
<SectionMain>
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Mime Types" main>
<!-- <BaseButton
v-if="can.create"
:route-name="stardust.route('settings.role.create')"
:icon="mdiPlus"
label="Add"
color="info"
rounded-full
small
/> -->
</SectionTitleLineWithButton>
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
{{ flash.message }}
</NotificationBar>
<CardBox class="mb-6" has-table>
</CardBox>
<CardBox class="mb-6" has-form-data>
<table>
<thead>
<tr>
<th>
<!-- <Sort label="Name" attribute="name" /> -->
Name
</th>
<th>
<!-- <Sort label="Sort Order" attribute="sort_order" /> -->
Status
</th>
<th v-if="can.edit">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="mimetype in mimetypes" :key="mimetype.id">
<td data-label="Name">
<!-- <Link
:href="stardust.route('settings.role.show', [role.id])"
class="no-underline hover:underline text-cyan-600 dark:text-cyan-400"
>
{{ license.name }}
</Link> -->
{{ mimetype.name }}
</td>
<td 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">
<BaseButtons type="justify-start lg:justify-end" no-wrap>
<BaseButton v-if="mimetype.enabled"
:route-name="stardust.route('settings.mimetype.down', [mimetype.id])"
color="warning" :icon="mdiSquareEditOutline" label="disable" small />
<BaseButton v-else
:route-name="stardust.route('settings.mimetype.up', [mimetype.id])" color="success"
:icon="mdiSquareEditOutline" label="enable" small />
</BaseButtons>
</td>
</tr>
</tbody>
</table>
<!-- <div class="py-4">
<Pagination v-bind:data="roles.meta" />
</div> -->
</CardBox>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@ -373,6 +373,11 @@ const submit = async () => {
}); });
}; };
const addNewAuthor = () => {
let newAuthor = { status: false, first_name: '', last_name: '', email: '', academic_title: '', identifier_orcid: '', name_type: 'Personal' };
form.authors.push(newAuthor);
};
const addTitle = () => { const addTitle = () => {
let newTitle: Title = { value: '', language: '', type: '' }; let newTitle: Title = { value: '', language: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]); //this.dataset.files.push(uploadedFiles[i]);
@ -463,10 +468,11 @@ Removes a selected keyword
Mit dem Setzen des Hakens bestätige ich hiermit Mit dem Setzen des Hakens bestätige ich hiermit
<ul class="list-decimal"> <ul class="list-decimal">
<li> <li>
die Data Policy von Tethys RDR sowie die Terms & Conditions von Tethys gelesen und verstanden zu haben (<a die Data Policy von Tethys RDR sowie die Terms & Conditions von Tethys gelesen und verstanden zu haben
href="/docs/HandbuchTethys.pdf" target="_blank">siehe hier</a>) (<a href="/docs/HandbuchTethys.pdf" target="_blank">siehe hier</a>)
</li> </li>
<li>das Einverständnis aller Co-Autoren über die bevorstehende Datenpublikation schriftlich eingeholt zu haben <li>das Einverständnis aller Co-Autoren über die bevorstehende Datenpublikation schriftlich eingeholt zu
haben
</li> </li>
<li>sowohl mit der Data Policy als auch mit den Terms & Conditions einverstanden zu sein</li> <li>sowohl mit der Data Policy als auch mit den Terms & Conditions einverstanden zu sein</li>
</ul> </ul>
@ -516,9 +522,8 @@ Removes a selected keyword
<div class="flex flex-col md:flex-row"> <div class="flex flex-col md:flex-row">
<FormField label="Language *" help="required: select dataset main language" <FormField label="Language *" help="required: select dataset main language"
:class="{ 'text-red-400': errors.language }" class="w-full mx-2 flex-1"> :class="{ 'text-red-400': errors.language }" class="w-full mx-2 flex-1">
<FormControl required v-model="language" :type="'select'" <FormControl required v-model="language" :type="'select'" placeholder="[Enter Language]"
placeholder="[Enter Language]" :errors="form.errors.language" :errors="form.errors.language" :options="{ de: 'de', en: 'en' }">
:options="{ de: 'de', en: 'en' }">
<div class="text-red-400 text-sm" v-if="form.errors.language"> <div class="text-red-400 text-sm" v-if="form.errors.language">
{{ form.errors.language.join(', ') }} {{ form.errors.language.join(', ') }}
</div> </div>
@ -528,7 +533,8 @@ Removes a selected keyword
<FormField label="Licenses" wrap-body :class="{ 'text-red-400': form.errors.licenses }" <FormField label="Licenses" wrap-body :class="{ 'text-red-400': form.errors.licenses }"
class="mt-8 w-full mx-2 flex-1"> class="mt-8 w-full mx-2 flex-1">
<FormCheckRadioGroup v-model="form.licenses" name="roles" is-column :options="props.licenses" /> <FormCheckRadioGroup v-model="form.licenses" name="roles" is-column
:options="props.licenses" />
</FormField> </FormField>
<!-- <label for="rights"> <!-- <label for="rights">
@ -557,8 +563,8 @@ Removes a selected keyword
<div class="flex flex-col md:flex-row"> <div class="flex flex-col md:flex-row">
<FormField label="Dataset Type *" help="required: dataset type" <FormField label="Dataset Type *" help="required: dataset type"
:class="{ 'text-red-400': form.errors.type }" class="w-full mx-2 flex-1"> :class="{ 'text-red-400': form.errors.type }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.type" :type="'select'" placeholder="-- select type --" <FormControl required v-model="form.type" :type="'select'"
:errors="errors.type" :options="doctypes"> placeholder="-- select type --" :errors="errors.type" :options="doctypes">
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
v-if="form.errors.type && Array.isArray(form.errors.type)"> v-if="form.errors.type && Array.isArray(form.errors.type)">
{{ form.errors.type.join(', ') }} {{ form.errors.type.join(', ') }}
@ -568,7 +574,8 @@ Removes a selected keyword
<!-- <div class="w-full mx-2 flex-1 svelte-1l8159u"></div> --> <!-- <div class="w-full mx-2 flex-1 svelte-1l8159u"></div> -->
<!-- Creating Corporation --> <!-- Creating Corporation -->
<FormField label="Creating Corporation *" <FormField label="Creating Corporation *"
:class="{ 'text-red-400': form.errors.creating_corporation }" class="w-full mx-2 flex-1"> :class="{ 'text-red-400': form.errors.creating_corporation }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.creating_corporation" type="text" <FormControl required v-model="form.creating_corporation" type="text"
placeholder="[enter creating corporation]" :is-read-only="true"> placeholder="[enter creating corporation]" :is-read-only="true">
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
@ -586,7 +593,8 @@ Removes a selected keyword
<!-- <div class="py-6 border-t border-gray-100 dark:border-slate-800"> --> <!-- <div class="py-6 border-t border-gray-100 dark:border-slate-800"> -->
<div class="flex flex-col md:flex-row"> <div class="flex flex-col md:flex-row">
<FormField label="Main Title *" help="required: main title" <FormField label="Main Title *" help="required: main title"
:class="{ 'text-red-400': form.errors['titles.0.value'] }" class="w-full mx-2 flex-1"> :class="{ 'text-red-400': form.errors['titles.0.value'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.titles[0].value" type="text" <FormControl required v-model="form.titles[0].value" type="text"
placeholder="[enter main title]"> placeholder="[enter main title]">
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
@ -676,7 +684,7 @@ Removes a selected keyword
<FormControl required v-model="form.descriptions[0].language" type="text" <FormControl required v-model="form.descriptions[0].language" type="text"
:is-read-only="true"> :is-read-only="true">
<div class="text-red-400 text-sm" v-if="form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.language']) <div class="text-red-400 text-sm" v-if="form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.language'])
"> ">
{{ form.errors['descriptions.0.language'].join(', ') }} {{ form.errors['descriptions.0.language'].join(', ') }}
</div> </div>
</FormControl> </FormControl>
@ -698,7 +706,7 @@ Removes a selected keyword
placeholder="[enter additional description]"> placeholder="[enter additional description]">
<div class="text-red-400 text-sm" v-if="form.errors[`descriptions.${index}.value`] && <div class="text-red-400 text-sm" v-if="form.errors[`descriptions.${index}.value`] &&
Array.isArray(form.errors[`descriptions.${index}.value`]) Array.isArray(form.errors[`descriptions.${index}.value`])
"> ">
{{ form.errors[`descriptions.${index}.value`].join(', ') }} {{ form.errors[`descriptions.${index}.value`].join(', ') }}
</div> </div>
</FormControl> </FormControl>
@ -710,7 +718,7 @@ Removes a selected keyword
:options="descriptiontypes" placeholder="[select description type]"> :options="descriptiontypes" placeholder="[select description type]">
<div class="text-red-400 text-sm" v-if="form.errors[`descriptions.${index}.type`] && <div class="text-red-400 text-sm" v-if="form.errors[`descriptions.${index}.type`] &&
Array.isArray(form.errors[`descriptions.${index}.type`]) Array.isArray(form.errors[`descriptions.${index}.type`])
"> ">
{{ form.errors[`descriptions.${index}.type`].join(', ') }} {{ form.errors[`descriptions.${index}.type`].join(', ') }}
</div> </div>
</FormControl> </FormControl>
@ -718,8 +726,9 @@ Removes a selected keyword
<FormField label="Description Language*" <FormField label="Description Language*"
:class="{ 'text-red-400': form.errors[`titdescriptionsles.${index}.language`] }" :class="{ 'text-red-400': form.errors[`titdescriptionsles.${index}.language`] }"
class="w-full mx-2 flex-1"> class="w-full mx-2 flex-1">
<FormControl required v-model="form.descriptions[index].language" type="select" <FormControl required v-model="form.descriptions[index].language"
:options="{ de: 'de', en: 'en' }" placeholder="[select title language]"> type="select" :options="{ de: 'de', en: 'en' }"
placeholder="[select title language]">
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
v-if="form.errors && Array.isArray(form.errors[`descriptions.${index}.language`])"> v-if="form.errors && Array.isArray(form.errors[`descriptions.${index}.language`])">
{{ form.errors[`descriptions.${index}.language`].join(', ') }} {{ form.errors[`descriptions.${index}.language`].join(', ') }}
@ -736,10 +745,16 @@ Removes a selected keyword
<SearchAutocomplete source="/api/persons" :response-property="'first_name'" <SearchAutocomplete source="/api/persons" :response-property="'first_name'"
placeholder="search in person table...." v-on:person="onAddAuthor"></SearchAutocomplete> placeholder="search in person table...." v-on:person="onAddAuthor"></SearchAutocomplete>
<TablePersons :persons="form.authors" v-if="form.authors.length > 0" /> <TablePersons :errors="form.errors" :persons="form.authors" :relation="'authors'" v-if="form.authors.length > 0" />
<div class="text-red-400 text-sm" v-if="errors.authors && Array.isArray(errors.authors)"> <div class="text-red-400 text-sm" v-if="errors.authors && Array.isArray(errors.authors)">
{{ errors.authors.join(', ') }} {{ errors.authors.join(', ') }}
</div> </div>
<div class="w-full md:w-1/2">
<label class="block" for="additionalCreators">Add additional creator(s) if creator is
not in database</label>
<button class="bg-blue-500 text-white py-2 px-4 rounded-sm"
@click.prevent="addNewAuthor()">+</button>
</div>
</CardBox> </CardBox>
<!-- contributors --> <!-- contributors -->
@ -748,7 +763,7 @@ Removes a selected keyword
placeholder="search in person table...." v-on:person="onAddContributor"> placeholder="search in person table...." v-on:person="onAddContributor">
</SearchAutocomplete> </SearchAutocomplete>
<TablePersons :persons="form.contributors" v-if="form.contributors.length > 0" <TablePersons :persons="form.contributors" :relation="'contributors'" v-if="form.contributors.length > 0"
:contributortypes="contributorTypes" :errors="form.errors" /> :contributortypes="contributorTypes" :errors="form.errors" />
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
v-if="form.errors.contributors && Array.isArray(form.errors.contributors)"> v-if="form.errors.contributors && Array.isArray(form.errors.contributors)">
@ -772,8 +787,8 @@ Removes a selected keyword
<FormField label="Embargo Date.." help="embargo date is optional" <FormField label="Embargo Date.." help="embargo date is optional"
:class="{ 'text-red-400': errors.embargo_date }" class="w-full mx-2 flex-1"> :class="{ 'text-red-400': errors.embargo_date }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.embargo_date" :type="'date'" placeholder="date('y-m-d')" <FormControl required v-model="form.embargo_date" :type="'date'"
:errors="form.errors.embargo_date"> placeholder="date('y-m-d')" :errors="form.errors.embargo_date">
<div class="text-red-400 text-sm" v-if="form.errors.embargo_date"> <div class="text-red-400 text-sm" v-if="form.errors.embargo_date">
{{ form.errors.embargo_date.join(', ') }} {{ form.errors.embargo_date.join(', ') }}
</div> </div>
@ -790,7 +805,8 @@ Removes a selected keyword
<div class="flex flex-col md:flex-row"> <div class="flex flex-col md:flex-row">
<!-- x min and max --> <!-- x min and max -->
<FormField label="Coverage X Min" :class="{ 'text-red-400': form.errors['coverage.x_min'] }" <FormField label="Coverage X Min"
:class="{ 'text-red-400': form.errors['coverage.x_min'] }"
class="w-full mx-2 flex-1"> class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.x_min" type="text" <FormControl required v-model="form.coverage.x_min" type="text"
placeholder="[enter x_min]"> placeholder="[enter x_min]">
@ -800,7 +816,8 @@ Removes a selected keyword
</div> </div>
</FormControl> </FormControl>
</FormField> </FormField>
<FormField label="Coverage X Max" :class="{ 'text-red-400': form.errors['coverage.x_max'] }" <FormField label="Coverage X Max"
:class="{ 'text-red-400': form.errors['coverage.x_max'] }"
class="w-full mx-2 flex-1"> class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.x_max" type="text" <FormControl required v-model="form.coverage.x_max" type="text"
placeholder="[enter x_max]"> placeholder="[enter x_max]">
@ -811,7 +828,8 @@ Removes a selected keyword
</FormControl> </FormControl>
</FormField> </FormField>
<!-- y min and max --> <!-- y min and max -->
<FormField label="Coverage Y Min" :class="{ 'text-red-400': form.errors['coverage.y_min'] }" <FormField label="Coverage Y Min"
:class="{ 'text-red-400': form.errors['coverage.y_min'] }"
class="w-full mx-2 flex-1"> class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.y_min" type="text" <FormControl required v-model="form.coverage.y_min" type="text"
placeholder="[enter y_min]"> placeholder="[enter y_min]">
@ -821,7 +839,8 @@ Removes a selected keyword
</div> </div>
</FormControl> </FormControl>
</FormField> </FormField>
<FormField label="Coverage Y Max" :class="{ 'text-red-400': form.errors['coverage.y_max'] }" <FormField label="Coverage Y Max"
:class="{ 'text-red-400': form.errors['coverage.y_max'] }"
class="w-full mx-2 flex-1"> class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.y_max" type="text" <FormControl required v-model="form.coverage.y_max" type="text"
placeholder="[enter y_max]"> placeholder="[enter y_max]">
@ -956,8 +975,8 @@ Removes a selected keyword
<td data-label="Reference Value"> <td data-label="Reference Value">
<!-- <input name="Reference Value" class="form-control" <!-- <input name="Reference Value" class="form-control"
placeholder="[VALUE]" v-model="item.value" /> --> placeholder="[VALUE]" v-model="item.value" /> -->
<FormControl required v-model="item.value" :type="'text'" placeholder="[VALUE]" <FormControl required v-model="item.value" :type="'text'"
:errors="form.errors.embargo_date"> placeholder="[VALUE]" :errors="form.errors.embargo_date">
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
v-if="form.errors[`references.${index}.value`] && Array.isArray(form.errors[`references.${index}.value`])"> v-if="form.errors[`references.${index}.value`] && Array.isArray(form.errors[`references.${index}.value`])">
{{ form.errors[`references.${index}.value`].join(', ') }} {{ form.errors[`references.${index}.value`].join(', ') }}
@ -980,8 +999,8 @@ Removes a selected keyword
['placeholder' => '[relationType]', 'v-model' => 'item.relation', ['placeholder' => '[relationType]', 'v-model' => 'item.relation',
'data-vv-scope' => 'step-2']) 'data-vv-scope' => 'step-2'])
!!} --> !!} -->
<FormControl required v-model="form.references[index].relation" type="select" <FormControl required v-model="form.references[index].relation"
:options="relationTypes" placeholder="[relation type]"> type="select" :options="relationTypes" placeholder="[relation type]">
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`references.${index}.relation`])"> v-if="Array.isArray(form.errors[`references.${index}.relation`])">
{{ form.errors[`references.${index}.relation`].join(', ') }} {{ form.errors[`references.${index}.relation`].join(', ') }}
@ -1036,7 +1055,8 @@ Removes a selected keyword
<FileUploadComponent :files="form.files"></FileUploadComponent> <FileUploadComponent :files="form.files"></FileUploadComponent>
<div class="text-red-400 text-sm" v-if="form.errors['file'] && Array.isArray(form.errors['file'])"> <div class="text-red-400 text-sm"
v-if="form.errors['file'] && Array.isArray(form.errors['file'])">
{{ form.errors['file'].join(', ') }} {{ form.errors['file'].join(', ') }}
</div> </div>
<div class="text-red-400 text-sm" <div class="text-red-400 text-sm"
@ -1068,7 +1088,8 @@ Removes a selected keyword
</button> </button>
</div> </div>
</div> </div>
<progress v-if="form.progress" :value="form.progress.percentage" max="100">{{ form.progress.percentage <progress v-if="form.progress" :value="form.progress.percentage" max="100">{{
form.progress.percentage
}}%</progress> }}%</progress>
</template> </template>
</CardBox> </CardBox>

View File

@ -10,6 +10,8 @@ import {
mdiLock, mdiLock,
mdiFormatListGroup, mdiFormatListGroup,
mdiShieldCrownOutline, mdiShieldCrownOutline,
mdiLicense,
mdiFileDocument,
} from '@mdi/js'; } from '@mdi/js';
export default [ export default [
@ -72,6 +74,18 @@ export default [
label: 'Roles', label: 'Roles',
roles: ['administrator'], roles: ['administrator'],
}, },
{
route: 'settings.mimetype.index',
icon: mdiFileDocument,
label: 'Mime Types',
roles: ['administrator'],
},
{
route: 'settings.license.index',
icon: mdiLicense,
label: 'Licenses',
roles: ['administrator'],
},
], ],
}, },

View File

@ -32,6 +32,8 @@ import AuthController from '#controllers/Http/Auth/AuthController';
import UserController from '#controllers/Http/Auth/UserController'; import UserController from '#controllers/Http/Auth/UserController';
import AdminuserController from '#controllers/Http/Admin/AdminuserController'; import AdminuserController from '#controllers/Http/Admin/AdminuserController';
import RoleController from '#controllers/Http/Admin/RoleController'; import RoleController from '#controllers/Http/Admin/RoleController';
import LicenseController from '#controllers/Http/Admin/LicenseController';
import MimetypeController from '#controllers/Http/Admin/MimetypeController';
import DatasetController from '#app/Controllers/Http/Submitter/DatasetController'; import DatasetController from '#app/Controllers/Http/Submitter/DatasetController';
import PersonController from '#app/Controllers/Http/Submitter/PersonController'; import PersonController from '#app/Controllers/Http/Submitter/PersonController';
@ -160,6 +162,17 @@ router.group(() => {
// // .as('role.destroy') // // .as('role.destroy')
// // .where('id', Route.matchers.number()) // // .where('id', Route.matchers.number())
// // .use(middleware.can(['user-delete'])); // // .use(middleware.can(['user-delete']));
router.get('/license', [LicenseController, 'index']).as('license.index');
// router.get('/license/:id/edit', [LicenseController, 'edit']).as('license.edit').where('id', router.matchers.number()).use(middleware.can(['settings']));
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']));
router.get('/mimetype', [MimetypeController, 'index']).as('mimetype.index');
// router.get('/mimetype/:id/edit', [MimetypeController, 'edit']).as('mimetype.edit').where('id', router.matchers.number()).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']));
}) })
.prefix('admin') .prefix('admin')
.as('settings') .as('settings')

View File

@ -0,0 +1,51 @@
/*
|--------------------------------------------------------------------------
| Preloaded File - node ace make:preload rules/translatedLanguage
|--------------------------------------------------------------------------
|*/
import { FieldContext } from '@vinejs/vine/types';
import vine from '@vinejs/vine';
// import { VineString } from '@vinejs/vine';
import { VineMultipartFile, isBodyParserFile } from '#providers/vinejs_provider';
import type { MultipartFile } from '@adonisjs/core/bodyparser';
/**
* Options accepted by the unique rule
*/
// type Options = {
// mainLanguageField: string;
// typeField: string;
// };
type Options = {
// size: string | number;
// extnames: string[];
clientNameSizeLimit: number
};
async function filenameLength(file: VineMultipartFile | unknown, options: Options, field: FieldContext) {
// if (typeof value !== 'string' && typeof value != 'number') {
// return;
// }
if (!isBodyParserFile(file)) {
return;
}
const validatedFile = file as MultipartFile;
if (validatedFile.clientName.length > options.clientNameSizeLimit) {
field.report(`Filename length should be less or equal than ${options.clientNameSizeLimit} characters`, 'filenameLength', field);
}
}
export const filenameLengthRule = vine.createRule(filenameLength);
declare module '#providers/vinejs_provider' {
interface VineMultipartFile {
filenameLength(options: Options): this;
}
}
VineMultipartFile.macro('filenameLength', function (this: VineMultipartFile, options: Options) {
return this.use(filenameLengthRule(options));
});

View File

@ -30,7 +30,7 @@ async function translatedLanguage(value: unknown, options: Options, field: Field
if (typeValue === 'Translated') { if (typeValue === 'Translated') {
if (value === mainLanguage) { if (value === mainLanguage) {
// report thattranlated language field is same as main language field of dataset // report thattranlated language field is same as main language field of dataset
field.report('The tranlated {{ field }} hast the same language seted as dataset language', 'translatedLanguage', field); field.report('The tranlated {{ field }} hast the same language as dataset language', 'translatedLanguage', field);
} }
} }
} }

View File

@ -0,0 +1,64 @@
/*
|--------------------------------------------------------------------------
| Preloaded File - node ace make:preload rules/uniquePerson
|--------------------------------------------------------------------------
|*/
import { FieldContext } from '@vinejs/vine/types';
import db from '@adonisjs/lucid/services/db';
import vine from '@vinejs/vine';
import { VineString, VineNumber } from '@vinejs/vine';
/**
* Options accepted by the unique rule
*/
type Options = {
table: string;
column: string;
// whereNot?: ((field: FieldContext) => string);
idField: string;
};
async function isUniquePerson(value: unknown, options: Options, field: FieldContext) {
if (typeof value !== 'string' && typeof value != 'number') {
return;
}
// let ignoreId: string | undefined;
// if (options.whereNot) {
// ignoreId = options.whereNot(field);
// }
const idValue = vine.helpers.getNestedValue(options.idField, field); //'Main' or 'Translated'
const builder = db.from(options.table).select(options.column).where(options.column, value);
// if existent id, exclide it from validating
if (idValue) {
builder.whereNot('id', '=', idValue);
}
const result = await builder.first();
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;
}
interface VineNumber {
isUniquePerson(options: Options): this;
}
}
VineString.macro('isUniquePerson', function (this: VineString, options: Options) {
return this.use(isUniquePersonRule(options));
});
VineNumber.macro('isUniquePerson', function (this: VineNumber, options: Options) {
return this.use(isUniquePersonRule(options));
});

View File

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