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.

271 lines
7.2 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. updatedAt: 'string'
  104. })
  105. }
  106. static async createPage(opts) {
  107. await WIKI.models.pages.query().insert({
  108. authorId: opts.authorId,
  109. content: opts.content,
  110. creatorId: opts.authorId,
  111. contentType: _.get(_.find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text'),
  112. description: opts.description,
  113. editorKey: opts.editor,
  114. hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' }),
  115. isPrivate: opts.isPrivate,
  116. isPublished: opts.isPublished,
  117. localeCode: opts.locale,
  118. path: opts.path,
  119. publishEndDate: opts.publishEndDate,
  120. publishStartDate: opts.publishStartDate,
  121. title: opts.title
  122. })
  123. const page = await WIKI.models.pages.getPageFromDb({
  124. path: opts.path,
  125. locale: opts.locale,
  126. userId: opts.authorId,
  127. isPrivate: opts.isPrivate
  128. })
  129. await WIKI.models.pages.renderPage(page)
  130. await WIKI.models.storage.pageEvent({
  131. event: 'created',
  132. page
  133. })
  134. return page
  135. }
  136. static async updatePage(opts) {
  137. const ogPage = await WIKI.models.pages.query().findById(opts.id)
  138. if (!ogPage) {
  139. throw new Error('Invalid Page Id')
  140. }
  141. await WIKI.models.pageHistory.addVersion(ogPage)
  142. await WIKI.models.pages.query().patch({
  143. authorId: opts.authorId,
  144. content: opts.content,
  145. description: opts.description,
  146. isPublished: opts.isPublished,
  147. publishEndDate: opts.publishEndDate,
  148. publishStartDate: opts.publishStartDate,
  149. title: opts.title
  150. }).where('id', ogPage.id)
  151. const page = await WIKI.models.pages.getPageFromDb({
  152. path: ogPage.path,
  153. locale: ogPage.localeCode,
  154. userId: ogPage.authorId,
  155. isPrivate: ogPage.isPrivate
  156. })
  157. await WIKI.models.pages.renderPage(page)
  158. await WIKI.models.storage.pageEvent({
  159. event: 'updated',
  160. page
  161. })
  162. return page
  163. }
  164. static async renderPage(page) {
  165. const pipeline = await WIKI.models.renderers.getRenderingPipeline(page.contentType)
  166. WIKI.queue.job.renderPage.add({
  167. page,
  168. pipeline
  169. }, {
  170. removeOnComplete: true,
  171. removeOnFail: true
  172. })
  173. }
  174. static async getPage(opts) {
  175. let page = await WIKI.models.pages.getPageFromCache(opts)
  176. if (!page) {
  177. page = await WIKI.models.pages.getPageFromDb(opts)
  178. if (page) {
  179. await WIKI.models.pages.savePageToCache(page)
  180. }
  181. }
  182. return page
  183. }
  184. static async getPageFromDb(opts) {
  185. const page = await WIKI.models.pages.query()
  186. .column([
  187. 'pages.*',
  188. {
  189. authorName: 'author.name',
  190. creatorName: 'creator.name'
  191. }
  192. ])
  193. .joinRelation('author')
  194. .joinRelation('creator')
  195. .where({
  196. 'pages.path': opts.path,
  197. 'pages.localeCode': opts.locale
  198. })
  199. .andWhere(builder => {
  200. builder.where({
  201. 'pages.isPublished': true
  202. }).orWhere({
  203. 'pages.isPublished': false,
  204. 'pages.authorId': opts.userId
  205. })
  206. })
  207. .andWhere(builder => {
  208. if (opts.isPrivate) {
  209. builder.where({ 'pages.isPrivate': true, 'pages.privateNS': opts.privateNS })
  210. } else {
  211. builder.where({ 'pages.isPrivate': false })
  212. }
  213. })
  214. .first()
  215. return page
  216. }
  217. static async savePageToCache(page) {
  218. const cachePath = path.join(process.cwd(), `data/cache/${page.hash}.bin`)
  219. await fs.outputFile(cachePath, WIKI.models.pages.cacheSchema.encode({
  220. authorId: page.authorId,
  221. authorName: page.authorName,
  222. createdAt: page.createdAt,
  223. creatorId: page.creatorId,
  224. creatorName: page.creatorName,
  225. description: page.description,
  226. isPrivate: page.isPrivate === 1,
  227. isPublished: page.isPublished === 1,
  228. publishEndDate: page.publishEndDate,
  229. publishStartDate: page.publishStartDate,
  230. render: page.render,
  231. title: page.title,
  232. updatedAt: page.updatedAt
  233. }))
  234. }
  235. static async getPageFromCache(opts) {
  236. const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale, privateNS: opts.isPrivate ? 'TODO' : '' })
  237. const cachePath = path.join(process.cwd(), `data/cache/${pageHash}.bin`)
  238. try {
  239. const pageBuffer = await fs.readFile(cachePath)
  240. let page = WIKI.models.pages.cacheSchema.decode(pageBuffer)
  241. return {
  242. ...page,
  243. path: opts.path,
  244. localeCode: opts.locale,
  245. isPrivate: opts.isPrivate
  246. }
  247. } catch (err) {
  248. if (err.code === 'ENOENT') {
  249. return false
  250. }
  251. WIKI.logger.error(err)
  252. throw err
  253. }
  254. }
  255. }