feat: local disk import all action + v1 import content (#1100)

This commit is contained in:
Nicolas Giard 2019-10-14 23:44:37 -04:00 committed by GitHub
parent 334d0a754e
commit cffd32dee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 51 deletions

View File

@ -1,7 +1,6 @@
const passport = require('passport') const passport = require('passport')
const passportJWT = require('passport-jwt') const passportJWT = require('passport-jwt')
const _ = require('lodash') const _ = require('lodash')
const path = require('path')
const jwt = require('jsonwebtoken') const jwt = require('jsonwebtoken')
const moment = require('moment') const moment = require('moment')
const Promise = require('bluebird') const Promise = require('bluebird')

View File

@ -4,6 +4,13 @@ const crypto = require('crypto')
const path = require('path') const path = require('path')
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
const contentToExt = {
markdown: 'md',
html: 'html'
}
const extToContent = _.invert(contentToExt)
/* global WIKI */ /* global WIKI */
@ -94,13 +101,34 @@ module.exports = {
* Get file extension from content type * Get file extension from content type
*/ */
getFileExtension(contentType) { getFileExtension(contentType) {
switch (contentType) { _.get(contentToExt, contentType, 'txt')
case 'markdown': },
return 'md' /**
case 'html': * Get content type from file extension
return 'html' */
default: getContentType (filePath) {
return 'txt' const ext = _.last(filePath.split('.'))
return _.get(extToContent, ext, false)
},
/**
* Get Page Meta object from disk path
*/
getPagePath (filePath) {
let fpath = filePath
if (process.platform === 'win32') {
fpath = filePath.replace(/\\/g, '/')
} }
let meta = {
locale: WIKI.config.lang.code,
path: _.initial(fpath.split('.')).join('')
}
const result = localeFolderRegex.exec(meta.path)
if (result[1]) {
meta = {
locale: result[1],
path: result[2]
}
}
return meta
} }
} }

View File

@ -99,6 +99,8 @@ module.exports = class Editor extends Model {
switch (contentType) { switch (contentType) {
case 'markdown': case 'markdown':
return 'markdown' return 'markdown'
case 'html':
return 'ckeditor'
default: default:
return 'code' return 'code'
} }

View File

@ -366,7 +366,7 @@ module.exports = class Page extends Model {
} }
// -> Perform move? // -> Perform move?
if (opts.locale !== page.localeCode || opts.path !== page.path) { if ((opts.locale && opts.locale !== page.localeCode) || (opts.path && opts.path !== page.path)) {
await WIKI.models.pages.movePage({ await WIKI.models.pages.movePage({
id: page.id, id: page.id,
destinationLocale: opts.locale, destinationLocale: opts.locale,

View File

@ -712,4 +712,14 @@ module.exports = class User extends Model {
user.permissions = user.getGlobalPermissions() user.permissions = user.getGlobalPermissions()
return user return user
} }
static async getRootUser () {
let user = await WIKI.models.users.query().findById(1)
if (!user) {
WIKI.logger.error('CRITICAL ERROR: Root Administrator user is missing!')
process.exit(1)
}
user.permissions = ['manage:system']
return user
}
} }

View File

@ -29,3 +29,6 @@ actions:
- handler: backup - handler: backup
label: Create Backup label: Create Backup
hint: Will create a manual backup archive at this point in time, in a subfolder named _manual, from the contents currently on disk. hint: Will create a manual backup archive at this point in time, in a subfolder named _manual, from the contents currently on disk.
- handler: importAll
label: Import Everything
hint: Will import all content currently in the local disk folder.

View File

@ -3,8 +3,10 @@ const path = require('path')
const tar = require('tar-fs') const tar = require('tar-fs')
const zlib = require('zlib') const zlib = require('zlib')
const stream = require('stream') const stream = require('stream')
const _ = require('lodash')
const Promise = require('bluebird') const Promise = require('bluebird')
const pipeline = Promise.promisify(stream.pipeline) const pipeline = Promise.promisify(stream.pipeline)
const klaw = require('klaw')
const pageHelper = require('../../../helpers/page.js') const pageHelper = require('../../../helpers/page.js')
const moment = require('moment') const moment = require('moment')
@ -113,5 +115,77 @@ module.exports = {
}, },
async backup() { async backup() {
return this.sync({ manual: true }) return this.sync({ manual: true })
},
async importAll() {
WIKI.logger.info(`(STORAGE/DISK) Importing all content from local disk folder to the DB...`)
const rootUser = await WIKI.models.users.getRootUser()
await pipeline(
klaw(this.config.path, {
filter: (f) => {
return !_.includes(f, '.git')
}
}),
new stream.Transform({
objectMode: true,
transform: async (file, enc, cb) => {
const relPath = file.path.substr(this.config.path.length + 1)
if (relPath && relPath.length > 3) {
WIKI.logger.info(`(STORAGE/DISK) Processing ${relPath}...`)
const contentType = pageHelper.getContentType(relPath)
if (!contentType) {
return cb()
}
const contentPath = pageHelper.getPagePath(relPath)
let itemContents = ''
try {
itemContents = await fs.readFile(path.join(this.config.path, relPath), 'utf8')
const pageData = WIKI.models.pages.parseMetadata(itemContents, contentType)
const currentPage = await WIKI.models.pages.query().findOne({
path: contentPath.path,
localeCode: contentPath.locale
})
if (currentPage) {
// Already in the DB, can mark as modified
WIKI.logger.info(`(STORAGE/DISK) Page marked as modified: ${relPath}`)
await WIKI.models.pages.updatePage({
id: currentPage.id,
title: _.get(pageData, 'title', currentPage.title),
description: _.get(pageData, 'description', currentPage.description) || '',
isPublished: _.get(pageData, 'isPublished', currentPage.isPublished),
isPrivate: false,
content: pageData.content,
user: rootUser,
skipStorage: true
})
} else {
// Not in the DB, can mark as new
WIKI.logger.info(`(STORAGE/DISK) Page marked as new: ${relPath}`)
const pageEditor = await WIKI.models.editors.getDefaultEditor(contentType)
await WIKI.models.pages.createPage({
path: contentPath.path,
locale: contentPath.locale,
title: _.get(pageData, 'title', _.last(contentPath.path.split('/'))),
description: _.get(pageData, 'description', '') || '',
isPublished: _.get(pageData, 'isPublished', true),
isPrivate: false,
content: pageData.content,
user: rootUser,
editor: pageEditor,
skipStorage: true
})
}
} catch (err) {
WIKI.logger.warn(`(STORAGE/DISK) Failed to process ${relPath}`)
WIKI.logger.warn(err)
}
}
cb()
}
})
)
WIKI.logger.info('(STORAGE/DISK) Import completed.')
} }
} }

View File

@ -8,41 +8,8 @@ const pipeline = Promise.promisify(stream.pipeline)
const klaw = require('klaw') const klaw = require('klaw')
const pageHelper = require('../../../helpers/page.js') const pageHelper = require('../../../helpers/page.js')
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
/* global WIKI */ /* global WIKI */
const getContenType = (filePath) => {
const ext = _.last(filePath.split('.'))
switch (ext) {
case 'md':
return 'markdown'
case 'html':
return 'html'
default:
return false
}
}
const getPagePath = (filePath) => {
let fpath = filePath
if (process.platform === 'win32') {
fpath = filePath.replace(/\\/g, '/')
}
let meta = {
locale: 'en',
path: _.initial(fpath.split('.')).join('')
}
const result = localeFolderRegex.exec(meta.path)
if (result[1]) {
meta = {
locale: result[1],
path: result[2]
}
}
return meta
}
module.exports = { module.exports = {
git: null, git: null,
repoPath: path.join(process.cwd(), 'data/repo'), repoPath: path.join(process.cwd(), 'data/repo'),
@ -145,6 +112,8 @@ module.exports = {
async sync() { async sync() {
const currentCommitLog = _.get(await this.git.log(['-n', '1', this.config.branch]), 'latest', {}) const currentCommitLog = _.get(await this.git.log(['-n', '1', this.config.branch]), 'latest', {})
const rootUser = await WIKI.models.users.getRootUser()
// Pull rebase // Pull rebase
if (_.includes(['sync', 'pull'], this.mode)) { if (_.includes(['sync', 'pull'], this.mode)) {
WIKI.logger.info(`(STORAGE/GIT) Performing pull rebase from origin on branch ${this.config.branch}...`) WIKI.logger.info(`(STORAGE/GIT) Performing pull rebase from origin on branch ${this.config.branch}...`)
@ -167,7 +136,7 @@ module.exports = {
const diff = await this.git.diffSummary(['-M', currentCommitLog.hash, latestCommitLog.hash]) const diff = await this.git.diffSummary(['-M', currentCommitLog.hash, latestCommitLog.hash])
if (_.get(diff, 'files', []).length > 0) { if (_.get(diff, 'files', []).length > 0) {
await this.processFiles(diff.files) await this.processFiles(diff.files, rootUser)
} }
} }
}, },
@ -176,13 +145,13 @@ module.exports = {
* *
* @param {Array<String>} files Array of files to process * @param {Array<String>} files Array of files to process
*/ */
async processFiles(files) { async processFiles(files, user) {
for (const item of files) { for (const item of files) {
const contentType = getContenType(item.file) const contentType = pageHelper.getContentType(item.file)
if (!contentType) { if (!contentType) {
continue continue
} }
const contentPath = getPagePath(item.file) const contentPath = pageHelper.getPagePath(item.file)
let itemContents = '' let itemContents = ''
try { try {
@ -202,7 +171,7 @@ module.exports = {
isPublished: _.get(pageData, 'isPublished', currentPage.isPublished), isPublished: _.get(pageData, 'isPublished', currentPage.isPublished),
isPrivate: false, isPrivate: false,
content: pageData.content, content: pageData.content,
authorId: 1, user: user,
skipStorage: true skipStorage: true
}) })
} else { } else {
@ -217,7 +186,7 @@ module.exports = {
isPublished: _.get(pageData, 'isPublished', true), isPublished: _.get(pageData, 'isPublished', true),
isPrivate: false, isPrivate: false,
content: pageData.content, content: pageData.content,
authorId: 1, user: user,
editor: pageEditor, editor: pageEditor,
skipStorage: true skipStorage: true
}) })
@ -233,8 +202,7 @@ module.exports = {
skipStorage: true skipStorage: true
}) })
} else { } else {
WIKI.logger.warn(`(STORAGE/GIT) Failed to open ${item.file}`) WIKI.logger.warn(`(STORAGE/GIT) Failed to process ${item.file}`)
console.error(err)
WIKI.logger.warn(err) WIKI.logger.warn(err)
} }
} }
@ -365,6 +333,9 @@ module.exports = {
*/ */
async importAll() { async importAll() {
WIKI.logger.info(`(STORAGE/GIT) Importing all content from local Git repo to the DB...`) WIKI.logger.info(`(STORAGE/GIT) Importing all content from local Git repo to the DB...`)
const rootUser = await WIKI.models.users.getRootUser()
await pipeline( await pipeline(
klaw(this.repoPath, { klaw(this.repoPath, {
filter: (f) => { filter: (f) => {
@ -378,10 +349,11 @@ module.exports = {
if (relPath && relPath.length > 3) { if (relPath && relPath.length > 3) {
WIKI.logger.info(`(STORAGE/GIT) Processing ${relPath}...`) WIKI.logger.info(`(STORAGE/GIT) Processing ${relPath}...`)
await this.processFiles([{ await this.processFiles([{
user: rootUser,
file: relPath, file: relPath,
deletions: 0, deletions: 0,
insertions: 0 insertions: 0
}]) }], rootUser)
} }
cb() cb()
} }
@ -391,6 +363,7 @@ module.exports = {
}, },
async syncUntracked() { async syncUntracked() {
WIKI.logger.info(`(STORAGE/GIT) Adding all untracked content...`) WIKI.logger.info(`(STORAGE/GIT) Adding all untracked content...`)
await pipeline( await pipeline(
WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt').select().from('pages').where({ WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt').select().from('pages').where({
isPrivate: false isPrivate: false