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.

589 lines
17 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. }
  162. } else {
  163. throw new WIKI.Error.PageViewForbidden()
  164. }
  165. } else {
  166. throw new WIKI.Error.PageNotFound()
  167. }
  168. },
  169. /**
  170. * FETCH TAGS
  171. */
  172. async tags (obj, args, context, info) {
  173. const pages = await WIKI.models.pages.query()
  174. .column([
  175. 'path',
  176. { locale: 'localeCode' }
  177. ])
  178. .withGraphJoined('tags')
  179. const allTags = _.filter(pages, r => {
  180. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  181. path: r.path,
  182. locale: r.locale
  183. })
  184. }).flatMap(r => r.tags)
  185. return _.orderBy(_.uniqBy(allTags, 'id'), ['tag'], ['asc'])
  186. },
  187. /**
  188. * SEARCH TAGS
  189. */
  190. async searchTags (obj, args, context, info) {
  191. const query = _.trim(args.query)
  192. const pages = await WIKI.models.pages.query()
  193. .column([
  194. 'path',
  195. { locale: 'localeCode' }
  196. ])
  197. .withGraphJoined('tags')
  198. .modifyGraph('tags', builder => {
  199. builder.select('tag')
  200. })
  201. .modify(queryBuilder => {
  202. queryBuilder.andWhere(builderSub => {
  203. if (WIKI.config.db.type === 'postgres') {
  204. builderSub.where('tags.tag', 'ILIKE', `%${query}%`)
  205. } else {
  206. builderSub.where('tags.tag', 'LIKE', `%${query}%`)
  207. }
  208. })
  209. })
  210. const allTags = _.filter(pages, r => {
  211. return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
  212. path: r.path,
  213. locale: r.locale
  214. })
  215. }).flatMap(r => r.tags).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. * Purge history
  557. */
  558. async purgeHistory (obj, args, context) {
  559. try {
  560. await WIKI.models.pageHistory.purge(args.olderThan)
  561. return {
  562. responseResult: graphHelper.generateSuccess('Page history purged successfully.')
  563. }
  564. } catch (err) {
  565. return graphHelper.generateError(err)
  566. }
  567. }
  568. },
  569. Page: {
  570. async tags (obj) {
  571. return WIKI.models.pages.relatedQuery('tags').for(obj.id)
  572. }
  573. // comments(pg) {
  574. // return pg.$relatedQuery('comments')
  575. // }
  576. }
  577. }