storage.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. const fs = require('fs-extra')
  2. const path = require('path')
  3. const tar = require('tar-fs')
  4. const zlib = require('zlib')
  5. const stream = require('stream')
  6. const _ = require('lodash')
  7. const Promise = require('bluebird')
  8. const pipeline = Promise.promisify(stream.pipeline)
  9. const klaw = require('klaw')
  10. const pageHelper = require('../../../helpers/page.js')
  11. const moment = require('moment')
  12. /* global WIKI */
  13. module.exports = {
  14. async activated() {
  15. // not used
  16. },
  17. async deactivated() {
  18. // not used
  19. },
  20. async init() {
  21. WIKI.logger.info('(STORAGE/DISK) Initializing...')
  22. await fs.ensureDir(this.config.path)
  23. WIKI.logger.info('(STORAGE/DISK) Initialization completed.')
  24. },
  25. async sync({ manual } = { manual: false }) {
  26. if (this.config.createDailyBackups || manual) {
  27. const dirPath = path.join(this.config.path, manual ? '_manual' : '_daily')
  28. await fs.ensureDir(dirPath)
  29. const dateFilename = moment().format(manual ? 'YYYYMMDD-HHmmss' : 'DD')
  30. WIKI.logger.info(`(STORAGE/DISK) Creating backup archive...`)
  31. await pipeline(
  32. tar.pack(this.config.path, {
  33. ignore: (filePath) => {
  34. return filePath.indexOf('_daily') >= 0 || filePath.indexOf('_manual') >= 0
  35. }
  36. }),
  37. zlib.createGzip(),
  38. fs.createWriteStream(path.join(dirPath, `wiki-${dateFilename}.tar.gz`))
  39. )
  40. WIKI.logger.info('(STORAGE/DISK) Backup archive created successfully.')
  41. }
  42. },
  43. async created(page) {
  44. WIKI.logger.info(`(STORAGE/DISK) Creating file ${page.path}...`)
  45. let fileName = `${page.path}.${pageHelper.getFileExtension(page.contentType)}`
  46. if (WIKI.config.lang.code !== page.localeCode) {
  47. fileName = `${page.localeCode}/${fileName}`
  48. }
  49. const filePath = path.join(this.config.path, fileName)
  50. await fs.outputFile(filePath, page.injectMetadata(), 'utf8')
  51. },
  52. async updated(page) {
  53. WIKI.logger.info(`(STORAGE/DISK) Updating file ${page.path}...`)
  54. let fileName = `${page.path}.${pageHelper.getFileExtension(page.contentType)}`
  55. if (WIKI.config.lang.code !== page.localeCode) {
  56. fileName = `${page.localeCode}/${fileName}`
  57. }
  58. const filePath = path.join(this.config.path, fileName)
  59. await fs.outputFile(filePath, page.injectMetadata(), 'utf8')
  60. },
  61. async deleted(page) {
  62. WIKI.logger.info(`(STORAGE/DISK) Deleting file ${page.path}...`)
  63. let fileName = `${page.path}.${pageHelper.getFileExtension(page.contentType)}`
  64. if (WIKI.config.lang.code !== page.localeCode) {
  65. fileName = `${page.localeCode}/${fileName}`
  66. }
  67. const filePath = path.join(this.config.path, fileName)
  68. await fs.unlink(filePath)
  69. },
  70. async renamed(page) {
  71. WIKI.logger.info(`(STORAGE/DISK) Renaming file ${page.path} to ${page.destinationPath}...`)
  72. let sourceFilePath = `${page.path}.${pageHelper.getFileExtension(page.contentType)}`
  73. let destinationFilePath = `${page.destinationPath}.${pageHelper.getFileExtension(page.contentType)}`
  74. if (WIKI.config.lang.namespacing) {
  75. if (WIKI.config.lang.code !== page.localeCode) {
  76. sourceFilePath = `${page.localeCode}/${sourceFilePath}`
  77. }
  78. if (WIKI.config.lang.code !== page.destinationLocaleCode) {
  79. destinationFilePath = `${page.destinationLocaleCode}/${destinationFilePath}`
  80. }
  81. }
  82. await fs.move(path.join(this.config.path, sourceFilePath), path.join(this.config.path, destinationFilePath), { overwrite: true })
  83. },
  84. /**
  85. * HANDLERS
  86. */
  87. async dump() {
  88. WIKI.logger.info(`(STORAGE/DISK) Dumping all content to disk...`)
  89. await pipeline(
  90. WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt').select().from('pages').where({
  91. isPrivate: false
  92. }).stream(),
  93. new stream.Transform({
  94. objectMode: true,
  95. transform: async (page, enc, cb) => {
  96. let fileName = `${page.path}.${pageHelper.getFileExtension(page.contentType)}`
  97. if (WIKI.config.lang.code !== page.localeCode) {
  98. fileName = `${page.localeCode}/${fileName}`
  99. }
  100. WIKI.logger.info(`(STORAGE/DISK) Dumping ${fileName}...`)
  101. const filePath = path.join(this.config.path, fileName)
  102. await fs.outputFile(filePath, pageHelper.injectPageMetadata(page), 'utf8')
  103. cb()
  104. }
  105. })
  106. )
  107. WIKI.logger.info('(STORAGE/DISK) All content was dumped to disk successfully.')
  108. },
  109. async backup() {
  110. return this.sync({ manual: true })
  111. },
  112. async importAll() {
  113. WIKI.logger.info(`(STORAGE/DISK) Importing all content from local disk folder to the DB...`)
  114. const rootUser = await WIKI.models.users.getRootUser()
  115. await pipeline(
  116. klaw(this.config.path, {
  117. filter: (f) => {
  118. return !_.includes(f, '.git')
  119. }
  120. }),
  121. new stream.Transform({
  122. objectMode: true,
  123. transform: async (file, enc, cb) => {
  124. const relPath = file.path.substr(this.config.path.length + 1)
  125. if (relPath && relPath.length > 3) {
  126. WIKI.logger.info(`(STORAGE/DISK) Processing ${relPath}...`)
  127. const contentType = pageHelper.getContentType(relPath)
  128. if (!contentType) {
  129. return cb()
  130. }
  131. const contentPath = pageHelper.getPagePath(relPath)
  132. let itemContents = ''
  133. try {
  134. itemContents = await fs.readFile(path.join(this.config.path, relPath), 'utf8')
  135. const pageData = WIKI.models.pages.parseMetadata(itemContents, contentType)
  136. const currentPage = await WIKI.models.pages.query().findOne({
  137. path: contentPath.path,
  138. localeCode: contentPath.locale
  139. })
  140. if (currentPage) {
  141. // Already in the DB, can mark as modified
  142. WIKI.logger.info(`(STORAGE/DISK) Page marked as modified: ${relPath}`)
  143. await WIKI.models.pages.updatePage({
  144. id: currentPage.id,
  145. title: _.get(pageData, 'title', currentPage.title),
  146. description: _.get(pageData, 'description', currentPage.description) || '',
  147. isPublished: _.get(pageData, 'isPublished', currentPage.isPublished),
  148. isPrivate: false,
  149. content: pageData.content,
  150. user: rootUser,
  151. skipStorage: true
  152. })
  153. } else {
  154. // Not in the DB, can mark as new
  155. WIKI.logger.info(`(STORAGE/DISK) Page marked as new: ${relPath}`)
  156. const pageEditor = await WIKI.models.editors.getDefaultEditor(contentType)
  157. await WIKI.models.pages.createPage({
  158. path: contentPath.path,
  159. locale: contentPath.locale,
  160. title: _.get(pageData, 'title', _.last(contentPath.path.split('/'))),
  161. description: _.get(pageData, 'description', '') || '',
  162. isPublished: _.get(pageData, 'isPublished', true),
  163. isPrivate: false,
  164. content: pageData.content,
  165. user: rootUser,
  166. editor: pageEditor,
  167. skipStorage: true
  168. })
  169. }
  170. } catch (err) {
  171. WIKI.logger.warn(`(STORAGE/DISK) Failed to process ${relPath}`)
  172. WIKI.logger.warn(err)
  173. }
  174. }
  175. cb()
  176. }
  177. })
  178. )
  179. WIKI.logger.info('(STORAGE/DISK) Import completed.')
  180. }
  181. }