page.js 17 KB

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