const { Controller } = require('libflitter') const ncp = require('ncp').ncp const fs = require('fs').promises const path = require('path') const md = require('markdown').markdown const rimraf = require('rimraf') const uuid = require('uuid/v4') class ExportController extends Controller { static get services() { return [...super.services, 'models', 'utility', 'upload'] } async get_export_list(req, res, next) { const Export = this.models.get('api:Export') const exports = await Export.find({ user_id: req.user.id, }) return res.api(exports) } async download_export(req, res, next) { const Export = this.models.get('api:Export') const exported = await Export.findOne({ UUID: req.params.ExportId, user_id: req.user.id }) if ( !exported ) { return res.status(404) .message('No export with that UUID found.') .api() } const File = this.models.get('upload::File') const file = await File.findOne({_id: File.ObjectId(exported.file_id)}) if ( !file ) return res.status(404).message('This export file has been deleted.').api({}) return file.send(res) } async export_subtree(req, res, next) { const Export = this.models.get('api:Export') const format = req.form.format const page = req.form.page if ( format === 'html' ) { const generated_export = await this.export_subtree_as_html(page, req.user) // Store the generated archive const uploader = this.upload.provider() const file = await uploader.store({ temp_path: generated_export, original_name: `export-${uuid()}.tar.gz`, mime_type: 'application/gzip', tag: 'generated_export', }) const exp = new Export({ user_id: req.user.id, format, subtree: true, file_id: file.id, PageId: page.UUID, }) await exp.save() return res.api(exp) } return res.status(400) .message('Invalid export format!') .api() } async export_subtree_as_html(page, user) { const flat_tree = [] await page.version_save('Exported page as HTML', user.id) const add_to_tree = async (page, level = 0) => { if ( await page.is_accessible_by(user, 'view') ) { flat_tree.push({ level, page, file_name: `${page.Name.replace(/\s/g, '-').replace(/[\/\\]/g, '-')}-${page.UUID}.html`, }) const children = await page.childPages for ( const child of children ) { await add_to_tree(child, level + 1) } } } await add_to_tree(page) const manifest = { sidebar: [], } for ( const item of flat_tree ) { manifest.sidebar.push({ title: item.page.Name, level: item.level, link: item.file_name, }) } // Copy the template over const work_dir = await this.scratch_dir() await this.copy_template(this.utility.path('app', 'assets', 'export', 'html'), work_dir) const html_template = await fs.readFile(path.resolve(work_dir, 'index.html'), 'utf-8') for ( const item of flat_tree ) { let item_template = html_template item_template = item_template.replace(/{{\s?MANIFEST\s?}}/g, JSON.stringify(manifest)) item_template = item_template.replace(/{{\s?GROUP_TITLE\s?}}/g, page.Name) item_template = item_template.replace(/{{\s?PAGE_TITLE\s?}}/g, item.page.Name) item_template = item_template.replace(/{{\s?PAGE_CONTENT\s?}}/g, await this.page_as_html(item.page, work_dir)) await fs.writeFile(path.resolve(work_dir, item.file_name), item_template) } // Write the main page redirect const redir_html = `
` await fs.writeFile(path.resolve(work_dir, 'index.html'), redir_html) // Create the archive const tar = require('tar') const archive_path = path.resolve(work_dir, '..', `export-${uuid()}.tar.gz`) await tar.c({ gzip: true, file: archive_path, cwd: path.resolve(work_dir, '..'), }, [path.basename(work_dir)]) await new Promise(res => { rimraf(work_dir, res) }) return archive_path } async copy_template(from, to) { return new Promise((res, rej) => { ncp(from, to, err => { if ( err ) rej(err) else res() }) }) } async scratch_dir() { const tmp = require('tmp') const gen_id = uuid() return new Promise((res, rej) => { tmp.dir((err, tmp_path) => { if ( err ) rej(err) else { fs.mkdir(path.resolve(tmp_path, `export`)).then(() => { res(path.resolve(tmp_path, `export`)) }) } }) }) } /** * Given a page and a template working directory, render that page as HTML. * @param {Page} page * @param {string} work_dir * @return {Promise