Commit 2c6a95f9 authored by Nicolas Giard's avatar Nicolas Giard

feat: page cache + save/load logic + db fixes

parent 572393aa
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
.subheading.grey--text Maintenance and troubleshooting tools .subheading.grey--text Maintenance and troubleshooting tools
v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows) v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)
v-tab(key='tools') Tools v-tab(key='tools') Tools
v-tab(key='cache') Cache
v-tab(key='telemetry') Telemetry v-tab(key='telemetry') Telemetry
v-tab(key='telemetry') Support v-tab(key='support') Support
v-tab-item(key='tools', :transition='false', :reverse-transition='false') v-tab-item(key='tools', :transition='false', :reverse-transition='false')
v-container(fluid, grid-list-lg, :class='$vuetify.dark ? "" : "grey lighten-5"') v-container(fluid, grid-list-lg, :class='$vuetify.dark ? "" : "grey lighten-5"')
......
...@@ -34,7 +34,7 @@ router.get('/*', async (req, res, next) => { ...@@ -34,7 +34,7 @@ router.get('/*', async (req, res, next) => {
path: pageArgs.path, path: pageArgs.path,
locale: pageArgs.locale, locale: pageArgs.locale,
userId: req.user.id, userId: req.user.id,
private: false isPrivate: false
}) })
if (page) { if (page) {
res.render('page', { page }) res.render('page', { page })
......
...@@ -5,6 +5,7 @@ exports.up = knex => { ...@@ -5,6 +5,7 @@ exports.up = knex => {
// ===================================== // =====================================
// ASSETS ------------------------------ // ASSETS ------------------------------
.createTable('assets', table => { .createTable('assets', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('filename').notNullable() table.string('filename').notNullable()
table.string('basename').notNullable() table.string('basename').notNullable()
...@@ -18,6 +19,7 @@ exports.up = knex => { ...@@ -18,6 +19,7 @@ exports.up = knex => {
}) })
// ASSET FOLDERS ----------------------- // ASSET FOLDERS -----------------------
.createTable('assetFolders', table => { .createTable('assetFolders', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('name').notNullable() table.string('name').notNullable()
table.string('slug').notNullable() table.string('slug').notNullable()
...@@ -25,6 +27,7 @@ exports.up = knex => { ...@@ -25,6 +27,7 @@ exports.up = knex => {
}) })
// AUTHENTICATION ---------------------- // AUTHENTICATION ----------------------
.createTable('authentication', table => { .createTable('authentication', table => {
table.charset('utf8mb4')
table.string('key').notNullable().primary() table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false) table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable() table.json('config').notNullable()
...@@ -34,6 +37,7 @@ exports.up = knex => { ...@@ -34,6 +37,7 @@ exports.up = knex => {
}) })
// COMMENTS ---------------------------- // COMMENTS ----------------------------
.createTable('comments', table => { .createTable('comments', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.text('content').notNullable() table.text('content').notNullable()
table.string('createdAt').notNullable() table.string('createdAt').notNullable()
...@@ -41,12 +45,14 @@ exports.up = knex => { ...@@ -41,12 +45,14 @@ exports.up = knex => {
}) })
// EDITORS ----------------------------- // EDITORS -----------------------------
.createTable('editors', table => { .createTable('editors', table => {
table.charset('utf8mb4')
table.string('key').notNullable().primary() table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false) table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable() table.json('config').notNullable()
}) })
// GROUPS ------------------------------ // GROUPS ------------------------------
.createTable('groups', table => { .createTable('groups', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('name').notNullable() table.string('name').notNullable()
table.string('createdAt').notNullable() table.string('createdAt').notNullable()
...@@ -54,6 +60,7 @@ exports.up = knex => { ...@@ -54,6 +60,7 @@ exports.up = knex => {
}) })
// LOCALES ----------------------------- // LOCALES -----------------------------
.createTable('locales', table => { .createTable('locales', table => {
table.charset('utf8mb4')
table.string('code', 2).notNullable().primary() table.string('code', 2).notNullable().primary()
table.json('strings') table.json('strings')
table.boolean('isRTL').notNullable().defaultTo(false) table.boolean('isRTL').notNullable().defaultTo(false)
...@@ -64,6 +71,7 @@ exports.up = knex => { ...@@ -64,6 +71,7 @@ exports.up = knex => {
}) })
// LOGGING ---------------------------- // LOGGING ----------------------------
.createTable('loggers', table => { .createTable('loggers', table => {
table.charset('utf8mb4')
table.string('key').notNullable().primary() table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false) table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('level').notNullable().defaultTo('warn') table.string('level').notNullable().defaultTo('warn')
...@@ -71,8 +79,10 @@ exports.up = knex => { ...@@ -71,8 +79,10 @@ exports.up = knex => {
}) })
// PAGE HISTORY ------------------------ // PAGE HISTORY ------------------------
.createTable('pageHistory', table => { .createTable('pageHistory', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('path').notNullable() table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable() table.string('title').notNullable()
table.string('description') table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false) table.boolean('isPrivate').notNullable().defaultTo(false)
...@@ -85,8 +95,10 @@ exports.up = knex => { ...@@ -85,8 +95,10 @@ exports.up = knex => {
}) })
// PAGES ------------------------------- // PAGES -------------------------------
.createTable('pages', table => { .createTable('pages', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('path').notNullable() table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable() table.string('title').notNullable()
table.string('description') table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false) table.boolean('isPrivate').notNullable().defaultTo(false)
...@@ -102,24 +114,28 @@ exports.up = knex => { ...@@ -102,24 +114,28 @@ exports.up = knex => {
}) })
// RENDERERS --------------------------- // RENDERERS ---------------------------
.createTable('renderers', table => { .createTable('renderers', table => {
table.charset('utf8mb4')
table.string('key').notNullable().primary() table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false) table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config') table.json('config')
}) })
// SEARCH ------------------------------ // SEARCH ------------------------------
.createTable('searchEngines', table => { .createTable('searchEngines', table => {
table.charset('utf8mb4')
table.string('key').notNullable().primary() table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false) table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config') table.json('config')
}) })
// SETTINGS ---------------------------- // SETTINGS ----------------------------
.createTable('settings', table => { .createTable('settings', table => {
table.charset('utf8mb4')
table.string('key').notNullable().primary() table.string('key').notNullable().primary()
table.json('value') table.json('value')
table.string('updatedAt').notNullable() table.string('updatedAt').notNullable()
}) })
// STORAGE ----------------------------- // STORAGE -----------------------------
.createTable('storage', table => { .createTable('storage', table => {
table.charset('utf8mb4')
table.string('key').notNullable().primary() table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false) table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push') table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
...@@ -127,6 +143,7 @@ exports.up = knex => { ...@@ -127,6 +143,7 @@ exports.up = knex => {
}) })
// TAGS -------------------------------- // TAGS --------------------------------
.createTable('tags', table => { .createTable('tags', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('tag').notNullable().unique() table.string('tag').notNullable().unique()
table.string('title') table.string('title')
...@@ -135,6 +152,7 @@ exports.up = knex => { ...@@ -135,6 +152,7 @@ exports.up = knex => {
}) })
// USERS ------------------------------- // USERS -------------------------------
.createTable('users', table => { .createTable('users', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.string('email').notNullable() table.string('email').notNullable()
table.string('name').notNullable() table.string('name').notNullable()
...@@ -155,18 +173,21 @@ exports.up = knex => { ...@@ -155,18 +173,21 @@ exports.up = knex => {
// ===================================== // =====================================
// PAGE HISTORY TAGS --------------------------- // PAGE HISTORY TAGS ---------------------------
.createTable('pageHistoryTags', table => { .createTable('pageHistoryTags', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE') table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE') table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
}) })
// PAGE TAGS --------------------------- // PAGE TAGS ---------------------------
.createTable('pageTags', table => { .createTable('pageTags', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE') table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE') table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
}) })
// USER GROUPS ------------------------- // USER GROUPS -------------------------
.createTable('userGroups', table => { .createTable('userGroups', table => {
table.charset('utf8mb4')
table.increments('id').primary() table.increments('id').primary()
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE') table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE') table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
......
const qs = require('querystring') const qs = require('querystring')
const _ = require('lodash') const _ = require('lodash')
const crypto = require('crypto')
module.exports = { module.exports = {
/** /**
...@@ -26,5 +27,11 @@ module.exports = { ...@@ -26,5 +27,11 @@ module.exports = {
} }
pathObj.path = _.join(pathParts, '/') pathObj.path = _.join(pathParts, '/')
return pathObj return pathObj
},
/**
* Generate unique hash from page
*/
generateHash(opts) {
return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
} }
} }
...@@ -20,7 +20,17 @@ module.exports = async (job) => { ...@@ -20,7 +20,17 @@ module.exports = async (job) => {
input: output input: output
}) })
} }
console.info(output)
// Save to DB
await WIKI.models.pages.query()
.patch({ render: output })
.where('id', job.data.page.id)
// Save to cache
await WIKI.models.pages.savePageToCache({
...job.data.page,
render: output
})
WIKI.logger.info(`Rendering page ${job.data.page.path}: [ COMPLETED ]`) WIKI.logger.info(`Rendering page ${job.data.page.path}: [ COMPLETED ]`)
} catch (err) { } catch (err) {
......
...@@ -16,6 +16,7 @@ module.exports = class PageHistory extends Model { ...@@ -16,6 +16,7 @@ module.exports = class PageHistory extends Model {
properties: { properties: {
id: {type: 'integer'}, id: {type: 'integer'},
path: {type: 'string'}, path: {type: 'string'},
hash: {type: 'string'},
title: {type: 'string'}, title: {type: 'string'},
description: {type: 'string'}, description: {type: 'string'},
isPublished: {type: 'boolean'}, isPublished: {type: 'boolean'},
...@@ -88,6 +89,7 @@ module.exports = class PageHistory extends Model { ...@@ -88,6 +89,7 @@ module.exports = class PageHistory extends Model {
content: opts.content, content: opts.content,
description: opts.description, description: opts.description,
editorKey: opts.editorKey, editorKey: opts.editorKey,
hash: opts.hash,
isPrivate: opts.isPrivate, isPrivate: opts.isPrivate,
isPublished: opts.isPublished, isPublished: opts.isPublished,
localeCode: opts.localeCode, localeCode: opts.localeCode,
......
const Model = require('objection').Model const Model = require('objection').Model
const _ = require('lodash') const _ = require('lodash')
const JSBinType = require('js-binary').Type
const pageHelper = require('../helpers/page')
const path = require('path')
const fs = require('fs-extra')
/* global WIKI */ /* global WIKI */
...@@ -17,6 +21,7 @@ module.exports = class Page extends Model { ...@@ -17,6 +21,7 @@ module.exports = class Page extends Model {
properties: { properties: {
id: {type: 'integer'}, id: {type: 'integer'},
path: {type: 'string'}, path: {type: 'string'},
hash: {type: 'string'},
title: {type: 'string'}, title: {type: 'string'},
description: {type: 'string'}, description: {type: 'string'},
isPublished: {type: 'boolean'}, isPublished: {type: 'boolean'},
...@@ -89,35 +94,33 @@ module.exports = class Page extends Model { ...@@ -89,35 +94,33 @@ module.exports = class Page extends Model {
this.updatedAt = new Date().toISOString() this.updatedAt = new Date().toISOString()
} }
static async getPage(opts) { static get cacheSchema() {
const page = await WIKI.models.pages.query().where({ return new JSBinType({
path: opts.path, authorId: 'uint',
localeCode: opts.locale authorName: 'string',
}).andWhere(builder => { createdAt: 'string',
builder.where({ creatorId: 'uint',
isPublished: true creatorName: 'string',
}).orWhere({ description: 'string',
isPublished: false, isPrivate: 'boolean',
authorId: opts.userId isPublished: 'boolean',
publishEndDate: 'string',
publishStartDate: 'string',
render: 'string',
title: 'string',
updatedAt: 'string'
}) })
}).andWhere(builder => {
if (opts.private) {
builder.where({ isPrivate: true, privateNS: opts.privateNS })
} else {
builder.where({ isPrivate: false })
}
}).first()
return page
} }
static async createPage(opts) { static async createPage(opts) {
const page = await WIKI.models.pages.query().insertAndFetch({ await WIKI.models.pages.query().insert({
authorId: opts.authorId, authorId: opts.authorId,
content: opts.content, content: opts.content,
creatorId: opts.authorId, creatorId: opts.authorId,
contentType: _.get(_.find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text'), contentType: _.get(_.find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text'),
description: opts.description, description: opts.description,
editorKey: opts.editor, editorKey: opts.editor,
hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' }),
isPrivate: opts.isPrivate, isPrivate: opts.isPrivate,
isPublished: opts.isPublished, isPublished: opts.isPublished,
localeCode: opts.locale, localeCode: opts.locale,
...@@ -126,6 +129,7 @@ module.exports = class Page extends Model { ...@@ -126,6 +129,7 @@ module.exports = class Page extends Model {
publishStartDate: opts.publishStartDate, publishStartDate: opts.publishStartDate,
title: opts.title title: opts.title
}) })
const page = await WIKI.models.pages.getPageFromDb(opts)
await WIKI.models.pages.renderPage(page) await WIKI.models.pages.renderPage(page)
await WIKI.models.storage.pageEvent({ await WIKI.models.storage.pageEvent({
event: 'created', event: 'created',
...@@ -140,7 +144,7 @@ module.exports = class Page extends Model { ...@@ -140,7 +144,7 @@ module.exports = class Page extends Model {
throw new Error('Invalid Page Id') throw new Error('Invalid Page Id')
} }
await WIKI.models.pageHistory.addVersion(ogPage) await WIKI.models.pageHistory.addVersion(ogPage)
const page = await WIKI.models.pages.query().patchAndFetchById(ogPage.id, { await WIKI.models.pages.query().patch({
authorId: opts.authorId, authorId: opts.authorId,
content: opts.content, content: opts.content,
description: opts.description, description: opts.description,
...@@ -148,7 +152,8 @@ module.exports = class Page extends Model { ...@@ -148,7 +152,8 @@ module.exports = class Page extends Model {
publishEndDate: opts.publishEndDate, publishEndDate: opts.publishEndDate,
publishStartDate: opts.publishStartDate, publishStartDate: opts.publishStartDate,
title: opts.title title: opts.title
}) }).where('id', ogPage.id)
const page = await WIKI.models.pages.getPageFromDb(opts)
await WIKI.models.pages.renderPage(page) await WIKI.models.pages.renderPage(page)
await WIKI.models.storage.pageEvent({ await WIKI.models.storage.pageEvent({
event: 'updated', event: 'updated',
...@@ -167,4 +172,88 @@ module.exports = class Page extends Model { ...@@ -167,4 +172,88 @@ module.exports = class Page extends Model {
removeOnFail: true removeOnFail: true
}) })
} }
static async getPage(opts) {
let page = await WIKI.models.pages.getPageFromCache(opts)
if (!page) {
page = await WIKI.models.pages.getPageFromDb(opts)
await WIKI.models.pages.savePageToCache(page)
}
return page
}
static async getPageFromDb(opts) {
const page = await WIKI.models.pages.query()
.column([
'pages.*',
{
authorName: 'author.name',
creatorName: 'creator.name'
}
])
.joinRelation('author')
.joinRelation('creator')
.where({
'pages.path': opts.path,
'pages.localeCode': opts.locale
})
.andWhere(builder => {
builder.where({
'pages.isPublished': true
}).orWhere({
'pages.isPublished': false,
'pages.authorId': opts.userId
})
})
.andWhere(builder => {
if (opts.isPrivate) {
builder.where({ 'pages.isPrivate': true, 'pages.privateNS': opts.privateNS })
} else {
builder.where({ 'pages.isPrivate': false })
}
})
.first()
return page
}
static async savePageToCache(page) {
const cachePath = path.join(process.cwd(), `data/cache/${page.hash}.bin`)
await fs.outputFile(cachePath, WIKI.models.pages.cacheSchema.encode({
authorId: page.authorId,
authorName: page.authorName,
createdAt: page.createdAt,
creatorId: page.creatorId,
creatorName: page.creatorName,
description: page.description,
isPrivate: page.isPrivate === 1,
isPublished: page.isPublished === 1,
publishEndDate: page.publishEndDate,
publishStartDate: page.publishStartDate,
render: page.render,
title: page.title,
updatedAt: page.updatedAt
}))
}
static async getPageFromCache(opts) {
const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' })
const cachePath = path.join(process.cwd(), `data/cache/${pageHash}.bin`)
try {
const pageBuffer = await fs.readFile(cachePath)
let page = WIKI.models.pages.cacheSchema.decode(pageBuffer)
return {
...page,
path: opts.path,
localeCode: opts.locale,
isPrivate: opts.isPrivate
}
} catch (err) {
if (err.code === 'ENOENT') {
return false
}
WIKI.logger.error(err)
throw err
}
}
} }
...@@ -90,10 +90,10 @@ module.exports = () => { ...@@ -90,10 +90,10 @@ module.exports = () => {
} }
// Create directory structure // Create directory structure
const tmpPath = path.join(os.tmpdir(), 'wikijs') const dataPath = path.join(process.cwd(), 'data')
await fs.ensureDir(tmpPath) await fs.ensureDir(dataPath)
await fs.ensureDir(path.join(tmpPath, 'cache')) await fs.ensureDir(path.join(dataPath, 'cache'))
await fs.ensureDir(path.join(tmpPath, 'uploads')) await fs.ensureDir(path.join(dataPath, 'uploads'))
// Set config // Set config
_.set(WIKI.config, 'defaultEditor', 'markdown') _.set(WIKI.config, 'defaultEditor', 'markdown')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment