page.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. const _ = require('lodash')
  2. const graphHelper = require('../../helpers/graph')
  3. /* global WIKI */
  4. module.exports = {
  5. Query: {
  6. /**
  7. * PAGE HISTORY
  8. */
  9. async pageHistoryById (obj, args, context, info) {
  10. const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.id)
  11. if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
  12. path: page.path,
  13. locale: page.localeCode
  14. })) {
  15. return WIKI.models.pageHistory.getHistory({
  16. pageId: args.id,
  17. offsetPage: args.offsetPage || 0,
  18. offsetSize: args.offsetSize || 100
  19. })
  20. } else {
  21. throw new WIKI.Error.PageHistoryForbidden()
  22. }
  23. },
  24. /**
  25. * PAGE VERSION
  26. */
  27. async pageVersionById (obj, args, context, info) {
  28. const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.pageId)
  29. if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
  30. path: page.path,
  31. locale: page.localeCode
  32. })) {
  33. return WIKI.models.pageHistory.getVersion({
  34. pageId: args.pageId,
  35. versionId: args.versionId
  36. })
  37. } else {
  38. throw new WIKI.Error.PageHistoryForbidden()
  39. }
  40. },
  41. /**
  42. * SEARCH PAGES
  43. */
  44. async searchPages (obj, args, context) {
  45. if (WIKI.data.searchEngine) {
  46. const resp = await WIKI.data.searchEngine.query(args.query, args)
  47. return {
  48. ...resp,
  49. results: _.filter(resp.results, r => {
  50. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  51. path: r.path,
  52. locale: r.locale,
  53. tags: r.tags // Tags are needed since access permissions can be limited by page tags too
  54. })
  55. })
  56. }
  57. } else {
  58. return {
  59. results: [],
  60. suggestions: [],
  61. totalHits: 0
  62. }
  63. }
  64. },
  65. /**
  66. * LIST PAGES
  67. */
  68. async pages (obj, args, context, info) {
  69. let results = await WIKI.models.pages.query().column([
  70. 'pages.id',
  71. 'path',
  72. { locale: 'localeCode' },
  73. 'title',
  74. 'description',
  75. 'isPublished',
  76. 'isPrivate',
  77. 'privateNS',
  78. 'contentType',
  79. 'createdAt',
  80. 'updatedAt'
  81. ])
  82. .withGraphJoined('tags')
  83. .modifyGraph('tags', builder => {
  84. builder.select('tag')
  85. })
  86. .modify(queryBuilder => {
  87. if (args.limit) {
  88. queryBuilder.limit(args.limit)
  89. }
  90. if (args.locale) {
  91. queryBuilder.where('localeCode', args.locale)
  92. }
  93. if (args.creatorId && args.authorId && args.creatorId > 0 && args.authorId > 0) {
  94. queryBuilder.where(function () {
  95. this.where('creatorId', args.creatorId).orWhere('authorId', args.authorId)
  96. })
  97. } else {
  98. if (args.creatorId && args.creatorId > 0) {
  99. queryBuilder.where('creatorId', args.creatorId)
  100. }
  101. if (args.authorId && args.authorId > 0) {
  102. queryBuilder.where('authorId', args.authorId)
  103. }
  104. }
  105. if (args.tags && args.tags.length > 0) {
  106. queryBuilder.whereIn('tags.tag', args.tags.map(t => _.trim(t).toLowerCase()))
  107. }
  108. const orderDir = args.orderByDirection === 'DESC' ? 'desc' : 'asc'
  109. switch (args.orderBy) {
  110. case 'CREATED':
  111. queryBuilder.orderBy('createdAt', orderDir)
  112. break
  113. case 'PATH':
  114. queryBuilder.orderBy('path', orderDir)
  115. break
  116. case 'TITLE':
  117. queryBuilder.orderBy('title', orderDir)
  118. break
  119. case 'UPDATED':
  120. queryBuilder.orderBy('updatedAt', orderDir)
  121. break
  122. default:
  123. queryBuilder.orderBy('pages.id', orderDir)
  124. break
  125. }
  126. })
  127. results = _.filter(results, r => {
  128. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  129. path: r.path,
  130. locale: r.locale
  131. })
  132. }).map(r => ({
  133. ...r,
  134. tags: _.map(r.tags, 'tag')
  135. }))
  136. if (args.tags && args.tags.length > 0) {
  137. results = _.filter(results, r => _.every(args.tags, t => _.includes(r.tags, t)))
  138. }
  139. return results
  140. },
  141. /**
  142. * FETCH SINGLE PAGE
  143. */
  144. async pageById (obj, args, context, info) {
  145. let page = await WIKI.models.pages.getPageFromDb(args.id)
  146. if (page) {
  147. if (WIKI.auth.checkAccess(context.req.user, ['manage:pages', 'delete:pages'], {
  148. path: page.path,
  149. locale: page.localeCode
  150. })) {
  151. return {
  152. ...page,
  153. locale: page.localeCode,
  154. editor: page.editorKey
  155. }
  156. } else {
  157. throw new WIKI.Error.PageViewForbidden()
  158. }
  159. } else {
  160. throw new WIKI.Error.PageNotFound()
  161. }
  162. },
  163. /**
  164. * FETCH TAGS
  165. */
  166. async tags (obj, args, context, info) {
  167. const pages = await WIKI.models.pages.query()
  168. .column([
  169. 'path',
  170. { locale: 'localeCode' }
  171. ])
  172. .withGraphJoined('tags')
  173. const allTags = _.filter(pages, r => {
  174. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  175. path: r.path,
  176. locale: r.locale
  177. })
  178. }).flatMap(r => r.tags)
  179. return _.orderBy(_.uniqBy(allTags, 'id'), ['tag'], ['asc'])
  180. },
  181. /**
  182. * SEARCH TAGS
  183. */
  184. async searchTags (obj, args, context, info) {
  185. const query = _.trim(args.query)
  186. const pages = await WIKI.models.pages.query()
  187. .column([
  188. 'path',
  189. { locale: 'localeCode' }
  190. ])
  191. .withGraphJoined('tags')
  192. .modifyGraph('tags', builder => {
  193. builder.select('tag')
  194. })
  195. .modify(queryBuilder => {
  196. queryBuilder.andWhere(builderSub => {
  197. if (WIKI.config.db.type === 'postgres') {
  198. builderSub.where('tags.tag', 'ILIKE', `%${query}%`)
  199. } else {
  200. builderSub.where('tags.tag', 'LIKE', `%${query}%`)
  201. }
  202. })
  203. })
  204. const allTags = _.filter(pages, r => {
  205. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  206. path: r.path,
  207. locale: r.locale
  208. })
  209. }).flatMap(r => r.tags).map(t => t.tag)
  210. return _.uniq(allTags).slice(0, 5)
  211. },
  212. /**
  213. * FETCH PAGE TREE
  214. */
  215. async pageTree (obj, args, context, info) {
  216. let curPage = null
  217. if (!args.locale) { args.locale = WIKI.config.lang.code }
  218. if (args.path && !args.parent) {
  219. curPage = await WIKI.models.knex('pageTree').first('parent', 'ancestors').where({
  220. path: args.path,
  221. localeCode: args.locale
  222. })
  223. if (curPage) {
  224. args.parent = curPage.parent || 0
  225. } else {
  226. return []
  227. }
  228. }
  229. const results = await WIKI.models.knex('pageTree').where(builder => {
  230. builder.where('localeCode', args.locale)
  231. switch (args.mode) {
  232. case 'FOLDERS':
  233. builder.andWhere('isFolder', true)
  234. break
  235. case 'PAGES':
  236. builder.andWhereNotNull('pageId')
  237. break
  238. }
  239. if (!args.parent || args.parent < 1) {
  240. builder.whereNull('parent')
  241. } else {
  242. builder.where('parent', args.parent)
  243. if (args.includeAncestors && curPage && curPage.ancestors.length > 0) {
  244. builder.orWhereIn('id', _.isString(curPage.ancestors) ? JSON.parse(curPage.ancestors) : curPage.ancestors)
  245. }
  246. }
  247. }).orderBy([{ column: 'isFolder', order: 'desc' }, 'title'])
  248. return results.filter(r => {
  249. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  250. path: r.path,
  251. locale: r.localeCode
  252. })
  253. }).map(r => ({
  254. ...r,
  255. parent: r.parent || 0,
  256. locale: r.localeCode
  257. }))
  258. },
  259. /**
  260. * FETCH PAGE LINKS
  261. */
  262. async pageLinks (obj, args, context, info) {
  263. let results
  264. if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') {
  265. results = await WIKI.models.knex('pages')
  266. .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' })
  267. .leftJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
  268. .where({
  269. 'pages.localeCode': args.locale
  270. })
  271. .unionAll(
  272. WIKI.models.knex('pageLinks')
  273. .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' })
  274. .leftJoin('pages', 'pageLinks.pageId', 'pages.id')
  275. .where({
  276. 'pages.localeCode': args.locale
  277. })
  278. )
  279. } else {
  280. results = await WIKI.models.knex('pages')
  281. .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' })
  282. .fullOuterJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
  283. .where({
  284. 'pages.localeCode': args.locale
  285. })
  286. }
  287. return _.reduce(results, (result, val) => {
  288. // -> Check if user has access to source and linked page
  289. if (
  290. !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.path, locale: args.locale }) ||
  291. !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.link, locale: val.locale })
  292. ) {
  293. return result
  294. }
  295. const existingEntry = _.findIndex(result, ['id', val.id])
  296. if (existingEntry >= 0) {
  297. if (val.link) {
  298. result[existingEntry].links.push(`${val.locale}/${val.link}`)
  299. }
  300. } else {
  301. result.push({
  302. id: val.id,
  303. title: val.title,
  304. path: `${args.locale}/${val.path}`,
  305. links: val.link ? [`${val.locale}/${val.link}`] : []
  306. })
  307. }
  308. return result
  309. }, [])
  310. },
  311. /**
  312. * CHECK FOR EDITING CONFLICT
  313. */
  314. async checkConflicts (obj, args, context, info) {
  315. let page = await WIKI.models.pages.query().select('path', 'localeCode', 'updatedAt').findById(args.id)
  316. if (page) {
  317. if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
  318. path: page.path,
  319. locale: page.localeCode
  320. })) {
  321. return page.updatedAt > args.checkoutDate
  322. } else {
  323. throw new WIKI.Error.PageUpdateForbidden()
  324. }
  325. } else {
  326. throw new WIKI.Error.PageNotFound()
  327. }
  328. },
  329. /**
  330. * FETCH LATEST VERSION FOR CONFLICT COMPARISON
  331. */
  332. async checkConflictsLatest (obj, args, context, info) {
  333. let page = await WIKI.models.pages.getPageFromDb(args.id)
  334. if (page) {
  335. if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
  336. path: page.path,
  337. locale: page.localeCode
  338. })) {
  339. return {
  340. ...page,
  341. tags: page.tags.map(t => t.tag),
  342. locale: page.localeCode
  343. }
  344. } else {
  345. throw new WIKI.Error.PageViewForbidden()
  346. }
  347. } else {
  348. throw new WIKI.Error.PageNotFound()
  349. }
  350. }
  351. },
  352. Mutation: {
  353. /**
  354. * CREATE PAGE
  355. */
  356. async createPage(obj, args, context) {
  357. try {
  358. const page = await WIKI.models.pages.createPage({
  359. ...args,
  360. user: context.req.user
  361. })
  362. return {
  363. responseResult: graphHelper.generateSuccess('Page created successfully.'),
  364. page
  365. }
  366. } catch (err) {
  367. return graphHelper.generateError(err)
  368. }
  369. },
  370. /**
  371. * UPDATE PAGE
  372. */
  373. async updatePage(obj, args, context) {
  374. try {
  375. const page = await WIKI.models.pages.updatePage({
  376. ...args,
  377. user: context.req.user
  378. })
  379. return {
  380. responseResult: graphHelper.generateSuccess('Page has been updated.'),
  381. page
  382. }
  383. } catch (err) {
  384. return graphHelper.generateError(err)
  385. }
  386. },
  387. /**
  388. * CONVERT PAGE
  389. */
  390. async convertPage(obj, args, context) {
  391. try {
  392. await WIKI.models.pages.convertPage({
  393. ...args,
  394. user: context.req.user
  395. })
  396. return {
  397. responseResult: graphHelper.generateSuccess('Page has been converted.')
  398. }
  399. } catch (err) {
  400. return graphHelper.generateError(err)
  401. }
  402. },
  403. /**
  404. * RENAME PAGE
  405. */
  406. async renamePage(obj, args, context) {
  407. try {
  408. await WIKI.models.pages.movePage({
  409. ...args,
  410. user: context.req.user
  411. })
  412. return {
  413. responseResult: graphHelper.generateSuccess('Page has been moved.')
  414. }
  415. } catch (err) {
  416. return graphHelper.generateError(err)
  417. }
  418. },
  419. /**
  420. * DELETE PAGE
  421. */
  422. async deletePage(obj, args, context) {
  423. try {
  424. await WIKI.models.pages.deletePage({
  425. ...args,
  426. user: context.req.user
  427. })
  428. return {
  429. responseResult: graphHelper.generateSuccess('Page has been deleted.')
  430. }
  431. } catch (err) {
  432. return graphHelper.generateError(err)
  433. }
  434. },
  435. /**
  436. * DELETE TAG
  437. */
  438. async deleteTag (obj, args, context) {
  439. try {
  440. const tagToDel = await WIKI.models.tags.query().findById(args.id)
  441. if (tagToDel) {
  442. await tagToDel.$relatedQuery('pages').unrelate()
  443. await WIKI.models.tags.query().deleteById(args.id)
  444. } else {
  445. throw new Error('This tag does not exist.')
  446. }
  447. return {
  448. responseResult: graphHelper.generateSuccess('Tag has been deleted.')
  449. }
  450. } catch (err) {
  451. return graphHelper.generateError(err)
  452. }
  453. },
  454. /**
  455. * UPDATE TAG
  456. */
  457. async updateTag (obj, args, context) {
  458. try {
  459. const affectedRows = await WIKI.models.tags.query()
  460. .findById(args.id)
  461. .patch({
  462. tag: _.trim(args.tag).toLowerCase(),
  463. title: _.trim(args.title)
  464. })
  465. if (affectedRows < 1) {
  466. throw new Error('This tag does not exist.')
  467. }
  468. return {
  469. responseResult: graphHelper.generateSuccess('Tag has been updated successfully.')
  470. }
  471. } catch (err) {
  472. return graphHelper.generateError(err)
  473. }
  474. },
  475. /**
  476. * FLUSH PAGE CACHE
  477. */
  478. async flushCache(obj, args, context) {
  479. try {
  480. await WIKI.models.pages.flushCache()
  481. WIKI.events.outbound.emit('flushCache')
  482. return {
  483. responseResult: graphHelper.generateSuccess('Pages Cache has been flushed successfully.')
  484. }
  485. } catch (err) {
  486. return graphHelper.generateError(err)
  487. }
  488. },
  489. /**
  490. * MIGRATE ALL PAGES FROM SOURCE LOCALE TO TARGET LOCALE
  491. */
  492. async migrateToLocale(obj, args, context) {
  493. try {
  494. const count = await WIKI.models.pages.migrateToLocale(args)
  495. return {
  496. responseResult: graphHelper.generateSuccess('Migrated content to target locale successfully.'),
  497. count
  498. }
  499. } catch (err) {
  500. return graphHelper.generateError(err)
  501. }
  502. },
  503. /**
  504. * REBUILD TREE
  505. */
  506. async rebuildPageTree(obj, args, context) {
  507. try {
  508. await WIKI.models.pages.rebuildTree()
  509. return {
  510. responseResult: graphHelper.generateSuccess('Page tree rebuilt successfully.')
  511. }
  512. } catch (err) {
  513. return graphHelper.generateError(err)
  514. }
  515. },
  516. /**
  517. * RENDER PAGE
  518. */
  519. async renderPage (obj, args, context) {
  520. try {
  521. const page = await WIKI.models.pages.query().findById(args.id)
  522. if (!page) {
  523. throw new WIKI.Error.PageNotFound()
  524. }
  525. await WIKI.models.pages.renderPage(page)
  526. return {
  527. responseResult: graphHelper.generateSuccess('Page rendered successfully.')
  528. }
  529. } catch (err) {
  530. return graphHelper.generateError(err)
  531. }
  532. },
  533. /**
  534. * RESTORE PAGE VERSION
  535. */
  536. async restorePage (obj, args, context) {
  537. try {
  538. const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.pageId)
  539. if (!page) {
  540. throw new WIKI.Error.PageNotFound()
  541. }
  542. if (!WIKI.auth.checkAccess(context.req.user, ['write:pages'], {
  543. path: page.path,
  544. locale: page.localeCode
  545. })) {
  546. throw new WIKI.Error.PageRestoreForbidden()
  547. }
  548. const targetVersion = await WIKI.models.pageHistory.getVersion({ pageId: args.pageId, versionId: args.versionId })
  549. if (!targetVersion) {
  550. throw new WIKI.Error.PageNotFound()
  551. }
  552. await WIKI.models.pages.updatePage({
  553. ...targetVersion,
  554. id: targetVersion.pageId,
  555. user: context.req.user,
  556. action: 'restored'
  557. })
  558. return {
  559. responseResult: graphHelper.generateSuccess('Page version restored successfully.')
  560. }
  561. } catch (err) {
  562. return graphHelper.generateError(err)
  563. }
  564. },
  565. /**
  566. * Purge history
  567. */
  568. async purgePagesHistory (obj, args, context) {
  569. try {
  570. await WIKI.models.pageHistory.purge(args.olderThan)
  571. return {
  572. responseResult: graphHelper.generateSuccess('Page history purged successfully.')
  573. }
  574. } catch (err) {
  575. return graphHelper.generateError(err)
  576. }
  577. }
  578. },
  579. Page: {
  580. async tags (obj) {
  581. return WIKI.models.pages.relatedQuery('tags').for(obj.id)
  582. }
  583. // comments(pg) {
  584. // return pg.$relatedQuery('comments')
  585. // }
  586. }
  587. }