123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- import { Model } from 'objection'
- import path from 'node:path'
- import fse from 'fs-extra'
- import { startsWith } from 'lodash-es'
- import { generateHash } from '../helpers/common.mjs'
- import { User } from './users.mjs'
- /**
- * Users model
- */
- export class Asset extends Model {
- 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'}
- }
- }
- }
- static get relationMappings() {
- return {
- author: {
- relation: Model.BelongsToOneRelation,
- modelClass: User,
- join: {
- from: 'assets.authorId',
- to: 'users.id'
- }
- }
- }
- }
- async $beforeUpdate(opt, context) {
- await super.$beforeUpdate(opt, context)
- this.updatedAt = new Date().toISOString()
- }
- async $beforeInsert(context) {
- await super.$beforeInsert(context)
- this.createdAt = new Date().toISOString()
- this.updatedAt = new Date().toISOString()
- }
- async getAssetPath() {
- let hierarchy = []
- if (this.folderId) {
- hierarchy = await WIKI.db.assetFolders.getHierarchy(this.folderId)
- }
- return (this.folderId) ? hierarchy.map(h => h.slug).join('/') + `/${this.filename}` : this.filename
- }
- async deleteAssetCache() {
- await fse.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${this.hash}.dat`))
- }
- static async upload(opts) {
- const fileInfo = path.parse(opts.originalname)
- // Check for existing asset
- let asset = await WIKI.db.assets.query().where({
- // hash: fileHash,
- folderId: opts.folderId
- }).first()
- // Build Object
- let assetRow = {
- filename: opts.originalname,
- ext: fileInfo.ext,
- kind: startsWith(opts.mimetype, 'image/') ? 'image' : 'binary',
- mime: opts.mimetype,
- fileSize: opts.size,
- folderId: opts.folderId
- }
- // Sanitize SVG contents
- if (
- WIKI.config.uploads.scanSVG &&
- (
- opts.mimetype.toLowerCase().startsWith('image/svg') ||
- fileInfo.ext.toLowerCase() === '.svg'
- )
- ) {
- const svgSanitizeJob = await WIKI.scheduler.registerJob({
- name: 'sanitize-svg',
- immediate: true,
- worker: true
- }, opts.path)
- await svgSanitizeJob.finished
- }
- // Save asset data
- try {
- const fileBuffer = await fse.readFile(opts.path)
- if (asset) {
- // Patch existing asset
- if (opts.mode === 'upload') {
- assetRow.authorId = opts.user.id
- }
- await WIKI.db.assets.query().patch(assetRow).findById(asset.id)
- await WIKI.db.knex('assetData').where({
- id: asset.id
- }).update({
- data: fileBuffer
- })
- } else {
- // Create asset entry
- assetRow.authorId = opts.user.id
- asset = await WIKI.db.assets.query().insert(assetRow)
- await WIKI.db.knex('assetData').insert({
- id: asset.id,
- data: fileBuffer
- })
- }
- // Move temp upload to cache
- // if (opts.mode === 'upload') {
- // await fs.move(opts.path, path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`), { overwrite: true })
- // } else {
- // await fs.copy(opts.path, path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`), { overwrite: true })
- // }
- // Add to Storage
- if (!opts.skipStorage) {
- await WIKI.db.storage.assetEvent({
- event: 'uploaded',
- asset: {
- ...asset,
- path: await asset.getAssetPath(),
- data: fileBuffer,
- authorId: opts.user.id,
- authorName: opts.user.name,
- authorEmail: opts.user.email
- }
- })
- }
- } catch (err) {
- WIKI.logger.warn(err)
- }
- }
- static async getThumbnail ({ id, path, locale, siteId }) {
- 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
- })
- .first()
- }
- static async getAsset({ pathArgs, siteId }, res) {
- try {
- const fileInfo = path.parse(pathArgs.path.toLowerCase())
- const fileHash = generateHash(pathArgs.path)
- const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${siteId}/${fileHash}.dat`)
- // Force unsafe extensions to download
- if (WIKI.config.security.forceAssetDownload && !['.png', '.apng', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'].includes(fileInfo.ext)) {
- res.set('Content-disposition', 'attachment; filename=' + encodeURIComponent(fileInfo.base))
- }
- if (await WIKI.db.assets.getAssetFromCache({ cachePath, extName: fileInfo.ext }, res)) {
- return
- }
- // if (await WIKI.db.assets.getAssetFromStorage(assetPath, res)) {
- // return
- // }
- await WIKI.db.assets.getAssetFromDb({ pathArgs, fileHash, cachePath, siteId }, res)
- } catch (err) {
- if (err.code === `ECONNABORTED` || err.code === `EPIPE`) {
- return
- }
- WIKI.logger.error(err)
- res.sendStatus(500)
- }
- }
- static async getAssetFromCache({ cachePath, extName }, res) {
- try {
- await fse.access(cachePath, fse.constants.R_OK)
- } catch (err) {
- return false
- }
- res.type(extName)
- await new Promise(resolve => res.sendFile(cachePath, { dotfiles: 'deny' }, resolve))
- return true
- }
- 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))) {
- const assetExists = await WIKI.db.assets.getAssetFromCache(assetPath, location.path, res)
- if (assetExists) {
- return true
- }
- }
- return false
- }
- static async getAssetFromDb({ pathArgs, fileHash, cachePath, siteId }, res) {
- const asset = await WIKI.db.knex('tree').where({
- siteId,
- hash: fileHash
- }).first()
- if (asset) {
- const assetData = await WIKI.db.knex('assets').where('id', asset.id).first()
- res.type(assetData.fileExt)
- res.send(assetData.data)
- await fse.outputFile(cachePath, assetData.data)
- } else {
- res.sendStatus(404)
- }
- }
- static async flushTempUploads() {
- return fse.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `uploads`))
- }
- }
|