You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
7.3 KiB

6 years ago
6 years ago
  1. const Model = require('objection').Model
  2. const _ = require('lodash')
  3. const JSBinType = require('js-binary').Type
  4. const pageHelper = require('../helpers/page')
  5. const path = require('path')
  6. const fs = require('fs-extra')
  7. /* global WIKI */
  8. /**
  9. * Pages model
  10. */
  11. module.exports = class Page extends Model {
  12. static get tableName() { return 'pages' }
  13. static get jsonSchema () {
  14. return {
  15. type: 'object',
  16. required: ['path', 'title'],
  17. properties: {
  18. id: {type: 'integer'},
  19. path: {type: 'string'},
  20. hash: {type: 'string'},
  21. title: {type: 'string'},
  22. description: {type: 'string'},
  23. isPublished: {type: 'boolean'},
  24. privateNS: {type: 'string'},
  25. publishStartDate: {type: 'string'},
  26. publishEndDate: {type: 'string'},
  27. content: {type: 'string'},
  28. contentType: {type: 'string'},
  29. createdAt: {type: 'string'},
  30. updatedAt: {type: 'string'}
  31. }
  32. }
  33. }
  34. static get relationMappings() {
  35. return {
  36. tags: {
  37. relation: Model.ManyToManyRelation,
  38. modelClass: require('./tags'),
  39. join: {
  40. from: 'pages.id',
  41. through: {
  42. from: 'pageTags.pageId',
  43. to: 'pageTags.tagId'
  44. },
  45. to: 'tags.id'
  46. }
  47. },
  48. author: {
  49. relation: Model.BelongsToOneRelation,
  50. modelClass: require('./users'),
  51. join: {
  52. from: 'pages.authorId',
  53. to: 'users.id'
  54. }
  55. },
  56. creator: {
  57. relation: Model.BelongsToOneRelation,
  58. modelClass: require('./users'),
  59. join: {
  60. from: 'pages.creatorId',
  61. to: 'users.id'
  62. }
  63. },
  64. editor: {
  65. relation: Model.BelongsToOneRelation,
  66. modelClass: require('./editors'),
  67. join: {
  68. from: 'pages.editorKey',
  69. to: 'editors.key'
  70. }
  71. },
  72. locale: {
  73. relation: Model.BelongsToOneRelation,
  74. modelClass: require('./locales'),
  75. join: {
  76. from: 'pages.localeCode',
  77. to: 'locales.code'
  78. }
  79. }
  80. }
  81. }
  82. $beforeUpdate() {
  83. this.updatedAt = new Date().toISOString()
  84. }
  85. $beforeInsert() {
  86. this.createdAt = new Date().toISOString()
  87. this.updatedAt = new Date().toISOString()
  88. }
  89. static get cacheSchema() {
  90. return new JSBinType({
  91. authorId: 'uint',
  92. authorName: 'string',
  93. createdAt: 'string',
  94. creatorId: 'uint',
  95. creatorName: 'string',
  96. description: 'string',
  97. isPrivate: 'boolean',
  98. isPublished: 'boolean',
  99. publishEndDate: 'string',
  100. publishStartDate: 'string',
  101. render: 'string',
  102. title: 'string',
  103. toc: 'string',
  104. updatedAt: 'string'
  105. })
  106. }
  107. static async createPage(opts) {
  108. await WIKI.models.pages.query().insert({
  109. authorId: opts.authorId,
  110. content: opts.content,
  111. creatorId: opts.authorId,
  112. contentType: _.get(_.find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text'),
  113. description: opts.description,
  114. editorKey: opts.editor,
  115. hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' }),
  116. isPrivate: opts.isPrivate,
  117. isPublished: opts.isPublished,
  118. localeCode: opts.locale,
  119. path: opts.path,
  120. publishEndDate: opts.publishEndDate || '',
  121. publishStartDate: opts.publishStartDate || '',
  122. title: opts.title,
  123. toc: '[]'
  124. })
  125. const page = await WIKI.models.pages.getPageFromDb({
  126. path: opts.path,
  127. locale: opts.locale,
  128. userId: opts.authorId,
  129. isPrivate: opts.isPrivate
  130. })
  131. await WIKI.models.pages.renderPage(page)
  132. await WIKI.models.storage.pageEvent({
  133. event: 'created',
  134. page
  135. })
  136. return page
  137. }
  138. static async updatePage(opts) {
  139. const ogPage = await WIKI.models.pages.query().findById(opts.id)
  140. if (!ogPage) {
  141. throw new Error('Invalid Page Id')
  142. }
  143. await WIKI.models.pageHistory.addVersion(ogPage)
  144. await WIKI.models.pages.query().patch({
  145. authorId: opts.authorId,
  146. content: opts.content,
  147. description: opts.description,
  148. isPublished: opts.isPublished,
  149. publishEndDate: opts.publishEndDate || '',
  150. publishStartDate: opts.publishStartDate || '',
  151. title: opts.title
  152. }).where('id', ogPage.id)
  153. const page = await WIKI.models.pages.getPageFromDb({
  154. path: ogPage.path,
  155. locale: ogPage.localeCode,
  156. userId: ogPage.authorId,
  157. isPrivate: ogPage.isPrivate
  158. })
  159. await WIKI.models.pages.renderPage(page)
  160. await WIKI.models.storage.pageEvent({
  161. event: 'updated',
  162. page
  163. })
  164. return page
  165. }
  166. static async renderPage(page) {
  167. const pipeline = await WIKI.models.renderers.getRenderingPipeline(page.contentType)
  168. WIKI.queue.job.renderPage.add({
  169. page,
  170. pipeline
  171. }, {
  172. removeOnComplete: true,
  173. removeOnFail: true
  174. })
  175. }
  176. static async getPage(opts) {
  177. let page = await WIKI.models.pages.getPageFromCache(opts)
  178. if (!page) {
  179. page = await WIKI.models.pages.getPageFromDb(opts)
  180. if (page) {
  181. await WIKI.models.pages.savePageToCache(page)
  182. }
  183. }
  184. return page
  185. }
  186. static async getPageFromDb(opts) {
  187. return WIKI.models.pages.query()
  188. .column([
  189. 'pages.*',
  190. {
  191. authorName: 'author.name',
  192. creatorName: 'creator.name'
  193. }
  194. ])
  195. .joinRelation('author')
  196. .joinRelation('creator')
  197. .where({
  198. 'pages.path': opts.path,
  199. 'pages.localeCode': opts.locale
  200. })
  201. .andWhere(builder => {
  202. builder.where({
  203. 'pages.isPublished': true
  204. }).orWhere({
  205. 'pages.isPublished': false,
  206. 'pages.authorId': opts.userId
  207. })
  208. })
  209. .andWhere(builder => {
  210. if (opts.isPrivate) {
  211. builder.where({ 'pages.isPrivate': true, 'pages.privateNS': opts.privateNS })
  212. } else {
  213. builder.where({ 'pages.isPrivate': false })
  214. }
  215. })
  216. .first()
  217. }
  218. static async savePageToCache(page) {
  219. const cachePath = path.join(process.cwd(), `data/cache/${page.hash}.bin`)
  220. await fs.outputFile(cachePath, WIKI.models.pages.cacheSchema.encode({
  221. authorId: page.authorId,
  222. authorName: page.authorName,
  223. createdAt: page.createdAt,
  224. creatorId: page.creatorId,
  225. creatorName: page.creatorName,
  226. description: page.description,
  227. isPrivate: page.isPrivate === 1 || page.isPrivate === true,
  228. isPublished: page.isPublished === 1 || page.isPublished === true,
  229. publishEndDate: page.publishEndDate,
  230. publishStartDate: page.publishStartDate,
  231. render: page.render,
  232. title: page.title,
  233. toc: _.isString(page.toc) ? page.toc : JSON.stringify(page.toc),
  234. updatedAt: page.updatedAt
  235. }))
  236. }
  237. static async getPageFromCache(opts) {
  238. const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' })
  239. const cachePath = path.join(process.cwd(), `data/cache/${pageHash}.bin`)
  240. try {
  241. const pageBuffer = await fs.readFile(cachePath)
  242. let page = WIKI.models.pages.cacheSchema.decode(pageBuffer)
  243. return {
  244. ...page,
  245. path: opts.path,
  246. localeCode: opts.locale,
  247. isPrivate: opts.isPrivate
  248. }
  249. } catch (err) {
  250. if (err.code === 'ENOENT') {
  251. return false
  252. }
  253. WIKI.logger.error(err)
  254. throw err
  255. }
  256. }
  257. }