forked from geolba/tethys.backend
- added own provider for drive methods
- renamed middleware Role and Can to role_middleware and can_middleware - added some typing for inertia vue3 components - npm updates
This commit is contained in:
parent
cb51a4136f
commit
296c8fd46e
|
@ -1,75 +0,0 @@
|
|||
{
|
||||
"typescript": true,
|
||||
"commands": [
|
||||
"./commands",
|
||||
"@adonisjs/core/build/commands/index.js",
|
||||
"@adonisjs/repl/build/commands",
|
||||
"@eidellev/inertia-adonisjs/build/commands",
|
||||
"@adonisjs/lucid/build/commands"
|
||||
],
|
||||
"exceptionHandlerNamespace": "App/Exceptions/Handler",
|
||||
"aliases": {
|
||||
"App": "app",
|
||||
"Config": "config",
|
||||
"Database": "database",
|
||||
"Contracts": "contracts"
|
||||
},
|
||||
"preloads": [
|
||||
"./start/routes",
|
||||
"./start/kernel",
|
||||
{
|
||||
"file": "./start/inertia",
|
||||
"environment": [
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./start/validator",
|
||||
"environment": [
|
||||
"web"
|
||||
]
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
"./providers/AppProvider",
|
||||
"@adonisjs/core",
|
||||
"@adonisjs/session",
|
||||
"@adonisjs/view",
|
||||
"@adonisjs/shield",
|
||||
"@eidellev/inertia-adonisjs",
|
||||
"@adonisjs/lucid",
|
||||
"@adonisjs/auth",
|
||||
"@eidellev/adonis-stardust",
|
||||
"./providers/QueryBuilderProvider",
|
||||
"./providers/TokenWorkerProvider",
|
||||
"@adonisjs/redis",
|
||||
"./providers/DoiProvider"
|
||||
],
|
||||
"metaFiles": [
|
||||
{
|
||||
"pattern": "public/**",
|
||||
"reloadServer": false
|
||||
},
|
||||
{
|
||||
"pattern": "resources/views/**/*.edge",
|
||||
"reloadServer": false
|
||||
}
|
||||
],
|
||||
"aceProviders": [
|
||||
"@adonisjs/repl"
|
||||
],
|
||||
"tests": {
|
||||
"suites": [
|
||||
{
|
||||
"name": "functional",
|
||||
"files": [
|
||||
"tests/functional/**/*.spec(.ts|.js)"
|
||||
],
|
||||
"timeout": 60000
|
||||
}
|
||||
]
|
||||
},
|
||||
"testProviders": [
|
||||
"@japa/preset-adonis/TestsProvider"
|
||||
]
|
||||
}
|
|
@ -2,7 +2,7 @@ root = true
|
|||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
|
|
@ -57,6 +57,7 @@ export default defineConfig({
|
|||
() => import('@adonisjs/shield/shield_provider'),
|
||||
// () => import('@eidellev/inertia-adonisjs'),
|
||||
// () => import('@adonisjs/inertia/inertia_provider'),
|
||||
() => import('#providers/app_provider'),
|
||||
() => import('#providers/inertia_provider'),
|
||||
() => import('@adonisjs/lucid/database_provider'),
|
||||
() => import('@adonisjs/auth/auth_provider'),
|
||||
|
@ -66,8 +67,9 @@ export default defineConfig({
|
|||
() => import('@adonisjs/static/static_provider'),
|
||||
() => import('#providers/stardust_provider'),
|
||||
() => import('#providers/query_builder_provider'),
|
||||
() => import('#providers/TokenWorkerProvider'),
|
||||
() => import('#providers/token_worker_provider'),
|
||||
() => import('#providers/validator_provider'),
|
||||
() => import('#providers/drive/provider/drive_provider')
|
||||
],
|
||||
metaFiles: [
|
||||
{
|
||||
|
|
|
@ -8,7 +8,8 @@ import UpdateUserValidator from '#app/Validators/UpdateUserValidator';
|
|||
// import Hash from '@ioc:Adonis/Core/Hash';
|
||||
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
|
||||
|
||||
export default class AdminUserController {
|
||||
export default class AdminuserController {
|
||||
|
||||
public async index({ auth, request, inertia }: HttpContext) {
|
||||
const page = request.input('page', 1);
|
||||
// const limit = 10
|
|
@ -1,7 +1,7 @@
|
|||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
// import TotpSecret from 'App/Models/TotpSecret';
|
||||
import User from '#app/Models/User';
|
||||
import TwoFactorAuthProvider from '#app/Services/TwoFactorAuthProvider';
|
||||
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||
import { StatusCodes } from 'http-status-codes';
|
||||
import { InvalidArgumentException } from 'node-exceptions';
|
||||
import { TotpState } from '#contracts/enums';
|
||||
|
|
|
@ -4,7 +4,7 @@ import User from '#models/User';
|
|||
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
|
||||
import AuthValidator from '#app/Validators/AuthValidator';
|
||||
|
||||
import TwoFactorAuthProvider from '#app/Services/TwoFactorAuthProvider';
|
||||
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||
// import { Authenticator } from '@adonisjs/auth';
|
||||
// import { LoginState } from 'Contracts/enums';
|
||||
// import { StatusCodes } from 'http-status-codes';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import User from '#app/Models/User';
|
||||
// import { RenderResponse } from '@ioc:EidelLev/Inertia';
|
||||
import TwoFactorAuthProvider from '#app/Services/TwoFactorAuthProvider';
|
||||
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||
import hash from '@adonisjs/core/services/hash';
|
||||
import { schema, rules } from '@adonisjs/validator';
|
||||
|
||||
|
@ -88,7 +88,7 @@ export default class UserController {
|
|||
}
|
||||
|
||||
public async disableTwoFactorAuthentication({ auth, response, session }: HttpContext): Promise<void> {
|
||||
const user = auth?.user;
|
||||
const user = auth.user;
|
||||
|
||||
user.twoFactorSecret = null;
|
||||
user.twoFactorRecoveryCodes = null;
|
||||
|
|
|
@ -9,11 +9,11 @@ import Language from '#app/Models/Language';
|
|||
import Coverage from '#app/Models/Coverage';
|
||||
import Collection from '#app/Models/Collection';
|
||||
import { schema, rules } from '@adonisjs/validator';
|
||||
import { CustomMessages } from "@adonisjs/validator/types";
|
||||
import { CustomMessages } from '@adonisjs/validator/types';
|
||||
import dayjs from 'dayjs';
|
||||
import Person from '#app/Models/Person';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
import { TransactionClientContract } from "@adonisjs/lucid/types/database";
|
||||
import { TransactionClientContract } from '@adonisjs/lucid/types/database';
|
||||
import Subject from '#app/Models/Subject';
|
||||
import CreateDatasetValidator from '#app/Validators/CreateDatasetValidator';
|
||||
import UpdateDatasetValidator from '#app/Validators/UpdateDatasetValidator';
|
||||
|
@ -27,14 +27,15 @@ import {
|
|||
DatasetTypes,
|
||||
SubjectTypes,
|
||||
} from '#contracts/enums';
|
||||
import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
|
||||
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
||||
import DatasetReference from '#app/Models/DatasetReference';
|
||||
import { cuid } from '@adonisjs/core/helpers';
|
||||
import File from '#app/Models/File';
|
||||
import ClamScan from 'clamscan';
|
||||
import { ValidationException } from '@adonisjs/validator';
|
||||
// import Drive from '@ioc:Adonis/Core/Drive';
|
||||
import { Exception } from "@adonisjs/core/exceptions";
|
||||
import drive from '#services/drive';
|
||||
import { Exception } from '@adonisjs/core/exceptions';
|
||||
import { MultipartFile } from '@adonisjs/core/types/bodyparser';
|
||||
import * as crypto from 'crypto';
|
||||
import app from '@adonisjs/core/services/app';
|
||||
|
@ -445,13 +446,10 @@ export default class DatasetController {
|
|||
const mimeType = file.headers['content-type'] || 'application/octet-stream'; // Fallback to a default MIME type
|
||||
const datasetFolder = `files/${dataset.id}`;
|
||||
// const size = file.size;
|
||||
await file.move(
|
||||
app.makePath( datasetFolder),
|
||||
{
|
||||
await file.move(app.makePath(datasetFolder), {
|
||||
name: fileName,
|
||||
overwrite: true, // overwrite in case of conflict
|
||||
},
|
||||
);
|
||||
});
|
||||
// save file metadata into db
|
||||
const newFile = new File();
|
||||
newFile.pathName = `${datasetFolder}/${fileName}`;
|
||||
|
@ -468,7 +466,10 @@ export default class DatasetController {
|
|||
}
|
||||
|
||||
private generateRandomString(length: number): string {
|
||||
return crypto.randomBytes(Math.ceil(length / 2)).toString('hex').slice(0, length);
|
||||
return crypto
|
||||
.randomBytes(Math.ceil(length / 2))
|
||||
.toString('hex')
|
||||
.slice(0, length);
|
||||
}
|
||||
|
||||
private generateFilename(extension: string): string {
|
||||
|
@ -919,11 +920,7 @@ export default class DatasetController {
|
|||
// move to disk:
|
||||
const fileName = `file-${cuid()}.${fileData.extname}`;
|
||||
const datasetFolder = `files/${dataset.id}`;
|
||||
await fileData.moveToDisk(
|
||||
datasetFolder,
|
||||
{ name: fileName, overwrite: true },
|
||||
'local'
|
||||
);
|
||||
await fileData.moveToDisk(datasetFolder, { name: fileName, overwrite: true }, 'local');
|
||||
// let path = coverImage.filePath;
|
||||
|
||||
//save to db:
|
||||
|
@ -1047,21 +1044,25 @@ export default class DatasetController {
|
|||
}
|
||||
}
|
||||
const datasetFolder = `files/${params.id}`;
|
||||
const folderExists = await Drive.exists(datasetFolder);
|
||||
const folderExists = await drive.exists(datasetFolder);
|
||||
if (folderExists) {
|
||||
const folderContents = await Drive.list(datasetFolder).toArray();
|
||||
const dirListing = drive.list(datasetFolder);
|
||||
const folderContents = await dirListing.toArray();
|
||||
if (folderContents.length === 0) {
|
||||
await Drive.delete(datasetFolder);
|
||||
await drive.delete(datasetFolder);
|
||||
}
|
||||
// delete dataset wirh relation in 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();
|
||||
// session.flash({
|
||||
// warning: `You cannot delete this dataset! Invalid server_state: "${dataset.server_state}"!`,
|
||||
// });
|
||||
return response
|
||||
.flash({ warning: `You cannot delete this dataset! Dataset folder "${datasetFolder}" doesn't exist!` })
|
||||
.redirect()
|
||||
.back();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -1070,10 +1071,7 @@ export default class DatasetController {
|
|||
throw error;
|
||||
} else if (error instanceof Exception) {
|
||||
// General exception handling
|
||||
return response
|
||||
.flash('errors', { error: error.message })
|
||||
.redirect()
|
||||
.back();
|
||||
return response.flash('errors', { error: error.message }).redirect().back();
|
||||
} else {
|
||||
session.flash({ error: 'An error occurred while deleting the dataset.' });
|
||||
return response.redirect().back();
|
||||
|
|
|
@ -29,28 +29,38 @@ export default class HttpExceptionHandler extends ExceptionHandler {
|
|||
* codes. You might want to enable them in production only, but feel
|
||||
* free to enable them in development as well.
|
||||
*/
|
||||
protected renderStatusPages = true;
|
||||
|
||||
// protected statusPages = {
|
||||
// '401,403': 'errors/unauthorized',
|
||||
// '404': 'errors/not-found',
|
||||
// '500..599': 'errors/server-error',
|
||||
// };
|
||||
protected renderStatusPages = true; //app.inProduction;
|
||||
|
||||
/**
|
||||
* Status pages is a collection of error code range and a callback
|
||||
* to return the HTML contents to send as a response.
|
||||
*/
|
||||
// protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
|
||||
// '401..403': (error, { view }) => {
|
||||
// return view.render('./errors/unauthorized', { error });
|
||||
// },
|
||||
// '404': (error, { view }) => {
|
||||
// return view.render('./errors/not-found', { error });
|
||||
// },
|
||||
// '500..599': (error, { view }) => {
|
||||
// return view.render('./errors/server-error', { error });
|
||||
// },
|
||||
// };
|
||||
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
|
||||
'401..403': (error, { view }) => {
|
||||
return view.render('./errors/unauthorized', { error });
|
||||
'404': (error, { inertia }) => {
|
||||
return inertia.render('Errors/ServerError', {
|
||||
error: error.message,
|
||||
code: error.status,
|
||||
});
|
||||
},
|
||||
'404': (error, { view }) => {
|
||||
return view.render('./errors/not-found', { error });
|
||||
},
|
||||
'500..599': (error, { view }) => {
|
||||
return view.render('./errors/server-error', { error });
|
||||
'401..403': async (error, { inertia }) => {
|
||||
// session.flash('errors', error.message);
|
||||
return inertia.render('Errors/ServerError', {
|
||||
error: error.message,
|
||||
code: error.status,
|
||||
});
|
||||
},
|
||||
'500..599': (error, { inertia }) => inertia.render('Errors/ServerError', { error: error.message, code: error.status }),
|
||||
};
|
||||
|
||||
// constructor() {
|
||||
|
@ -58,7 +68,7 @@ export default class HttpExceptionHandler extends ExceptionHandler {
|
|||
// }
|
||||
|
||||
public async handle(error: any, ctx: HttpContext) {
|
||||
const { response, request, session } = ctx;
|
||||
// const { response, request, session, inertia } = ctx;
|
||||
|
||||
/**
|
||||
* Handle failed authentication attempt
|
||||
|
@ -75,17 +85,26 @@ export default class HttpExceptionHandler extends ExceptionHandler {
|
|||
// https://github.com/inertiajs/inertia-laravel/issues/56
|
||||
// let test = response.getStatus(); //200
|
||||
// let header = request.header('X-Inertia'); // true
|
||||
if (request.header('X-Inertia') && [500, 503, 404, 403, 401, 200].includes(response.getStatus())) {
|
||||
// session.flash('errors', error.messages.errors);
|
||||
session.flash('errors', error.messages);
|
||||
return response.redirect().back();
|
||||
// return inertia.render('Error', {
|
||||
// status: response.getStatus(),
|
||||
// message: error.message,
|
||||
// });
|
||||
// ->toResponse($request)
|
||||
// ->setStatusCode($response->status());
|
||||
}
|
||||
// if (request.header('X-Inertia') && [500, 503, 404, 403, 401, 200].includes(response.getStatus())) {
|
||||
// // session.flash('errors', error.messages.errors);
|
||||
// session.flash('errors', error.messages);
|
||||
// return response.redirect().back();
|
||||
// // return inertia.render('errors/server_error', {
|
||||
// // return inertia.render('errors/server_error', {
|
||||
// // // status: response.getStatus(),
|
||||
// // error: error,
|
||||
// // });
|
||||
// // ->toResponse($request)
|
||||
// // ->setStatusCode($response->status());
|
||||
// }
|
||||
// Dynamically change the error templates based on the absence of X-Inertia header
|
||||
// if (!ctx.request.header('X-Inertia')) {
|
||||
// this.statusPages = {
|
||||
// '401..403': (error, { view }) => view.render('./errors/unauthorized', { error }),
|
||||
// '404': (error, { view }) => view.render('./errors/not-found', { error }),
|
||||
// '500..599': (error, { view }) => view.render('./errors/server-error', { error }),
|
||||
// };
|
||||
// }
|
||||
|
||||
/**
|
||||
* Forward rest of the exceptions to the parent class
|
||||
|
|
|
@ -7,7 +7,8 @@ import BaseModel from './BaseModel.js';
|
|||
import * as fs from 'fs';
|
||||
import crypto from 'crypto';
|
||||
// import Drive from '@ioc:Adonis/Core/Drive';
|
||||
import Drive from '@adonisjs/drive';
|
||||
// import Drive from '@adonisjs/drive';
|
||||
import drive from '#services/drive';
|
||||
|
||||
import type { HasMany } from "@adonisjs/lucid/types/relations";
|
||||
import type { BelongsTo } from "@adonisjs/lucid/types/relations";
|
||||
|
@ -164,7 +165,7 @@ export default class File extends BaseModel {
|
|||
public async delete() {
|
||||
if (this.pathName) {
|
||||
// Delete file from additional storage
|
||||
await Drive.delete(this.pathName);
|
||||
await drive.delete(this.pathName);
|
||||
}
|
||||
|
||||
// Call the original delete method of the BaseModel to remove the record from the database
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { DateTime } from 'luxon';
|
||||
import { withAuthFinder } from '@adonisjs/auth';
|
||||
import { column, beforeSave, manyToMany, hasMany } from '@adonisjs/lucid/orm';
|
||||
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'
|
||||
import { column, manyToMany, hasMany } from '@adonisjs/lucid/orm';
|
||||
import hash from '@adonisjs/core/services/hash';
|
||||
import Role from './Role.js';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
// import Config from '@ioc:Adonis/Core/Config';
|
||||
import config from '@adonisjs/core/services/config';
|
||||
import Dataset from './Dataset.js';
|
||||
import BaseModel from './BaseModel.js';
|
||||
|
@ -84,12 +83,12 @@ export default class User extends compose(BaseModel, AuthFinder) {
|
|||
// })
|
||||
// public totp_secret: HasOne<typeof TotpSecret>;
|
||||
|
||||
@beforeSave()
|
||||
public static async hashPassword(user: User) {
|
||||
if (user.$dirty.password) {
|
||||
user.password = await hash.use('laravel').make(user.password);
|
||||
}
|
||||
}
|
||||
// @beforeSave()
|
||||
// public static async hashPassword(user: User) {
|
||||
// if (user.$dirty.password) {
|
||||
// user.password = await hash.use('laravel').make(user.password);
|
||||
// }
|
||||
// }
|
||||
|
||||
public get isTwoFactorEnabled(): boolean {
|
||||
return Boolean(this?.twoFactorSecret && this.state == TotpState.STATE_ENABLED);
|
||||
|
|
13
app/services/drive.ts
Normal file
13
app/services/drive.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
// import app from './app.js';
|
||||
import DriveManager from "#providers/drive/src/drive_manager";
|
||||
import app from "@adonisjs/core/services/app";
|
||||
|
||||
let drive: DriveManager;
|
||||
/**
|
||||
* Returns a singleton instance of the router class from
|
||||
* the container
|
||||
*/
|
||||
await app.booted(async () => {
|
||||
drive = await app.container.make(DriveManager);
|
||||
});
|
||||
export { drive as default };
|
|
@ -9,26 +9,33 @@ import { Client } from '@opensearch-project/opensearch';
|
|||
import { getDomain } from '#app/Utils/utility-functions';
|
||||
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
||||
import { CommandOptions } from '@adonisjs/core/types/ace';
|
||||
import env from '#start/env';
|
||||
// import db from '@adonisjs/lucid/services/db';
|
||||
// import { default as Dataset } from '#app/Models/Dataset';
|
||||
|
||||
const opensearchNode = process.env.OPENSEARCH_HOST || 'localhost';
|
||||
const opensearchNode = env.get('OPENSEARCH_HOST', 'localhost');
|
||||
const client = new Client({ node: `http://${opensearchNode}` }); // replace with your OpenSearch endpoint
|
||||
|
||||
export default class IndexDatasets extends BaseCommand {
|
||||
static commandName = 'index:datasets';
|
||||
static description = 'Index datasets based on publish_id';
|
||||
|
||||
public static needsApplication = true;
|
||||
|
||||
@flags.number({ alias: 'p' })
|
||||
public publish_id: number;
|
||||
|
||||
static options: CommandOptions = {
|
||||
loadApp: true,
|
||||
public static options: CommandOptions = {
|
||||
startApp: true,
|
||||
staysAlive: false,
|
||||
};
|
||||
|
||||
|
||||
async run() {
|
||||
this.logger.info('Hello world!');
|
||||
const { default: Dataset } = await import('#app/Models/Dataset');
|
||||
const datasets = await Dataset.query().where('server_state', 'published').exec(); //this.getDatasets();
|
||||
// const { default: Dataset } = await import('#app/Models/Dataset');
|
||||
// const datasets = await Dataset.query().where('server_state', 'published').exec(); //this.getDatasets();
|
||||
const datasets = await this.getDatasets();
|
||||
const proc = readFileSync('public/assets2/solr.sef.json');
|
||||
const index_name = 'tethys-records';
|
||||
|
||||
|
@ -40,15 +47,19 @@ export default class IndexDatasets extends BaseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
// private async getDatasets(): Promise<any[]> {
|
||||
// // const { default: Dataset } = await import('#app/Models/Dataset');
|
||||
// // const Dataset = (await import('#app/Models/Dataset')).default
|
||||
// const query = Dataset.query().where('server_state', 'published');
|
||||
// if (this.publish_id) {
|
||||
// query.where('publish_id', this.publish_id);
|
||||
// }
|
||||
// return await query;
|
||||
// }
|
||||
private async getDatasets(): Promise<any[]> {
|
||||
// const { default: Dataset } = await import('#app/Models/Dataset');
|
||||
// const Dataset = (await import('#app/Models/Dataset')).default
|
||||
// const Dataset = (
|
||||
// await this.app.container.make('#app/Models/Dataset')
|
||||
// ).default;
|
||||
// const query: ModelQueryBuilder<Dataset, any> = db.from(Dataset);
|
||||
const query = Dataset.query().preload('xmlCache').where('server_state', 'published');
|
||||
if (this.publish_id) {
|
||||
query.where('publish_id', this.publish_id);
|
||||
}
|
||||
return await query.exec();
|
||||
}
|
||||
|
||||
private async indexDocument(dataset: Dataset, index_name: string, proc: Buffer): Promise<void> {
|
||||
try {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { BaseCommand } from '@adonisjs/core/ace'
|
||||
import type { CommandOptions } from '@adonisjs/core/types/ace'
|
||||
import { BaseCommand } from '@adonisjs/core/ace';
|
||||
import type { CommandOptions } from '@adonisjs/core/types/ace';
|
||||
|
||||
export default class IndexTest extends BaseCommand {
|
||||
static commandName = 'index:test'
|
||||
static description = ''
|
||||
static commandName = 'index:test';
|
||||
static description = '';
|
||||
|
||||
static options: CommandOptions = {}
|
||||
static options: CommandOptions = {};
|
||||
|
||||
async run() {
|
||||
this.logger.info('Hello world from "IndexTest"')
|
||||
this.logger.info('Hello world from "IndexTest"');
|
||||
}
|
||||
}
|
|
@ -4,10 +4,10 @@
|
|||
* Feel free to let us know via PR, if you find something broken in this config
|
||||
* file.
|
||||
*/
|
||||
|
||||
import { defineConfig } from '#providers/drive/src/types/define_config';
|
||||
import env from '#start/env';
|
||||
// import { driveConfig } from '@adonisjs/core/build/config';
|
||||
import { driveConfig } from "@adonisjs/drive/build/config.js";
|
||||
// import { driveConfig } from "@adonisjs/drive/build/config.js";
|
||||
// import Application from '@ioc:Adonis/Core/Application';
|
||||
|
||||
/*
|
||||
|
@ -19,7 +19,7 @@ import { driveConfig } from "@adonisjs/drive/build/config.js";
|
|||
| defined inside the `contracts` directory.
|
||||
|
|
||||
*/
|
||||
export default driveConfig({
|
||||
export default defineConfig({
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default disk
|
||||
|
|
|
@ -11,22 +11,22 @@ export default defineConfig({
|
|||
* Data that should be shared with all rendered pages
|
||||
*/
|
||||
sharedData: {
|
||||
errors: (ctx) => ctx.session.flashMessages.get('errors'),
|
||||
errors: (ctx) => ctx.session?.flashMessages.get('errors'),
|
||||
|
||||
user_id: (ctx) => {
|
||||
return ctx.session.flashMessages.get('user_id');
|
||||
return ctx.session?.flashMessages.get('user_id');
|
||||
},
|
||||
|
||||
flash: (ctx) => {
|
||||
return {
|
||||
message: ctx.session.flashMessages.get('message'),
|
||||
warning: ctx.session.flashMessages.get('warning'),
|
||||
message: ctx.session?.flashMessages.get('message'),
|
||||
warning: ctx.session?.flashMessages.get('warning'),
|
||||
};
|
||||
},
|
||||
|
||||
// params: ({ params }) => params,
|
||||
authUser: async ({ auth }: HttpContext) => {
|
||||
if (auth.user) {
|
||||
if (auth?.user) {
|
||||
await auth.user.load('roles');
|
||||
return auth.user;
|
||||
// {
|
||||
|
@ -38,6 +38,14 @@ export default defineConfig({
|
|||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Options for the server-side rendering
|
||||
*/
|
||||
ssr: {
|
||||
enabled: false,
|
||||
entrypoint: 'inertia/app/ssr.ts',
|
||||
},
|
||||
});
|
||||
|
||||
// import { InertiaConfig } from '@ioc:EidelLev/Inertia';
|
||||
|
|
|
@ -7,8 +7,8 @@ interface OaiConfig {
|
|||
}
|
||||
const config: OaiConfig = {
|
||||
max: {
|
||||
listidentifiers: parseInt(env.get('OAI_LIST_SIZE', 100), 10),
|
||||
listrecords: parseInt(env.get('OAI_LIST_SIZE', 100), 10),
|
||||
listidentifiers: env.get('OAI_LIST_SIZE', 100),
|
||||
listrecords: env.get('OAI_LIST_SIZE', 100),
|
||||
},
|
||||
workspacePath: 'workspace',
|
||||
redis: {
|
||||
|
|
2467
package-lock.json
generated
2467
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -58,7 +58,7 @@
|
|||
"naive-ui": "^2.35.0",
|
||||
"numeral": "^2.0.6",
|
||||
"pinia": "^2.0.30",
|
||||
"pino-pretty": "^10.3.1",
|
||||
"pino-pretty": "^11.0.0",
|
||||
"postcss-loader": "^7.3.0",
|
||||
"prettier": "^3.0.0",
|
||||
"supertest": "^6.3.3",
|
||||
|
@ -98,6 +98,7 @@
|
|||
"dayjs": "^1.11.7",
|
||||
"edge.js": "^6.0.1",
|
||||
"focus-trap": "^7.5.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
"http-status-codes": "^2.2.0",
|
||||
"leaflet": "^1.9.3",
|
||||
"luxon": "^3.2.1",
|
||||
|
@ -118,7 +119,7 @@
|
|||
"#controllers/*": "./app/Controllers/*.js",
|
||||
"#exceptions/*": "./app/Exceptions/*.js",
|
||||
"#models/*": "./app/Models/*.js",
|
||||
"#services/*": "./app/Services/*.js",
|
||||
"#services/*": "./app/services/*.js",
|
||||
"#listeners/*": "./app/listeners/*.js",
|
||||
"#events/*": "./app/events/*.js",
|
||||
"#middleware/*": "./app/Middleware/*.js",
|
||||
|
|
|
@ -11,8 +11,7 @@ export type PbkdfConfig = {
|
|||
rounds: number;
|
||||
saltSize?: number;
|
||||
version?: number;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const saltRounds = 10;
|
||||
export class LaravelDriver implements HashDriverContract {
|
||||
|
@ -25,7 +24,7 @@ export class LaravelDriver implements HashDriverContract {
|
|||
rounds: 10,
|
||||
saltSize: 16,
|
||||
version: 98,
|
||||
...config
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -135,9 +134,8 @@ export class LaravelDriver implements HashDriverContract {
|
|||
* Factory function to reference the driver
|
||||
* inside the config file.
|
||||
*/
|
||||
export function laravelDriver (config: PbkdfConfig): ManagerDriverFactory {
|
||||
export function laravelDriver(config: PbkdfConfig): ManagerDriverFactory {
|
||||
return () => {
|
||||
return new LaravelDriver(config)
|
||||
}
|
||||
}
|
||||
|
||||
return new LaravelDriver(config);
|
||||
};
|
||||
}
|
||||
|
|
133
providers/drive/drivers/local.ts
Normal file
133
providers/drive/drivers/local.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
import fsExtra from 'fs-extra'
|
||||
// import { RouterContract } from '@ioc:Adonis/Core/Route';
|
||||
// import { Visibility, DriveFileStats, ContentHeaders, LocalDriverConfig, LocalDriverContract, DirectoryListingContract, LocalDriveListItem } from '@ioc:Adonis/Core/Drive';
|
||||
|
||||
import { CannotGetMetaDataException, CannotDeleteFileException, CannotListDirectoryException } from '../exceptions/index.js';
|
||||
import PathPrefixer from '../path_prefixer/index.js';
|
||||
import {
|
||||
LocalDriverContract,
|
||||
LocalDriverConfig,
|
||||
DriverContract,
|
||||
DriveListItem,
|
||||
} from '../src/types/drive.js';
|
||||
import { AsyncIterableArray } from '../src/iterable_array.js';
|
||||
/**
|
||||
* Local driver interacts with the local file system
|
||||
*/
|
||||
export class LocalDriver implements LocalDriverContract {
|
||||
private diskName: string;
|
||||
private config;
|
||||
// private router;
|
||||
// private routeName;
|
||||
|
||||
/**
|
||||
* Reference to the underlying adapter. Which is
|
||||
* fs-extra
|
||||
*/
|
||||
adapter: typeof fsExtra;
|
||||
|
||||
/**
|
||||
* Name of the driver
|
||||
*/
|
||||
name: 'local';
|
||||
/**
|
||||
* Path prefixer used for prefixing paths with disk root
|
||||
*/
|
||||
private prefixer;
|
||||
|
||||
// constructor(diskName: string, config: LocalDriverConfig, router: RouterContract);
|
||||
constructor(diskName: string, config: LocalDriverConfig) {
|
||||
this.diskName = diskName;
|
||||
this.config = config;
|
||||
// this.router = router;
|
||||
// this.routeName = LocalFileServer_1.LocalFileServer.makeRouteName(this.diskName);
|
||||
/**
|
||||
* Reference to the underlying adapter. Which is
|
||||
* fs-extra
|
||||
*/
|
||||
this.adapter = fsExtra;
|
||||
/**
|
||||
* Name of the driver
|
||||
*/
|
||||
this.name = 'local';
|
||||
/**
|
||||
* Path prefixer used for prefixing paths with disk root
|
||||
*/
|
||||
this.prefixer = PathPrefixer.fromPath(this.config.root); //root: '/storage/app/public',
|
||||
}
|
||||
|
||||
/**
|
||||
* A boolean to find if the location path exists or not
|
||||
*/
|
||||
exists(location: string): Promise<boolean>;
|
||||
/**
|
||||
* A boolean to find if the location path exists or not
|
||||
*/
|
||||
public async exists(location: string): Promise<boolean> {
|
||||
try {
|
||||
return await this.adapter.pathExists(this.makePath(location));
|
||||
} catch (error) {
|
||||
throw CannotGetMetaDataException.invoke(location, 'exists', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make absolute path to a given location
|
||||
*/
|
||||
public makePath(location: string): string {
|
||||
return this.prefixer.prefixPath(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given location path
|
||||
*/
|
||||
// delete(location: string, ...args: any[]): Promise<void>;
|
||||
public async delete(location: string): Promise<void> {
|
||||
try {
|
||||
await this.adapter.remove(this.makePath(location));
|
||||
} catch (error) {
|
||||
throw CannotDeleteFileException.invoke(location, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a listing directory iterator for given location.
|
||||
*/
|
||||
public list(location: string): AsyncIterableArray<DriverContract, DriveListItem<any>> {
|
||||
// public async list(location: string): Promise<DirectoryListing<DriverContract, DriveListItem<any>>> {
|
||||
const fullPath = this.makePath(location); //'/storage/app/public/files/307'
|
||||
|
||||
// let dirListing: DirectoryListing<DriverContract, DriveListItem<any>> = new DirectoryListing(this, () => this.getListing(fullPath, location));
|
||||
let dirListing: AsyncIterableArray<DriverContract, DriveListItem<any>> = new AsyncIterableArray(this, () => this.getListing(fullPath, location));
|
||||
return dirListing;
|
||||
// let listing: DriveListItem<fsExtra.Dirent>[] = await this.getListing(fullPath, location);
|
||||
// let test = new DirectoryListing(this, listing);
|
||||
// return test;
|
||||
}
|
||||
|
||||
// Example usage
|
||||
// private async *generateNumbers(): AsyncGenerator<number, void, unknown> {
|
||||
// yield 1;
|
||||
// yield 2;
|
||||
// yield 3;
|
||||
// }
|
||||
|
||||
private async *getListing(fullPath: string, location: string): AsyncGenerator<DriveListItem<fsExtra.Dirent>, void, unknown> {
|
||||
// private async getListing(fullPath: string, location: string): Promise<DriveListItem<fsExtra.Dirent>[]> {
|
||||
try {
|
||||
const dir = await this.adapter.opendir(fullPath);
|
||||
const prefixer = this.prefixer.withStrippedPrefix(fullPath);
|
||||
// const listing: DriveListItem<fsExtra.Dirent>[] = new Array();
|
||||
for await (const dirent of dir) {
|
||||
yield {
|
||||
location: prefixer.prefixPath(dirent.name),
|
||||
isFile: dirent.isFile(),
|
||||
original: dirent,
|
||||
};
|
||||
}
|
||||
// return listing;
|
||||
} catch (error) {
|
||||
throw CannotListDirectoryException.invoke(location, error);
|
||||
}
|
||||
}
|
||||
}
|
73
providers/drive/exceptions/index.ts
Normal file
73
providers/drive/exceptions/index.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
// const utils_1 = require("@poppinss/utils");
|
||||
// import * as utils_1 from "@poppinss/utils";
|
||||
import { Exception } from '@poppinss/utils';
|
||||
|
||||
/**
|
||||
* Custom exception for when a file cannot be deleted from a specified location
|
||||
*/
|
||||
export class CannotDeleteFileException extends Exception {
|
||||
location: string;
|
||||
original: any;
|
||||
|
||||
static invoke(location: string, original: any): CannotDeleteFileException {
|
||||
const error = new this(`Cannot delete file at location "${location}"`, {
|
||||
code: 'E_CANNOT_DELETE_FILE',
|
||||
status: 500,
|
||||
});
|
||||
error.location = location;
|
||||
error.original = original;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom exception for when file metadata cannot be retrieved
|
||||
*/
|
||||
export class CannotGetMetaDataException extends Exception {
|
||||
location: string;
|
||||
operation: string;
|
||||
original: any;
|
||||
|
||||
static invoke(location: string, operation: string, original: any): CannotGetMetaDataException {
|
||||
const error = new this(`Unable to retrieve the "${operation}" for file at location "${location}"`, {
|
||||
code: 'E_CANNOT_GET_METADATA',
|
||||
status: 500,
|
||||
});
|
||||
error.location = location;
|
||||
error.operation = operation;
|
||||
error.original = original;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given location is trying to traverse beyond the root path
|
||||
*/
|
||||
export class PathTraversalDetectedException extends Exception {
|
||||
location: string;
|
||||
static invoke(location: string) {
|
||||
const error = new this(`Path traversal detected: "${location}"`, {
|
||||
code: 'E_PATH_TRAVERSAL_DETECTED',
|
||||
status: 500,
|
||||
});
|
||||
error.location = location;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unable to list directory contents of given location
|
||||
*/
|
||||
export class CannotListDirectoryException extends Exception {
|
||||
location: string;
|
||||
original: any;
|
||||
static invoke(location: string, original: any): CannotListDirectoryException {
|
||||
const error = new this(`Cannot list directory contents of location "${location}"`, {
|
||||
status: 500,
|
||||
code: 'E_CANNOT_LIST_DIRECTORY',
|
||||
});
|
||||
error.location = location;
|
||||
error.original = original;
|
||||
return error;
|
||||
}
|
||||
}
|
96
providers/drive/path_prefixer/index.ts
Normal file
96
providers/drive/path_prefixer/index.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
import { slash } from '@poppinss/utils';
|
||||
import { relative, normalize } from 'path';
|
||||
import { PathTraversalDetectedException } from '../exceptions/index.js';
|
||||
/**
|
||||
* Path prefixer for resolving and prefixing paths for disk drivers
|
||||
*/
|
||||
export default class PathPrefixer {
|
||||
/**
|
||||
* Separator used for dividing path segments is always unix-style forward slash
|
||||
*/
|
||||
separator: '/';
|
||||
/**
|
||||
* Prefix used for path prefixing. Can be empty string for cloud drivers.
|
||||
*/
|
||||
prefix: string;
|
||||
// constructor(prefix?: string);
|
||||
|
||||
constructor(prefix: string = '') {
|
||||
/**
|
||||
* Separator used for dividing path segments is always unix-style forward slash
|
||||
*/
|
||||
this.separator = '/';
|
||||
// strip slashes from the end of the prefix
|
||||
this.prefix = prefix.replace(/\/+$/g, '');
|
||||
// always end prefix with separator if it is not empty
|
||||
if (this.prefix !== '' || prefix === this.separator) {
|
||||
this.prefix += this.separator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize given path to always use `/` as separator and resolve relative paths using `.` and `..`.
|
||||
* It also guards against path traversal beyond the root.
|
||||
*/
|
||||
normalizePath(path: string): string {
|
||||
// const converted = (0, utils_1.slash)(path);
|
||||
const converted = slash(path);
|
||||
const parts = [];
|
||||
for (const part of converted.split(this.separator)) {
|
||||
if (['', '.'].includes(part)) {
|
||||
continue;
|
||||
}
|
||||
if (part === '..') {
|
||||
// if we are traversing beyond the root
|
||||
if (parts.length === 0) {
|
||||
throw PathTraversalDetectedException.invoke(converted);
|
||||
}
|
||||
parts.pop();
|
||||
} else {
|
||||
parts.push(part);
|
||||
}
|
||||
}
|
||||
return parts.join(this.separator);
|
||||
}
|
||||
/**
|
||||
* Ruturns normalized and prefixed location path.
|
||||
*/
|
||||
prefixPath(location: string): string {
|
||||
return this.prefix + this.normalizePath(location);
|
||||
}
|
||||
/**
|
||||
* Ruturns normalized and prefixed location path for directory so always ending with slash.
|
||||
* Useful for cloud drivers prefix when listitng files.
|
||||
*/
|
||||
prefixDirectoryPath(location: string): string {
|
||||
return this.prefixPath(location) + this.separator;
|
||||
}
|
||||
/**
|
||||
* Returns normalized path after stripping the current prefix from it.
|
||||
* It is a reverse operation of `prefixPath`.
|
||||
*/
|
||||
stripPrefix(location: string): string {
|
||||
// const path = (0, path_1.relative)(this.prefix, (0, utils_1.slash)(location));
|
||||
const path = relative(this.prefix, slash(location));
|
||||
return this.normalizePath(path);
|
||||
}
|
||||
/**
|
||||
* Returns a new instance of `PathPrefixer` which is using as prefix stripped prefix from path of current `PathPrefixer`.
|
||||
*/
|
||||
withStrippedPrefix(path: string): PathPrefixer {
|
||||
return new PathPrefixer(this.stripPrefix(path));
|
||||
}
|
||||
/**
|
||||
* Returns a new instance of `PathPrefixer` which is using as prefix current prefix merged with provided prefix.
|
||||
*/
|
||||
withPrefix(prefix: string): PathPrefixer {
|
||||
return new PathPrefixer(this.prefixPath(prefix));
|
||||
}
|
||||
/**
|
||||
* Returns a new instance of `PathPrefixer` which is using as prefix provided normalized path.
|
||||
*/
|
||||
static fromPath(path: string): PathPrefixer {
|
||||
// return new this((0, utils_1.slash)((0, path_1.normalize)(path)));
|
||||
return new this(slash(normalize(path)));
|
||||
}
|
||||
}
|
62
providers/drive/provider/drive_provider.ts
Normal file
62
providers/drive/provider/drive_provider.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import type { ApplicationService } from '@adonisjs/core/types';
|
||||
import { RuntimeException } from '@poppinss/utils';
|
||||
// import DriveManager from '../src/drive_manager.js';
|
||||
import { DriveConfig } from '../src/types/drive.js';
|
||||
|
||||
|
||||
|
||||
export default class DriveProvider {
|
||||
constructor(protected app: ApplicationService) {}
|
||||
|
||||
/**
|
||||
* Register bindings to the container
|
||||
*/
|
||||
async register() {
|
||||
const { default: DriveManager } = await import('../src/drive_manager.js');
|
||||
this.app.container.singleton(DriveManager, async () => {
|
||||
// 1. import the oai configuration
|
||||
// const ttl: number = 86400;
|
||||
const config: DriveConfig = this.app.config.get('drive');
|
||||
// const config: DriveConfig | null = await configProvider.resolve(this.app, driveConfigProvider);
|
||||
// const vite = await this.app.container.make("vite");
|
||||
if (!config) {
|
||||
throw new RuntimeException('Invalid "config/drive.ts" file. Make sure you are using the "defineConfig" method');
|
||||
}
|
||||
|
||||
return new DriveManager(this.app, config);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register drive with the container
|
||||
*/
|
||||
// registerDrive() {
|
||||
// this.app.container.singleton('Adonis/Core/Drive', () => {
|
||||
// const { DriveManager } = require('../src/DriveManager');
|
||||
// const Router = this.app.container.resolveBinding('Adonis/Core/Route');
|
||||
// const Config = this.app.container.resolveBinding('Adonis/Core/Config');
|
||||
// const Logger = this.app.container.resolveBinding('Adonis/Core/Logger');
|
||||
// return new DriveManager(this.app, Router, Logger, Config.get('drive'));
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* The container bindings have booted
|
||||
*/
|
||||
async boot() {}
|
||||
|
||||
/**
|
||||
* The application has been booted
|
||||
*/
|
||||
async start() {}
|
||||
|
||||
/**
|
||||
* The process has been started
|
||||
*/
|
||||
async ready() {}
|
||||
|
||||
/**
|
||||
* Preparing to shutdown the app
|
||||
*/
|
||||
async shutdown() {}
|
||||
}
|
154
providers/drive/src/drive_manager.ts
Normal file
154
providers/drive/src/drive_manager.ts
Normal file
|
@ -0,0 +1,154 @@
|
|||
import type { ApplicationService } from '@adonisjs/core/types';
|
||||
import { Manager } from '@poppinss/manager';
|
||||
import { Exception } from '@poppinss/utils';
|
||||
import { DriverContract, DriversList, DriveConfig, DriveListItem, DirectoryListingContract, LocalDriverConfig } from './types/drive.js';
|
||||
// import { LocalDriver } from './drivers/local.js';
|
||||
import { LocalDriver } from '../drivers/local.js';
|
||||
|
||||
type test = {
|
||||
[P in keyof DriversList]: DriversList[P];
|
||||
};
|
||||
// type DriveMappings = {
|
||||
// local: string
|
||||
// fake: string
|
||||
// }
|
||||
|
||||
// interface DriversList {
|
||||
// local: {
|
||||
// implementation: LocalDriverContract;
|
||||
// config: LocalDriverConfig;
|
||||
// };
|
||||
// }
|
||||
|
||||
// type DriverConfig = {
|
||||
// disk: keyof DriversList
|
||||
// disks: {
|
||||
// [K in keyof DriversList]: any
|
||||
// }
|
||||
// }
|
||||
|
||||
// const mailerConfig: DriveConfig = {
|
||||
// disk: 'local',
|
||||
|
||||
// disks: {
|
||||
// local: {
|
||||
// driver: 'local',
|
||||
// root: '',
|
||||
// visibility: '',
|
||||
// serveFiles: false,
|
||||
// basePath: '',
|
||||
// },
|
||||
|
||||
// // 'fake': {
|
||||
// // driver: 'fake',
|
||||
// // root: '',
|
||||
// // },
|
||||
// },
|
||||
// };
|
||||
|
||||
export default class DriveManager extends Manager<
|
||||
ApplicationService,
|
||||
DriverContract,
|
||||
DriverContract,
|
||||
{
|
||||
[P in keyof DriversList]: DriversList[P]['implementation'];
|
||||
}
|
||||
> {
|
||||
protected singleton = true;
|
||||
private config;
|
||||
/**
|
||||
* Find if drive is ready to be used
|
||||
*/
|
||||
private isReady: boolean;
|
||||
|
||||
protected getDefaultMappingName() {
|
||||
return this.config.disk; // "local"
|
||||
}
|
||||
|
||||
protected getMappingConfig(mappingName: string) {
|
||||
return this.config.disks[mappingName];
|
||||
}
|
||||
|
||||
protected getMappingDriver(mappingName: string) {
|
||||
return this.config.disks[mappingName].driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make instance of the local driver
|
||||
*/
|
||||
protected createLocal(diskName: keyof DriversList, config: LocalDriverConfig) {
|
||||
// const { LocalDriver } = await import('../drivers/local.js');
|
||||
return new LocalDriver(diskName, config);
|
||||
}
|
||||
|
||||
constructor(application: ApplicationService, config: DriveConfig) {
|
||||
super(application);
|
||||
this.config = config;
|
||||
|
||||
/**
|
||||
* Find if drive is ready to be used
|
||||
*/
|
||||
this.isReady = false;
|
||||
|
||||
this.validateConfig();
|
||||
}
|
||||
/**
|
||||
* Validate config
|
||||
*/
|
||||
private validateConfig() {
|
||||
if (!this.config) {
|
||||
return;
|
||||
}
|
||||
// const validator = new utils_1.ManagerConfigValidator(this.config, 'drive', 'config/drive');
|
||||
// validator.validateDefault('disk');
|
||||
// validator.validateList('disks', 'disk');
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve instance for a disk
|
||||
*/
|
||||
use(disk?: keyof test): DriversList[keyof DriversList]['implementation'] {
|
||||
if (!this.isReady) {
|
||||
throw new Exception('Missing configuration for drive. Visit https://bit.ly/2WnR5j9 for setup instructions', {
|
||||
status: 500,
|
||||
code: 'E_MISSING_DRIVE_CONFIG',
|
||||
});
|
||||
}
|
||||
disk = disk || this.getDefaultMappingName();
|
||||
// if (this.fakeDrive.isFaked(disk)) {
|
||||
// return this.fakeDrive.use(disk);
|
||||
// }
|
||||
return super.use(disk);
|
||||
}
|
||||
|
||||
/**
|
||||
* A boolean to find if the location path exists or not
|
||||
*/
|
||||
exists(location: string): Promise<boolean> {
|
||||
const driver = this.use();
|
||||
return driver.exists(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given location path
|
||||
*/
|
||||
delete(location: string): Promise<void> {
|
||||
const driver = this.use();
|
||||
return driver.delete(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a listing directory iterator for given location.
|
||||
*/
|
||||
list(location: string): DirectoryListingContract<DriverContract, DriveListItem> {
|
||||
const driver = this.use();
|
||||
if (typeof driver.list !== 'function') {
|
||||
throw new Exception(`List is not supported by the "${driver.name}" driver.`, {
|
||||
status: 500,
|
||||
code: 'E_LIST_NOT_SUPPORTED',
|
||||
});
|
||||
}
|
||||
return driver.list(location);
|
||||
}
|
||||
}
|
68
providers/drive/src/iterable_array.ts
Normal file
68
providers/drive/src/iterable_array.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { DriveListItem, DriverContract } from './types/drive.js';
|
||||
// import * as fsExtra from 'fs-extra';
|
||||
import { DirectoryListingContract } from './types/drive.js';
|
||||
|
||||
// class AsyncIterableArray<T> implements AsyncIterable<T> {
|
||||
export class AsyncIterableArray<SpecificDriver extends DriverContract, T extends DriveListItem>
|
||||
implements DirectoryListingContract<SpecificDriver, T>
|
||||
{
|
||||
public driver: SpecificDriver;
|
||||
private generator: () => AsyncGenerator<T, void, unknown>;
|
||||
// private generator: () => AsyncGenerator<T, void, unknown>;
|
||||
private chain: any[];
|
||||
|
||||
constructor(driver: SpecificDriver, generator: () => AsyncGenerator<T, void, unknown>) {
|
||||
// constructor(driver: SpecificDriver, listing: T) {
|
||||
this.driver = driver;
|
||||
this.generator = generator;
|
||||
/**
|
||||
* Functions chain to be executed for transforming generated listing iterable
|
||||
*/
|
||||
this.chain = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert directory listing to array.
|
||||
*/
|
||||
// public async toArray(): Promise<T[]> {
|
||||
// const arr = [];
|
||||
// for await (const item of this.toIterable()) {
|
||||
// arr.push(item);
|
||||
// }
|
||||
// return arr;
|
||||
// }
|
||||
async toArray(): Promise<T[]> {
|
||||
const arr: T[] = [];
|
||||
for await (const item of this) {
|
||||
arr.push(item);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that returns the default async iterator for an object.
|
||||
*/
|
||||
public async *[Symbol.asyncIterator](): AsyncIterableIterator<T> {
|
||||
// yield* this.toIterable();
|
||||
for await (const item of this.generator.call(this.driver)) {
|
||||
yield item;
|
||||
}
|
||||
// yield 1
|
||||
// // await something()
|
||||
// yield 2
|
||||
// // await somethingElse()
|
||||
// yield 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final async iterable after passing directory listing through chain of piping functions modifying the output.
|
||||
*/
|
||||
public toIterable(): AsyncIterable<T> {
|
||||
const generator = this.generator.call(this.driver);
|
||||
const iterable = this.chain.reduce((prevIterable, currentIterable) => {
|
||||
return currentIterable.call(this.driver, prevIterable);
|
||||
}, generator);
|
||||
|
||||
return iterable;
|
||||
}
|
||||
}
|
5
providers/drive/src/types/define_config.ts
Normal file
5
providers/drive/src/types/define_config.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { DriveConfig } from "./drive.js";
|
||||
|
||||
export function defineConfig(config: DriveConfig): DriveConfig {
|
||||
return config;
|
||||
}
|
176
providers/drive/src/types/drive.ts
Normal file
176
providers/drive/src/types/drive.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
import fsExtra from 'fs-extra';
|
||||
import { LocalDriver } from '#providers/drive/drivers/local';
|
||||
|
||||
/**
|
||||
* List item returned by the drive drivers
|
||||
*/
|
||||
export interface DriveListItem<T = any> {
|
||||
/**
|
||||
* Location of list item on disk which can be used in driver methods
|
||||
*/
|
||||
location: string;
|
||||
|
||||
/**
|
||||
* Flag to know if item represents file or directory
|
||||
*/
|
||||
isFile: boolean;
|
||||
|
||||
/**
|
||||
* Original list item returned from underlying driver
|
||||
*/
|
||||
original: T;
|
||||
}
|
||||
/**
|
||||
* List item returned from local disk driver
|
||||
*/
|
||||
export interface LocalDriveListItem extends DriveListItem<fsExtra.Dirent> {}
|
||||
|
||||
export interface DirectoryListingContract<Driver extends DriverContract, T> extends AsyncIterable<T> {
|
||||
/**
|
||||
* Reference to the driver for which the listing was created.
|
||||
*/
|
||||
driver: Driver;
|
||||
|
||||
/**
|
||||
* Filter generated items of listing with the given predicate function.
|
||||
*/
|
||||
// filter(predicate: (item: T, index: number, driver: Driver) => Promise<boolean> | boolean): DirectoryListingContract<Driver, T>;
|
||||
|
||||
/**
|
||||
* Transform generated items of listing with the given mapper function.
|
||||
*/
|
||||
// map<M>(mapper: (item: T, index: number, driver: Driver) => Promise<M> | M): DirectoryListingContract<Driver, M>;
|
||||
|
||||
/**
|
||||
* Do recursive listing of items. Without the next function it will do listing of leaf nodes only.
|
||||
* For advanced usage you can pass the next function which will get as parameter current item and it should
|
||||
* return the next location for list or null if the recursion should stop and yield the current item.
|
||||
* For advanced usage you can also limit the depth of recursion using the second argument of next function.
|
||||
*/
|
||||
// recursive(
|
||||
// next?: (current: T, depth: number, driver: Driver) => Promise<string | null> | string | null,
|
||||
// ): DirectoryListingContract<Driver, T>;
|
||||
|
||||
/**
|
||||
* Add a piping chain function which gets the current async iterable and returns
|
||||
* new async iterable with modified directory listing output.
|
||||
* Function this is bound to instance of driver for which the listing is generated.
|
||||
* This allows using async generator functions and reference the driver methods easily.
|
||||
* Piping will always return clone of the current instance and add the function
|
||||
* to the chain of new cloned instance only to prevent side effects.
|
||||
*/
|
||||
// pipe<U>(fn: (this: Driver, source: AsyncIterable<T>) => AsyncIterable<U>): DirectoryListingContract<Driver, U>;
|
||||
|
||||
/**
|
||||
* Get the final async iterable after passing directory listing through chain of piping functions modifying the output.
|
||||
*/
|
||||
toIterable(): AsyncIterable<T>;
|
||||
|
||||
/**
|
||||
* Convert directory listing to array.
|
||||
*/
|
||||
toArray(): Promise<T[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shape of the generic driver
|
||||
*/
|
||||
export interface DriverContract {
|
||||
/**
|
||||
* Name of the driver
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* A boolean to find if the location path exists or not
|
||||
*/
|
||||
exists(location: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Remove a given location path
|
||||
*/
|
||||
delete(location: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Return a listing directory iterator for given location.
|
||||
* @experimental
|
||||
*/
|
||||
list?(location: string): DirectoryListingContract<DriverContract, DriveListItem>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shape of the local disk driver
|
||||
*/
|
||||
export interface LocalDriverContract extends DriverContract {
|
||||
name: 'local';
|
||||
|
||||
/**
|
||||
* Reference to the underlying adapter. Which is fs-extra
|
||||
*/
|
||||
adapter: typeof fsExtra;
|
||||
|
||||
/**
|
||||
* Make path to a given file location
|
||||
*/
|
||||
makePath(location: string): string;
|
||||
}
|
||||
|
||||
// interface LocalDriverContract {
|
||||
// delete(): Promise<void>;
|
||||
// }
|
||||
export type LocalDriverConfig = {
|
||||
driver: 'local';
|
||||
// visibility: Visibility
|
||||
root: string;
|
||||
|
||||
/**
|
||||
* Base path is always required when "serveFiles = true"
|
||||
*/
|
||||
serveFiles?: boolean;
|
||||
basePath?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* List of registered drivers. Drivers shipped via other packages
|
||||
* should merge drivers to this interface
|
||||
*/
|
||||
export interface DriversList {
|
||||
// [key: string]: {implementation : DriverContract, config: {}};
|
||||
local: {
|
||||
implementation: LocalDriver;
|
||||
config: {
|
||||
driver: string;
|
||||
visibility: string;
|
||||
root: string;
|
||||
serveFiles: boolean;
|
||||
basePath: string;
|
||||
};
|
||||
};
|
||||
|
||||
// [key: string]: DriverContract;
|
||||
// local: LocalDriver;
|
||||
// fake: {
|
||||
// implementation: LocalDriverContract;
|
||||
// config: LocalDriverConfig;
|
||||
// };
|
||||
}
|
||||
|
||||
export type DriveConfig = {
|
||||
disk: keyof DriversList;
|
||||
// disks: {
|
||||
// [name: string]: {
|
||||
// driver: DriverContract;
|
||||
// };
|
||||
// };
|
||||
disks: {
|
||||
[name: string]: {
|
||||
[K in keyof DriversList]: DriversList[K]['config'] & {
|
||||
driver: K;
|
||||
visibility: string;
|
||||
root: string;
|
||||
serveFiles: boolean;
|
||||
basePath: string;
|
||||
};
|
||||
}[keyof DriversList];
|
||||
};
|
||||
};
|
|
@ -26,6 +26,8 @@
|
|||
"assets/resources_js_Pages_Editor_Dataset_Publish_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Publish_vue.js",
|
||||
"assets/resources_js_Pages_Editor_Dataset_Receive_vue.js": "http://localhost:8080/assets/resources_js_Pages_Editor_Dataset_Receive_vue.js",
|
||||
"assets/resources_js_Pages_Error_vue.js": "http://localhost:8080/assets/resources_js_Pages_Error_vue.js",
|
||||
"assets/resources_js_Pages_Errors_ServerError_vue.js": "http://localhost:8080/assets/resources_js_Pages_Errors_ServerError_vue.js",
|
||||
"assets/resources_js_Pages_Errors_not_found_vue.js": "http://localhost:8080/assets/resources_js_Pages_Errors_not_found_vue.js",
|
||||
"assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.css": "http://localhost:8080/assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.css",
|
||||
"assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.js": "http://localhost:8080/assets/resources_js_Pages_Map_vue-resources_js_Components_Map_draw_component_vue-resources_js_Compon-b0925c.js",
|
||||
"assets/resources_js_Pages_ProfileView_vue.js": "http://localhost:8080/assets/resources_js_Pages_ProfileView_vue.js",
|
||||
|
@ -42,14 +44,15 @@
|
|||
"assets/resources_js_Pages_Submitter_Dataset_Release_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Dataset_Release_vue.js",
|
||||
"assets/resources_js_Pages_Submitter_Person_Index_vue.js": "http://localhost:8080/assets/resources_js_Pages_Submitter_Person_Index_vue.js",
|
||||
"assets/resources_js_Pages_register-view_register-view-component_vue.js": "http://localhost:8080/assets/resources_js_Pages_register-view_register-view-component_vue.js",
|
||||
"assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_j-02166b.js": "http://localhost:8080/assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_j-02166b.js",
|
||||
"assets/vendors-node_modules_mdi_js_mdi_js-node_modules_vue-loader_dist_exportHelper_js.js": "http://localhost:8080/assets/vendors-node_modules_mdi_js_mdi_js-node_modules_vue-loader_dist_exportHelper_js.js",
|
||||
"assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_js.js": "http://localhost:8080/assets/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_notiwind_dist_index_esm_js.js",
|
||||
"assets/vendors-node_modules_vue-facing-decorator_dist_esm_utils_js.js": "http://localhost:8080/assets/vendors-node_modules_vue-facing-decorator_dist_esm_utils_js.js",
|
||||
"assets/vendors-node_modules_leaflet_src_control_Control_Attribution_js-node_modules_leaflet_src_laye-fbc1b4.js": "http://localhost:8080/assets/vendors-node_modules_leaflet_src_control_Control_Attribution_js-node_modules_leaflet_src_laye-fbc1b4.js",
|
||||
"assets/vendors-node_modules_buffer_index_js-node_modules_leaflet_src_layer_tile_TileLayer_WMS_js-nod-e7bc71.js": "http://localhost:8080/assets/vendors-node_modules_buffer_index_js-node_modules_leaflet_src_layer_tile_TileLayer_WMS_js-nod-e7bc71.js",
|
||||
"assets/vendors-node_modules_numeral_numeral_js-node_modules_chart_js_dist_chart_js.js": "http://localhost:8080/assets/vendors-node_modules_numeral_numeral_js-node_modules_chart_js_dist_chart_js.js",
|
||||
"assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_layer_LayerGroup_js.js": "http://localhost:8080/assets/vendors-node_modules_leaflet_dist_leaflet-src_js-node_modules_leaflet_src_layer_LayerGroup_js.js",
|
||||
"assets/vendors-node_modules_toastify-js_src_toastify_js.js": "http://localhost:8080/assets/vendors-node_modules_toastify-js_src_toastify_js.js",
|
||||
"assets/resources_js_Components_BaseButton_vue-resources_js_Components_BaseDivider_vue-resources_js_C-0c87b5.js": "http://localhost:8080/assets/resources_js_Components_BaseButton_vue-resources_js_Components_BaseDivider_vue-resources_js_C-0c87b5.js",
|
||||
"assets/resources_js_Stores_main_ts-resources_js_Components_BaseButton_vue-resources_js_Components_Ba-dd870d.js": "http://localhost:8080/assets/resources_js_Stores_main_ts-resources_js_Components_BaseButton_vue-resources_js_Components_Ba-dd870d.js",
|
||||
"assets/resources_js_Components_SectionMain_vue-resources_js_Layouts_LayoutAuthenticated_vue.css": "http://localhost:8080/assets/resources_js_Components_SectionMain_vue-resources_js_Layouts_LayoutAuthenticated_vue.css",
|
||||
"assets/resources_js_Components_SectionMain_vue-resources_js_Layouts_LayoutAuthenticated_vue.js": "http://localhost:8080/assets/resources_js_Components_SectionMain_vue-resources_js_Layouts_LayoutAuthenticated_vue.js",
|
||||
"assets/resources_js_Components_BaseButtons_vue-resources_js_Components_FormControl_vue-resources_js_-d830d6.js": "http://localhost:8080/assets/resources_js_Components_BaseButtons_vue-resources_js_Components_FormControl_vue-resources_js_-d830d6.js",
|
||||
|
@ -80,5 +83,10 @@
|
|||
"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",
|
||||
"assets/resources_js_Pages_Errors_App_vue.js": "http://localhost:8080/assets/resources_js_Pages_Errors_App_vue.js",
|
||||
"assets/resources_js_Pages_Errors_ServerError2_vue.js": "http://localhost:8080/assets/resources_js_Pages_Errors_ServerError2_vue.js",
|
||||
"assets/resources_js_Components_BaseButton_vue.js": "http://localhost:8080/assets/resources_js_Components_BaseButton_vue.js",
|
||||
"assets/resources_js_Stores_main_ts-resources_js_Components_BaseDivider_vue-resources_js_Components_C-b45805.js": "http://localhost:8080/assets/resources_js_Stores_main_ts-resources_js_Components_BaseDivider_vue-resources_js_Components_C-b45805.js"
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
<script setup>
|
||||
<script lang="ts" setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -29,17 +28,15 @@ const props = defineProps({
|
|||
// null
|
||||
// total:
|
||||
// 19
|
||||
const nextPageLink = computed(() => {
|
||||
let url = new URL(document.location);
|
||||
url.searchParams.set('page', props.data.currentPage + 1);
|
||||
const calculateNextPageLink = computed(() => {
|
||||
let url = new URL(document.location.href);
|
||||
url.searchParams.set('page', String(props.data.currentPage + 1));
|
||||
return url.href;
|
||||
// return url + '&page=' + (Number(props.data.currentPage) + 1);
|
||||
});
|
||||
const prevPageLink = computed(() => {
|
||||
let url = new URL(document.location);
|
||||
url.searchParams.set('page', props.data.currentPage - 1);
|
||||
const calculatePrevPageLink = computed(() => {
|
||||
let url = new URL(document.location.href);
|
||||
url.searchParams.set('page', String(props.data.currentPage - 1));
|
||||
return url.href;
|
||||
// return url + '&page=' + (props.data.currentPage - 1);
|
||||
});
|
||||
const toPage = computed(() => {
|
||||
let currentPage = props.data.currentPage;
|
||||
|
@ -58,54 +55,26 @@ const fromPage = computed(() => {
|
|||
});
|
||||
</script>
|
||||
|
||||
<!-- currentPage:
|
||||
1
|
||||
firstPage:
|
||||
1
|
||||
firstPageUrl:
|
||||
'/?page=1'
|
||||
lastPage:
|
||||
3
|
||||
lastPageUrl:
|
||||
'/?page=3'
|
||||
nextPageUrl:
|
||||
'/?page=2'
|
||||
perPage:
|
||||
5
|
||||
previousPageUrl:
|
||||
null
|
||||
total:
|
||||
15 -->
|
||||
|
||||
<template>
|
||||
<!-- <nav v-if="data.links.length > 3" -->
|
||||
<nav v-if="data.total > 3" role="navigation" aria-label="Pagination Navigation" class="flex items-center justify-between">
|
||||
<nav v-if="data.total > 3" role="navigation" aria-label="Pagination Navigation"
|
||||
class="flex items-center justify-between">
|
||||
<div class="flex justify-between flex-1 sm:hidden">
|
||||
<span
|
||||
v-if="data.currentPage <= 1"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md"
|
||||
>
|
||||
<span v-if="data.currentPage <= 1"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
|
||||
Previous
|
||||
</span>
|
||||
<Link
|
||||
v-else
|
||||
:href="data.previousPageUrl"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
|
||||
>
|
||||
<Link v-else :href="data.previousPageUrl"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Previous
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
v-if="data.currentPage < data.lastPage"
|
||||
:href="data.nextPageUrl"
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
|
||||
>
|
||||
<Link v-if="data.currentPage < data.lastPage" :href="data.nextPageUrl"
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Next
|
||||
</Link>
|
||||
<span
|
||||
v-else
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md"
|
||||
>
|
||||
<span v-else
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
|
||||
Next
|
||||
</span>
|
||||
</div>
|
||||
|
@ -128,32 +97,23 @@ total:
|
|||
<span v-if="props.data.currentPage <= 1" aria-disabled="true" aria-label="Previous">
|
||||
<span
|
||||
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
aria-hidden="true">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- <Link v-else :href="data.previousPageUrl" rel="prev" -->
|
||||
<Link
|
||||
v-else
|
||||
:href="prevPageLink"
|
||||
rel="prev"
|
||||
<Link v-else :href="calculatePrevPageLink" rel="prev"
|
||||
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
|
||||
aria-label="Previous"
|
||||
>
|
||||
aria-label="Previous">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</Link>
|
||||
|
||||
|
@ -169,35 +129,26 @@ total:
|
|||
|
||||
<Link v-else :key="`link-${key}`" :href="link.url" v-html="link.label" class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:text-gray-500 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150" aria-label="`Go to page ${link.label}`" />
|
||||
</template>
|
||||
</template> -->
|
||||
</template> -->
|
||||
|
||||
<Link
|
||||
v-if="props.data.currentPage < props.data.lastPage"
|
||||
:href="nextPageLink"
|
||||
rel="next"
|
||||
<Link v-if="props.data.currentPage < props.data.lastPage" :href="calculateNextPageLink" rel="next"
|
||||
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
|
||||
aria-label="Next"
|
||||
>
|
||||
aria-label="Next">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</Link>
|
||||
<!-- else disabled link -->
|
||||
<span v-else aria-disabled="true" aria-label="Next">
|
||||
<span
|
||||
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
aria-hidden="true">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, computed, ComputedRef } from 'vue';
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
// import { Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { mdiMinus, mdiPlus } from '@mdi/js';
|
||||
import { getButtonColor } from '@/colors.js';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
|
|
@ -5,7 +5,7 @@ import { stardust } from '@eidellev/adonis-stardust/client';
|
|||
import { mdiLogout, mdiClose } from '@mdi/js';
|
||||
import { computed } from 'vue';
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import AsideMenuList from '@/Components/AsideMenuList.vue';
|
||||
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { computed, defineProps } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import NotificationBarInCard from '@/Components/NotificationBarInCard.vue';
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<div class="bg-slate-100 py-3 px-6 flex justify-between items-center mb-6">
|
||||
<div class="flex items-center space-x-6">
|
||||
<h3 class="font-bold">AdonisJS InertiaJS Example</h3>
|
||||
<h3 class="font-bold">TethysCloud Errors</h3>
|
||||
<nav class="flex items-center text-sm">
|
||||
<Link href="/app">Home</Link>
|
||||
<Link href="/apps/dashboard">Home</Link>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-6 text-sm">
|
||||
<!-- <div class="flex items-center space-x-6 text-sm">
|
||||
<Link href="/app/login">Login</Link>
|
||||
<Link href="/app/register">Register</Link>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// import * as util from '../core/utilities';
|
||||
import { EventEmitter } from './EventEmitter.js';
|
||||
import type { Map } from 'leaflet/src/map/index';
|
||||
import type { Map } from 'leaflet/src/map/index.js';
|
||||
// import type { Map } from 'leaflet';
|
||||
|
||||
export abstract class Control<T> extends EventEmitter<T> {
|
||||
// @section
|
||||
|
@ -8,7 +9,7 @@ export abstract class Control<T> extends EventEmitter<T> {
|
|||
public options = {
|
||||
position: 'topright',
|
||||
};
|
||||
protected _map;
|
||||
protected _map: Map;
|
||||
protected _container;
|
||||
|
||||
// constructor(defaults?) {
|
||||
|
@ -28,7 +29,7 @@ export abstract class Control<T> extends EventEmitter<T> {
|
|||
return this._container;
|
||||
}
|
||||
|
||||
public abstract onRemove(map): void;
|
||||
public abstract onRemove(map: Map): void;
|
||||
|
||||
public abstract onAdd(map: any): HTMLElement;
|
||||
|
||||
|
@ -51,7 +52,7 @@ export abstract class Control<T> extends EventEmitter<T> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public removeFrom(map) {
|
||||
public removeFrom(map: Map) {
|
||||
let pos = this.getPosition();
|
||||
let corner = map._controlCorners[pos];
|
||||
|
||||
|
|
|
@ -6,8 +6,7 @@ import { ComputedRef } from 'vue';
|
|||
|
||||
import { computed, ref } from 'vue';
|
||||
import { containerMaxW } from '@/config.js';
|
||||
// import { MainService } from '@/Stores/main.js';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import {
|
||||
mdiForwardburger,
|
||||
|
@ -15,14 +14,12 @@ import {
|
|||
mdiClose,
|
||||
mdiDotsVertical,
|
||||
mdiMenu,
|
||||
// mdiClockOutline,
|
||||
mdiCloudDownloadOutline,
|
||||
mdiCloud,
|
||||
mdiCrop,
|
||||
mdiAccountCog,
|
||||
mdiFormatListGroup,
|
||||
mdiFormatListNumbered,
|
||||
// mdiEmail,
|
||||
mdiLogout,
|
||||
mdiGithub,
|
||||
mdiThemeLightDark,
|
||||
|
@ -46,9 +43,6 @@ import FirstrunWizard from '@/Components/FirstrunWizard/FirstrunWizard.vue'
|
|||
// import SwapHorizontal from 'vue-material-design-icons/SwapHorizontal.vue'
|
||||
// import AccountGroup from 'vue-material-design-icons/AccountGroup.vue'
|
||||
|
||||
// const mainStore = MainService();
|
||||
// const userName = computed(() =>mainStore.userName);
|
||||
|
||||
const styleService = StyleService();
|
||||
const props = defineProps({
|
||||
showBurger: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
// import { Link } from '@inertiajs/vue3'
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { computed } from 'vue';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js';
|
||||
import NavBarItem from '@/Components/NavBarItem.vue';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
|
||||
defineProps({
|
||||
zIndex: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { gradientBgPurplePink, gradientBgDark, gradientBgPinkRed, gradientBgGreenBlue } from '@/colors';
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
// import { MainService } from '@/Stores/main';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { mdiTrashCan } from '@mdi/js';
|
||||
// import CardBoxModal from '@/Components/CardBoxModal.vue';
|
||||
// import TableCheckboxCell from '@/Components/TableCheckboxCell.vue';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
// import { MainService } from '@/Stores/main';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { mdiTrashCan } from '@mdi/js';
|
||||
import { mdiDragVariant } from '@mdi/js';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { mdiEye, mdiTrashCan } from '@mdi/js';
|
||||
import CardBoxModal from '@/Components/CardBoxModal.vue';
|
||||
import TableCheckboxCell from '@/Components/TableCheckboxCell.vue';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import NavBar from '@/Components/NavBar.vue';
|
||||
import AsideMenu from '@/Components/AsideMenu.vue';
|
||||
import FooterBar from '@/Components/FooterBar.vue';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
const styleService = StyleService();
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm, router } from '@inertiajs/vue3';
|
||||
import { mdiAccountKey, mdiArrowLeftBoldOutline, mdiAccount, mdiNoteText, mdiFormTextarea } from '@mdi/js';
|
||||
<script lang="ts" setup>
|
||||
import { Head, useForm } from '@inertiajs/vue3';
|
||||
import { mdiAccountKey, mdiArrowLeftBoldOutline, mdiFormTextarea } from '@mdi/js';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
|
||||
import {
|
||||
mdiContrastCircle,
|
||||
|
|
60
resources/js/Pages/Errors/ServerError.vue
Normal file
60
resources/js/Pages/Errors/ServerError.vue
Normal file
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-100">
|
||||
<div class="max-w-md w-full p-6 bg-white rounded-md shadow-md">
|
||||
<h1 class="text-2xl font-bold text-red-500 mb-4">Error!</h1>
|
||||
<p class="text-gray-700 mb-4">{{ error }}</p>
|
||||
|
||||
<SectionTitleLineWithButton :icon="mdiLightbulbAlert" :title="code" :main="true">
|
||||
<BaseButton @click.prevent="handleAction" :icon="mdiArrowLeftBoldOutline" label="Dashboard"
|
||||
color="white" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-facing-decorator';
|
||||
import { Link, router } from '@inertiajs/vue3';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import { mdiLightbulbAlert, mdiArrowLeftBoldOutline } from '@mdi/js';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
@Component({
|
||||
// options: {
|
||||
// layout: DefaultLayout,
|
||||
// },
|
||||
name: 'AppComponent',
|
||||
|
||||
components: {
|
||||
Link,
|
||||
BaseButton,
|
||||
SectionTitleLineWithButton,
|
||||
},
|
||||
})
|
||||
export default class AppComponent extends Vue {
|
||||
// Component Property
|
||||
@Prop({
|
||||
type: String,
|
||||
default: () => '',
|
||||
})
|
||||
error: string;
|
||||
|
||||
@Prop({
|
||||
type: String,
|
||||
default: () => '',
|
||||
})
|
||||
code: string;
|
||||
|
||||
// class properties
|
||||
mdiLightbulbAlert = mdiLightbulbAlert;
|
||||
mdiArrowLeftBoldOutline = mdiArrowLeftBoldOutline;
|
||||
|
||||
public async handleAction() {
|
||||
// Add your logic here for handling the button action (e.g., logout or go back)
|
||||
await router.get(stardust.route('dashboard'));
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
7
resources/js/Pages/Errors/not_found.vue
Normal file
7
resources/js/Pages/Errors/not_found.vue
Normal file
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="title">Page not found</div>
|
||||
|
||||
<span>This page does not exist.</span>
|
||||
</div>
|
||||
</template>
|
|
@ -167,7 +167,7 @@ export const MainService = defineStore('main', {
|
|||
this.totpState = state;
|
||||
},
|
||||
|
||||
async fetchChartData(year) {
|
||||
async fetchChartData(year: string) {
|
||||
// sampleDataKey= authors or datasets
|
||||
axios
|
||||
.get(`/api/statistic/${year}`)
|
||||
|
|
|
@ -9,23 +9,23 @@ export const MapService = defineStore('map', {
|
|||
}),
|
||||
actions: {
|
||||
// payload = authenticated user
|
||||
setUser(payload) {
|
||||
if (payload.name) {
|
||||
this.userName = payload.name;
|
||||
}
|
||||
if (payload.email) {
|
||||
this.userEmail = payload.email;
|
||||
}
|
||||
if (payload.avatar) {
|
||||
this.userAvatar = payload.avatar;
|
||||
}
|
||||
},
|
||||
// setUser(payload: any) {
|
||||
// if (payload.name) {
|
||||
// this.userName = payload.name;
|
||||
// }
|
||||
// if (payload.email) {
|
||||
// this.userEmail = payload.email;
|
||||
// }
|
||||
// if (payload.avatar) {
|
||||
// this.userAvatar = payload.avatar;
|
||||
// }
|
||||
// },
|
||||
|
||||
getMap(id: string) {
|
||||
return this.mapService.get(id);
|
||||
},
|
||||
|
||||
setMap(id: string, map) {
|
||||
setMap(id: string, map: any) {
|
||||
this.mapService.set(id, map);
|
||||
},
|
||||
|
||||
|
|
|
@ -1,10 +1,27 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import * as styles from '@/styles';
|
||||
import styles from '@/styles';
|
||||
import { darkModeKey, styleKey } from '@/config';
|
||||
|
||||
|
||||
interface StyleState {
|
||||
[key: string]: string | boolean;
|
||||
asideStyle: string;
|
||||
asideScrollbarsStyle: string;
|
||||
asideBrandStyle: string;
|
||||
asideMenuItemStyle: string;
|
||||
asideMenuItemActiveStyle: string;
|
||||
asideMenuDropdownStyle: string;
|
||||
navBarItemLabelStyle: string;
|
||||
navBarItemLabelHoverStyle: string;
|
||||
navBarItemLabelActiveColorStyle: string;
|
||||
overlayStyle: string;
|
||||
darkMode: boolean;
|
||||
}
|
||||
|
||||
// Define StyleService store
|
||||
export const StyleService = defineStore('style', {
|
||||
state: () => ({
|
||||
/* Styles */
|
||||
state: (): StyleState => ({
|
||||
// Styles
|
||||
asideStyle: '',
|
||||
asideScrollbarsStyle: '',
|
||||
asideBrandStyle: '',
|
||||
|
@ -15,39 +32,32 @@ export const StyleService = defineStore('style', {
|
|||
navBarItemLabelHoverStyle: '',
|
||||
navBarItemLabelActiveColorStyle: '',
|
||||
overlayStyle: '',
|
||||
|
||||
/* Dark mode default false */
|
||||
// Dark mode default false
|
||||
darkMode: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// style payload = 'basic' or 'white' with blue font
|
||||
setStyle(payload) {
|
||||
// Set style based on payload value ('basic' or 'white')
|
||||
setStyle(payload: 'basic' | 'white') {
|
||||
if (!styles[payload]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(styleKey, payload);
|
||||
}
|
||||
|
||||
const style = styles[payload];
|
||||
|
||||
const style = styles[payload] as Record<string, string>;
|
||||
for (const key in style) {
|
||||
this[`${key}Style`] = style[key];
|
||||
// let keyStyle: string = `${key}Style`;//key as keyof typeof style;
|
||||
this[`${key}Style` as keyof StyleState] = style[key];
|
||||
}
|
||||
},
|
||||
// toggle dark mode
|
||||
setDarkMode(payload = null) {
|
||||
this.darkMode = payload !== null ? payload : !this.darkMode;
|
||||
|
||||
// Toggle dark mode
|
||||
setDarkMode(payload?: boolean) {
|
||||
this.darkMode = payload !== undefined ? payload : !this.darkMode;
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(darkModeKey, this.darkMode ? '1' : '0');
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
document.body.classList[this.darkMode ? 'add' : 'remove']('dark-scrollbars');
|
||||
|
||||
document.documentElement.classList[this.darkMode ? 'add' : 'remove']('dark-scrollbars-compat');
|
||||
}
|
||||
},
|
|
@ -1,21 +1,28 @@
|
|||
import '../css/app.css';
|
||||
import { createApp, h } from 'vue';
|
||||
import { createApp, h, App, Plugin } from 'vue';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
|
||||
import { createInertiaApp, Link, usePage } from '@inertiajs/vue3';
|
||||
import { createInertiaApp } from '@inertiajs/vue3';
|
||||
// import DefaultLayout from '@/Layouts/Default.vue';
|
||||
|
||||
import { createPinia } from 'pinia';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import { darkModeKey, styleKey } from '@/config';
|
||||
// import type { DefineComponent } from 'vue';
|
||||
// import { resolvePageComponent } from '@adonisjs/inertia/helpers';
|
||||
const pinia = createPinia();
|
||||
import { EmitterPlugin } from '@/EmitterDirective';
|
||||
|
||||
import { initRoutes } from '@eidellev/adonis-stardust/client/index.js';
|
||||
initRoutes();
|
||||
|
||||
interface SetupOptions {
|
||||
el: Element;
|
||||
App: App;
|
||||
props: Record<string, any>;
|
||||
plugin: Plugin;
|
||||
}
|
||||
|
||||
// import '@fontsource/archivo-black/index.css';
|
||||
// import '@fontsource/inter/index.css';
|
||||
|
||||
|
@ -35,7 +42,7 @@ createInertiaApp({
|
|||
// // }
|
||||
// return page;
|
||||
// },
|
||||
resolve: async (name) => {
|
||||
resolve: async (name: string) => {
|
||||
// Dynamically import the Vue component using import
|
||||
const { default: page } = await import(`./Pages/${name}.vue`);
|
||||
// const page = require(`./Pages/${name}.vue`).default;
|
||||
|
@ -44,12 +51,14 @@ createInertiaApp({
|
|||
// }
|
||||
return page;
|
||||
},
|
||||
// resolve: async (name) => {
|
||||
// const firstPath = `@/Pages/${name}.vue`;
|
||||
// const module = await import(firstPath);
|
||||
// return module.default || null;
|
||||
// resolve: (name) => {
|
||||
// return resolvePageComponent(
|
||||
// `./Pages/${name}.vue`,
|
||||
// import.meta.glob<DefineComponent>('./pages/**/*.vue'),
|
||||
// )
|
||||
// },
|
||||
setup({ el, App, props, plugin }) {
|
||||
|
||||
setup({ el, App, props, plugin} : SetupOptions) {
|
||||
createApp({ render: () => h(App, props) })
|
||||
.use(plugin)
|
||||
.use(pinia)
|
||||
|
@ -72,8 +81,9 @@ if ((!localStorage[darkModeKey] && window.matchMedia('(prefers-color-scheme: dar
|
|||
styleService.setDarkMode(true);
|
||||
}
|
||||
|
||||
|
||||
/* Collapse mobile aside menu on route change */
|
||||
Inertia.on('navigate', (event) => {
|
||||
Inertia.on('navigate', () => {
|
||||
layoutService.isAsideMobileExpanded = false;
|
||||
layoutService.isAsideLgActive = false;
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
export const basic = {
|
||||
const styles = {
|
||||
basic: {
|
||||
aside: 'bg-gray-800',
|
||||
asideScrollbars: 'aside-scrollbars-gray',
|
||||
asideBrand: 'bg-gray-900 text-white',
|
||||
|
@ -11,9 +12,9 @@ export const basic = {
|
|||
// navBarItemLabelActiveColor: 'text-blue-600',
|
||||
navBarItemLabelActiveColor: 'text-lime-dark',
|
||||
overlay: 'from-gray-700 via-gray-900 to-gray-700',
|
||||
};
|
||||
},
|
||||
|
||||
export const white = {
|
||||
white: {
|
||||
aside: 'bg-white',
|
||||
asideScrollbars: 'aside-scrollbars-light',
|
||||
asideBrand: '',
|
||||
|
@ -24,4 +25,6 @@ export const white = {
|
|||
navBarItemLabelHover: 'hover:text-black',
|
||||
navBarItemLabelActiveColor: 'text-black',
|
||||
overlay: 'from-white via-gray-100 to-white',
|
||||
},
|
||||
};
|
||||
export default styles;
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
// tsconfig.vue.json
|
||||
"extends": "@vue/tsconfig/tsconfig.json",
|
||||
"include": ["./resources/js/**/*"],
|
||||
|
||||
// "include": ["./resources/js/**/*"],
|
||||
"include": ["env.d.ts", "./**/*.ts", "./**/*.vue"],
|
||||
"compilerOptions": {
|
||||
// "module": "commonjs", //for tehys.api...alos nodenext
|
||||
// Process & infer types from .js files.
|
||||
|
@ -14,9 +14,13 @@
|
|||
"experimentalDecorators": true, //neu
|
||||
"strictPropertyInitialization": false //neu
|
||||
},
|
||||
// "paths": {
|
||||
// "App/*": ["./app/*"], // for App/modles/User
|
||||
// "@/*": ["./resources/js/*"]
|
||||
// },
|
||||
"paths": {
|
||||
"App/*": ["./app/*"], // for App/modles/User
|
||||
"@/*": ["./resources/js/*"]
|
||||
"@/*": ["./*"],
|
||||
"~/*": ["../*"]
|
||||
},
|
||||
"files": ["./index.d.ts"]
|
||||
// "files": ["./index.d.ts"]
|
||||
}
|
|
@ -1,9 +1,4 @@
|
|||
// import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
||||
// import { LucidModel } from '@adonisjs/lucid/types/model';
|
||||
import { Request, Response } from '@adonisjs/core/http';
|
||||
// import BaseModel from '#app/Models/BaseModel';
|
||||
// import { LucidModel } from '@adonisjs/lucid/types/model';
|
||||
// import BaseModel from '#app/Models/BaseModel';
|
||||
|
||||
Request.macro('wantsJSON', function (this: Request) {
|
||||
const firstType = this.types()[0];
|
||||
|
@ -22,11 +17,31 @@ declare module '@adonisjs/core/http' {
|
|||
|
||||
// By specifying this: Response in the function signature, you're explicitly stating
|
||||
// that 'this' within the function should be of type 'Response', which resolves the TypeScript error.
|
||||
Response.macro('flash', function (this: Response, key: string, message: any) {
|
||||
// Response.macro('flash', function (this: Response, message: any) {
|
||||
// if (!this.ctx) {
|
||||
// throw new Error('Context is not available');
|
||||
// }
|
||||
// this.ctx!.session.flash(message);
|
||||
// return this;
|
||||
// });
|
||||
// Response.macro('flash', function (this: Response, key: string, message: any) {
|
||||
// if (!this.ctx) {
|
||||
// throw new Error('Context is not available');
|
||||
// }
|
||||
// this.ctx!.session.flash(key, message);
|
||||
// return this;
|
||||
// });
|
||||
Response.macro('flash', function (this: Response, message: any, key?: string,) {
|
||||
if (!this.ctx) {
|
||||
throw new Error('Context is not available');
|
||||
}
|
||||
|
||||
if (key !== undefined) {
|
||||
this.ctx!.session.flash(key, message);
|
||||
} else {
|
||||
this.ctx!.session.flash(message);
|
||||
}
|
||||
|
||||
return this;
|
||||
});
|
||||
Response.macro('toRoute', function (this: Response, route: string) {
|
||||
|
@ -35,6 +50,7 @@ Response.macro('toRoute', function (this: Response, route: string) {
|
|||
});
|
||||
declare module '@adonisjs/core/http' {
|
||||
interface Response {
|
||||
flash(message: any): Response;
|
||||
flash(key: string, message: any): Response;
|
||||
toRoute(route: string): Response;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ export default await Env.create(new URL("../", import.meta.url), {
|
|||
APP_KEY: Env.schema.string(),
|
||||
APP_NAME: Env.schema.string(),
|
||||
CACHE_VIEWS: Env.schema.boolean(),
|
||||
SESSION_DRIVER: Env.schema.string(),
|
||||
SESSION_DRIVER: Env.schema.enum(["cookie" ,"memory"] as const),
|
||||
|
||||
DRIVE_DISK: Env.schema.enum(['local'] as const),
|
||||
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
|
||||
|
@ -31,6 +31,9 @@ export default await Env.create(new URL("../", import.meta.url), {
|
|||
PG_DB_NAME: Env.schema.string(),
|
||||
|
||||
REDIS_HOST: Env.schema.string({ format: 'host' }),
|
||||
REDIS_PORT: Env.schema.number()
|
||||
REDIS_PORT: Env.schema.number(),
|
||||
|
||||
HASH_DRIVER: Env.schema.enum(["scrypt", "argon", "bcrypt", "laravel", undefined] as const),
|
||||
OAI_LIST_SIZE: Env.schema.number()
|
||||
})
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ server.errorHandler(
|
|||
server.use([
|
||||
() => import('#middleware/container_bindings_middleware'),
|
||||
() => import('@adonisjs/static/static_middleware'),
|
||||
// () => import('@adonisjs/cors/cors_middleware'),
|
||||
() => import('@adonisjs/inertia/inertia_middleware'),
|
||||
])
|
||||
|
||||
|
@ -46,10 +47,8 @@ router.use([
|
|||
* the routes or the routes group.
|
||||
*/
|
||||
export const middleware = router.named({
|
||||
// guest: () => import('#middleware/guest_middleware'),
|
||||
// stardust: () => import('#middleware/stardust_middleware'),
|
||||
// guest: () => import('#middleware/guest_middleware'),
|
||||
guest: () => import('#middleware/guest_middleware'),
|
||||
auth: () => import('#middleware/auth_middleware'),
|
||||
is: () => import('#middleware/Role'),
|
||||
can: () => import('#middleware/Can'),
|
||||
is: () => import('#middleware/role_middleware'),
|
||||
can: () => import('#middleware/can_middleware'),
|
||||
})
|
||||
|
|
|
@ -29,7 +29,7 @@ import AuthValidator from '#app/Validators/AuthValidator';
|
|||
import User from '#app/Models/User';
|
||||
import AuthController from '#controllers/Http/Auth/AuthController';
|
||||
import UserController from '#controllers/Http/Auth/UserController';
|
||||
import AdminUserController from '#controllers/Http/Admin/UsersController';
|
||||
import AdminuserController from '#controllers/Http/Admin/AdminuserController';
|
||||
import RoleController from '#controllers/Http/Admin/RoleController';
|
||||
|
||||
import DatasetController from '#app/Controllers/Http/Submitter/DatasetController';
|
||||
|
@ -131,20 +131,20 @@ router.group(() => {
|
|||
.as('overview');
|
||||
|
||||
// user routes
|
||||
router.get('/user', [AdminUserController, 'index']).as('user.index').use(middleware.can(['user-list']));
|
||||
router.get('/user/create', [AdminUserController, 'create']).as('user.create').use(middleware.can(['user-create']));
|
||||
router.post('/user/store', [AdminUserController, 'store']).as('user.store').use(middleware.can(['user-create']));
|
||||
router.get('/user/:id', [AdminUserController, 'show']).as('user.show').where('id', router.matchers.number());
|
||||
router.get('/user/:id/edit', [AdminUserController, 'edit']).as('user.edit').where('id', router.matchers.number()).use(middleware.can(['user-edit']));
|
||||
router.put('/user/:id/update', [AdminUserController, 'update'])
|
||||
router.get('/user', [AdminuserController, 'index']).as('user.index').use(middleware.can(['user-list']));
|
||||
router.get('/user/create', [AdminuserController, 'create']).as('user.create').use(middleware.can(['user-create']));
|
||||
router.post('/user/store', [AdminuserController, 'store']).as('user.store').use(middleware.can(['user-create']));
|
||||
router.get('/user/:id', [AdminuserController, 'show']).as('user.show').where('id', router.matchers.number());
|
||||
router.get('/user/:id/edit', [AdminuserController, 'edit']).as('user.edit').where('id', router.matchers.number()).use(middleware.can(['user-edit']));
|
||||
router.put('/user/:id/update', [AdminuserController, 'update'])
|
||||
.as('user.update')
|
||||
.where('id', router.matchers.number())
|
||||
.use(middleware.can(['user-edit']));
|
||||
// // Route.delete('/user/:id', [AdminUserController, 'destroy'])
|
||||
// // Route.delete('/user/:id', [AdminuserController, 'destroy'])
|
||||
// // .as('user.destroy')
|
||||
// // .where('id', Route.matchers.number())
|
||||
// // .use(middleware.can(['user-delete']));
|
||||
// // Route.resource('user', 'AdminUserController');
|
||||
// // Route.resource('user', 'AdminuserController');
|
||||
|
||||
router.get('/role', [RoleController, 'index']).as('role.index').use(middleware.can(['user-list']));
|
||||
router.get('/role/create', [RoleController, 'create']).as('role.create').use(middleware.can(['user-create']));
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
{
|
||||
"extends": "@adonisjs/tsconfig/tsconfig.app.json",
|
||||
"include": [
|
||||
"**/*"
|
||||
"**/*",
|
||||
// "resources/**/*.ts",
|
||||
// "resources/**/*.d.ts",
|
||||
// "resources/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build",
|
||||
"public",
|
||||
"resources"
|
||||
// "resources/js/**/*",
|
||||
],
|
||||
// "exclude": ["./inertia/**/*"]
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "build",
|
||||
|
@ -21,8 +25,9 @@
|
|||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./resources/js/*"
|
||||
"@/*": ["./resources/js/*.js"],
|
||||
"#resources/*": [
|
||||
"./resources/js/*.js"
|
||||
],
|
||||
"#controllers/*": [
|
||||
"./app/controllers/*.js"
|
||||
|
|
|
@ -96,7 +96,7 @@ Encore.setPublicPath('/assets');
|
|||
| entrypoints.
|
||||
|
|
||||
*/
|
||||
Encore.addEntry('app', './resources/js/app.js');
|
||||
Encore.addEntry('app', './resources/js/app.ts');
|
||||
|
||||
|
||||
/*
|
||||
|
@ -305,7 +305,7 @@ Encore.addLoader({
|
|||
Encore.addLoader(babelLoader)
|
||||
// Encore.enableTypeScriptLoader(config => {
|
||||
// // Loader-specific options
|
||||
// config.configFile = 'tsconfig.vue.json';
|
||||
// config.configFile = 'resources/js/tsconfig.vue.json';
|
||||
// config.appendTsSuffixTo = [/\.vue$/];
|
||||
// config.transpileOnly = true;
|
||||
// config.happyPackMode = false;
|
||||
|
@ -317,11 +317,11 @@ Encore.addLoader(babelLoader)
|
|||
.addAliases({
|
||||
'@': join(__dirname, 'resources/js'),
|
||||
'vue$': 'vue/dist/vue.runtime.esm-bundler.js',
|
||||
})
|
||||
.configureDefinePlugin((options) => {
|
||||
options['__VUE_OPTIONS_API__'] = true;
|
||||
options['__VUE_PROD_DEVTOOLS__'] = false;
|
||||
});
|
||||
// .configureDefinePlugin((options) => {
|
||||
// options['__VUE_OPTIONS_API__'] = true;
|
||||
// options['__VUE_PROD_DEVTOOLS__'] = false;
|
||||
// });
|
||||
|
||||
// Encore.addAliases({
|
||||
// '@': resolve(__dirname, 'resources/js')
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
const { join } = require("path");
|
||||
const Encore = require("@symfony/webpack-encore");
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
const dotenv = require('dotenv-webpack');
|
||||
|
||||
// Load the environment variables from the.env file
|
||||
Encore.addPlugin(
|
||||
new dotenv({
|
||||
path: ".env",
|
||||
defaults: ".env",
|
||||
systemvars: true,
|
||||
allowEmptyValues: true,
|
||||
})
|
||||
)
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encore runtime environment
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
if (!Encore.isRuntimeEnvironmentConfigured()) {
|
||||
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || "dev");
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Output path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The output path for writing the compiled files. It should always
|
||||
| be inside the public directory, so that AdonisJS can serve it.
|
||||
|
|
||||
*/
|
||||
Encore.setOutputPath("./public/assets");
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Public URI
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The public URI to access the static files. It should always be
|
||||
| relative from the "public" directory.
|
||||
|
|
||||
*/
|
||||
Encore.setPublicPath("/assets");
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Entrypoints
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Entrypoints are script files that boots your frontend application. Ideally
|
||||
| a single entrypoint is used by majority of applications. However, feel
|
||||
| free to add more (if required).
|
||||
|
|
||||
| Also, make sure to read the docs on "Assets bundler" to learn more about
|
||||
| entrypoints.
|
||||
|
|
||||
*/
|
||||
Encore.addEntry("app", "./resources/js/app.js");
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Copy assets
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Since the edge templates are not part of the Webpack compile lifecycle, any
|
||||
| images referenced by it will not be processed by Webpack automatically. Hence
|
||||
| we must copy them manually.
|
||||
|
|
||||
*/
|
||||
// Encore.copyFiles({
|
||||
// from: './resources/images',
|
||||
// to: 'images/[path][name].[hash:8].[ext]',
|
||||
// })
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Split shared code
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Instead of bundling duplicate code in all the bundles, generate a separate
|
||||
| bundle for the shared code.
|
||||
|
|
||||
| https://symfony.com/doc/current/frontend/encore/split-chunks.html
|
||||
| https://webpack.js.org/plugins/split-chunks-plugin/
|
||||
|
|
||||
*/
|
||||
// Encore.splitEntryChunks()
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Isolated entrypoints
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Treat each entry point and its dependencies as its own isolated module.
|
||||
|
|
||||
*/
|
||||
Encore.disableSingleRuntimeChunk();
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cleanup output folder
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| It is always nice to cleanup the build output before creating a build. It
|
||||
| will ensure that all unused files from the previous build are removed.
|
||||
|
|
||||
*/
|
||||
Encore.cleanupOutputBeforeBuild();
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Source maps
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Enable source maps in production
|
||||
|
|
||||
*/
|
||||
Encore.enableSourceMaps(!Encore.isProduction());
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Assets versioning
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Enable assets versioning to leverage lifetime browser and CDN cache
|
||||
|
|
||||
*/
|
||||
Encore.enableVersioning(Encore.isProduction());
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Configure dev server
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here we configure the dev server to enable live reloading for edge templates.
|
||||
| Remember edge templates are not processed by Webpack and hence we need
|
||||
| to watch them explicitly and livereload the browser.
|
||||
|
|
||||
*/
|
||||
Encore.configureDevServerOptions((options) => {
|
||||
/**
|
||||
* Normalize "options.static" property to an array
|
||||
*/
|
||||
if (!options.static) {
|
||||
options.static = [];
|
||||
} else if (!Array.isArray(options.static)) {
|
||||
options.static = [options.static];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable live reload and add views directory
|
||||
*/
|
||||
options.liveReload = true;
|
||||
options.static.push({
|
||||
directory: join(__dirname, "./resources/views"),
|
||||
watch: true,
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| CSS precompilers support
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Uncomment one of the following lines of code to enable support for your
|
||||
| favorite CSS precompiler
|
||||
|
|
||||
*/
|
||||
// Encore.enableSassLoader()
|
||||
// Encore.enableLessLoader()
|
||||
// Encore.enableStylusLoader()
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| CSS loaders
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Uncomment one of the following line of code to enable support for
|
||||
| PostCSS or CSS.
|
||||
|
|
||||
*/
|
||||
Encore.enablePostCssLoader()
|
||||
// Encore.configureCssLoader(() => {})
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Enable Vue loader
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Uncomment the following lines of code to enable support for vue. Also make
|
||||
| sure to install the required dependencies.
|
||||
|
|
||||
*/
|
||||
// Encore.enableVueLoader(() => {}, {
|
||||
// version: 3,
|
||||
// runtimeCompilerBuild: false,
|
||||
// useJsx: false
|
||||
// });
|
||||
|
||||
Encore.addLoader({
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
// loaders: {
|
||||
// ts: 'ts-loader',
|
||||
// },
|
||||
cacheDirectory: 'C:\\Users\\kaiarn\\Documents\\Software\\tethys.viewer\\node_modules\\.cache\\vue-loader',
|
||||
cacheIdentifier: 'f930df3e',
|
||||
babelParserPlugins: ['jsx', 'classProperties', 'decorators-legacy'],
|
||||
},
|
||||
}).addPlugin(new VueLoaderPlugin());
|
||||
|
||||
|
||||
Encore.enableTypeScriptLoader(config => {
|
||||
// Loader-specific options
|
||||
config.configFile = 'tsconfig.vue.json';
|
||||
config.appendTsSuffixTo = [/\.vue$/];
|
||||
config.transpileOnly = true;
|
||||
config.happyPackMode = false;
|
||||
}, {
|
||||
// Directly change the exclude rule
|
||||
exclude: /node_modules/,
|
||||
|
||||
})
|
||||
.addAliases({
|
||||
'@': join(__dirname, 'resources/js'),
|
||||
'vue$': 'vue/dist/vue.runtime.esm-bundler.js',
|
||||
})
|
||||
.configureDefinePlugin((options) => {
|
||||
options['__VUE_OPTIONS_API__'] = true;
|
||||
options['__VUE_PROD_DEVTOOLS__'] = false;
|
||||
})
|
||||
.addAliases({
|
||||
'@': join(__dirname, 'resources/js'),
|
||||
'vue$': 'vue/dist/vue.runtime.esm-bundler.js',
|
||||
})
|
||||
.configureDefinePlugin((options) => {
|
||||
options['__VUE_OPTIONS_API__'] = true;
|
||||
options['__VUE_PROD_DEVTOOLS__'] = false;
|
||||
});
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Configure logging
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| To keep the terminal clean from unnecessary info statements , we only
|
||||
| log warnings and errors. If you want all the logs, you can change
|
||||
| the level to "info".
|
||||
|
|
||||
*/
|
||||
const config = Encore.getWebpackConfig();
|
||||
config.infrastructureLogging = {
|
||||
level: "warn",
|
||||
};
|
||||
config.stats = "errors-warnings";
|
||||
config.resolve.extensions = ['.tsx', '.ts', '.mjs', '.js', '.jsx', '.vue', '.json', '.wasm'];
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Export config
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Export config for webpack to do its job
|
||||
|
|
||||
*/
|
||||
module.exports = config;
|
Loading…
Reference in New Issue
Block a user