page.js 18 KB

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