From 2e2799f2d315908d734e44d74d1eb1dfe365a49f Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Thu, 30 Dec 2021 13:22:31 +0000 Subject: [PATCH] Moved client-side markdown rendering to this server --- package-lock.json | 91 ++++++++++++++++++++++++++++- package.json | 3 +- server/database/models/articles.js | 5 ++ server/database/models/revisions.js | 13 +++++ server/news/edit.js | 3 + server/news/publish.js | 4 +- server/news/query.js | 4 +- server/news/remove.js | 1 + server/server.js | 44 ++++++++++++++ tools/migrations/version-1.4.0.sql | 4 ++ 10 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 tools/migrations/version-1.4.0.sql diff --git a/package-lock.json b/package-lock.json index 68ca2b4..1f15782 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "news-server", - "version": "1.3.1", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "news-server", - "version": "1.3.1", + "version": "1.4.0", "license": "ISC", "dependencies": { "cors": "^2.8.5", @@ -14,6 +14,7 @@ "express": "^4.17.1", "jsonwebtoken": "^8.5.1", "mariadb": "^2.5.4", + "markdown-it": "^12.3.0", "sequelize": "^6.6.5" }, "devDependencies": { @@ -120,6 +121,11 @@ "node": ">= 8" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -567,6 +573,14 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -1032,6 +1046,14 @@ "node": ">=8" } }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1149,6 +1171,26 @@ "node": ">=0.10.0" } }, + "node_modules/markdown-it": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.0.tgz", + "integrity": "sha512-T345UZZ6ejQWTjG6PSEHplzNy5m4kF6zvUpHVDv8Snl/pEU0OxIK0jGg8YLVNwJvT8E0YJC7/2UvssJDk/wQCQ==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1903,6 +1945,11 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "node_modules/undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", @@ -2136,6 +2183,11 @@ "picomatch": "^2.0.4" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -2492,6 +2544,11 @@ "once": "^1.4.0" } }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -2859,6 +2916,14 @@ "package-json": "^6.3.0" } }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2959,6 +3024,23 @@ } } }, + "markdown-it": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.0.tgz", + "integrity": "sha512-T345UZZ6ejQWTjG6PSEHplzNy5m4kF6zvUpHVDv8Snl/pEU0OxIK0jGg8YLVNwJvT8E0YJC7/2UvssJDk/wQCQ==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3519,6 +3601,11 @@ "is-typedarray": "^1.0.0" } }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", diff --git a/package.json b/package.json index f60fdf8..6bd1b8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "news-server", - "version": "1.3.1", + "version": "1.4.0", "description": "An API centric news server. Uses Sequelize and mariaDB by default.", "main": "server/server.js", "scripts": { @@ -24,6 +24,7 @@ "express": "^4.17.1", "jsonwebtoken": "^8.5.1", "mariadb": "^2.5.4", + "markdown-it": "^12.3.0", "sequelize": "^6.6.5" }, "devDependencies": { diff --git a/server/database/models/articles.js b/server/database/models/articles.js index a7bc948..da443ec 100644 --- a/server/database/models/articles.js +++ b/server/database/models/articles.js @@ -25,6 +25,11 @@ const articles = sequelize.define('articles', { defaultValue: '' }, + rendered: { + type: Sequelize.TEXT, + defaultValue: '' + }, + edits: { type: Sequelize.INTEGER(11), defaultValue: 0 diff --git a/server/database/models/revisions.js b/server/database/models/revisions.js index 1d90a53..34324ee 100644 --- a/server/database/models/revisions.js +++ b/server/database/models/revisions.js @@ -2,6 +2,14 @@ const Sequelize = require('sequelize'); const sequelize = require('..'); const revisions = sequelize.define('revisions', { + index: { + type: Sequelize.INTEGER(11), + allowNull: false, + autoIncrement: true, + primaryKey: true, + unique: true + }, + title: { type: Sequelize.TEXT, defaultValue: '' @@ -17,6 +25,11 @@ const revisions = sequelize.define('revisions', { defaultValue: '' }, + rendered: { + type: Sequelize.TEXT, + defaultValue: '' + }, + originalIndex: { type: Sequelize.INTEGER(11), default: null diff --git a/server/news/edit.js b/server/news/edit.js index 03e50c2..d0647d5 100644 --- a/server/news/edit.js +++ b/server/news/edit.js @@ -1,5 +1,6 @@ const { Op } = require('sequelize'); const { articles, revisions } = require('../database/models'); +const markdownIt = require('markdown-it')(); const route = async (req, res) => { //get the existing record @@ -20,6 +21,7 @@ const route = async (req, res) => { title: record.title, author: record.author, body: record.body, + rendered: record.rendered, originalIndex: record.index }); @@ -28,6 +30,7 @@ const route = async (req, res) => { title: req.body.title || record.title, author: req.body.author || record.author, body: req.body.body || record.body, + rendered: markdownIt.render(req.body.body) || record.rendered, edits: record.edits + 1 }, { where: { diff --git a/server/news/publish.js b/server/news/publish.js index c776977..04418db 100644 --- a/server/news/publish.js +++ b/server/news/publish.js @@ -1,4 +1,5 @@ const { articles } = require('../database/models'); +const markdownIt = require('markdown-it')(); const route = async (req, res) => { //check for missing data @@ -18,7 +19,8 @@ const route = async (req, res) => { const [instance, created] = await articles.upsert({ title: req.body.title, author: req.body.author, - body: req.body.body + body: req.body.body, + rendered: markdownIt.render(req.body.body), }); if (!created) { diff --git a/server/news/query.js b/server/news/query.js index 30ffbf4..fe7d1c3 100644 --- a/server/news/query.js +++ b/server/news/query.js @@ -7,7 +7,7 @@ const query = (ascending, metadataOnly) => async (req, res) => { if (req.params.id && typeof(parseInt(req.params.id)) === 'number') { const result = await articles.findOne({ attributes: [ - 'index', 'title', 'author', 'edits', 'createdAt', 'updatedAt', ...(!metadataOnly ? ['body'] : []) + 'index', 'title', 'author', 'edits', 'createdAt', 'updatedAt', ...(!metadataOnly ? ['body', 'rendered'] : []) ], where: { index: { @@ -24,7 +24,7 @@ const query = (ascending, metadataOnly) => async (req, res) => { else { const result = await articles.findAndCountAll({ attributes: [ - 'index', 'title', 'author', 'edits', 'createdAt', 'updatedAt', ...(!metadataOnly ? ['body'] : []) + 'index', 'title', 'author', 'edits', 'createdAt', 'updatedAt', ...(!metadataOnly ? ['body', 'rendered'] : []) ], order: [ ['index', ascending ? 'ASC' : 'DESC'] diff --git a/server/news/remove.js b/server/news/remove.js index 0660577..55bf323 100644 --- a/server/news/remove.js +++ b/server/news/remove.js @@ -20,6 +20,7 @@ const route = async (req, res) => { title: record.title, author: record.author, body: record.body, + rendered: record.rendered, originalIndex: record.index }); diff --git a/server/server.js b/server/server.js index e823067..23491e4 100644 --- a/server/server.js +++ b/server/server.js @@ -26,4 +26,48 @@ app.get('*', (req, res) => { server.listen(process.env.WEB_PORT || 3100, async (err) => { await database.sync(); console.log(`listening to localhost:${process.env.WEB_PORT || 3100}`); + + //parse the unrendered data from the database + const markdownIt = require('markdown-it')(); + const { articles, revisions } = require('./database/models'); + + const missingArticles = await articles.findAll({ + where: { + rendered: '' + } + }); + + const missingRevisions = await revisions.findAll({ + where: { + rendered: '' + } + }); + + await Promise.all( + missingArticles.map(async ma => { + ma.update({ + rendered: markdownIt.render(ma.body) + }, { + where: { + index: ma.index + } + }); + }) + ) + .then(result => {if (result.length > 0) console.log('Rendered articles in HTML'); }) + ; + + await Promise.all( + missingRevisions.map(async mr => { + mr.update({ + rendered: markdownIt.render(mr.body) + }, { + where: { + index: mr.index + } + }); + }) + ) + .then(result => {if (result.length > 0) console.log('Rendered revisions in HTML'); }) + ; }); diff --git a/tools/migrations/version-1.4.0.sql b/tools/migrations/version-1.4.0.sql new file mode 100644 index 0000000..4c66e42 --- /dev/null +++ b/tools/migrations/version-1.4.0.sql @@ -0,0 +1,4 @@ +ALTER TABLE articles ADD COLUMN rendered TEXT DEFAULT "" AFTER body; + +ALTER TABLE revisions ADD COLUMN rendered TEXT DEFAULT "" AFTER body; +