- added own provider for drive methods
Some checks failed
CI Pipeline / japa-tests (push) Failing after 1m13s

- 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:
Kaimbacher 2024-04-23 19:36:45 +02:00
parent cb51a4136f
commit 296c8fd46e
67 changed files with 2515 additions and 1913 deletions

View File

@ -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"
]
}

View File

@ -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

View File

@ -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: [
{

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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;

View File

@ -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';
@ -428,7 +429,7 @@ export default class DatasetController {
}
// save data files
const uploadedFiles: MultipartFile[] = request.files('files');
const uploadedFiles: MultipartFile[] = request.files('files');
for (const [index, file] of uploadedFiles.entries()) {
try {
await this.scanFileForViruses(file.tmpPath); //, 'gitea.lan', 3310);
@ -439,19 +440,16 @@ export default class DatasetController {
}
// clientName: 'Gehaltsschema.png'
// extname: 'png'
// fieldName: 'file'
// fieldName: 'file'
// const fileName = `file-${this.generateRandomString(32)}.${file.extname}`;
const fileName = this.generateFilename(file.extname as string);
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),
{
name: fileName,
overwrite: true, // overwrite in case of conflict
},
);
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 {
@ -477,9 +478,9 @@ export default class DatasetController {
const randomString3 = this.generateRandomString(4);
const randomString4 = this.generateRandomString(4);
const randomString5 = this.generateRandomString(12);
return `file-${randomString1}-${randomString2}-${randomString3}-${randomString4}-${randomString5}.${extension}`;
}
}
private async scanFileForViruses(filePath: string | undefined, host?: string, port?: number): Promise<void> {
// const clamscan = await (new ClamScan().init());
@ -660,7 +661,7 @@ export default class DatasetController {
});
try {
await request.validate({
await request.validate({
schema: newSchema,
// reporter: validator.reporters.vanilla,
});
@ -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();

View File

@ -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

View File

@ -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

View File

@ -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
View 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 };

View File

@ -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 {

View File

@ -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"')
}
}
async run() {
this.logger.info('Hello world from "IndexTest"');
}
}

View File

@ -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

View File

@ -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';

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -11,8 +11,7 @@ export type PbkdfConfig = {
rounds: number;
saltSize?: number;
version?: number;
}
};
const saltRounds = 10;
export class LaravelDriver implements HashDriverContract {
@ -24,8 +23,8 @@ export class LaravelDriver implements HashDriverContract {
this.config = {
rounds: 10,
saltSize: 16,
version: 98,
...config
version: 98,
...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);
};
}

View 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);
}
}
}

View 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;
}
}

View 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)));
}
}

View 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() {}
}

View 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);
}
}

View 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;
}
}

View File

@ -0,0 +1,5 @@
import { DriveConfig } from "./drive.js";
export function defineConfig(config: DriveConfig): DriveConfig {
return config;
}

View 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];
};
};

View File

@ -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"
}

View File

@ -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"
>
Previous
<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"
>
Next
<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,33 +97,24 @@ 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"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<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"
/>
</svg>
aria-label="Previous">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<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" />
</svg>
</Link>
<!-- <template v-for="(link, key) in data.links">
@ -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"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<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"
/>
</svg>
aria-label="Next">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<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" />
</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>

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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>

View File

@ -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];

View File

@ -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: {
@ -205,4 +199,4 @@ const showAbout = async () => {
</div>
</div>
</nav>
</template>
</template>

View File

@ -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';

View File

@ -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';
@ -48,4 +48,4 @@ onBeforeUnmount(() => {
<slot name="dropdown" />
</div>
</NavBarItem>
</template>
</template>

View File

@ -1,5 +1,5 @@
<script setup>
import { StyleService } from '@/Stores/style';
import { StyleService } from '@/Stores/style.service';
defineProps({
zIndex: {
@ -42,4 +42,4 @@ const styleStore = StyleService();
<slot />
</transition>
</div>
</template>
</template>

View File

@ -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({
@ -35,4 +35,4 @@ const colorClass = computed(() => {
<div class="mx-auto md:h-screen flex flex-col justify-center items-center px-6 pt-8 pt:mt-0" :class="colorClass">
<slot card-class="w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12 shadow-2xl" />
</div>
</template>
</template>

View File

@ -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';
@ -204,4 +204,4 @@ const removeItem = (key) => {
tr:nth-child(od) {
background: white;
} */
</style>
</style>

View File

@ -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';
@ -223,4 +223,4 @@ const removeAuthor = (key) => {
<small>Page {{ currentPageHuman }} of {{ numPages }}</small>
</BaseLevel> -->
</div>
</template>
</template>

View File

@ -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';
@ -143,4 +143,4 @@ const checked = (isChecked, client) => {
<small>Page {{ currentPageHuman }} of {{ numPages }}</small>
</BaseLevel>
</div>
</template>
</template>

View File

@ -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';
@ -42,4 +42,4 @@ const props = defineProps({
</div>
</div>
<NotificationToast></NotificationToast>
</template>
</template>

View File

@ -1,5 +1,5 @@
<script setup>
import { StyleService } from '@/Stores/style';
import { StyleService } from '@/Stores/style.service';
const styleService = StyleService();
</script>

View File

@ -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';

View File

@ -1,6 +1,6 @@
<script setup>
import { ref, computed } from 'vue';
import { StyleService } from '@/Stores/style';
import { StyleService } from '@/Stores/style.service';
import {
mdiContrastCircle,

View 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>

View 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>

View File

@ -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}`)

View File

@ -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);
},

View File

@ -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,41 +32,34 @@ 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');
}
},
},
});
});

View File

@ -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';
@ -23,7 +30,7 @@ createInertiaApp({
progress: {
// color: '#4B5563',
color: '#22C55E',
},
},
// Webpack
// resolve: (name) => require(`./Pages/${name}`),
// resolve: (name) => require(`./Pages/${name}.vue`),
@ -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;
// },
setup({ el, App, props, plugin }) {
// resolve: (name) => {
// return resolvePageComponent(
// `./Pages/${name}.vue`,
// import.meta.glob<DefineComponent>('./pages/**/*.vue'),
// )
// },
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;
});

View File

@ -1,27 +1,30 @@
export const basic = {
aside: 'bg-gray-800',
asideScrollbars: 'aside-scrollbars-gray',
asideBrand: 'bg-gray-900 text-white',
asideMenuItem: 'text-gray-300 hover:text-white',
asideMenuItemActive: 'font-bold text-cyan-300',
asideMenuDropdown: 'bg-gray-700/50',
navBarItemLabel: 'text-black',
// navBarItemLabelHover: 'hover:text-blue-500',
navBarItemLabelHover: 'hover:text-lime-dark',
// navBarItemLabelActiveColor: 'text-blue-600',
navBarItemLabelActiveColor: 'text-lime-dark',
overlay: 'from-gray-700 via-gray-900 to-gray-700',
};
const styles = {
basic: {
aside: 'bg-gray-800',
asideScrollbars: 'aside-scrollbars-gray',
asideBrand: 'bg-gray-900 text-white',
asideMenuItem: 'text-gray-300 hover:text-white',
asideMenuItemActive: 'font-bold text-cyan-300',
asideMenuDropdown: 'bg-gray-700/50',
navBarItemLabel: 'text-black',
// navBarItemLabelHover: 'hover:text-blue-500',
navBarItemLabelHover: 'hover:text-lime-dark',
// navBarItemLabelActiveColor: 'text-blue-600',
navBarItemLabelActiveColor: 'text-lime-dark',
overlay: 'from-gray-700 via-gray-900 to-gray-700',
},
export const white = {
aside: 'bg-white',
asideScrollbars: 'aside-scrollbars-light',
asideBrand: '',
asideMenuItem: 'text-blue-600 hover:text-black dark:text-white',
asideMenuItemActive: 'font-bold text-black dark:text-white',
asideMenuDropdown: 'bg-gray-100/75',
navBarItemLabel: 'text-blue-600',
navBarItemLabelHover: 'hover:text-black',
navBarItemLabelActiveColor: 'text-black',
overlay: 'from-white via-gray-100 to-white',
white: {
aside: 'bg-white',
asideScrollbars: 'aside-scrollbars-light',
asideBrand: '',
asideMenuItem: 'text-blue-600 hover:text-black dark:text-white',
asideMenuItemActive: 'font-bold text-black dark:text-white',
asideMenuDropdown: 'bg-gray-100/75',
navBarItemLabel: 'text-blue-600',
navBarItemLabelHover: 'hover:text-black',
navBarItemLabelActiveColor: 'text-black',
overlay: 'from-white via-gray-100 to-white',
},
};
export default styles;

View File

@ -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"]
}

View File

@ -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');
}
this.ctx!.session.flash(key, message);
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;
}

View File

@ -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()
})

View File

@ -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'),
})

View File

@ -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']));

View File

@ -1,14 +1,18 @@
{
"extends": "@adonisjs/tsconfig/tsconfig.app.json",
"include": [
"**/*"
"**/*",
// "resources/**/*.ts",
// "resources/**/*.d.ts",
// "resources/**/*.vue"
],
"exclude": [
"node_modules",
"build",
"public",
"resources"
"public",
// "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"

View File

@ -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')

View File

@ -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;