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.

145 lines
5.3 KiB

5 years ago
5 years ago
5 years ago
5 years ago
  1. const S3 = require('aws-sdk/clients/s3')
  2. const stream = require('stream')
  3. const Promise = require('bluebird')
  4. const pipeline = Promise.promisify(stream.pipeline)
  5. const _ = require('lodash')
  6. const pageHelper = require('../../../helpers/page.js')
  7. /* global WIKI */
  8. /**
  9. * Deduce the file path given the `page` object and the object's key to the page's path.
  10. */
  11. const getFilePath = (page, pathKey) => {
  12. const fileName = `${page[pathKey]}.${pageHelper.getFileExtension(page.contentType)}`
  13. const withLocaleCode = WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode
  14. return withLocaleCode ? `${page.localeCode}/${fileName}` : fileName
  15. }
  16. /**
  17. * Can be used with S3 compatible storage.
  18. */
  19. module.exports = class S3CompatibleStorage {
  20. constructor(storageName) {
  21. this.storageName = storageName
  22. }
  23. async activated() {
  24. // not used
  25. }
  26. async deactivated() {
  27. // not used
  28. }
  29. async init() {
  30. WIKI.logger.info(`(STORAGE/${this.storageName}) Initializing...`)
  31. const { accessKeyId, secretAccessKey, region, bucket, endpoint } = this.config
  32. this.s3 = new S3({
  33. accessKeyId,
  34. secretAccessKey,
  35. region,
  36. endpoint,
  37. params: { Bucket: bucket },
  38. apiVersions: '2006-03-01'
  39. })
  40. // determine if a bucket exists and you have permission to access it
  41. await this.s3.headBucket().promise()
  42. WIKI.logger.info(`(STORAGE/${this.storageName}) Initialization completed.`)
  43. }
  44. async created(page) {
  45. WIKI.logger.info(`(STORAGE/${this.storageName}) Creating file ${page.path}...`)
  46. const filePath = getFilePath(page, 'path')
  47. await this.s3.putObject({ Key: filePath, Body: page.injectMetadata() }).promise()
  48. }
  49. async updated(page) {
  50. WIKI.logger.info(`(STORAGE/${this.storageName}) Updating file ${page.path}...`)
  51. const filePath = getFilePath(page, 'path')
  52. await this.s3.putObject({ Key: filePath, Body: page.injectMetadata() }).promise()
  53. }
  54. async deleted(page) {
  55. WIKI.logger.info(`(STORAGE/${this.storageName}) Deleting file ${page.path}...`)
  56. const filePath = getFilePath(page, 'path')
  57. await this.s3.deleteObject({ Key: filePath }).promise()
  58. }
  59. async renamed(page) {
  60. WIKI.logger.info(`(STORAGE/${this.storageName}) Renaming file ${page.path} to ${page.destinationPath}...`)
  61. let sourceFilePath = getFilePath(page, 'path')
  62. let destinationFilePath = getFilePath(page, 'destinationPath')
  63. if (WIKI.config.lang.namespacing) {
  64. if (WIKI.config.lang.code !== page.localeCode) {
  65. sourceFilePath = `${page.localeCode}/${sourceFilePath}`
  66. }
  67. if (WIKI.config.lang.code !== page.destinationLocaleCode) {
  68. destinationFilePath = `${page.destinationLocaleCode}/${destinationFilePath}`
  69. }
  70. }
  71. await this.s3.copyObject({ CopySource: sourceFilePath, Key: destinationFilePath }).promise()
  72. await this.s3.deleteObject({ Key: sourceFilePath }).promise()
  73. }
  74. /**
  75. * ASSET UPLOAD
  76. *
  77. * @param {Object} asset Asset to upload
  78. */
  79. async assetUploaded (asset) {
  80. WIKI.logger.info(`(STORAGE/${this.storageName}) Creating new file ${asset.path}...`)
  81. await this.s3.putObject({ Key: asset.path, Body: asset.data }).promise()
  82. }
  83. /**
  84. * ASSET DELETE
  85. *
  86. * @param {Object} asset Asset to delete
  87. */
  88. async assetDeleted (asset) {
  89. WIKI.logger.info(`(STORAGE/${this.storageName}) Deleting file ${asset.path}...`)
  90. await this.s3.deleteObject({ Key: asset.path }).promise()
  91. }
  92. /**
  93. * ASSET RENAME
  94. *
  95. * @param {Object} asset Asset to rename
  96. */
  97. async assetRenamed (asset) {
  98. WIKI.logger.info(`(STORAGE/${this.storageName}) Renaming file from ${asset.path} to ${asset.destinationPath}...`)
  99. await this.s3.copyObject({ CopySource: asset.path, Key: asset.destinationPath }).promise()
  100. await this.s3.deleteObject({ Key: asset.path }).promise()
  101. }
  102. /**
  103. * HANDLERS
  104. */
  105. async exportAll() {
  106. WIKI.logger.info(`(STORAGE/${this.storageName}) Exporting all content to the cloud provider...`)
  107. // -> Pages
  108. await pipeline(
  109. WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt').select().from('pages').where({
  110. isPrivate: false
  111. }).stream(),
  112. new stream.Transform({
  113. objectMode: true,
  114. transform: async (page, enc, cb) => {
  115. const filePath = getFilePath(page, 'path')
  116. WIKI.logger.info(`(STORAGE/${this.storageName}) Adding page ${filePath}...`)
  117. await this.s3.putObject({ Key: filePath, Body: pageHelper.injectPageMetadata(page) }).promise()
  118. cb()
  119. }
  120. })
  121. )
  122. // -> Assets
  123. const assetFolders = await WIKI.models.assetFolders.getAllPaths()
  124. await pipeline(
  125. WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),
  126. new stream.Transform({
  127. objectMode: true,
  128. transform: async (asset, enc, cb) => {
  129. const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename
  130. WIKI.logger.info(`(STORAGE/${this.storageName}) Adding asset ${filename}...`)
  131. await this.s3.putObject({ Key: filename, Body: asset.data }).promise()
  132. cb()
  133. }
  134. })
  135. )
  136. WIKI.logger.info(`(STORAGE/${this.storageName}) All content has been pushed to the cloud provider.`)
  137. }
  138. }