diff --git a/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js b/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js new file mode 100644 index 0000000000..72d4bc85ad --- /dev/null +++ b/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js @@ -0,0 +1,72 @@ +const logging = require('../../../../../shared/logging'); +const {createIrreversibleMigration} = require('../../utils'); +const mobiledocLib = require('../../../../lib/mobiledoc'); +const htmlToText = require('html-to-text'); + +module.exports = createIrreversibleMigration(async (knex) => { + logging.info('Starting re-generation of posts html.'); + + await knex.transaction(async (trx) => { + // get list of posts ids, use .forUpdate to lock rows until the transaction is finished + const postIdRows = await knex('posts') + .transacting(trx) + .forUpdate() + .select('id'); + + // transform each post individually to avoid dumping all posts into memory and + // pushing all queries into the query builder buffer in parallel + // https://stackoverflow.com/questions/54105280/how-to-loop-through-multi-line-sql-query-and-use-them-in-knex-transactions + + for (const postIdRow of postIdRows) { + const {id} = postIdRow; + const [post] = await knex('posts') + .transacting(trx) + .where({id}) + .select('mobiledoc', 'html', 'plaintext'); + + let mobiledoc; + + try { + mobiledoc = JSON.parse(post.mobiledoc || null); + + if (!mobiledoc) { + return logging.warn(`No mobiledoc for ${id}. Skipping.`); + } + } catch (err) { + return logging.warn(`Invalid JSON structure for ${id}. Skipping`); + } + + const html = mobiledocLib.mobiledocHtmlRenderer.render(mobiledoc); + + const updatedAttrs = { + html: html + }; + + // NOTE: block comes straight from the Post model + // https://github.com/TryGhost/Ghost/blob/4.0.0-alpha.2/core/server/models/post.js#L484 + if (html !== post.html || !post.plaintext) { + const plaintext = htmlToText.fromString(html, { + wordwrap: 80, + ignoreImage: true, + hideLinkHrefIfSameAsText: true, + preserveNewlines: true, + returnDomByDefault: true, + uppercaseHeadings: false + }); + + if (plaintext !== post.plaintext) { + updatedAttrs.plaintext = plaintext; + } + } + + await knex('posts') + .transacting(trx) + .where({id}) + .update(updatedAttrs); + } + + return 'transaction complete'; + }); + + logging.info('Finished re-generation of posts html.'); +});