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.

607 lines
18 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  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. /**
  172. * FETCH TAGS
  173. */
  174. async tags (obj, args, context, info) {
  175. const pages = await WIKI.models.pages.query()
  176. .column([
  177. 'path',
  178. { locale: 'localeCode' }
  179. ])
  180. .withGraphJoined('tags')
  181. const allTags = _.filter(pages, r => {
  182. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  183. path: r.path,
  184. locale: r.locale
  185. })
  186. }).flatMap(r => r.tags)
  187. return _.orderBy(_.uniqBy(allTags, 'id'), ['tag'], ['asc'])
  188. },
  189. /**
  190. * SEARCH TAGS
  191. */
  192. async searchTags (obj, args, context, info) {
  193. const query = _.trim(args.query)
  194. const pages = await WIKI.models.pages.query()
  195. .column([
  196. 'path',
  197. { locale: 'localeCode' }
  198. ])
  199. .withGraphJoined('tags')
  200. .modifyGraph('tags', builder => {
  201. builder.select('tag')
  202. })
  203. .modify(queryBuilder => {
  204. queryBuilder.andWhere(builderSub => {
  205. if (WIKI.config.db.type === 'postgres') {
  206. builderSub.where('tags.tag', 'ILIKE', `%${query}%`)
  207. } else {
  208. builderSub.where('tags.tag', 'LIKE', `%${query}%`)
  209. }
  210. })
  211. })
  212. const allTags = _.filter(pages, r => {
  213. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  214. path: r.path,
  215. locale: r.locale
  216. })
  217. }).flatMap(r => r.tags).map(t => t.tag)
  218. return _.uniq(allTags).slice(0, 5)
  219. },
  220. /**
  221. * FETCH PAGE TREE
  222. */
  223. async tree (obj, args, context, info) {
  224. let curPage = null
  225. if (!args.locale) { args.locale = WIKI.config.lang.code }
  226. if (args.path && !args.parent) {
  227. curPage = await WIKI.models.knex('pageTree').first('parent', 'ancestors').where({
  228. path: args.path,
  229. localeCode: args.locale
  230. })
  231. if (curPage) {
  232. args.parent = curPage.parent || 0
  233. } else {
  234. return []
  235. }
  236. }
  237. const results = await WIKI.models.knex('pageTree').where(builder => {
  238. builder.where('localeCode', args.locale)
  239. switch (args.mode) {
  240. case 'FOLDERS':
  241. builder.andWhere('isFolder', true)
  242. break
  243. case 'PAGES':
  244. builder.andWhereNotNull('pageId')
  245. break
  246. }
  247. if (!args.parent || args.parent < 1) {
  248. builder.whereNull('parent')
  249. } else {
  250. builder.where('parent', args.parent)
  251. if (args.includeAncestors && curPage && curPage.ancestors.length > 0) {
  252. builder.orWhereIn('id', _.isString(curPage.ancestors) ? JSON.parse(curPage.ancestors) : curPage.ancestors)
  253. }
  254. }
  255. }).orderBy([{ column: 'isFolder', order: 'desc' }, 'title'])
  256. return results.filter(r => {
  257. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  258. path: r.path,
  259. locale: r.localeCode
  260. })
  261. }).map(r => ({
  262. ...r,
  263. parent: r.parent || 0,
  264. locale: r.localeCode
  265. }))
  266. },
  267. /**
  268. * FETCH PAGE LINKS
  269. */
  270. async links (obj, args, context, info) {
  271. let results
  272. if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') {
  273. results = await WIKI.models.knex('pages')
  274. .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' })
  275. .leftJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
  276. .where({
  277. 'pages.localeCode': args.locale
  278. })
  279. .unionAll(
  280. WIKI.models.knex('pageLinks')
  281. .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' })
  282. .leftJoin('pages', 'pageLinks.pageId', 'pages.id')
  283. .where({
  284. 'pages.localeCode': args.locale
  285. })
  286. )
  287. } else {
  288. results = await WIKI.models.knex('pages')
  289. .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' })
  290. .fullOuterJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
  291. .where({
  292. 'pages.localeCode': args.locale
  293. })
  294. }
  295. return _.reduce(results, (result, val) => {
  296. // -> Check if user has access to source and linked page
  297. if (
  298. !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.path, locale: args.locale }) ||
  299. !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.link, locale: val.locale })
  300. ) {
  301. return result
  302. }
  303. const existingEntry = _.findIndex(result, ['id', val.id])
  304. if (existingEntry >= 0) {
  305. if (val.link) {
  306. result[existingEntry].links.push(`${val.locale}/${val.link}`)
  307. }
  308. } else {
  309. result.push({
  310. id: val.id,
  311. title: val.title,
  312. path: `${args.locale}/${val.path}`,
  313. links: val.link ? [`${val.locale}/${val.link}`] : []
  314. })
  315. }
  316. return result
  317. }, [])
  318. },
  319. /**
  320. * CHECK FOR EDITING CONFLICT
  321. */
  322. async checkConflicts (obj, args, context, info) {
  323. let page = await WIKI.models.pages.query().select('path', 'localeCode', 'updatedAt').findById(args.id)
  324. if (page) {
  325. if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
  326. path: page.path,
  327. locale: page.localeCode
  328. })) {
  329. return page.updatedAt > args.checkoutDate
  330. } else {
  331. throw new WIKI.Error.PageUpdateForbidden()
  332. }
  333. } else {
  334. throw new WIKI.Error.PageNotFound()
  335. }
  336. },
  337. /**
  338. * FETCH LATEST VERSION FOR CONFLICT COMPARISON
  339. */
  340. async conflictLatest (obj, args, context, info) {
  341. let page = await WIKI.models.pages.getPageFromDb(args.id)
  342. if (page) {
  343. if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
  344. path: page.path,
  345. locale: page.localeCode
  346. })) {
  347. return {
  348. ...page,
  349. tags: page.tags.map(t => t.tag),
  350. locale: page.localeCode
  351. }
  352. } else {
  353. throw new WIKI.Error.PageViewForbidden()
  354. }
  355. } else {
  356. throw new WIKI.Error.PageNotFound()
  357. }
  358. }
  359. },
  360. PageMutation: {
  361. /**
  362. * CREATE PAGE
  363. */
  364. async create(obj, args, context) {
  365. try {
  366. const page = await WIKI.models.pages.createPage({
  367. ...args,
  368. user: context.req.user
  369. })
  370. return {
  371. responseResult: graphHelper.generateSuccess('Page created successfully.'),
  372. page
  373. }
  374. } catch (err) {
  375. return graphHelper.generateError(err)
  376. }
  377. },
  378. /**
  379. * UPDATE PAGE
  380. */
  381. async update(obj, args, context) {
  382. try {
  383. const page = await WIKI.models.pages.updatePage({
  384. ...args,
  385. user: context.req.user
  386. })
  387. return {
  388. responseResult: graphHelper.generateSuccess('Page has been updated.'),
  389. page
  390. }
  391. } catch (err) {
  392. return graphHelper.generateError(err)
  393. }
  394. },
  395. /**
  396. * CONVERT PAGE
  397. */
  398. async convert(obj, args, context) {
  399. try {
  400. await WIKI.models.pages.convertPage({
  401. ...args,
  402. user: context.req.user
  403. })
  404. return {
  405. responseResult: graphHelper.generateSuccess('Page has been converted.')
  406. }
  407. } catch (err) {
  408. return graphHelper.generateError(err)
  409. }
  410. },
  411. /**
  412. * MOVE PAGE
  413. */
  414. async move(obj, args, context) {
  415. try {
  416. await WIKI.models.pages.movePage({
  417. ...args,
  418. user: context.req.user
  419. })
  420. return {
  421. responseResult: graphHelper.generateSuccess('Page has been moved.')
  422. }
  423. } catch (err) {
  424. return graphHelper.generateError(err)
  425. }
  426. },
  427. /**
  428. * DELETE PAGE
  429. */
  430. async delete(obj, args, context) {
  431. try {
  432. await WIKI.models.pages.deletePage({
  433. ...args,
  434. user: context.req.user
  435. })
  436. return {
  437. responseResult: graphHelper.generateSuccess('Page has been deleted.')
  438. }
  439. } catch (err) {
  440. return graphHelper.generateError(err)
  441. }
  442. },
  443. /**
  444. * DELETE TAG
  445. */
  446. async deleteTag (obj, args, context) {
  447. try {
  448. const tagToDel = await WIKI.models.tags.query().findById(args.id)
  449. if (tagToDel) {
  450. await tagToDel.$relatedQuery('pages').unrelate()
  451. await WIKI.models.tags.query().deleteById(args.id)
  452. } else {
  453. throw new Error('This tag does not exist.')
  454. }
  455. return {
  456. responseResult: graphHelper.generateSuccess('Tag has been deleted.')
  457. }
  458. } catch (err) {
  459. return graphHelper.generateError(err)
  460. }
  461. },
  462. /**
  463. * UPDATE TAG
  464. */
  465. async updateTag (obj, args, context) {
  466. try {
  467. const affectedRows = await WIKI.models.tags.query()
  468. .findById(args.id)
  469. .patch({
  470. tag: _.trim(args.tag).toLowerCase(),
  471. title: _.trim(args.title)
  472. })
  473. if (affectedRows < 1) {
  474. throw new Error('This tag does not exist.')
  475. }
  476. return {
  477. responseResult: graphHelper.generateSuccess('Tag has been updated successfully.')
  478. }
  479. } catch (err) {
  480. return graphHelper.generateError(err)
  481. }
  482. },
  483. /**
  484. * FLUSH PAGE CACHE
  485. */
  486. async flushCache(obj, args, context) {
  487. try {
  488. await WIKI.models.pages.flushCache()
  489. WIKI.events.outbound.emit('flushCache')
  490. return {
  491. responseResult: graphHelper.generateSuccess('Pages Cache has been flushed successfully.')
  492. }
  493. } catch (err) {
  494. return graphHelper.generateError(err)
  495. }
  496. },
  497. /**
  498. * MIGRATE ALL PAGES FROM SOURCE LOCALE TO TARGET LOCALE
  499. */
  500. async migrateToLocale(obj, args, context) {
  501. try {
  502. const count = await WIKI.models.pages.migrateToLocale(args)
  503. return {
  504. responseResult: graphHelper.generateSuccess('Migrated content to target locale successfully.'),
  505. count
  506. }
  507. } catch (err) {
  508. return graphHelper.generateError(err)
  509. }
  510. },
  511. /**
  512. * REBUILD TREE
  513. */
  514. async rebuildTree(obj, args, context) {
  515. try {
  516. await WIKI.models.pages.rebuildTree()
  517. return {
  518. responseResult: graphHelper.generateSuccess('Page tree rebuilt successfully.')
  519. }
  520. } catch (err) {
  521. return graphHelper.generateError(err)
  522. }
  523. },
  524. /**
  525. * RENDER PAGE
  526. */
  527. async render (obj, args, context) {
  528. try {
  529. const page = await WIKI.models.pages.query().findById(args.id)
  530. if (!page) {
  531. throw new WIKI.Error.PageNotFound()
  532. }
  533. await WIKI.models.pages.renderPage(page)
  534. return {
  535. responseResult: graphHelper.generateSuccess('Page rendered successfully.')
  536. }
  537. } catch (err) {
  538. return graphHelper.generateError(err)
  539. }
  540. },
  541. /**
  542. * RESTORE PAGE VERSION
  543. */
  544. async restore (obj, args, context) {
  545. try {
  546. const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.pageId)
  547. if (!page) {
  548. throw new WIKI.Error.PageNotFound()
  549. }
  550. if (!WIKI.auth.checkAccess(context.req.user, ['write:pages'], {
  551. path: page.path,
  552. locale: page.localeCode
  553. })) {
  554. throw new WIKI.Error.PageRestoreForbidden()
  555. }
  556. const targetVersion = await WIKI.models.pageHistory.getVersion({ pageId: args.pageId, versionId: args.versionId })
  557. if (!targetVersion) {
  558. throw new WIKI.Error.PageNotFound()
  559. }
  560. await WIKI.models.pages.updatePage({
  561. ...targetVersion,
  562. id: targetVersion.pageId,
  563. user: context.req.user,
  564. action: 'restored'
  565. })
  566. return {
  567. responseResult: graphHelper.generateSuccess('Page version restored successfully.')
  568. }
  569. } catch (err) {
  570. return graphHelper.generateError(err)
  571. }
  572. },
  573. /**
  574. * Purge history
  575. */
  576. async purgeHistory (obj, args, context) {
  577. try {
  578. await WIKI.models.pageHistory.purge(args.olderThan)
  579. return {
  580. responseResult: graphHelper.generateSuccess('Page history purged successfully.')
  581. }
  582. } catch (err) {
  583. return graphHelper.generateError(err)
  584. }
  585. }
  586. },
  587. Page: {
  588. async tags (obj) {
  589. return WIKI.models.pages.relatedQuery('tags').for(obj.id)
  590. }
  591. // comments(pg) {
  592. // return pg.$relatedQuery('comments')
  593. // }
  594. }
  595. }