diff --git a/adonisrc.ts b/adonisrc.ts
index 97e4528..19e946c 100644
--- a/adonisrc.ts
+++ b/adonisrc.ts
@@ -35,6 +35,7 @@ export default defineConfig({
() => import('#start/rules/dependent_array_min_length'),
() => import('#start/rules/referenceValidation'),
() => import('#start/rules/valid_mimetype'),
+ () => import('#start/rules/array_contains_types'),
],
/*
|--------------------------------------------------------------------------
diff --git a/app/Controllers/Http/Submitter/DatasetController.ts b/app/Controllers/Http/Submitter/DatasetController.ts
index d39d6d2..42ae5c3 100644
--- a/app/Controllers/Http/Submitter/DatasetController.ts
+++ b/app/Controllers/Http/Submitter/DatasetController.ts
@@ -207,7 +207,8 @@ export default class DatasetController {
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ // .minLength(2)
+ .arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
descriptions: vine
.array(
vine.object({
@@ -221,7 +222,8 @@ export default class DatasetController {
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ // .minLength(1),
+ .arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
authors: vine
.array(
vine.object({
@@ -270,7 +272,6 @@ export default class DatasetController {
}
return response.redirect().back();
}
-
public async thirdStep({ request, response }: HttpContext) {
const newDatasetSchema = vine.object({
// first step
@@ -296,7 +297,8 @@ export default class DatasetController {
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ // .minLength(2)
+ .arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
descriptions: vine
.array(
vine.object({
@@ -310,7 +312,8 @@ export default class DatasetController {
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ // .minLength(1),
+ .arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
authors: vine
.array(
vine.object({
@@ -539,11 +542,6 @@ export default class DatasetController {
try {
await multipart.process();
// // Instead of letting an error abort the controller, check if any error occurred
- // if (fileUploadError) {
- // // Flash the error and return an inertia view that shows the error message.
- // session.flash('errors', { 'upload error': [fileUploadError.message] });
- // return response.redirect().back();
- // }
} catch (error) {
// This is where you'd expect to catch any errors.
session.flash('errors', error.messages);
diff --git a/app/validators/dataset.ts b/app/validators/dataset.ts
index cdd1fbf..864f1ba 100644
--- a/app/validators/dataset.ts
+++ b/app/validators/dataset.ts
@@ -40,7 +40,8 @@ export const createDatasetValidator = vine.compile(
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ // .minLength(2)
+ .arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
descriptions: vine
.array(
vine.object({
@@ -54,7 +55,8 @@ export const createDatasetValidator = vine.compile(
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ // .minLength(1),
+ .arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
authors: vine
.array(
vine.object({
@@ -156,8 +158,7 @@ export const createDatasetValidator = vine.compile(
.fileScan({ removeInfected: true }),
)
.minLength(1),
- }),
-);
+ }),);
/**
* Validates the dataset's update action
@@ -187,7 +188,8 @@ export const updateDatasetValidator = vine.compile(
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ // .minLength(2)
+ .arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
descriptions: vine
.array(
vine.object({
@@ -201,7 +203,7 @@ export const updateDatasetValidator = vine.compile(
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
- .minLength(1),
+ .arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
authors: vine
.array(
vine.object({
diff --git a/database/migrations/dataset_7_collections.ts b/database/migrations/dataset_7_collections.ts
index 4fe4b8b..1e970c5 100644
--- a/database/migrations/dataset_7_collections.ts
+++ b/database/migrations/dataset_7_collections.ts
@@ -5,7 +5,7 @@ export default class Collections extends BaseSchema {
public async up() {
this.schema.createTable(this.tableName, (table) => {
- table.increments('id').defaultTo("nextval('collections_id_seq')");
+ table.increments('id');//.defaultTo("nextval('collections_id_seq')");
table.integer('role_id').unsigned();
table
.foreign('role_id', 'collections_role_id_foreign')
diff --git a/resources/js/Components/FormCheckRadio.vue b/resources/js/Components/FormCheckRadio.vue
index 8cda06e..ca27675 100644
--- a/resources/js/Components/FormCheckRadio.vue
+++ b/resources/js/Components/FormCheckRadio.vue
@@ -56,27 +56,15 @@ const isChecked = computed(() => {
return Array.isArray(computedValue.value) &&
computedValue.value.length > 0 &&
computedValue.value[0] === props.inputValue;
+ } else if (props.type === 'checkbox') {
+ return Array.isArray(computedValue.value) &&
+ computedValue.value.length > 0 &&
+ computedValue.value.includes(props.inputValue);
}
return computedValue.value === props.inputValue;
});
-
-
-
-
diff --git a/resources/js/Components/FormCheckRadioGroup.vue b/resources/js/Components/FormCheckRadioGroup.vue
index ef86f4b..c995cab 100644
--- a/resources/js/Components/FormCheckRadioGroup.vue
+++ b/resources/js/Components/FormCheckRadioGroup.vue
@@ -47,7 +47,7 @@ const computedValue = computed({
if (props.modelValue.every((item) => typeof item === 'number')) {
return props.modelValue;
} else if (props.modelValue.every((item) => hasIdAttribute(item))) {
- const ids = props.modelValue.map((obj) => obj.id.toString());
+ const ids = props.modelValue.map((obj) => obj.id);
return ids;
}
return props.modelValue;
@@ -109,6 +109,6 @@ const inputElClass = computed(() => {
+ :name="name" :input-value="Number(key)" :label="value" :class="componentClass" />
diff --git a/start/rules/array_contains_types.ts b/start/rules/array_contains_types.ts
new file mode 100644
index 0000000..30c2363
--- /dev/null
+++ b/start/rules/array_contains_types.ts
@@ -0,0 +1,80 @@
+import { FieldContext } from '@vinejs/vine/types';
+import vine, { VineArray } from '@vinejs/vine';
+import { SchemaTypes } from '@vinejs/vine/types';
+
+type Options = {
+ typeA: string;
+ typeB: string;
+};
+
+/**
+ * Custom rule to validate an array of titles contains at least one title
+ * with type 'main' and one with type 'translated'.
+ *
+ * This rule expects the validated value to be an array of objects,
+ * where each object has a "type" property.
+ */
+async function arrayContainsTypes(value: unknown, options: Options, field: FieldContext) {
+ if (!Array.isArray(value)) {
+ field.report(`The {{field}} must be an array of titles.`, 'array.titlesContainsMainAndTranslated', field);
+ return false;
+ }
+
+ const typeAExpected = options.typeA.toLowerCase();
+ const typeBExpected = options.typeB.toLowerCase();
+
+ // const hasMain = value.some((title: any) => {
+ // return typeof title === 'object' && title !== null && String(title.type).toLowerCase() === 'main';
+ // });
+
+ // const hasTranslated = value.some((title: any) => {
+ // return typeof title === 'object' && title !== null && String(title.type).toLowerCase() === 'translated';
+ // });
+ const hasTypeA = value.some((item: any) => {
+ return typeof item === 'object' && item !== null && String(item.type).toLowerCase() === typeAExpected;
+ });
+
+ const hasTypeB = value.some((item: any) => {
+ return typeof item === 'object' && item !== null && String(item.type).toLowerCase() === typeBExpected;
+ });
+ if (!hasTypeA || !hasTypeB) {
+ let errorMessage = `The ${field.getFieldPath()} array must have at least one '${options.typeA}' item and one '${options.typeB}' item.`;
+
+ // Check for specific field names to produce a more readable message.
+ if (field.getFieldPath() === 'titles') {
+ // For titles we expect one main and minimum one translated title.
+ if (!hasTypeA && !hasTypeB) {
+ errorMessage = 'For titles, define one main title and minimum one translated title.';
+ } else if (!hasTypeA) {
+ errorMessage = 'For titles, define one main title.';
+ } else if (!hasTypeB) {
+ errorMessage = 'For titles, define minimum one translated title.';
+ }
+ } else if (field.getFieldPath() === 'descriptions') {
+ // For descriptions we expect one abstracts description and minimum one translated description.
+ if (!hasTypeA && !hasTypeB) {
+ errorMessage = 'For descriptions, define one abstracts description and minimum one translated description.';
+ } else if (!hasTypeA) {
+ errorMessage = 'For descriptions, define one abstracts description.';
+ } else if (!hasTypeB) {
+ errorMessage = 'For descriptions, define minimum one translated description.';
+ }
+ }
+
+ field.report(errorMessage, 'array.containsTypes', field, options);
+ return false;
+ }
+ return true;
+}
+
+export const arrayContainsMainAndTranslatedRule = vine.createRule(arrayContainsTypes);
+
+declare module '@vinejs/vine' {
+ interface VineArray {
+ arrayContainsTypes(options: Options): this;
+ }
+}
+
+VineArray.macro('arrayContainsTypes', function (this: VineArray, options: Options) {
+ return this.use(arrayContainsMainAndTranslatedRule(options));
+});
diff --git a/tailwind.config.js b/tailwind.config.js
index af6d6b2..e41b62d 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -14,6 +14,7 @@ module.exports = {
extend: {
backgroundImage: {
'radio-checked': "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E\")",
+ 'checkbox-checked': "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E\")",
},
colors: {
'primary': '#22C55E',