| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 | const _ = require('lodash')const cfgHelper = require('../helpers/config')const Promise = require('bluebird')const fs = require('fs-extra')const path = require('path')const zlib = require('zlib')const stream = require('stream')const pipeline = Promise.promisify(stream.pipeline)/* global WIKI */module.exports = {  updates: {    channel: 'BETA',    version: WIKI.version,    releaseDate: WIKI.releaseDate,    minimumVersionRequired: '2.0.0-beta.0',    minimumNodeRequired: '10.12.0'  },  exportStatus: {    status: 'notrunning',    progress: 0,    message: '',    updatedAt: null  },  init() {    // Clear content cache    fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))    return this  },  /**   * Upgrade from WIKI.js 1.x - MongoDB database   *   * @param {Object} opts Options object   */  async upgradeFromMongo (opts) {    WIKI.logger.info('Upgrading from MongoDB...')    let mongo = require('mongodb').MongoClient    let parsedMongoConStr = cfgHelper.parseConfigValue(opts.mongoCnStr)    return new Promise((resolve, reject) => {      // Connect to MongoDB      mongo.connect(parsedMongoConStr, {        autoReconnect: false,        reconnectTries: 2,        reconnectInterval: 1000,        connectTimeoutMS: 5000,        socketTimeoutMS: 5000      }, async (err, db) => {        try {          if (err !== null) { throw err }          let users = db.collection('users')          // Check if users table is populated          let userCount = await users.count()          if (userCount < 2) {            throw new Error('MongoDB Upgrade: Users table is empty!')          }          // Import all users          let userData = await users.find({            email: {              $not: 'guest'            }          }).toArray()          await WIKI.models.User.bulkCreate(_.map(userData, usr => {            return {              email: usr.email,              name: usr.name || 'Imported User',              password: usr.password || '',              provider: usr.provider || 'local',              providerId: usr.providerId || '',              role: 'user',              createdAt: usr.createdAt            }          }))          resolve(true)        } catch (errc) {          reject(errc)        }        db.close()      })    })  },  /**   * Export Wiki to Disk   */  async export (opts) {    this.exportStatus.status = 'running'    this.exportStatus.progress = 0    this.exportStatus.message = ''    this.exportStatus.startedAt = new Date()    WIKI.logger.info(`Export started to path ${opts.path}`)    WIKI.logger.info(`Entities to export: ${opts.entities.join(', ')}`)    const progressMultiplier = 1 / opts.entities.length    try {      for (const entity of opts.entities) {        switch (entity) {          // -----------------------------------------          // ASSETS          // -----------------------------------------          case 'assets': {            WIKI.logger.info('Exporting assets...')            const assetFolders = await WIKI.models.assetFolders.getAllPaths()            const assetsCountRaw = await WIKI.models.assets.query().count('* as total').first()            const assetsCount = parseInt(assetsCountRaw.total)            if (assetsCount < 1) {              WIKI.logger.warn('There are no assets to export! Skipping...')              break            }            const assetsProgressMultiplier = progressMultiplier / Math.ceil(assetsCount / 50)            WIKI.logger.info(`Found ${assetsCount} assets to export. Streaming to disk...`)            await pipeline(              WIKI.models.knex.select('filename', 'folderId', 'data').from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),              new stream.Transform({                objectMode: true,                transform: async (asset, enc, cb) => {                  const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename                  WIKI.logger.info(`Exporting asset ${filename}...`)                  await fs.outputFile(path.join(opts.path, 'assets', filename), asset.data)                  this.exportStatus.progress += assetsProgressMultiplier * 100                  cb()                }              })            )            WIKI.logger.info('Export: assets saved to disk successfully.')            break          }          // -----------------------------------------          // COMMENTS          // -----------------------------------------          case 'comments': {            WIKI.logger.info('Exporting comments...')            const outputPath = path.join(opts.path, 'comments.json.gz')            const commentsCountRaw = await WIKI.models.comments.query().count('* as total').first()            const commentsCount = parseInt(commentsCountRaw.total)            if (commentsCount < 1) {              WIKI.logger.warn('There are no comments to export! Skipping...')              break            }            const commentsProgressMultiplier = progressMultiplier / Math.ceil(commentsCount / 50)            WIKI.logger.info(`Found ${commentsCount} comments to export. Streaming to file...`)            const rs = stream.Readable({ objectMode: true })            rs._read = () => {}            const fetchCommentsBatch = async (offset) => {              const comments = await WIKI.models.comments.query().offset(offset).limit(50).withGraphJoined({                author: true,                page: true              }).modifyGraph('author', builder => {                builder.select('users.id', 'users.name', 'users.email', 'users.providerKey')              }).modifyGraph('page', builder => {                builder.select('pages.id', 'pages.path', 'pages.localeCode', 'pages.title')              })              if (comments.length > 0) {                for (const cmt of comments) {                  rs.push(cmt)                }                fetchCommentsBatch(offset + 50)              } else {                rs.push(null)              }              this.exportStatus.progress += commentsProgressMultiplier * 100            }            fetchCommentsBatch(0)            let marker = 0            await pipeline(              rs,              new stream.Transform({                objectMode: true,                transform (chunk, encoding, callback) {                  marker++                  let outputStr = marker === 1 ? '[\n' : ''                  outputStr += JSON.stringify(chunk, null, 2)                  if (marker < commentsCount) {                    outputStr += ',\n'                  }                  callback(null, outputStr)                },                flush (callback) {                  callback(null, '\n]')                }              }),              zlib.createGzip(),              fs.createWriteStream(outputPath)            )            WIKI.logger.info('Export: comments.json.gz created successfully.')            break          }          // -----------------------------------------          // GROUPS          // -----------------------------------------          case 'groups': {            WIKI.logger.info('Exporting groups...')            const outputPath = path.join(opts.path, 'groups.json')            const groups = await WIKI.models.groups.query()            await fs.outputJSON(outputPath, groups, { spaces: 2 })            WIKI.logger.info('Export: groups.json created successfully.')            this.exportStatus.progress += progressMultiplier * 100            break          }          // -----------------------------------------          // HISTORY          // -----------------------------------------          case 'history': {            WIKI.logger.info('Exporting pages history...')            const outputPath = path.join(opts.path, 'pages-history.json.gz')            const pagesCountRaw = await WIKI.models.pageHistory.query().count('* as total').first()            const pagesCount = parseInt(pagesCountRaw.total)            if (pagesCount < 1) {              WIKI.logger.warn('There are no pages history to export! Skipping...')              break            }            const pagesProgressMultiplier = progressMultiplier / Math.ceil(pagesCount / 10)            WIKI.logger.info(`Found ${pagesCount} pages history to export. Streaming to file...`)            const rs = stream.Readable({ objectMode: true })            rs._read = () => {}            const fetchPagesBatch = async (offset) => {              const pages = await WIKI.models.pageHistory.query().offset(offset).limit(10).withGraphJoined({                author: true,                page: true,                tags: true              }).modifyGraph('author', builder => {                builder.select('users.id', 'users.name', 'users.email', 'users.providerKey')              }).modifyGraph('page', builder => {                builder.select('pages.id', 'pages.title', 'pages.path', 'pages.localeCode')              }).modifyGraph('tags', builder => {                builder.select('tags.tag', 'tags.title')              })              if (pages.length > 0) {                for (const page of pages) {                  rs.push(page)                }                fetchPagesBatch(offset + 10)              } else {                rs.push(null)              }              this.exportStatus.progress += pagesProgressMultiplier * 100            }            fetchPagesBatch(0)            let marker = 0            await pipeline(              rs,              new stream.Transform({                objectMode: true,                transform (chunk, encoding, callback) {                  marker++                  let outputStr = marker === 1 ? '[\n' : ''                  outputStr += JSON.stringify(chunk, null, 2)                  if (marker < pagesCount) {                    outputStr += ',\n'                  }                  callback(null, outputStr)                },                flush (callback) {                  callback(null, '\n]')                }              }),              zlib.createGzip(),              fs.createWriteStream(outputPath)            )            WIKI.logger.info('Export: pages-history.json.gz created successfully.')            break          }          // -----------------------------------------          // NAVIGATION          // -----------------------------------------          case 'navigation': {            WIKI.logger.info('Exporting navigation...')            const outputPath = path.join(opts.path, 'navigation.json')            const navigationRaw = await WIKI.models.navigation.query()            const navigation = navigationRaw.reduce((obj, cur) => {              obj[cur.key] = cur.config              return obj            }, {})            await fs.outputJSON(outputPath, navigation, { spaces: 2 })            WIKI.logger.info('Export: navigation.json created successfully.')            this.exportStatus.progress += progressMultiplier * 100            break          }          // -----------------------------------------          // PAGES          // -----------------------------------------          case 'pages': {            WIKI.logger.info('Exporting pages...')            const outputPath = path.join(opts.path, 'pages.json.gz')            const pagesCountRaw = await WIKI.models.pages.query().count('* as total').first()            const pagesCount = parseInt(pagesCountRaw.total)            if (pagesCount < 1) {              WIKI.logger.warn('There are no pages to export! Skipping...')              break            }            const pagesProgressMultiplier = progressMultiplier / Math.ceil(pagesCount / 10)            WIKI.logger.info(`Found ${pagesCount} pages to export. Streaming to file...`)            const rs = stream.Readable({ objectMode: true })            rs._read = () => {}            const fetchPagesBatch = async (offset) => {              const pages = await WIKI.models.pages.query().offset(offset).limit(10).withGraphJoined({                author: true,                creator: true,                tags: true              }).modifyGraph('author', builder => {                builder.select('users.id', 'users.name', 'users.email', 'users.providerKey')              }).modifyGraph('creator', builder => {                builder.select('users.id', 'users.name', 'users.email', 'users.providerKey')              }).modifyGraph('tags', builder => {                builder.select('tags.tag', 'tags.title')              })              if (pages.length > 0) {                for (const page of pages) {                  rs.push(page)                }                fetchPagesBatch(offset + 10)              } else {                rs.push(null)              }              this.exportStatus.progress += pagesProgressMultiplier * 100            }            fetchPagesBatch(0)            let marker = 0            await pipeline(              rs,              new stream.Transform({                objectMode: true,                transform (chunk, encoding, callback) {                  marker++                  let outputStr = marker === 1 ? '[\n' : ''                  outputStr += JSON.stringify(chunk, null, 2)                  if (marker < pagesCount) {                    outputStr += ',\n'                  }                  callback(null, outputStr)                },                flush (callback) {                  callback(null, '\n]')                }              }),              zlib.createGzip(),              fs.createWriteStream(outputPath)            )            WIKI.logger.info('Export: pages.json.gz created successfully.')            break          }          // -----------------------------------------          // SETTINGS          // -----------------------------------------          case 'settings': {            WIKI.logger.info('Exporting settings...')            const outputPath = path.join(opts.path, 'settings.json')            const config = {              ...WIKI.config,              modules: {                analytics: await WIKI.models.analytics.query(),                authentication: (await WIKI.models.authentication.query()).map(a => ({                  ...a,                  domainWhitelist: _.get(a, 'domainWhitelist.v', []),                  autoEnrollGroups: _.get(a, 'autoEnrollGroups.v', [])                })),                commentProviders: await WIKI.models.commentProviders.query(),                renderers: await WIKI.models.renderers.query(),                searchEngines: await WIKI.models.searchEngines.query(),                storage: await WIKI.models.storage.query()              },              apiKeys: await WIKI.models.apiKeys.query().where('isRevoked', false)            }            await fs.outputJSON(outputPath, config, { spaces: 2 })            WIKI.logger.info('Export: settings.json created successfully.')            this.exportStatus.progress += progressMultiplier * 100            break          }          // -----------------------------------------          // USERS          // -----------------------------------------          case 'users': {            WIKI.logger.info('Exporting users...')            const outputPath = path.join(opts.path, 'users.json.gz')            const usersCountRaw = await WIKI.models.users.query().count('* as total').first()            const usersCount = parseInt(usersCountRaw.total)            if (usersCount < 1) {              WIKI.logger.warn('There are no users to export! Skipping...')              break            }            const usersProgressMultiplier = progressMultiplier / Math.ceil(usersCount / 50)            WIKI.logger.info(`Found ${usersCount} users to export. Streaming to file...`)            const rs = stream.Readable({ objectMode: true })            rs._read = () => {}            const fetchUsersBatch = async (offset) => {              const users = await WIKI.models.users.query().offset(offset).limit(50).withGraphJoined({                groups: true,                provider: true              }).modifyGraph('groups', builder => {                builder.select('groups.id', 'groups.name')              }).modifyGraph('provider', builder => {                builder.select('authentication.key', 'authentication.strategyKey', 'authentication.displayName')              })              if (users.length > 0) {                for (const usr of users) {                  rs.push(usr)                }                fetchUsersBatch(offset + 50)              } else {                rs.push(null)              }              this.exportStatus.progress += usersProgressMultiplier * 100            }            fetchUsersBatch(0)            let marker = 0            await pipeline(              rs,              new stream.Transform({                objectMode: true,                transform (chunk, encoding, callback) {                  marker++                  let outputStr = marker === 1 ? '[\n' : ''                  outputStr += JSON.stringify(chunk, null, 2)                  if (marker < usersCount) {                    outputStr += ',\n'                  }                  callback(null, outputStr)                },                flush (callback) {                  callback(null, '\n]')                }              }),              zlib.createGzip(),              fs.createWriteStream(outputPath)            )            WIKI.logger.info('Export: users.json.gz created successfully.')            break          }        }      }      this.exportStatus.status = 'success'      this.exportStatus.progress = 100    } catch (err) {      this.exportStatus.status = 'error'      this.exportStatus.message = err.message    }  }}
 |