2
0
Эх сурвалжийг харах

fix: assetById permissions + set min pg requirement to 16

NGPixel 2 өдөр өмнө
parent
commit
7a3d78bbac

+ 2 - 2
README.md

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

+ 3 - 3
server/graph/resolvers/analytics.mjs

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

+ 15 - 12
server/graph/resolvers/asset.mjs

@@ -1,7 +1,7 @@
 import _ from 'lodash-es'
 import sanitize from 'sanitize-filename'
 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 fs from 'fs-extra'
 import { v4 as uuid } from 'uuid'
@@ -9,10 +9,13 @@ import { pipeline } from 'node:stream/promises'
 
 export default {
   Query: {
-    async assetById(obj, args, context) {
-      // FIXME: Perm
-      const asset = await WIKI.db.assets.query().findById(args.id)
+    async assetById (obj, args, context) {
+      const asset = await WIKI.db.assets.query().findById(args.id).withGraphFetched('tree')
       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
       } else {
         throw new Error('ERR_ASSET_NOT_FOUND')
@@ -23,7 +26,7 @@ export default {
     /**
      * Rename an Asset
      */
-    async renameAsset(obj, args, context) {
+    async renameAsset (obj, args, context) {
       try {
         const filename = sanitize(args.fileName).toLowerCase()
 
@@ -50,13 +53,13 @@ export default {
           }
 
           // 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 })) {
             throw new Error('ERR_FORBIDDEN')
           }
 
           // 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 })) {
             throw new Error('ERR_TARGET_FORBIDDEN')
           }
@@ -102,12 +105,12 @@ export default {
     /**
      * Delete an Asset
      */
-    async deleteAsset(obj, args, context) {
+    async deleteAsset (obj, args, context) {
       try {
         const treeItem = await WIKI.db.tree.query().findById(args.id)
         if (treeItem) {
           // 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 })) {
             throw new Error('ERR_FORBIDDEN')
           }
@@ -144,7 +147,7 @@ export default {
     /**
      * Upload Assets
      */
-    async uploadAssets(obj, args, context) {
+    async uploadAssets (obj, args, context) {
       try {
         // FIXME: Perm
         // -> Get Folder
@@ -354,7 +357,7 @@ export default {
         const failedResults = results.filter(r => r.status === 'rejected')
         if (failedResults.length > 0) {
           // -> 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) {
             WIKI.logger.warn(failedResult.reason)
           }
@@ -380,7 +383,7 @@ export default {
     /**
      * Flush Temporary Uploads
      */
-    async flushTempUploads(obj, args, context) {
+    async flushTempUploads (obj, args, context) {
       try {
         if (!WIKI.auth.checkAccess(context.req.user, ['manage:system'])) {
           throw new Error('ERR_FORBIDDEN')

+ 4 - 6
server/graph/resolvers/tree.mjs

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

+ 0 - 20
server/helpers/common.mjs

@@ -50,26 +50,6 @@ export function encodeTreePath (str) {
   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
  *

+ 43 - 31
server/models/assets.mjs

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

+ 13 - 15
server/models/tree.mjs

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

+ 3 - 3
ux/package.json

@@ -112,18 +112,18 @@
     "@quasar/app-vite": "2.3.0",
     "@quasar/vite-plugin": "1.10.0",
     "@types/lodash": "4.17.20",
-    "@vue/devtools": "7.7.7",
     "@vue/language-plugin-pug": "3.0.4",
     "autoprefixer": "10.4.21",
     "browserlist": "latest",
     "eslint": "9.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-vue": "10.3.0",
     "eslint-plugin-vue-pug": "1.0.0-alpha.3",
     "neostandard": "0.12.2",
-    "sass": "1.89.2"
+    "sass": "1.89.2",
+    "vite-plugin-vue-devtools": "8.0.0"
   },
   "engines": {
     "node": ">= 18.0",

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 198 - 245
ux/pnpm-lock.yaml


+ 3 - 1
ux/vite.config.js

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

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно