You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
5.3 KiB

  1. /* global WIKI */
  2. const Model = require('objection').Model
  3. const moment = require('moment')
  4. const path = require('path')
  5. const fs = require('fs-extra')
  6. const _ = require('lodash')
  7. const assetHelper = require('../helpers/asset')
  8. /**
  9. * Users model
  10. */
  11. module.exports = class Asset extends Model {
  12. static get tableName() { return 'assets' }
  13. static get jsonSchema () {
  14. return {
  15. type: 'object',
  16. properties: {
  17. id: {type: 'integer'},
  18. filename: {type: 'string'},
  19. hash: {type: 'string'},
  20. ext: {type: 'string'},
  21. kind: {type: 'string'},
  22. mime: {type: 'string'},
  23. fileSize: {type: 'integer'},
  24. metadata: {type: 'object'},
  25. createdAt: {type: 'string'},
  26. updatedAt: {type: 'string'}
  27. }
  28. }
  29. }
  30. static get relationMappings() {
  31. return {
  32. author: {
  33. relation: Model.BelongsToOneRelation,
  34. modelClass: require('./users'),
  35. join: {
  36. from: 'assets.authorId',
  37. to: 'users.id'
  38. }
  39. },
  40. folder: {
  41. relation: Model.BelongsToOneRelation,
  42. modelClass: require('./assetFolders'),
  43. join: {
  44. from: 'assets.folderId',
  45. to: 'assetFolders.id'
  46. }
  47. }
  48. }
  49. }
  50. async $beforeUpdate(opt, context) {
  51. await super.$beforeUpdate(opt, context)
  52. this.updatedAt = moment.utc().toISOString()
  53. }
  54. async $beforeInsert(context) {
  55. await super.$beforeInsert(context)
  56. this.createdAt = moment.utc().toISOString()
  57. this.updatedAt = moment.utc().toISOString()
  58. }
  59. async getAssetPath() {
  60. let hierarchy = []
  61. if (this.folderId) {
  62. hierarchy = await WIKI.models.assetFolders.getHierarchy(this.folderId)
  63. }
  64. return (this.folderId) ? hierarchy.map(h => h.slug).join('/') + `/${this.filename}` : this.filename
  65. }
  66. async deleteAssetCache() {
  67. await fs.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${this.hash}.dat`))
  68. }
  69. static async upload(opts) {
  70. const fileInfo = path.parse(opts.originalname)
  71. const fileHash = assetHelper.generateHash(opts.assetPath)
  72. // Check for existing asset
  73. let asset = await WIKI.models.assets.query().where({
  74. hash: fileHash,
  75. folderId: opts.folderId
  76. }).first()
  77. // Build Object
  78. let assetRow = {
  79. filename: opts.originalname,
  80. hash: fileHash,
  81. ext: fileInfo.ext,
  82. kind: _.startsWith(opts.mimetype, 'image/') ? 'image' : 'binary',
  83. mime: opts.mimetype,
  84. fileSize: opts.size,
  85. folderId: opts.folderId
  86. }
  87. // Save asset data
  88. try {
  89. const fileBuffer = await fs.readFile(opts.path)
  90. if (asset) {
  91. // Patch existing asset
  92. if (opts.mode === 'upload') {
  93. assetRow.authorId = opts.user.id
  94. }
  95. await WIKI.models.assets.query().patch(assetRow).findById(asset.id)
  96. await WIKI.models.knex('assetData').where({
  97. id: asset.id
  98. }).update({
  99. data: fileBuffer
  100. })
  101. } else {
  102. // Create asset entry
  103. assetRow.authorId = opts.user.id
  104. asset = await WIKI.models.assets.query().insert(assetRow)
  105. await WIKI.models.knex('assetData').insert({
  106. id: asset.id,
  107. data: fileBuffer
  108. })
  109. }
  110. // Move temp upload to cache
  111. if (opts.mode === 'upload') {
  112. await fs.move(opts.path, path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`), { overwrite: true })
  113. } else {
  114. await fs.copy(opts.path, path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`), { overwrite: true })
  115. }
  116. // Add to Storage
  117. if (!opts.skipStorage) {
  118. await WIKI.models.storage.assetEvent({
  119. event: 'uploaded',
  120. asset: {
  121. ...asset,
  122. path: await asset.getAssetPath(),
  123. data: fileBuffer,
  124. authorId: opts.user.id,
  125. authorName: opts.user.name,
  126. authorEmail: opts.user.email
  127. }
  128. })
  129. }
  130. } catch (err) {
  131. WIKI.logger.warn(err)
  132. }
  133. }
  134. static async getAsset(assetPath, res) {
  135. let assetExists = await WIKI.models.assets.getAssetFromCache(assetPath, res)
  136. if (!assetExists) {
  137. await WIKI.models.assets.getAssetFromDb(assetPath, res)
  138. }
  139. }
  140. static async getAssetFromCache(assetPath, res) {
  141. const fileHash = assetHelper.generateHash(assetPath)
  142. const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`)
  143. return new Promise((resolve, reject) => {
  144. res.type(path.extname(assetPath))
  145. res.sendFile(cachePath, { dotfiles: 'deny' }, err => {
  146. if (err) {
  147. resolve(false)
  148. } else {
  149. resolve(true)
  150. }
  151. })
  152. })
  153. }
  154. static async getAssetFromDb(assetPath, res) {
  155. const fileHash = assetHelper.generateHash(assetPath)
  156. const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`)
  157. const asset = await WIKI.models.assets.query().where('hash', fileHash).first()
  158. if (asset) {
  159. const assetData = await WIKI.models.knex('assetData').where('id', asset.id).first()
  160. res.type(asset.ext)
  161. res.send(assetData.data)
  162. await fs.outputFile(cachePath, assetData.data)
  163. } else {
  164. res.sendStatus(404)
  165. }
  166. }
  167. static async flushTempUploads() {
  168. return fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `uploads`))
  169. }
  170. }