diff --git a/app/Controllers/Http/Editor/DatasetsController.ts b/app/Controllers/Http/Editor/DatasetController.ts similarity index 65% rename from app/Controllers/Http/Editor/DatasetsController.ts rename to app/Controllers/Http/Editor/DatasetController.ts index 35cfebe..2189bb0 100644 --- a/app/Controllers/Http/Editor/DatasetsController.ts +++ b/app/Controllers/Http/Editor/DatasetController.ts @@ -1,11 +1,13 @@ import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; import { Client } from '@opensearch-project/opensearch'; +import User from 'App/Models/User'; import Dataset from 'App/Models/Dataset'; import XmlModel from 'App/Library/XmlModel'; import { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; import { create } from 'xmlbuilder2'; import { readFileSync } from 'fs'; import { transform } from 'saxon-js'; +import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm'; // Create a new instance of the client const client = new Client({ node: 'http://localhost:9200' }); // replace with your OpenSearch endpoint @@ -19,7 +21,86 @@ export default class DatasetsController { // this.proc = readFileSync('public/assets2/datasetxml2oai.sef.json'); } - public async index({}: HttpContextContract) {} + // public async index({}: HttpContextContract) {} + public async index({ auth, request, inertia }: HttpContextContract) { + const user = (await User.find(auth.user?.id)) as User; + const page = request.input('page', 1); + let datasets: ModelQueryBuilderContract = Dataset.query(); + + // if (request.input('search')) { + // // users = users.whereRaw('name like %?%', [request.input('search')]) + // const searchTerm = request.input('search'); + // datasets.where('name', 'ilike', `%${searchTerm}%`); + // } + + if (request.input('sort')) { + type SortOrder = 'asc' | 'desc' | undefined; + let attribute = request.input('sort'); + let sortOrder: SortOrder = 'asc'; + + if (attribute.substr(0, 1) === '-') { + sortOrder = 'desc'; + // attribute = substr(attribute, 1); + attribute = attribute.substr(1); + } + datasets.orderBy(attribute, sortOrder); + } else { + // users.orderBy('created_at', 'desc'); + datasets.orderBy('id', 'asc'); + } + + // const users = await User.query().orderBy('login').paginate(page, limit); + const myDatasets = await datasets + .where('server_state', 'released') + .orWhere((dQuery) => { + dQuery + .whereIn('server_state', ['editor_accepted', 'rejected_reviewer', 'reviewed', 'published']) + .where('editor_id', user.id); + }) + .preload('titles') + .preload('user', (query) => query.select('id', 'login')) + .paginate(page, 10); + + return inertia.render('Editor/Dataset/Index', { + datasets: myDatasets.serialize(), + filters: request.all(), + can: { + // create: await auth.user?.can(['dataset-submit']), + receive: await auth.user?.can(['dataset-receive']), + edit: await auth.user?.can(['dataset-editor-edit']), + delete: await auth.user?.can(['dataset-editor-delete']), + }, + }); + } + + public async receive({ request, inertia, response }: HttpContextContract) { + const id = request.param('id'); + const dataset = await Dataset.query() + .where('id', id) + .preload('titles') + .preload('descriptions') + .preload('user', (builder) => { + builder.select('id', 'login'); + }) + + .firstOrFail(); + + const validStates = ['released']; + if (!validStates.includes(dataset.server_state)) { + // session.flash('errors', 'Invalid server state!'); + return response + .flash( + 'warning', + `Invalid server state. Dataset with id ${id} cannot be received. Datset has server state ${dataset.server_state}.`, + ) + .redirect() + .back(); + } + + return inertia.render('Editor/Dataset/Receive', { + dataset, + }); + } public async create({}: HttpContextContract) {} diff --git a/app/Controllers/Http/Submitter/DatasetController.ts b/app/Controllers/Http/Submitter/DatasetController.ts index 8d29a3a..06c0e57 100644 --- a/app/Controllers/Http/Submitter/DatasetController.ts +++ b/app/Controllers/Http/Submitter/DatasetController.ts @@ -884,24 +884,23 @@ export default class DatasetController { const uploadedFiles: MultipartFileContract[] = request.files('files'); if (Array.isArray(uploadedFiles) && uploadedFiles.length > 0) { for (const [index, fileData] of uploadedFiles.entries()) { + try { + await this.scanFileForViruses(fileData.tmpPath); //, 'gitea.lan', 3310); + // await this.scanFileForViruses("/tmp/testfile.txt"); + } catch (error) { + // If the file is infected or there's an error scanning the file, throw a validation exception + throw error; + } + + // move to disk: const fileName = `file-${cuid()}.${fileData.extname}`; const datasetFolder = `files/${dataset.id}`; - await fileData.moveToDisk(datasetFolder, { name: fileName, overwrite: true }, 'local'); // let path = coverImage.filePath; + //save to db: const { clientFileName, sortOrder } = this.extractVariableNameAndSortOrder(fileData.clientName); const mimeType = fileData.headers['content-type'] || 'application/octet-stream'; // Fallback to a default MIME type - // save file metadata into db - // const newFile = new File(); - // newFile.pathName = `${datasetFolder}/${fileName}`; - // newFile.fileSize = fileData.size; - // newFile.mimeType = mimeType; - // newFile.label = clientFileName; - // newFile.sortOrder = sortOrder ? sortOrder : index; - // newFile.visibleInFrontdoor = true; - // newFile.visibleInOai = true; - const newFile = await dataset .useTransaction(trx) .related('files') diff --git a/app/Models/Dataset.ts b/app/Models/Dataset.ts index 2ba2c1c..6bf2c6e 100644 --- a/app/Models/Dataset.ts +++ b/app/Models/Dataset.ts @@ -95,7 +95,13 @@ export default class Dataset extends DatasetExtension { }) public created_at: DateTime; - @column.dateTime({ autoCreate: true, autoUpdate: true, columnName: 'server_date_modified' }) + @column.dateTime({ + serialize: (value: Date | null) => { + return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value; + }, + autoCreate: true, + autoUpdate: true, + columnName: 'server_date_modified' }) public server_date_modified: DateTime; @manyToMany(() => Person, { @@ -188,6 +194,15 @@ export default class Dataset extends DatasetExtension { return mainTitle ? mainTitle.value : null; } + @computed({ + serializeAs: 'main_abstract', + }) + public get mainAbstract() { + // return `${this.firstName} ${this.lastName}`; + const mainTitle = this.descriptions?.find((desc) => desc.type === 'Abstract'); + return mainTitle ? mainTitle.value : null; + } + @manyToMany(() => Person, { pivotForeignKey: 'document_id', pivotRelatedForeignKey: 'person_id', diff --git a/package-lock.json b/package-lock.json index 5f7d9ae..684535f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3978,9 +3978,9 @@ "dev": true }, "node_modules/@types/eslint": { - "version": "8.44.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", - "integrity": "sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==", + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", "dev": true, "peer": true, "dependencies": { @@ -4166,9 +4166,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "version": "20.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz", + "integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==", "dependencies": { "undici-types": "~5.26.4" } @@ -8194,9 +8194,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.596", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.596.tgz", - "integrity": "sha512-zW3zbZ40Icb2BCWjm47nxwcFGYlIgdXkAx85XDO7cyky9J4QQfq8t0W19/TLZqq3JPQXtlv8BPIGmfa9Jb4scg==", + "version": "1.4.597", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.597.tgz", + "integrity": "sha512-0XOQNqHhg2YgRVRUrS4M4vWjFCFIP2ETXcXe/0KIQBjXE9Cpy+tgzzYfuq6HGai3hWq0YywtG+5XK8fyG08EjA==", "dev": true }, "node_modules/emittery": { diff --git a/resources/js/Components/AsideMenuItem.vue b/resources/js/Components/AsideMenuItem.vue index 131d0e5..00ab4e8 100644 --- a/resources/js/Components/AsideMenuItem.vue +++ b/resources/js/Components/AsideMenuItem.vue @@ -37,18 +37,13 @@ const styleService = StyleService(); const hasColor = computed(() => props.item && props.item.color); -const isDropdownOpen = ref(false); +// const isDropdownOpen = ref(false); -const isChildSelected = computed(() => { - if (props.item.children && props.item.children.length > 0) { - return children.value.some(childItem => stardust.isCurrent(childItem.route)); - } - return false; -}); - -// const value = computed({ -// get: () => props.modelValue, -// set: (value) => emit('update:modelValue', value), +// const isChildSelected = computed(() => { +// if (props.item.children && props.item.children.length > 0) { +// return children.value.some(childItem => stardust.isCurrent(childItem.route)); +// } +// return false; // }); @@ -154,7 +149,7 @@ const hasRoles = computed(() => { - + + + {{ flash.message }} + + + {{ flash.warning }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Dataset Title + Submitter + + Server StateEditorDate of last modificationActions
+ + + {{ dataset.main_title }} + {{ dataset.user.login }} + {{ dataset.server_state }} + + + Preferred reviewer: {{ dataset.preferred_reviewer }} + + + + In approval by: {{ dataset.editor?.login }} + + + {{ dataset.editor?.login }} + + + {{ dataset.server_date_modified }} + + + + + + +
+
+ + +
+
+ + + + + diff --git a/resources/js/Pages/Editor/Dataset/Receive.vue b/resources/js/Pages/Editor/Dataset/Receive.vue new file mode 100644 index 0000000..7dd4f31 --- /dev/null +++ b/resources/js/Pages/Editor/Dataset/Receive.vue @@ -0,0 +1,105 @@ + + + diff --git a/resources/js/Pages/Submitter/Dataset/Index.vue b/resources/js/Pages/Submitter/Dataset/Index.vue index 6d04bb0..7cdc468 100644 --- a/resources/js/Pages/Submitter/Dataset/Index.vue +++ b/resources/js/Pages/Submitter/Dataset/Index.vue @@ -6,13 +6,11 @@ import { mdiSquareEditOutline, mdiTrashCan, mdiAlertBoxOutline, mdiLockOpen } fr import { computed } 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'; const props = defineProps({ @@ -34,38 +32,41 @@ const props = defineProps({ // } }); -// const search = computed(() => { -// return props.filters.search; -// }); - const flash: ComputedRef = computed(() => { // let test = usePage(); // console.log(test); return usePage().props.flash; }); -// const errors: ComputedRef = computed(() => { -// return usePage().props.errors; -// }); +const validStates = ['inprogress', 'rejected_editor']; -// const form = useForm({ -// search: props.filters.search, -// }); +const getRowClass = (dataset) => { + // (props.options ? 'select' : props.type) + let rowclass = ''; + if (dataset.server_state == 'inprogress') { + rowclass = 'inprogress'; + } else if (dataset.server_state == 'released') { + rowclass = 'released'; + } else if (dataset.server_state == 'editor_accepted' || dataset.server_state == 'ejected_reviewer') { + rowclass = 'editor_accepted'; + } else if (dataset.server_state == 'approved') { + rowclass = 'approved'; + } else if (dataset.server_state == 'eviewed') { + rowclass = 'eviewed'; + } else if (dataset.server_state == 'ejected_editor') { + rowclass = 'ejected_editor'; + } else { + rowclass = ''; + } + return rowclass; +}; -// const formDelete = useForm({}); - -// async function destroy(id) { -// const destroy = async (id) => { -// // if (confirm('Are you sure you want to delete?')) { -// // await formDelete.delete(stardust.route('dataset.destroy', [id])); -// // } -// }; + + diff --git a/resources/js/menu.ts b/resources/js/menu.ts index b7e57e8..d5b085f 100644 --- a/resources/js/menu.ts +++ b/resources/js/menu.ts @@ -1,4 +1,13 @@ -import { mdiMonitor, mdiGithub, mdiAccountEye, mdiAccountGroup, mdiDatabasePlus } from '@mdi/js'; +import { + mdiMonitor, + mdiGithub, + mdiAccountEye, + mdiAccountGroup, + mdiAccountEdit, + mdiPublish, + mdiAccountArrowUp, + mdiFormatListNumbered, +} from '@mdi/js'; export default [ { @@ -36,23 +45,42 @@ export default [ }, { // route: 'dataset.create', - icon: mdiDatabasePlus, + icon: mdiAccountArrowUp, label: 'Submitter', roles: ['submitter'], isOpen: false, children: [ { route: 'dataset.list', - icon: mdiDatabasePlus, + icon: mdiFormatListNumbered, label: 'All my datasets', }, { route: 'dataset.create', - icon: mdiDatabasePlus, + icon: mdiPublish, label: 'Create Dataset', }, ], }, + { + // route: 'dataset.create', + icon: mdiAccountEdit, + label: 'Editor', + roles: ['editor'], + isOpen: false, + children: [ + { + route: 'editor.dataset.list', + icon: mdiFormatListNumbered, + label: 'All my datasets', + }, + // { + // route: 'dataset.create', + // icon: mdiPublish, + // label: 'Create Dataset', + // }, + ], + }, // { // route: 'dataset.create', // icon: mdiDatabasePlus, diff --git a/start/routes.ts b/start/routes.ts index 6861edd..ef1b9fe 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -94,7 +94,6 @@ Route.post('/app/login', 'Auth/AuthController.login').as('login.store'); // Route.post("/signup", "AuthController.signup"); Route.post('/signout', 'Auth/AuthController.logout').as('logout'); - // administrator Route.group(() => { Route.get('/settings', async ({ inertia }) => { @@ -135,10 +134,6 @@ Route.group(() => { // .middleware(['auth', 'can:dataset-list,dataset-publish']); .middleware(['auth', 'is:administrator,moderator']); - - - - Route.get('/edit-account-info', 'UsersController.accountInfo') .as('admin.account.info') .namespace('App/Controllers/Http/Admin') @@ -179,10 +174,10 @@ Route.group(() => { .where('id', Route.matchers.number()) .middleware(['auth', 'can:dataset-edit']); Route.put('/dataset/:id/update', 'DatasetController.update') - .as('dataset.update') + .as('dataset.update') .where('id', Route.matchers.number()) .middleware(['auth', 'can:dataset-edit']); - + Route.get('/dataset/:id/delete', 'DatasetController.delete').as('dataset.delete').middleware(['auth', 'can:dataset-delete']); Route.put('/dataset/:id/deleteupdate', 'DatasetController.deleteUpdate') .as('dataset.deleteUpdate') @@ -199,8 +194,17 @@ Route.group(() => { // .middleware(['auth', 'can:dataset-list,dataset-publish']); // .middleware(['auth', 'is:submitter']); +// editor: Route.group(() => { - Route.put('/dataset/:id/update', 'DatasetsController.update').as('editor.dataset.update').middleware(['auth', 'can:dataset-editor-edit']); + Route.get('/dataset', 'DatasetController.index').as('editor.dataset.list').middleware(['auth', 'can:dataset-editor-list']); + Route.get('dataset/:id/receive', 'DatasetController.receive') + .as('editor.dataset.receive') + .where('id', Route.matchers.number()) + .middleware(['auth', 'can:dataset-receive']); + + Route.put('/dataset/:id/update', 'DatasetController.update') + .as('editor.dataset.update') + .middleware(['auth', 'can:dataset-editor-edit']); }) .namespace('App/Controllers/Http/Editor') .prefix('editor');