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.

481 lines
15 KiB

  1. const express = require('express')
  2. const router = express.Router()
  3. const pageHelper = require('../helpers/page')
  4. const _ = require('lodash')
  5. /* global WIKI */
  6. const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/
  7. /**
  8. * Robots.txt
  9. */
  10. router.get('/robots.txt', (req, res, next) => {
  11. res.type('text/plain')
  12. if (_.includes(WIKI.config.seo.robots, 'noindex')) {
  13. res.send('User-agent: *\nDisallow: /')
  14. } else {
  15. res.status(200).end()
  16. }
  17. })
  18. /**
  19. * Health Endpoint
  20. */
  21. router.get('/healthz', (req, res, next) => {
  22. if (WIKI.models.knex.client.pool.numFree() < 1 && WIKI.models.knex.client.pool.numUsed() < 1) {
  23. res.status(503).json({ ok: false }).end()
  24. } else {
  25. res.status(200).json({ ok: true }).end()
  26. }
  27. })
  28. /**
  29. * Administration
  30. */
  31. router.get(['/a', '/a/*'], (req, res, next) => {
  32. _.set(res.locals, 'pageMeta.title', 'Admin')
  33. res.render('admin')
  34. })
  35. /**
  36. * Download Page / Version
  37. */
  38. router.get(['/d', '/d/*'], async (req, res, next) => {
  39. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  40. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  41. const page = await WIKI.models.pages.getPageFromDb({
  42. path: pageArgs.path,
  43. locale: pageArgs.locale,
  44. userId: req.user.id,
  45. isPrivate: false
  46. })
  47. pageArgs.tags = _.get(page, 'tags', [])
  48. if (versionId > 0) {
  49. if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
  50. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  51. return res.render('unauthorized', { action: 'downloadVersion' })
  52. }
  53. } else {
  54. if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
  55. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  56. return res.render('unauthorized', { action: 'download' })
  57. }
  58. }
  59. if (page) {
  60. const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType)
  61. res.attachment(fileName)
  62. if (versionId > 0) {
  63. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: page.id, versionId })
  64. res.send(pageHelper.injectPageMetadata(pageVersion))
  65. } else {
  66. res.send(pageHelper.injectPageMetadata(page))
  67. }
  68. } else {
  69. res.status(404).end()
  70. }
  71. })
  72. /**
  73. * Create/Edit document
  74. */
  75. router.get(['/e', '/e/*'], async (req, res, next) => {
  76. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  77. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  78. return res.redirect(`/e/${pageArgs.locale}/${pageArgs.path}`)
  79. }
  80. // -> Set Editor Lang
  81. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  82. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  83. // -> Check for reserved path
  84. if (pageHelper.isReservedPath(pageArgs.path)) {
  85. return next(new Error('Cannot create this page because it starts with a system reserved path.'))
  86. }
  87. // -> Get page data from DB
  88. let page = await WIKI.models.pages.getPageFromDb({
  89. path: pageArgs.path,
  90. locale: pageArgs.locale,
  91. userId: req.user.id,
  92. isPrivate: false
  93. })
  94. pageArgs.tags = _.get(page, 'tags', [])
  95. const injectCode = {
  96. css: WIKI.config.theming.injectCSS,
  97. head: WIKI.config.theming.injectHead,
  98. body: WIKI.config.theming.injectBody
  99. }
  100. if (page) {
  101. // -> EDIT MODE
  102. if (!WIKI.auth.checkAccess(req.user, ['write:pages', 'manage:pages'], pageArgs)) {
  103. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  104. return res.render('unauthorized', { action: 'edit' })
  105. }
  106. // -> Get page tags
  107. await page.$relatedQuery('tags')
  108. page.tags = _.map(page.tags, 'tag')
  109. _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
  110. _.set(res.locals, 'pageMeta.description', page.description)
  111. page.mode = 'update'
  112. page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
  113. page.content = Buffer.from(page.content).toString('base64')
  114. } else {
  115. // -> CREATE MODE
  116. if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
  117. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  118. return res.render('unauthorized', { action: 'create' })
  119. }
  120. _.set(res.locals, 'pageMeta.title', `New Page`)
  121. page = {
  122. path: pageArgs.path,
  123. localeCode: pageArgs.locale,
  124. editorKey: null,
  125. mode: 'create',
  126. content: null,
  127. title: null,
  128. description: null,
  129. updatedAt: new Date().toISOString()
  130. }
  131. // -> From Template
  132. if (req.query.from && tmplCreateRegex.test(req.query.from)) {
  133. let tmplPageId = 0
  134. let tmplVersionId = 0
  135. if (req.query.from.indexOf(',')) {
  136. const q = req.query.from.split(',')
  137. tmplPageId = _.toSafeInteger(q[0])
  138. tmplVersionId = _.toSafeInteger(q[1])
  139. } else {
  140. tmplPageId = _.toSafeInteger(req.query.from)
  141. }
  142. if (tmplVersionId > 0) {
  143. // -> From Page Version
  144. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: tmplPageId, versionId: tmplVersionId })
  145. if (!pageVersion) {
  146. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  147. return res.status(404).render('notfound', { action: 'template' })
  148. }
  149. if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
  150. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  151. return res.render('unauthorized', { action: 'sourceVersion' })
  152. }
  153. page.content = Buffer.from(pageVersion.content).toString('base64')
  154. page.editorKey = pageVersion.editor
  155. page.title = pageVersion.title
  156. page.description = pageVersion.description
  157. } else {
  158. // -> From Page Live
  159. const pageOriginal = await WIKI.models.pages.query().findById(tmplPageId)
  160. if (!pageOriginal) {
  161. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  162. return res.status(404).render('notfound', { action: 'template' })
  163. }
  164. if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
  165. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  166. return res.render('unauthorized', { action: 'source' })
  167. }
  168. page.content = Buffer.from(pageOriginal.content).toString('base64')
  169. page.editorKey = pageOriginal.editorKey
  170. page.title = pageOriginal.title
  171. page.description = pageOriginal.description
  172. }
  173. }
  174. }
  175. res.render('editor', { page, injectCode })
  176. })
  177. /**
  178. * History
  179. */
  180. router.get(['/h', '/h/*'], async (req, res, next) => {
  181. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  182. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  183. return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
  184. }
  185. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  186. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  187. const page = await WIKI.models.pages.getPageFromDb({
  188. path: pageArgs.path,
  189. locale: pageArgs.locale,
  190. userId: req.user.id,
  191. isPrivate: false
  192. })
  193. if (!page) {
  194. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  195. return res.status(404).render('notfound', { action: 'history' })
  196. }
  197. pageArgs.tags = _.get(page, 'tags', [])
  198. if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
  199. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  200. return res.render('unauthorized', { action: 'history' })
  201. }
  202. if (page) {
  203. _.set(res.locals, 'pageMeta.title', page.title)
  204. _.set(res.locals, 'pageMeta.description', page.description)
  205. res.render('history', { page })
  206. } else {
  207. res.redirect(`/${pageArgs.path}`)
  208. }
  209. })
  210. /**
  211. * Page ID redirection
  212. */
  213. router.get(['/i', '/i/:id'], async (req, res, next) => {
  214. const pageId = _.toSafeInteger(req.params.id)
  215. if (pageId <= 0) {
  216. return res.redirect('/')
  217. }
  218. const page = await WIKI.models.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
  219. if (!page) {
  220. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  221. return res.status(404).render('notfound', { action: 'view' })
  222. }
  223. if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
  224. locale: page.localeCode,
  225. path: page.path,
  226. private: page.isPrivate,
  227. privateNS: page.privateNS,
  228. explicitLocale: false,
  229. tags: page.tags
  230. })) {
  231. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  232. return res.render('unauthorized', { action: 'view' })
  233. }
  234. if (WIKI.config.lang.namespacing) {
  235. return res.redirect(`/${page.localeCode}/${page.path}`)
  236. } else {
  237. return res.redirect(`/${page.path}`)
  238. }
  239. })
  240. /**
  241. * Profile
  242. */
  243. router.get(['/p', '/p/*'], (req, res, next) => {
  244. if (!req.user || req.user.id < 1 || req.user.id === 2) {
  245. return res.render('unauthorized', { action: 'view' })
  246. }
  247. _.set(res.locals, 'pageMeta.title', 'User Profile')
  248. res.render('profile')
  249. })
  250. /**
  251. * Source
  252. */
  253. router.get(['/s', '/s/*'], async (req, res, next) => {
  254. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  255. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  256. const page = await WIKI.models.pages.getPageFromDb({
  257. path: pageArgs.path,
  258. locale: pageArgs.locale,
  259. userId: req.user.id,
  260. isPrivate: false
  261. })
  262. pageArgs.tags = _.get(page, 'tags', [])
  263. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  264. return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
  265. }
  266. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  267. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  268. if (versionId > 0) {
  269. if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
  270. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  271. return res.render('unauthorized', { action: 'sourceVersion' })
  272. }
  273. } else {
  274. if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
  275. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  276. return res.render('unauthorized', { action: 'source' })
  277. }
  278. }
  279. if (page) {
  280. if (versionId > 0) {
  281. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: page.id, versionId })
  282. _.set(res.locals, 'pageMeta.title', pageVersion.title)
  283. _.set(res.locals, 'pageMeta.description', pageVersion.description)
  284. res.render('source', {
  285. page: {
  286. ...page,
  287. ...pageVersion
  288. }
  289. })
  290. } else {
  291. _.set(res.locals, 'pageMeta.title', page.title)
  292. _.set(res.locals, 'pageMeta.description', page.description)
  293. res.render('source', { page })
  294. }
  295. } else {
  296. res.redirect(`/${pageArgs.path}`)
  297. }
  298. })
  299. /**
  300. * Tags
  301. */
  302. router.get(['/t', '/t/*'], (req, res, next) => {
  303. _.set(res.locals, 'pageMeta.title', 'Tags')
  304. res.render('tags')
  305. })
  306. /**
  307. * View document / asset
  308. */
  309. router.get('/*', async (req, res, next) => {
  310. const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
  311. const pageArgs = pageHelper.parsePath(req.path, { stripExt })
  312. const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
  313. if (isPage) {
  314. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  315. return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
  316. }
  317. req.i18n.changeLanguage(pageArgs.locale)
  318. try {
  319. // -> Get Page from cache
  320. const page = await WIKI.models.pages.getPage({
  321. path: pageArgs.path,
  322. locale: pageArgs.locale,
  323. userId: req.user.id,
  324. isPrivate: false
  325. })
  326. pageArgs.tags = _.get(page, 'tags', [])
  327. // -> Check User Access
  328. if (!WIKI.auth.checkAccess(req.user, ['read:pages'], pageArgs)) {
  329. if (req.user.id === 2) {
  330. res.cookie('loginRedirect', req.path, {
  331. maxAge: 15 * 60 * 1000
  332. })
  333. }
  334. if (pageArgs.path === 'home' && req.user.id === 2) {
  335. return res.redirect('/login')
  336. }
  337. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  338. return res.status(403).render('unauthorized', {
  339. action: 'view'
  340. })
  341. }
  342. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  343. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  344. if (page) {
  345. _.set(res.locals, 'pageMeta.title', page.title)
  346. _.set(res.locals, 'pageMeta.description', page.description)
  347. // -> Build sidebar navigation
  348. let sdi = 1
  349. const sidebar = (await WIKI.models.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({
  350. i: `sdi-${sdi++}`,
  351. k: n.kind,
  352. l: n.label,
  353. c: n.icon,
  354. y: n.targetType,
  355. t: n.target
  356. }))
  357. // -> Build theme code injection
  358. const injectCode = {
  359. css: WIKI.config.theming.injectCSS,
  360. head: WIKI.config.theming.injectHead,
  361. body: WIKI.config.theming.injectBody
  362. }
  363. if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
  364. // -> Convert page TOC
  365. if (_.isString(page.toc)) {
  366. page.toc = JSON.parse(page.toc)
  367. }
  368. // -> Render legacy view
  369. res.render('legacy/page', {
  370. page,
  371. sidebar,
  372. injectCode,
  373. isAuthenticated: req.user && req.user.id !== 2
  374. })
  375. } else {
  376. // -> Convert page TOC
  377. if (!_.isString(page.toc)) {
  378. page.toc = JSON.stringify(page.toc)
  379. }
  380. // -> Inject comments variables
  381. if (WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
  382. [
  383. { key: 'pageUrl', value: `${WIKI.config.host}/i/${page.id}` },
  384. { key: 'pageId', value: page.id }
  385. ].forEach((cfg) => {
  386. WIKI.data.commentProvider.head = _.replace(WIKI.data.commentProvider.head, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  387. WIKI.data.commentProvider.body = _.replace(WIKI.data.commentProvider.body, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  388. WIKI.data.commentProvider.main = _.replace(WIKI.data.commentProvider.main, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  389. })
  390. }
  391. // -> Render view
  392. res.render('page', {
  393. page,
  394. sidebar,
  395. injectCode,
  396. comments: WIKI.data.commentProvider
  397. })
  398. }
  399. } else if (pageArgs.path === 'home') {
  400. _.set(res.locals, 'pageMeta.title', 'Welcome')
  401. res.render('welcome', { locale: pageArgs.locale })
  402. } else {
  403. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  404. if (WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
  405. res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
  406. } else {
  407. res.status(404).render('notfound', { action: 'view' })
  408. }
  409. }
  410. } catch (err) {
  411. next(err)
  412. }
  413. } else {
  414. if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
  415. return res.sendStatus(403)
  416. }
  417. await WIKI.models.assets.getAsset(pageArgs.path, res)
  418. }
  419. })
  420. module.exports = router