fix: assetById permissions + set min pg requirement to 16

vega
NGPixel 2 months ago
parent c3f562b315
commit 7a3d78bbac
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -117,8 +117,8 @@ The server **dev** should already be available under **Servers**. If that's not
### Requirements ### Requirements
- PostgreSQL **12** or later *(**16** or later recommended)* - PostgreSQL **16** or later
- Node.js **20.x** or later - Node.js **24.x** or later
- [pnpm](https://pnpm.io/installation#using-corepack) - [pnpm](https://pnpm.io/installation#using-corepack)
### Usage ### Usage

@ -3,7 +3,7 @@ import { generateError, generateSuccess } from '../../helpers/graph.mjs'
export default { export default {
Query: { Query: {
async analyticsProviders(obj, args, context, info) { async analyticsProviders (obj, args, context, info) {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) { if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN') throw new Error('ERR_FORBIDDEN')
} }
@ -30,13 +30,13 @@ export default {
} }
}, },
Mutation: { Mutation: {
async updateAnalyticsProviders(obj, args, context) { async updateAnalyticsProviders (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) { if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN') throw new Error('ERR_FORBIDDEN')
} }
for (let str of args.providers) { for (const str of args.providers) {
await WIKI.db.analytics.query().patch({ await WIKI.db.analytics.query().patch({
isEnabled: str.isEnabled, isEnabled: str.isEnabled,
config: reduce(str.config, (result, value, key) => { config: reduce(str.config, (result, value, key) => {

@ -1,7 +1,7 @@
import _ from 'lodash-es' import _ from 'lodash-es'
import sanitize from 'sanitize-filename' import sanitize from 'sanitize-filename'
import { generateError, generateSuccess } from '../../helpers/graph.mjs' import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import { decodeFolderPath, decodeTreePath, generateHash } from '../../helpers/common.mjs' import { decodeTreePath, generateHash } from '../../helpers/common.mjs'
import path from 'node:path' import path from 'node:path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
@ -9,10 +9,13 @@ import { pipeline } from 'node:stream/promises'
export default { export default {
Query: { Query: {
async assetById(obj, args, context) { async assetById (obj, args, context) {
// FIXME: Perm const asset = await WIKI.db.assets.query().findById(args.id).withGraphFetched('tree')
const asset = await WIKI.db.assets.query().findById(args.id)
if (asset) { if (asset) {
const assetPath = asset.tree.folderPath ? `${decodeTreePath(asset.tree.folderPath)}/${asset.tree.fileName}` : asset.tree.fileName
if (!WIKI.auth.checkAccess(context.req.user, ['read:assets'], { path: assetPath })) {
throw new Error('ERR_FORBIDDEN')
}
return asset return asset
} else { } else {
throw new Error('ERR_ASSET_NOT_FOUND') throw new Error('ERR_ASSET_NOT_FOUND')
@ -23,7 +26,7 @@ export default {
/** /**
* Rename an Asset * Rename an Asset
*/ */
async renameAsset(obj, args, context) { async renameAsset (obj, args, context) {
try { try {
const filename = sanitize(args.fileName).toLowerCase() const filename = sanitize(args.fileName).toLowerCase()
@ -50,13 +53,13 @@ export default {
} }
// Check source asset permissions // Check source asset permissions
const assetSourcePath = (treeItem.folderPath) ? decodeTreePath(decodeFolderPath(treeItem.folderPath)) + `/${treeItem.fileName}` : treeItem.fileName const assetSourcePath = (treeItem.folderPath) ? decodeTreePath(treeItem.folderPath) + `/${treeItem.fileName}` : treeItem.fileName
if (!WIKI.auth.checkAccess(context.req.user, ['manage:assets'], { path: assetSourcePath })) { if (!WIKI.auth.checkAccess(context.req.user, ['manage:assets'], { path: assetSourcePath })) {
throw new Error('ERR_FORBIDDEN') throw new Error('ERR_FORBIDDEN')
} }
// Check target asset permissions // Check target asset permissions
const assetTargetPath = (treeItem.folderPath) ? decodeTreePath(decodeFolderPath(treeItem.folderPath)) + `/${filename}` : filename const assetTargetPath = (treeItem.folderPath) ? decodeTreePath(treeItem.folderPath) + `/${filename}` : filename
if (!WIKI.auth.checkAccess(context.req.user, ['write:assets'], { path: assetTargetPath })) { if (!WIKI.auth.checkAccess(context.req.user, ['write:assets'], { path: assetTargetPath })) {
throw new Error('ERR_TARGET_FORBIDDEN') throw new Error('ERR_TARGET_FORBIDDEN')
} }
@ -102,12 +105,12 @@ export default {
/** /**
* Delete an Asset * Delete an Asset
*/ */
async deleteAsset(obj, args, context) { async deleteAsset (obj, args, context) {
try { try {
const treeItem = await WIKI.db.tree.query().findById(args.id) const treeItem = await WIKI.db.tree.query().findById(args.id)
if (treeItem) { if (treeItem) {
// Check permissions // Check permissions
const assetPath = (treeItem.folderPath) ? decodeTreePath(decodeFolderPath(treeItem.folderPath)) + `/${treeItem.fileName}` : treeItem.fileName const assetPath = (treeItem.folderPath) ? decodeTreePath(treeItem.folderPath) + `/${treeItem.fileName}` : treeItem.fileName
if (!WIKI.auth.checkAccess(context.req.user, ['manage:assets'], { path: assetPath })) { if (!WIKI.auth.checkAccess(context.req.user, ['manage:assets'], { path: assetPath })) {
throw new Error('ERR_FORBIDDEN') throw new Error('ERR_FORBIDDEN')
} }
@ -144,7 +147,7 @@ export default {
/** /**
* Upload Assets * Upload Assets
*/ */
async uploadAssets(obj, args, context) { async uploadAssets (obj, args, context) {
try { try {
// FIXME: Perm // FIXME: Perm
// -> Get Folder // -> Get Folder
@ -354,7 +357,7 @@ export default {
const failedResults = results.filter(r => r.status === 'rejected') const failedResults = results.filter(r => r.status === 'rejected')
if (failedResults.length > 0) { if (failedResults.length > 0) {
// -> One or more thrown errors // -> One or more thrown errors
WIKI.logger.warn(`Failed to upload one or more assets:`) WIKI.logger.warn('Failed to upload one or more assets:')
for (const failedResult of failedResults) { for (const failedResult of failedResults) {
WIKI.logger.warn(failedResult.reason) WIKI.logger.warn(failedResult.reason)
} }
@ -380,7 +383,7 @@ export default {
/** /**
* Flush Temporary Uploads * Flush Temporary Uploads
*/ */
async flushTempUploads(obj, args, context) { async flushTempUploads (obj, args, context) {
try { try {
if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) { if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
throw new Error('ERR_FORBIDDEN') throw new Error('ERR_FORBIDDEN')

@ -1,7 +1,5 @@
import _ from 'lodash-es' import _ from 'lodash-es'
import { import {
decodeFolderPath,
encodeFolderPath,
decodeTreePath, decodeTreePath,
encodeTreePath encodeTreePath
} from '../../helpers/common.mjs' } from '../../helpers/common.mjs'
@ -50,12 +48,12 @@ export default {
if (args.parentId) { if (args.parentId) {
const parent = await WIKI.db.knex('tree').where('id', args.parentId).first() const parent = await WIKI.db.knex('tree').where('id', args.parentId).first()
if (parent) { if (parent) {
parentPath = (parent.folderPath ? `${decodeFolderPath(parent.folderPath)}.${parent.fileName}` : parent.fileName) parentPath = (parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName)
} }
} else if (args.parentPath) { } else if (args.parentPath) {
parentPath = encodeTreePath(args.parentPath) parentPath = encodeTreePath(args.parentPath)
} }
const folderPathCondition = parentPath ? `${encodeFolderPath(parentPath)}.${depthCondition}` : depthCondition const folderPathCondition = parentPath ? `${parentPath}.${depthCondition}` : depthCondition
// Fetch Items // Fetch Items
const items = await WIKI.db.knex('tree') const items = await WIKI.db.knex('tree')
@ -67,7 +65,7 @@ export default {
const parentPathParts = parentPath.split('.') const parentPathParts = parentPath.split('.')
for (let i = 0; i <= parentPathParts.length; i++) { for (let i = 0; i <= parentPathParts.length; i++) {
builder.orWhere({ builder.orWhere({
folderPath: encodeFolderPath(_.dropRight(parentPathParts, i).join('.')), folderPath: _.dropRight(parentPathParts, i).join('.'),
fileName: _.nth(parentPathParts, i * -1), fileName: _.nth(parentPathParts, i * -1),
type: 'folder' type: 'folder'
}) })
@ -103,7 +101,7 @@ export default {
id: item.id, id: item.id,
depth: item.depth, depth: item.depth,
type: item.type, type: item.type,
folderPath: decodeTreePath(decodeFolderPath(item.folderPath)), folderPath: decodeTreePath(item.folderPath),
fileName: item.fileName, fileName: item.fileName,
title: item.title, title: item.title,
tags: item.tags ?? [], tags: item.tags ?? [],

@ -50,26 +50,6 @@ export function encodeTreePath (str) {
return str?.toLowerCase()?.replaceAll('/', '.') || '' return str?.toLowerCase()?.replaceAll('/', '.') || ''
} }
/**
* Encode a folder path (to support legacy PostgresSQL ltree)
*
* @param {string} val String to encode
* @returns Encoded folder path
*/
export function encodeFolderPath (val) {
return WIKI.db.LEGACY ? val?.replaceAll('-', '_') : val
}
/**
* Decode a folder path (to support legacy PostgresSQL ltree)
*
* @param {string} val String to decode
* @returns Decoded folder path
*/
export function decodeFolderPath (val) {
return WIKI.db.LEGACY ? val?.replaceAll('_', '-') : val
}
/** /**
* Generate SHA-1 Hash of a string * Generate SHA-1 Hash of a string
* *

@ -4,34 +4,35 @@ import fse from 'fs-extra'
import { startsWith } from 'lodash-es' import { startsWith } from 'lodash-es'
import { generateHash } from '../helpers/common.mjs' import { generateHash } from '../helpers/common.mjs'
import { Tree } from './tree.mjs'
import { User } from './users.mjs' import { User } from './users.mjs'
/** /**
* Users model * Users model
*/ */
export class Asset extends Model { export class Asset extends Model {
static get tableName() { return 'assets' } static get tableName () { return 'assets' }
static get jsonSchema () { static get jsonSchema () {
return { return {
type: 'object', type: 'object',
properties: { properties: {
id: {type: 'string'}, id: { type: 'string' },
filename: {type: 'string'}, filename: { type: 'string' },
hash: {type: 'string'}, hash: { type: 'string' },
ext: {type: 'string'}, ext: { type: 'string' },
kind: {type: 'string'}, kind: { type: 'string' },
mime: {type: 'string'}, mime: { type: 'string' },
fileSize: {type: 'integer'}, fileSize: { type: 'integer' },
metadata: {type: 'object'}, metadata: { type: 'object' },
createdAt: {type: 'string'}, createdAt: { type: 'string' },
updatedAt: {type: 'string'} updatedAt: { type: 'string' }
} }
} }
} }
static get relationMappings() { static get relationMappings () {
return { return {
author: { author: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
@ -40,23 +41,32 @@ export class Asset extends Model {
from: 'assets.authorId', from: 'assets.authorId',
to: 'users.id' to: 'users.id'
} }
},
tree: {
relation: Model.HasOneRelation,
modelClass: Tree,
join: {
from: 'assets.id',
to: 'tree.id'
}
} }
} }
} }
async $beforeUpdate(opt, context) { async $beforeUpdate (opt, context) {
await super.$beforeUpdate(opt, context) await super.$beforeUpdate(opt, context)
this.updatedAt = new Date().toISOString() this.updatedAt = new Date().toISOString()
} }
async $beforeInsert(context) {
async $beforeInsert (context) {
await super.$beforeInsert(context) await super.$beforeInsert(context)
this.createdAt = new Date().toISOString() this.createdAt = new Date().toISOString()
this.updatedAt = new Date().toISOString() this.updatedAt = new Date().toISOString()
} }
async getAssetPath() { async getAssetPath () {
let hierarchy = [] let hierarchy = []
if (this.folderId) { if (this.folderId) {
hierarchy = await WIKI.db.assetFolders.getHierarchy(this.folderId) hierarchy = await WIKI.db.assetFolders.getHierarchy(this.folderId)
@ -64,11 +74,11 @@ export class Asset extends Model {
return (this.folderId) ? hierarchy.map(h => h.slug).join('/') + `/${this.filename}` : this.filename return (this.folderId) ? hierarchy.map(h => h.slug).join('/') + `/${this.filename}` : this.filename
} }
async deleteAssetCache() { async deleteAssetCache () {
await fse.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${this.hash}.dat`)) await fse.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${this.hash}.dat`))
} }
static async upload(opts) { static async upload (opts) {
const fileInfo = path.parse(opts.originalname) const fileInfo = path.parse(opts.originalname)
// Check for existing asset // Check for existing asset
@ -78,7 +88,7 @@ export class Asset extends Model {
}).first() }).first()
// Build Object // Build Object
let assetRow = { const assetRow = {
filename: opts.originalname, filename: opts.originalname,
ext: fileInfo.ext, ext: fileInfo.ext,
kind: startsWith(opts.mimetype, 'image/') ? 'image' : 'binary', kind: startsWith(opts.mimetype, 'image/') ? 'image' : 'binary',
@ -158,15 +168,17 @@ export class Asset extends Model {
return WIKI.db.tree.query() return WIKI.db.tree.query()
.select('tree.*', 'assets.preview', 'assets.previewState') .select('tree.*', 'assets.preview', 'assets.previewState')
.innerJoin('assets', 'tree.id', 'assets.id') .innerJoin('assets', 'tree.id', 'assets.id')
.where(id ? { 'tree.id': id } : { .where(id
'tree.hash': generateHash(path), ? { 'tree.id': id }
'tree.locale': locale, : {
'tree.siteId': siteId 'tree.hash': generateHash(path),
}) 'tree.locale': locale,
'tree.siteId': siteId
})
.first() .first()
} }
static async getAsset({ pathArgs, siteId }, res) { static async getAsset ({ pathArgs, siteId }, res) {
try { try {
const fileInfo = path.parse(pathArgs.path.toLowerCase()) const fileInfo = path.parse(pathArgs.path.toLowerCase())
const fileHash = generateHash(pathArgs.path) const fileHash = generateHash(pathArgs.path)
@ -185,7 +197,7 @@ export class Asset extends Model {
// } // }
await WIKI.db.assets.getAssetFromDb({ pathArgs, fileHash, cachePath, siteId }, res) await WIKI.db.assets.getAssetFromDb({ pathArgs, fileHash, cachePath, siteId }, res)
} catch (err) { } catch (err) {
if (err.code === `ECONNABORTED` || err.code === `EPIPE`) { if (err.code === 'ECONNABORTED' || err.code === 'EPIPE') {
return return
} }
WIKI.logger.error(err) WIKI.logger.error(err)
@ -193,7 +205,7 @@ export class Asset extends Model {
} }
} }
static async getAssetFromCache({ cachePath, extName }, res) { static async getAssetFromCache ({ cachePath, extName }, res) {
try { try {
await fse.access(cachePath, fse.constants.R_OK) await fse.access(cachePath, fse.constants.R_OK)
} catch (err) { } catch (err) {
@ -204,13 +216,13 @@ export class Asset extends Model {
return true return true
} }
static async getAssetFromStorage(assetPath, res) { static async getAssetFromStorage (assetPath, res) {
const localLocations = await WIKI.db.storage.getLocalLocations({ const localLocations = await WIKI.db.storage.getLocalLocations({
asset: { asset: {
path: assetPath path: assetPath
} }
}) })
for (let location of localLocations.filter(location => Boolean(location.path))) { for (const location of localLocations.filter(location => Boolean(location.path))) {
const assetExists = await WIKI.db.assets.getAssetFromCache(assetPath, location.path, res) const assetExists = await WIKI.db.assets.getAssetFromCache(assetPath, location.path, res)
if (assetExists) { if (assetExists) {
return true return true
@ -219,7 +231,7 @@ export class Asset extends Model {
return false return false
} }
static async getAssetFromDb({ pathArgs, fileHash, cachePath, siteId }, res) { static async getAssetFromDb ({ pathArgs, fileHash, cachePath, siteId }, res) {
const asset = await WIKI.db.knex('tree').where({ const asset = await WIKI.db.knex('tree').where({
siteId, siteId,
hash: fileHash hash: fileHash
@ -234,7 +246,7 @@ export class Asset extends Model {
} }
} }
static async flushTempUploads() { static async flushTempUploads () {
return fse.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `uploads`)) return fse.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'))
} }
} }

@ -1,9 +1,7 @@
import { Model } from 'objection' import { Model } from 'objection'
import { differenceWith, dropRight, last, nth } from 'lodash-es' import { differenceWith, dropRight, last, nth } from 'lodash-es'
import { import {
decodeFolderPath,
decodeTreePath, decodeTreePath,
encodeFolderPath,
encodeTreePath, encodeTreePath,
generateHash generateHash
} from '../helpers/common.mjs' } from '../helpers/common.mjs'
@ -85,7 +83,7 @@ export class Tree extends Model {
const parentPath = encodeTreePath(path) const parentPath = encodeTreePath(path)
const parentPathParts = parentPath.split('.') const parentPathParts = parentPath.split('.')
const parentFilter = { const parentFilter = {
folderPath: encodeFolderPath(dropRight(parentPathParts).join('.')), folderPath: dropRight(parentPathParts).join('.'),
fileName: last(parentPathParts) fileName: last(parentPathParts)
} }
const parent = await WIKI.db.knex('tree').where({ const parent = await WIKI.db.knex('tree').where({
@ -143,7 +141,7 @@ export class Tree extends Model {
const pageEntry = await WIKI.db.knex('tree').insert({ const pageEntry = await WIKI.db.knex('tree').insert({
id, id,
folderPath: encodeFolderPath(folderPath), folderPath,
fileName, fileName,
type: 'page', type: 'page',
title, title,
@ -191,7 +189,7 @@ export class Tree extends Model {
const assetEntry = await WIKI.db.knex('tree').insert({ const assetEntry = await WIKI.db.knex('tree').insert({
id, id,
folderPath: encodeFolderPath(folderPath), folderPath,
fileName, fileName,
type: 'asset', type: 'asset',
title, title,
@ -231,7 +229,7 @@ export class Tree extends Model {
WIKI.logger.debug(`Creating new folder ${pathName}...`) WIKI.logger.debug(`Creating new folder ${pathName}...`)
const parentPathParts = parentPath.split('.') const parentPathParts = parentPath.split('.')
const parentFilter = { const parentFilter = {
folderPath: encodeFolderPath(dropRight(parentPathParts).join('.')), folderPath: dropRight(parentPathParts).join('.'),
fileName: last(parentPathParts) fileName: last(parentPathParts)
} }
@ -242,7 +240,7 @@ export class Tree extends Model {
if (!parent) { if (!parent) {
throw new Error('ERR_FOLDER_PARENT_INVALID') throw new Error('ERR_FOLDER_PARENT_INVALID')
} }
parentPath = parent.folderPath ? `${decodeFolderPath(parent.folderPath)}.${parent.fileName}` : parent.fileName parentPath = parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName
} else if (parentPath) { } else if (parentPath) {
parent = await WIKI.db.knex('tree').where(parentFilter).first() parent = await WIKI.db.knex('tree').where(parentFilter).first()
} else { } else {
@ -253,7 +251,7 @@ export class Tree extends Model {
const existingFolder = await WIKI.db.knex('tree').select('id').where({ const existingFolder = await WIKI.db.knex('tree').select('id').where({
siteId, siteId,
locale, locale,
folderPath: encodeFolderPath(parentPath), folderPath: parentPath,
fileName: pathName, fileName: pathName,
type: 'folder' type: 'folder'
}).first() }).first()
@ -268,7 +266,7 @@ export class Tree extends Model {
const parentPathParts = parentPath.split('.') const parentPathParts = parentPath.split('.')
for (let i = 1; i <= parentPathParts.length; i++) { for (let i = 1; i <= parentPathParts.length; i++) {
const ancestor = { const ancestor = {
folderPath: encodeFolderPath(dropRight(parentPathParts, i).join('.')), folderPath: dropRight(parentPathParts, i).join('.'),
fileName: nth(parentPathParts, i * -1) fileName: nth(parentPathParts, i * -1)
} }
expectedAncestors.push(ancestor) expectedAncestors.push(ancestor)
@ -303,7 +301,7 @@ export class Tree extends Model {
// Create folder // Create folder
const fullPath = parentPath ? `${decodeTreePath(parentPath)}/${pathName}` : pathName const fullPath = parentPath ? `${decodeTreePath(parentPath)}/${pathName}` : pathName
const folder = await WIKI.db.knex('tree').insert({ const folder = await WIKI.db.knex('tree').insert({
folderPath: encodeFolderPath(parentPath), folderPath: parentPath,
fileName: pathName, fileName: pathName,
type: 'folder', type: 'folder',
title, title,
@ -372,8 +370,8 @@ export class Tree extends Model {
} }
// Build new paths // Build new paths
const oldFolderPath = encodeFolderPath(folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName) const oldFolderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
const newFolderPath = encodeFolderPath(folder.folderPath ? `${folder.folderPath}.${pathName}` : pathName) const newFolderPath = folder.folderPath ? `${folder.folderPath}.${pathName}` : pathName
// Update children nodes // Update children nodes
WIKI.logger.debug(`Updating parent path of children nodes from ${oldFolderPath} to ${newFolderPath} ...`) WIKI.logger.debug(`Updating parent path of children nodes from ${oldFolderPath} to ${newFolderPath} ...`)
@ -385,7 +383,7 @@ export class Tree extends Model {
}) })
// Rename the folder itself // Rename the folder itself
const fullPath = folder.folderPath ? `${decodeFolderPath(folder.folderPath)}/${pathName}` : pathName const fullPath = folder.folderPath ? `${folder.folderPath}/${pathName}` : pathName
await WIKI.db.knex('tree').where('id', folder.id).update({ await WIKI.db.knex('tree').where('id', folder.id).update({
fileName: pathName, fileName: pathName,
title, title,
@ -416,7 +414,7 @@ export class Tree extends Model {
WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`) WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`)
// Delete all children // Delete all children
const deletedNodes = await WIKI.db.knex('tree').where('folderPath', '<@', encodeFolderPath(folderPath)).del().returning(['id', 'type']) const deletedNodes = await WIKI.db.knex('tree').where('folderPath', '<@', folderPath).del().returning(['id', 'type'])
// Delete folders // Delete folders
const deletedFolders = deletedNodes.filter(n => n.type === 'folder').map(n => n.id) const deletedFolders = deletedNodes.filter(n => n.type === 'folder').map(n => n.id)
@ -447,7 +445,7 @@ export class Tree extends Model {
if (folder.folderPath) { if (folder.folderPath) {
const parentPathParts = folder.folderPath.split('.') const parentPathParts = folder.folderPath.split('.')
const parent = await WIKI.db.knex('tree').where({ const parent = await WIKI.db.knex('tree').where({
folderPath: encodeFolderPath(dropRight(parentPathParts).join('.')), folderPath: dropRight(parentPathParts).join('.'),
fileName: last(parentPathParts) fileName: last(parentPathParts)
}).first() }).first()
await WIKI.db.knex('tree').where('id', parent.id).update({ await WIKI.db.knex('tree').where('id', parent.id).update({

@ -112,18 +112,18 @@
"@quasar/app-vite": "2.3.0", "@quasar/app-vite": "2.3.0",
"@quasar/vite-plugin": "1.10.0", "@quasar/vite-plugin": "1.10.0",
"@types/lodash": "4.17.20", "@types/lodash": "4.17.20",
"@vue/devtools": "7.7.7",
"@vue/language-plugin-pug": "3.0.4", "@vue/language-plugin-pug": "3.0.4",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.21",
"browserlist": "latest", "browserlist": "latest",
"eslint": "9.32.0", "eslint": "9.32.0",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"eslint-plugin-n": "17.21.0", "eslint-plugin-n": "17.21.2",
"eslint-plugin-promise": "7.2.1", "eslint-plugin-promise": "7.2.1",
"eslint-plugin-vue": "10.3.0", "eslint-plugin-vue": "10.3.0",
"eslint-plugin-vue-pug": "1.0.0-alpha.3", "eslint-plugin-vue-pug": "1.0.0-alpha.3",
"neostandard": "0.12.2", "neostandard": "0.12.2",
"sass": "1.89.2" "sass": "1.89.2",
"vite-plugin-vue-devtools": "8.0.0"
}, },
"engines": { "engines": {
"node": ">= 18.0", "node": ">= 18.0",

File diff suppressed because it is too large Load Diff

@ -4,6 +4,7 @@ import yaml from 'js-yaml'
import fs from 'node:fs' import fs from 'node:fs'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { quasar, transformAssetUrls } from '@quasar/vite-plugin' import { quasar, transformAssetUrls } from '@quasar/vite-plugin'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
@ -55,7 +56,8 @@ export default defineConfig(({ mode }) => {
quasar({ quasar({
autoImportComponentCase: 'kebab', autoImportComponentCase: 'kebab',
sassVariables: '@/css/_theme.scss' sassVariables: '@/css/_theme.scss'
}) }),
vueDevTools()
], ],
resolve: { resolve: {
alias: { alias: {

Loading…
Cancel
Save