From 537c6fd81aa7031c7805c59620eceb72b502da4c Mon Sep 17 00:00:00 2001 From: Arno Kaimbacher Date: Sun, 12 Jan 2025 15:47:25 +0100 Subject: [PATCH] feat: Add and refactor MIME type management - Added BaseModel with fillable attributes and mergeFillableAttributes method - Refactored MimeType model to extend BaseModel - Implemented destroy method in MimetypeController for deleting MIME types - Updated Create.vue component with refactoring and improved type safety - Fixed issues with ref usage in Create.vue - Updated routes to include new and refactored endpoints --- .../Http/Admin/MimetypeController.ts | 44 ++- app/models/base_model.ts | 4 +- app/models/traits/dataset_extension.ts | 2 +- providers/query_builder_provider.ts | 19 +- public/assets/manifest.json | 4 +- resources/js/Pages/Admin/Mimetype/Create.vue | 216 +++------- resources/js/Pages/Admin/Mimetype/Delete.vue | 80 ++++ resources/js/Pages/Admin/Mimetype/Index.vue | 27 +- resources/js/tsconfig.json | 1 + start/routes.ts | 373 +++++------------- 10 files changed, 321 insertions(+), 449 deletions(-) create mode 100644 resources/js/Pages/Admin/Mimetype/Delete.vue diff --git a/app/Controllers/Http/Admin/MimetypeController.ts b/app/Controllers/Http/Admin/MimetypeController.ts index 6d0663b..716c4fb 100644 --- a/app/Controllers/Http/Admin/MimetypeController.ts +++ b/app/Controllers/Http/Admin/MimetypeController.ts @@ -106,7 +106,7 @@ export default class MimetypeController { public async down({ request, response }: HttpContext) { const id = request.param('id'); - const mimetype = await MimeType.findOrFail(id); + const mimetype = (await MimeType.findOrFail(id)) as MimeType; mimetype.enabled = false; await mimetype.save(); @@ -138,4 +138,46 @@ export default class MimetypeController { // // roleHasPermissions: Object.keys(rolerHasPermissions).map((key) => rolerHasPermissions[key]), //convert object to array with role ids // }); // } + public async delete({ request, inertia, response, session }: HttpContext) { + const id = request.param('id'); + try { + const mimetype = await MimeType.query() + // .preload('user', (builder) => { + // builder.select('id', 'login'); + // }) + .where('id', id) + // .preload('files') + .firstOrFail(); + // const validStates = ['inprogress', 'rejected_editor']; + // 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 deleted. Datset has server state ${dataset.server_state}.`, + // ) + // .redirect() + // .toRoute('dataset.list'); + // } + return inertia.render('Admin/Mimetype/Delete', { + mimetype, + }); + } catch (error) { + if (error.code == 'E_ROW_NOT_FOUND') { + session.flash({ warning: 'Mimetype is not found in database' }); + } else { + session.flash({ warning: 'general error occured, you cannot delete the mimetype' }); + } + return response.redirect().toRoute('mimetype.index'); + } + } + + public async deleteStore({ request, response, session }: HttpContext) { + const id = request.param('id'); + const mimetype = await MimeType.findOrFail(id); + await mimetype.delete(); + + session.flash('message', `Mimetype ${mimetype.name} has been deleted.`); + return response.redirect().toRoute('settings.mimetype.index'); + } } diff --git a/app/models/base_model.ts b/app/models/base_model.ts index 5cb4d12..87319d4 100644 --- a/app/models/base_model.ts +++ b/app/models/base_model.ts @@ -1,3 +1,4 @@ +// import { BaseModel as LucidBaseModel } from '@adonisjs/lucid/orm'; import { BaseModel as LucidBaseModel } from '@adonisjs/lucid/orm'; // import { ManyToManyQueryClient } from '@ioc:Adonis/Lucid/Orm'; @@ -29,8 +30,9 @@ export default class BaseModel extends LucidBaseModel { */ // private fillInvoked: boolean = false; - [key: string]: any; + // [key: string]: any; + public static fillable: string[] = []; public fill(attributes: any, allowExtraProperties: boolean = false): this { diff --git a/app/models/traits/dataset_extension.ts b/app/models/traits/dataset_extension.ts index cc28732..69fc197 100644 --- a/app/models/traits/dataset_extension.ts +++ b/app/models/traits/dataset_extension.ts @@ -36,7 +36,7 @@ export default abstract class DatasetExtension extends LucidBaseModel { // which fields shoud#t be published protected internalFields: Record = {}; protected fields: Record = {}; - [key: string]: any; + // [key: string]: any; private getExternalFields(): Record { // External fields definition diff --git a/providers/query_builder_provider.ts b/providers/query_builder_provider.ts index 05384cd..6509fae 100644 --- a/providers/query_builder_provider.ts +++ b/providers/query_builder_provider.ts @@ -2,10 +2,10 @@ import { ApplicationService } from '@adonisjs/core/types'; // import { Database, DatabaseQueryBuilder } from '@adonisjs/lucid/database'; import { ModelQueryBuilder } from '@adonisjs/lucid/orm'; // import db from '@adonisjs/lucid/services/db'; -import { LucidModel, LucidRow } from '@adonisjs/lucid/types/model'; +// import { LucidModel, LucidRow } from '@adonisjs/lucid/types/model'; import { ChainableContract, ExcutableQueryBuilderContract } from '@adonisjs/lucid/types/querybuilder'; -import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model'; +// import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model'; /* |-------------------------------------------------------------------------- | Provider @@ -37,16 +37,23 @@ declare module '@adonisjs/lucid/types/model' { // selectId(primaryKey?: string): Promise; // selectIdOrFail(primaryKey?: string): Promise; // pluck(valueColumn: string, id?: string): Promise<{ [key: string]: any }>; - pluck(valueColumn: string, id?: string): Promise<{ [key: string]: any }>; + pluck(valueColumn: string, id?: string): Promise<{ [key: string]: any }>; + } + + export interface LucidRow { + [key: string]: any; } } declare module '@adonisjs/lucid/orm' { - - class ModelQueryBuilder extends Chainable implements ModelQueryBuilderContract { - public pluck(valueColumn: string, id?: string): Promise<{ [key: string]: any }>; + interface ModelQueryBuilder { + pluck(valueColumn: string, id?: string): Promise<{ [key: string]: any }>; } + + // class ModelQueryBuilder implements ModelQueryBuilderContract { + // public pluck(valueColumn: string, id?: string): Promise<{ [key: string]: any }>; + // } } diff --git a/public/assets/manifest.json b/public/assets/manifest.json index 87b56fd..9062764 100644 --- a/public/assets/manifest.json +++ b/public/assets/manifest.json @@ -5,6 +5,7 @@ "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", @@ -97,5 +98,6 @@ "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/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" } \ No newline at end of file diff --git a/resources/js/Pages/Admin/Mimetype/Create.vue b/resources/js/Pages/Admin/Mimetype/Create.vue index 96b1f64..60ce1a9 100644 --- a/resources/js/Pages/Admin/Mimetype/Create.vue +++ b/resources/js/Pages/Admin/Mimetype/Create.vue @@ -27,23 +27,42 @@ const props = defineProps({ ctrlKFocus: Boolean, }); -const isReadOnly = true; -// Get keys from standardTypes and otherTypes +const isReadOnly: boolean = true; const standardMimeTypes = Object.keys(standardTypes); const otherMimeTypes = Object.keys(otherTypes); -// MIME types list (you can expand this as needed) -// const mimeTypes = Object.keys(standardTypes); - -// Concatenate the keys from both types const mimeTypes = [...standardMimeTypes, ...otherMimeTypes]; -const file_extensions = reactive({}); -const form = useForm({ +const file_extensions = reactive>({}); +interface FormData { + name: string; + file_extension: string[]; + enabled: boolean; +} +const form = useForm({ name: '', - file_extension: [] as Array, + file_extension: [], enabled: true, }); +const filteredMimetypes = ref([]); // Stores the filtered MIME types for the dropdown +const showDropdown = ref(false); // Controls the visibility of the autocomplete dropdown +const selectedIndex: Ref = ref(0); // Track selected MIME type in the dropdown +const ul: Ref = ref([]); +const newExtension: Ref = ref(''); //reactive([] as Array); +const mimetypeError = ref(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', + }); + } +}); + // Function to reset the object function resetFileExtensions() { // Reset to an empty object @@ -66,89 +85,15 @@ const inputElClass = computed(() => { return base; }); - -// Handle form submission -const submit = async () => { - if (isValidForm()) { - await form.post(stardust.route('settings.mimetype.store'), form); - } -}; - -// Form validation before submission -const isValidForm = (): boolean => { - if (!form.name) { - form.errors.name = 'Name is required.'; - return false; - } else { - form.errors.name = ''; - } - if (!form.file_extension.length) { - form.errors.file_extension = ['At least one file extension is required.']; - return false; - } - return true; -}; - -const newExtension: Ref = ref(''); //reactive([] as Array); -const filteredMimetypes = ref([]); // Stores the filtered MIME types for the dropdown -const showDropdown = ref(false); // Controls the visibility of the autocomplete dropdown -const selectedIndex: Ref = ref(0); // Track selected MIME type in the dropdown -const ul: Ref> = ref([]); -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 extensionError = ref(null); -const mimetypeError = ref(null); - -// const addFileExtension = () => { -// if (newExtension.value && !form.file_extensions.includes(newExtension.value)) { -// if (isValidFileExtension(newExtension.value)) { -// form.file_extensions.push(newExtension.value); -// newExtension.value = ''; // Clear the input field -// extensionError.value = null; // Clear any existing error -// } else { -// extensionError.value = 'Invalid file extension or MIME type.'; -// } -// } -// }; - -// // Helper to validate file extension -// const isValidFileExtension = (extension: string): boolean => { -// const mimeType = mime.getType(extension); // e.g. 'application/pdf' -// return mimeType !== null; -// }; - - -// const removeFileExtension = (index: number) => { -// file_extensions.splice(index, 1); // Remove the extension at the given index -// }; - +// Check if the MIME type is valid const isValidMimeType = (mimeType: string): boolean => { let extensions = mime.getExtension(mimeType) return extensions !== null; }; -// watch(newExtension, (value) => { -// if (value) { -// filteredMimetypes.value = mimeTypes.filter(mimeType => -// mimeType.toLowerCase().includes(value.toLowerCase()) -// ); -// showDropdown.value = true; -// } else { -// showDropdown.value = false; -// } -// }); async function handleInputChange(e: Event) { - // const target = e.target; - + const target = e.target; + newExtension.value = target.value; if (newExtension.value.length >= 2) { showDropdown.value = true; @@ -172,15 +117,10 @@ const selectResult = (mimeType: string) => { if (form.name && isValidMimeType(form.name)) { const extensions = mime.getAllExtensions(form.name) as Set; - // Convert the Set to an array of objects - // Convert the Set to an object // Iterate over each extension and set both key and value to the extension Array.from(extensions).forEach(extension => { file_extensions[extension] = extension; }); - - // file_extensions.push(...Array.from(formattedExtensions)); - } else { mimetypeError.value = 'Invalid MIME type.'; } @@ -201,7 +141,6 @@ function onArrowUp() { function onEnter() { if (Array.isArray(filteredMimetypes.value) && filteredMimetypes.value.length && selectedIndex.value !== -1 && selectedIndex.value < filteredMimetypes.value.length) { - //this.display = this.results[this.selectedIndex]; const mimeType = filteredMimetypes.value[selectedIndex.value]; // this.$emit('person', person); form.name = mimeType; @@ -221,7 +160,7 @@ function onEnter() { // Convert the Set to an object Array.from(extensions).forEach(extension => { file_extensions[extension] = extension; - }); + }); // file_extensions.push(...formattedExtensions); } else { @@ -230,6 +169,31 @@ function onEnter() { } } } + +// 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.'; + return false; + } else { + form.errors.name = ''; + } + if (!form.file_extension.length) { + form.errors.file_extension = 'At least one file extension is required.'; + return false; + } + return true; +};