pageHistory.mjs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import { Model } from 'objection'
  2. import { get, reduce, reverse } from 'lodash-es'
  3. import { DateTime, Duration } from 'luxon'
  4. import { Locale } from './locales.mjs'
  5. import { Page } from './pages.mjs'
  6. import { User } from './users.mjs'
  7. import { Tag } from './tags.mjs'
  8. /**
  9. * Page History model
  10. */
  11. export class PageHistory extends Model {
  12. static get tableName() { return 'pageHistory' }
  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. publishState: {type: 'string'},
  24. publishStartDate: {type: 'string'},
  25. publishEndDate: {type: 'string'},
  26. content: {type: 'string'},
  27. contentType: {type: 'string'},
  28. createdAt: {type: 'string'}
  29. }
  30. }
  31. }
  32. static get relationMappings() {
  33. return {
  34. tags: {
  35. relation: Model.ManyToManyRelation,
  36. modelClass: Tag,
  37. join: {
  38. from: 'pageHistory.id',
  39. through: {
  40. from: 'pageHistoryTags.pageId',
  41. to: 'pageHistoryTags.tagId'
  42. },
  43. to: 'tags.id'
  44. }
  45. },
  46. page: {
  47. relation: Model.BelongsToOneRelation,
  48. modelClass: Page,
  49. join: {
  50. from: 'pageHistory.pageId',
  51. to: 'pages.id'
  52. }
  53. },
  54. author: {
  55. relation: Model.BelongsToOneRelation,
  56. modelClass: User,
  57. join: {
  58. from: 'pageHistory.authorId',
  59. to: 'users.id'
  60. }
  61. }
  62. }
  63. }
  64. $beforeInsert() {
  65. this.createdAt = new Date().toISOString()
  66. }
  67. /**
  68. * Create Page Version
  69. */
  70. static async addVersion(opts) {
  71. await WIKI.db.pageHistory.query().insert({
  72. action: opts.historyData?.action ?? 'updated',
  73. affectedFields: JSON.stringify(opts.historyData?.affectedFields ?? []),
  74. alias: opts.alias,
  75. config: JSON.stringify(opts.config ?? {}),
  76. authorId: opts.authorId,
  77. content: opts.content,
  78. contentType: opts.contentType,
  79. description: opts.description,
  80. editor: opts.editor,
  81. hash: opts.hash,
  82. icon: opts.icon,
  83. locale: opts.locale,
  84. pageId: opts.id,
  85. path: opts.path,
  86. publishEndDate: opts.publishEndDate?.toISO(),
  87. publishStartDate: opts.publishStartDate?.toISO(),
  88. publishState: opts.publishState,
  89. reason: opts.historyData?.reason,
  90. relations: JSON.stringify(opts.relations ?? []),
  91. render: opts.render,
  92. scripts: JSON.stringify(opts.scripts ?? {}),
  93. siteId: opts.siteId,
  94. title: opts.title,
  95. toc: JSON.stringify(opts.toc ?? []),
  96. versionDate: opts.versionDate
  97. })
  98. }
  99. /**
  100. * Get Page Version
  101. */
  102. static async getVersion({ pageId, versionId }) {
  103. const version = await WIKI.db.pageHistory.query()
  104. .column([
  105. 'pageHistory.path',
  106. 'pageHistory.title',
  107. 'pageHistory.description',
  108. 'pageHistory.isPublished',
  109. 'pageHistory.publishStartDate',
  110. 'pageHistory.publishEndDate',
  111. 'pageHistory.content',
  112. 'pageHistory.contentType',
  113. 'pageHistory.createdAt',
  114. 'pageHistory.action',
  115. 'pageHistory.authorId',
  116. 'pageHistory.pageId',
  117. 'pageHistory.versionDate',
  118. {
  119. versionId: 'pageHistory.id',
  120. editor: 'pageHistory.editorKey',
  121. locale: 'pageHistory.locale',
  122. authorName: 'author.name'
  123. }
  124. ])
  125. .joinRelated('author')
  126. .where({
  127. 'pageHistory.id': versionId,
  128. 'pageHistory.pageId': pageId
  129. }).first()
  130. if (version) {
  131. return {
  132. ...version,
  133. updatedAt: version.createdAt || null,
  134. tags: []
  135. }
  136. } else {
  137. return null
  138. }
  139. }
  140. /**
  141. * Get History Trail of a Page
  142. */
  143. static async getHistory({ pageId, offsetPage = 0, offsetSize = 100 }) {
  144. const history = await WIKI.db.pageHistory.query()
  145. .column([
  146. 'pageHistory.id',
  147. 'pageHistory.path',
  148. 'pageHistory.authorId',
  149. 'pageHistory.action',
  150. 'pageHistory.versionDate',
  151. {
  152. authorName: 'author.name'
  153. }
  154. ])
  155. .joinRelated('author')
  156. .where({
  157. 'pageHistory.pageId': pageId
  158. })
  159. .orderBy('pageHistory.versionDate', 'desc')
  160. .page(offsetPage, offsetSize)
  161. let prevPh = null
  162. const upperLimit = (offsetPage + 1) * offsetSize
  163. if (history.total >= upperLimit) {
  164. prevPh = await WIKI.db.pageHistory.query()
  165. .column([
  166. 'pageHistory.id',
  167. 'pageHistory.path',
  168. 'pageHistory.authorId',
  169. 'pageHistory.action',
  170. 'pageHistory.versionDate',
  171. {
  172. authorName: 'author.name'
  173. }
  174. ])
  175. .joinRelated('author')
  176. .where({
  177. 'pageHistory.pageId': pageId
  178. })
  179. .orderBy('pageHistory.versionDate', 'desc')
  180. .offset((offsetPage + 1) * offsetSize)
  181. .limit(1)
  182. .first()
  183. }
  184. return {
  185. trail: reduce(reverse(history.results), (res, ph) => {
  186. let actionType = 'edit'
  187. let valueBefore = null
  188. let valueAfter = null
  189. if (!prevPh && history.total < upperLimit) {
  190. actionType = 'initial'
  191. } else if (get(prevPh, 'path', '') !== ph.path) {
  192. actionType = 'move'
  193. valueBefore = get(prevPh, 'path', '')
  194. valueAfter = ph.path
  195. }
  196. res.unshift({
  197. versionId: ph.id,
  198. authorId: ph.authorId,
  199. authorName: ph.authorName,
  200. actionType,
  201. valueBefore,
  202. valueAfter,
  203. versionDate: ph.versionDate
  204. })
  205. prevPh = ph
  206. return res
  207. }, []),
  208. total: history.total
  209. }
  210. }
  211. /**
  212. * Purge history older than X
  213. *
  214. * @param {String} olderThan ISO 8601 Duration
  215. */
  216. static async purge (olderThan) {
  217. const dur = Duration.fromISO(olderThan)
  218. const olderThanISO = DateTime.utc().minus(dur)
  219. await WIKI.db.pageHistory.query().where('versionDate', '<', olderThanISO.toISO()).del()
  220. }
  221. }