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.

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