From b6fdfbff4170ea4a8fb6ad4163d1d943368f8c44 Mon Sep 17 00:00:00 2001 From: Arno Kaimbacher Date: Mon, 27 Nov 2023 17:17:22 +0100 Subject: [PATCH] - addes @adonisjs/redis fo saving session into redis with redis.ts contract and config - npm updated - added createHashValues and dlete inside File.ts - added dataset_count property inside Subject.ts - corrected rotes.ts with correct permissions --- .adonisrc.json | 23 ++- .env.example | 4 + .../Http/Submitter/DatasetController.ts | 157 ++++++++++++------ app/Models/Dataset.ts | 2 +- app/Models/File.ts | 42 +++-- app/Models/Person.ts | 6 + app/Models/Subject.ts | 49 +++--- app/Validators/CreateDatasetValidator.ts | 2 +- app/Validators/UpdateDatasetValidator.ts | 2 +- config/redis.ts | 46 +++++ contracts/redis.ts | 13 ++ package-lock.json | 92 ++++++++++ package.json | 1 + resources/js/Components/AsideMenu.vue | 1 + resources/js/Components/AsideMenuItem.vue | 21 ++- resources/js/Components/AsideMenuList.vue | 6 +- resources/js/Components/FileUpload.vue | 5 +- resources/js/Components/NavBar.vue | 7 +- .../Components/SearchCategoryAutocomplete.vue | 88 +++------- resources/js/Components/TableKeywords.vue | 8 +- resources/js/Dataset.ts | 23 ++- .../js/Pages/Submitter/Dataset/Create.vue | 9 +- resources/js/Pages/Submitter/Dataset/Edit.vue | 32 +++- .../js/Pages/Submitter/Dataset/Index.vue | 2 +- resources/js/Pages/Submitter/EditComponent.ts | 4 +- resources/js/menu.ts | 1 + start/inertia.ts | 3 +- start/routes.ts | 21 ++- tsconfig.json | 27 ++- 29 files changed, 496 insertions(+), 201 deletions(-) create mode 100644 config/redis.ts create mode 100644 contracts/redis.ts diff --git a/.adonisrc.json b/.adonisrc.json index 3afa196..6deab27 100644 --- a/.adonisrc.json +++ b/.adonisrc.json @@ -19,11 +19,15 @@ "./start/kernel", { "file": "./start/inertia", - "environment": ["web"] + "environment": [ + "web" + ] }, { "file": "./start/validator", - "environment": ["web"] + "environment": [ + "web" + ] } ], "providers": [ @@ -37,7 +41,8 @@ "@adonisjs/auth", "@eidellev/adonis-stardust", "./providers/QueryBuilderProvider", - "./providers/TokenWorkerProvider" + "./providers/TokenWorkerProvider", + "@adonisjs/redis" ], "metaFiles": [ { @@ -49,15 +54,21 @@ "reloadServer": false } ], - "aceProviders": ["@adonisjs/repl"], + "aceProviders": [ + "@adonisjs/repl" + ], "tests": { "suites": [ { "name": "functional", - "files": ["tests/functional/**/*.spec(.ts|.js)"], + "files": [ + "tests/functional/**/*.spec(.ts|.js)" + ], "timeout": 60000 } ] }, - "testProviders": ["@japa/preset-adonis/TestsProvider"] + "testProviders": [ + "@japa/preset-adonis/TestsProvider" + ] } diff --git a/.env.example b/.env.example index 50ee29c..befe751 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,7 @@ PG_PORT=5432 PG_USER=lucid PG_PASSWORD= PG_DB_NAME=lucid +REDIS_CONNECTION=local +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 +REDIS_PASSWORD= diff --git a/app/Controllers/Http/Submitter/DatasetController.ts b/app/Controllers/Http/Submitter/DatasetController.ts index b2cfbaf..34dc21c 100644 --- a/app/Controllers/Http/Submitter/DatasetController.ts +++ b/app/Controllers/Http/Submitter/DatasetController.ts @@ -34,6 +34,7 @@ import ClamScan from 'clamscan'; import { ValidationException } from '@ioc:Adonis/Core/Validator'; import Drive from '@ioc:Adonis/Core/Drive'; import { Exception } from '@adonisjs/core/build/standalone'; +import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser'; export default class DatasetController { public async index({ auth, request, inertia }: HttpContextContract) { @@ -335,8 +336,8 @@ export default class DatasetController { } session.flash('message', 'Dataset has been created successfully'); - // return response.redirect().toRoute('user.index'); - return response.redirect().back(); + return response.redirect().toRoute('user.index'); + // return response.redirect().back(); } private async createDatasetAndAssociations(user: User, request: HttpContextContract['request'], trx: TransactionClientContract) { @@ -691,7 +692,10 @@ export default class DatasetController { .preload('licenses') .preload('authors') .preload('contributors') - .preload('subjects') + // .preload('subjects') + .preload('subjects', (builder) => { + builder.orderBy('id', 'asc').withCount('datasets'); + }) .preload('references') .preload('files'); @@ -779,6 +783,7 @@ export default class DatasetController { throw error; // return response.badRequest(error.messages); } + // await request.validate(UpdateDatasetValidator); const id = request.param('id'); let trx: TransactionClientContract | null = null; @@ -843,6 +848,25 @@ export default class DatasetController { } } + // await dataset.useTransaction(trx).related('subjects').sync([]); + const keywords = request.input('subjects'); + for (const keywordData of keywords) { + if (keywordData.id) { + const subject = await Subject.findOrFail(keywordData.id); + // await dataset.useTransaction(trx).related('subjects').attach([keywordData.id]); + subject.value = keywordData.value; + subject.type = keywordData.type; + subject.external_key = keywordData.external_key; + if (subject.$isDirty) { + await subject.save(); + } + } else { + const keyword = new Subject(); + keyword.fill(keywordData); + await dataset.useTransaction(trx).related('subjects').save(keyword, false); + } + } + // Save already existing files const files = request.input('fileInputs', []); for (const fileData of files) { @@ -857,43 +881,57 @@ export default class DatasetController { } // handle new uploaded files: - const uploadedFiles = request.files('files'); + const uploadedFiles: MultipartFileContract[] = request.files('files'); if (Array.isArray(uploadedFiles) && uploadedFiles.length > 0) { - // let index = 1; - // for (const key in files) { - // const formFile = files[key] - // for (const fileData of files) { for (const [index, fileData] of uploadedFiles.entries()) { - // const uploads = request.file('uploads'); - // const fileIndex = formFile.file; - // const file = uploads[fileIndex]; - const fileName = `file-${cuid()}.${fileData.extname}`; - const mimeType = fileData.headers['content-type'] || 'application/octet-stream'; // Fallback to a default MIME type const datasetFolder = `files/${dataset.id}`; - await fileData.moveToDisk( - datasetFolder, - { - name: fileName, - overwrite: true, // overwrite in case of conflict - }, - 'local', - ); - // save file metadata into db - const newFile = new File(); - newFile.pathName = `${datasetFolder}/${fileName}`; - newFile.fileSize = fileData.size; - newFile.mimeType = mimeType; - newFile.label = fileData.clientName; - newFile.sortOrder = index; - newFile.visibleInFrontdoor = true; - newFile.visibleInOai = true; + + await fileData.moveToDisk(datasetFolder, { name: fileName, overwrite: true }, 'local'); // let path = coverImage.filePath; - await dataset.useTransaction(trx).related('files').save(newFile); - await newFile.createHashValues(); + + 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') + .create({ + pathName: `${datasetFolder}/${fileName}`, + fileSize: fileData.size, + mimeType, + label: clientFileName, + sortOrder: sortOrder || index, + visibleInFrontdoor: true, + visibleInOai: true, + }); + + // save many related HashValue Instances to the file: + await newFile.createHashValues(trx); } } + // save collection + // const collection: Collection | null = await Collection.query().where('id', 21).first(); + // collection && (await dataset.useTransaction(trx).related('collections').attach([collection.id])); + + // // Save coverage + // if (data.coverage && !this.containsOnlyNull(data.coverage)) { + // const formCoverage = request.input('coverage'); + // const coverage = await dataset.related('coverage').updateOrCreate({ dataset_id: dataset.id }, formCoverage); + // } else if (data.coverage && this.containsOnlyNull(data.coverage) && !dataset.coverage) { + // await dataset.coverage().delete(); + // } + const input = request.only(['project_id', 'embargo_date', 'language', 'type', 'creating_corporation']); // dataset.type = request.input('type'); dataset.merge(input); @@ -911,11 +949,30 @@ export default class DatasetController { throw error; } - session.flash('message', 'Dataset has been created successfully'); + session.flash('message', 'Dataset has been updated successfully'); // return response.redirect().toRoute('user.index'); return response.redirect().back(); } + private extractVariableNameAndSortOrder(inputString: string): { clientFileName: string; sortOrder?: number } { + const regex = /^([^?]+)(?:\?([^=]+)=([^&]+))?/; + const match = inputString.match(regex); + + if (match) { + const clientFileName = match[1]; + + const param = match[2]; + let sortOrder; + if (param && param.toLowerCase() === 'sortorder') { + sortOrder = parseInt(match[3], 10); + } + + return { clientFileName, sortOrder }; + } else { + return { clientFileName: '', sortOrder: undefined }; // Or handle as needed for no match + } + } + public async delete({ request, inertia, response, session }) { const id = request.param('id'); try { @@ -923,8 +980,8 @@ export default class DatasetController { .preload('user', (builder) => { builder.select('id', 'login'); }) - .preload('files') .where('id', id) + .preload('files') .firstOrFail(); const validStates = ['inprogress', 'rejected_editor']; if (!validStates.includes(dataset.server_state)) { @@ -958,21 +1015,27 @@ export default class DatasetController { if (validStates.includes(dataset.server_state)) { if (dataset.files && dataset.files.length > 0) { for (const file of dataset.files) { - if (file.pathName) { - // delete file from filesystem - await Drive.delete(file.pathName); - } + // overwriten delete method also delets file on filespace + await file.delete(); } } - // delete dataset wirh relation from db - await dataset.delete(); - session.flash({ message: 'You have deleted 1 dataset!' }); - return response.redirect().toRoute('dataset.list'); - } else { - session.flash({ - warning: `You cannot delete this dataset! The status of this dataset is "${dataset.server_state}"!`, - }); - return response.redirect().back(); + const datasetFolder = `files/${params.id}`; + const folderExists = await Drive.exists(datasetFolder); + if (folderExists) { + const folderContents = await Drive.list(datasetFolder).toArray(); + if (folderContents.length === 0) { + await Drive.delete(datasetFolder); + } + // delete dataset wirh relation from db + await dataset.delete(); + session.flash({ message: 'You have deleted 1 dataset!' }); + return response.redirect().toRoute('dataset.list'); + } else { + session.flash({ + warning: `You cannot delete this dataset! Invalid server_state: "${dataset.server_state}"!`, + }); + return response.status(400).redirect().back(); + } } } catch (error) { if (error instanceof ValidationException) { diff --git a/app/Models/Dataset.ts b/app/Models/Dataset.ts index c70824d..2ba2c1c 100644 --- a/app/Models/Dataset.ts +++ b/app/Models/Dataset.ts @@ -203,7 +203,7 @@ export default class Dataset extends DatasetExtension { pivotForeignKey: 'document_id', pivotRelatedForeignKey: 'person_id', pivotTable: 'link_documents_persons', - pivotColumns: ['role', 'sort_order', 'allow_email_contact'], + pivotColumns: ['role', 'sort_order', 'allow_email_contact', 'contributor_type'], onQuery(query) { query.wherePivot('role', 'contributor'); }, diff --git a/app/Models/File.ts b/app/Models/File.ts index ab62f54..9649f45 100644 --- a/app/Models/File.ts +++ b/app/Models/File.ts @@ -7,6 +7,7 @@ import BaseModel from './BaseModel'; import * as fs from 'fs'; import crypto from 'crypto'; import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database'; +import Drive from '@ioc:Adonis/Core/Drive'; export default class File extends BaseModel { // private readonly _data: Uint8Array; @@ -116,21 +117,20 @@ export default class File extends BaseModel { serializeAs: 'fileData', }) public get fileData(): string { - // return this.fileData; - // const fileData = fs.readFileSync(path.resolve(__dirname, this.filePath)); - // const fileData = fs.readFileSync(this.filePath); - const fileContent: Buffer = fs.readFileSync(this.filePath); - // Create a Blob from the file content - // const blob = new Blob([fileContent], { type: this.type }); // Adjust - // let fileSrc = URL.createObjectURL(blob); - // return fileSrc; + try { + const fileContent: Buffer = fs.readFileSync(this.filePath); + // Create a Blob from the file content + // const blob = new Blob([fileContent], { type: this.type }); // Adjust + // let fileSrc = URL.createObjectURL(blob); + // return fileSrc; - // return Buffer.from(fileContent); - // get the buffer from somewhere - // const buff = fs.readFileSync('./test.bin'); - // create a JSON string that contains the data in the property "blob" - const json = JSON.stringify({ blob: fileContent.toString('base64') }); - return json; + // create a JSON string that contains the data in the property "blob" + const json = JSON.stringify({ blob: fileContent.toString('base64') }); + return json; + } catch (err) { + // console.error(`Error reading file: ${err}`); + return ''; + } } public async createHashValues(trx?: TransactionClientContract) { @@ -139,7 +139,7 @@ export default class File extends BaseModel { for (const type of hashtypes) { const hash = new HashValue(); hash.type = type; - const hashString = await this.checksumFile(this.filePath, type); // Assuming getRealHash is a method in the same model + const hashString = await this._checksumFile(this.filePath, type); // Assuming getRealHash is a method in the same model hash.value = hashString; // https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289 @@ -152,7 +152,17 @@ export default class File extends BaseModel { } } - private async checksumFile(path, hashName = 'md5'): Promise { + public async delete() { + if (this.pathName) { + // Delete file from additional storage + await Drive.delete(this.pathName); + } + + // Call the original delete method of the BaseModel to remove the record from the database + await super.delete(); + } + + private async _checksumFile(path, hashName = 'md5'): Promise { return new Promise((resolve, reject) => { const hash = crypto.createHash(hashName); const stream = fs.createReadStream(path); diff --git a/app/Models/Person.ts b/app/Models/Person.ts index 8b15d85..88b5413 100644 --- a/app/Models/Person.ts +++ b/app/Models/Person.ts @@ -69,6 +69,12 @@ export default class Person extends BaseModel { return stock; } + @computed() + public get pivot_contributor_type() { + const contributor_type = this.$extras.pivot_contributor_type; //my pivot column name was "stock" + return contributor_type; + } + @manyToMany(() => Dataset, { pivotForeignKey: 'person_id', pivotRelatedForeignKey: 'document_id', diff --git a/app/Models/Subject.ts b/app/Models/Subject.ts index e3cec00..afb177d 100644 --- a/app/Models/Subject.ts +++ b/app/Models/Subject.ts @@ -1,4 +1,4 @@ -import { column, SnakeCaseNamingStrategy, manyToMany, ManyToMany, beforeCreate, beforeUpdate } from '@ioc:Adonis/Lucid/Orm'; +import { column, SnakeCaseNamingStrategy, manyToMany, ManyToMany, computed} from '@ioc:Adonis/Lucid/Orm'; import BaseModel from './BaseModel'; import { DateTime } from 'luxon'; @@ -44,28 +44,33 @@ export default class Subject extends BaseModel { }) public updated_at: DateTime; - @beforeCreate() - @beforeUpdate() - public static async resetDate(role) { - role.created_at = this.formatDateTime(role.created_at); - role.updated_at = this.formatDateTime(role.updated_at); - } + // @beforeCreate() + // @beforeUpdate() + // public static async resetDate(role) { + // role.created_at = this.formatDateTime(role.created_at); + // role.updated_at = this.formatDateTime(role.updated_at); + // } - private static formatDateTime(datetime) { - let value = new Date(datetime); - return datetime - ? value.getFullYear() + - '-' + - (value.getMonth() + 1) + - '-' + - value.getDate() + - ' ' + - value.getHours() + - ':' + - value.getMinutes() + - ':' + - value.getSeconds() - : datetime; + // private static formatDateTime(datetime) { + // let value = new Date(datetime); + // return datetime + // ? value.getFullYear() + + // '-' + + // (value.getMonth() + 1) + + // '-' + + // value.getDate() + + // ' ' + + // value.getHours() + + // ':' + + // value.getMinutes() + + // ':' + + // value.getSeconds() + // : datetime; + // } + @computed() + public get dataset_count() : number{ + const count = this.$extras.datasets_count; //my pivot column name was "stock" + return count; } @manyToMany(() => Dataset, { diff --git a/app/Validators/CreateDatasetValidator.ts b/app/Validators/CreateDatasetValidator.ts index b9a0ea0..cfe602a 100644 --- a/app/Validators/CreateDatasetValidator.ts +++ b/app/Validators/CreateDatasetValidator.ts @@ -135,7 +135,7 @@ export default class CreateDatasetValidator { 'required': '{{ field }} is required', 'unique': '{{ field }} must be unique, and this value is already taken', // 'confirmed': '{{ field }} is not correct', - 'licenses.minLength': 'at least {{ options.minLength }} permission must be defined', + 'licenses.minLength': 'at least {{ options.minLength }} licenses must be defined', 'licenses.*.number': 'Define licences as valid numbers', 'rights.equalTo': 'you must agree to continue', diff --git a/app/Validators/UpdateDatasetValidator.ts b/app/Validators/UpdateDatasetValidator.ts index 57d75a5..1000542 100644 --- a/app/Validators/UpdateDatasetValidator.ts +++ b/app/Validators/UpdateDatasetValidator.ts @@ -136,7 +136,7 @@ export default class UpdateDatasetValidator { 'required': '{{ field }} is required', 'unique': '{{ field }} must be unique, and this value is already taken', // 'confirmed': '{{ field }} is not correct', - 'licenses.minLength': 'at least {{ options.minLength }} permission must be defined', + 'licenses.minLength': 'at least {{ options.minLength }} licenses must be defined', 'licenses.*.number': 'Define licences as valid numbers', 'rights.equalTo': 'you must agree to continue', diff --git a/config/redis.ts b/config/redis.ts new file mode 100644 index 0000000..9138ae6 --- /dev/null +++ b/config/redis.ts @@ -0,0 +1,46 @@ +/** + * Config source: https://git.io/JemcF + * + * Feel free to let us know via PR, if you find something broken in this config + * file. + */ + +import Env from '@ioc:Adonis/Core/Env' +import { redisConfig } from '@adonisjs/redis/build/config' + +/* +|-------------------------------------------------------------------------- +| Redis configuration +|-------------------------------------------------------------------------- +| +| Following is the configuration used by the Redis provider to connect to +| the redis server and execute redis commands. +| +| Do make sure to pre-define the connections type inside `contracts/redis.ts` +| file for AdonisJs to recognize connections. +| +| Make sure to check `contracts/redis.ts` file for defining extra connections +*/ +export default redisConfig({ + connection: Env.get('REDIS_CONNECTION'), + + connections: { + /* + |-------------------------------------------------------------------------- + | The default connection + |-------------------------------------------------------------------------- + | + | The main connection you want to use to execute redis commands. The same + | connection will be used by the session provider, if you rely on the + | redis driver. + | + */ + local: { + host: Env.get('REDIS_HOST'), + port: Env.get('REDIS_PORT'), + password: Env.get('REDIS_PASSWORD', ''), + db: 0, + keyPrefix: '', + }, + }, +}) diff --git a/contracts/redis.ts b/contracts/redis.ts new file mode 100644 index 0000000..c70ccf3 --- /dev/null +++ b/contracts/redis.ts @@ -0,0 +1,13 @@ +/** + * Contract source: https://git.io/JemcN + * + * Feel free to let us know via PR, if you find something broken in this config + * file. + */ + +import { InferConnectionsFromConfig } from '@adonisjs/redis/build/config' +import redisConfig from '../config/redis' + +declare module '@ioc:Adonis/Addons/Redis' { + interface RedisConnectionsList extends InferConnectionsFromConfig {} +} diff --git a/package-lock.json b/package-lock.json index a12c562..3ecb05c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@adonisjs/auth": "^8.2.3", "@adonisjs/core": "^5.9.0", "@adonisjs/lucid": "^18.3.0", + "@adonisjs/redis": "^7.3.4", "@adonisjs/repl": "^3.1.11", "@adonisjs/session": "^6.4.0", "@adonisjs/shield": "^7.1.0", @@ -529,6 +530,19 @@ "truncatise": "0.0.8" } }, + "node_modules/@adonisjs/redis": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@adonisjs/redis/-/redis-7.3.4.tgz", + "integrity": "sha512-74SApmgimjwU8QflnhANeo7CpQeP9aoObM217LJ51AtKwTvnb0yXaqdj2v60G9uCqcqZAIFWJmeUdXGgUwGcXw==", + "dependencies": { + "@poppinss/utils": "^5.0.0", + "@types/ioredis": "^4.28.10", + "ioredis": "^5.2.3" + }, + "peerDependencies": { + "@adonisjs/core": "^5.1.0" + } + }, "node_modules/@adonisjs/repl": { "version": "3.1.11", "resolved": "https://registry.npmjs.org/@adonisjs/repl/-/repl-3.1.11.tgz", @@ -2886,6 +2900,11 @@ "vue": "^3.0.0" } }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "node_modules/@japa/api-client": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@japa/api-client/-/api-client-1.4.4.tgz", @@ -4055,6 +4074,14 @@ "@types/node": "*" } }, + "node_modules/@types/ioredis": { + "version": "4.28.10", + "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.28.10.tgz", + "integrity": "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -7794,6 +7821,14 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -10365,6 +10400,29 @@ "node": ">= 0.10" } }, + "node_modules/ioredis": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", + "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -11493,6 +11551,11 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -11505,6 +11568,11 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -14363,6 +14431,25 @@ "@redis/time-series": "1.0.5" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -15514,6 +15601,11 @@ "get-source": "^2.0.12" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", diff --git a/package.json b/package.json index 31c5d39..33ba2c8 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@adonisjs/auth": "^8.2.3", "@adonisjs/core": "^5.9.0", "@adonisjs/lucid": "^18.3.0", + "@adonisjs/redis": "^7.3.4", "@adonisjs/repl": "^3.1.11", "@adonisjs/session": "^6.4.0", "@adonisjs/shield": "^7.1.0", diff --git a/resources/js/Components/AsideMenu.vue b/resources/js/Components/AsideMenu.vue index 05b5b38..e2d46d1 100644 --- a/resources/js/Components/AsideMenu.vue +++ b/resources/js/Components/AsideMenu.vue @@ -10,6 +10,7 @@ import OverlayLayer from '@/Components/OverlayLayer.vue'; // let menu = reactive({}); // menu = computed(() => usePage().props.navigation?.menu); + const layoutService = LayoutService(); diff --git a/resources/js/Components/AsideMenuItem.vue b/resources/js/Components/AsideMenuItem.vue index bdee6fc..924c3b3 100644 --- a/resources/js/Components/AsideMenuItem.vue +++ b/resources/js/Components/AsideMenuItem.vue @@ -1,6 +1,6 @@ -