common.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. const express = require('express')
  2. const router = express.Router()
  3. const pageHelper = require('../helpers/page')
  4. const _ = require('lodash')
  5. const CleanCSS = require('clean-css')
  6. const moment = require('moment')
  7. const path = require('path')
  8. const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/
  9. const siteAssetsPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets')
  10. /**
  11. * Robots.txt
  12. */
  13. router.get('/robots.txt', (req, res, next) => {
  14. res.type('text/plain')
  15. if (_.includes(WIKI.config.seo.robots, 'noindex')) {
  16. res.send('User-agent: *\nDisallow: /')
  17. } else {
  18. res.status(200).end()
  19. }
  20. })
  21. /**
  22. * Health Endpoint
  23. */
  24. router.get('/healthz', (req, res, next) => {
  25. if (WIKI.db.knex.client.pool.numFree() < 1 && WIKI.db.knex.client.pool.numUsed() < 1) {
  26. res.status(503).json({ ok: false }).end()
  27. } else {
  28. res.status(200).json({ ok: true }).end()
  29. }
  30. })
  31. /**
  32. * Site Asset
  33. */
  34. router.get('/_site/:siteId?/:resource', async (req, res, next) => {
  35. const site = req.params.siteId ? WIKI.sites[req.params.siteId] : await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
  36. if (!site) {
  37. return res.status(404).send('Site Not Found')
  38. }
  39. switch (req.params.resource) {
  40. case 'logo': {
  41. if (site.config.assets.logo) {
  42. res.sendFile(path.join(siteAssetsPath, `logo-${site.id}.${site.config.assets.logoExt}`))
  43. } else {
  44. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
  45. }
  46. break
  47. }
  48. case 'favicon': {
  49. if (site.config.assets.favicon) {
  50. res.sendFile(path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`))
  51. } else {
  52. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
  53. }
  54. break
  55. }
  56. case 'loginbg': {
  57. if (site.config.assets.loginBg) {
  58. res.sendFile(path.join(siteAssetsPath, `loginbg-${site.id}.jpg`))
  59. } else {
  60. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/bg/login.jpg'))
  61. }
  62. break
  63. }
  64. default: {
  65. return res.status(404).send('Invalid Site Resource')
  66. }
  67. }
  68. })
  69. /**
  70. * New v3 vue app
  71. */
  72. router.get([
  73. '/_admin',
  74. '/_admin/*',
  75. '/_profile',
  76. '/_profile/*',
  77. '/_error',
  78. '/_error/*',
  79. '/_welcome'
  80. ], (req, res, next) => {
  81. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
  82. })
  83. // router.get(['/_admin', '/_admin/*'], (req, res, next) => {
  84. // if (!WIKI.auth.checkAccess(req.user, [
  85. // 'manage:system',
  86. // 'write:users',
  87. // 'manage:users',
  88. // 'write:groups',
  89. // 'manage:groups',
  90. // 'manage:navigation',
  91. // 'manage:theme',
  92. // 'manage:api'
  93. // ])) {
  94. // _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  95. // return res.status(403).render('unauthorized', { action: 'view' })
  96. // }
  97. // _.set(res.locals, 'pageMeta.title', 'Admin')
  98. // res.render('admin')
  99. // })
  100. /**
  101. * Download Page / Version
  102. */
  103. router.get(['/d', '/d/*'], async (req, res, next) => {
  104. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  105. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  106. const page = await WIKI.db.pages.getPageFromDb({
  107. path: pageArgs.path,
  108. locale: pageArgs.locale,
  109. userId: req.user.id,
  110. isPrivate: false
  111. })
  112. pageArgs.tags = _.get(page, 'tags', [])
  113. if (versionId > 0) {
  114. if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
  115. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  116. return res.render('unauthorized', { action: 'downloadVersion' })
  117. }
  118. } else {
  119. if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
  120. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  121. return res.render('unauthorized', { action: 'download' })
  122. }
  123. }
  124. if (page) {
  125. const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType)
  126. res.attachment(fileName)
  127. if (versionId > 0) {
  128. const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
  129. res.send(pageHelper.injectPageMetadata(pageVersion))
  130. } else {
  131. res.send(pageHelper.injectPageMetadata(page))
  132. }
  133. } else {
  134. res.status(404).end()
  135. }
  136. })
  137. /**
  138. * Create/Edit document
  139. */
  140. router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
  141. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  142. if (pageArgs.path === '') {
  143. return res.redirect(`/_edit/home`)
  144. }
  145. // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  146. // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`)
  147. // }
  148. // req.i18n.changeLanguage(pageArgs.locale)
  149. // -> Set Editor Lang
  150. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  151. // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  152. // -> Check for reserved path
  153. if (pageHelper.isReservedPath(pageArgs.path)) {
  154. return next(new Error('Cannot create this page because it starts with a system reserved path.'))
  155. }
  156. // -> Get page data from DB
  157. let page = await WIKI.db.pages.getPageFromDb({
  158. path: pageArgs.path,
  159. locale: pageArgs.locale,
  160. userId: req.user.id,
  161. isPrivate: false
  162. })
  163. pageArgs.tags = _.get(page, 'tags', [])
  164. // -> Effective Permissions
  165. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  166. const injectCode = {
  167. css: '', // WIKI.config.theming.injectCSS,
  168. head: '', // WIKI.config.theming.injectHead,
  169. body: '' // WIKI.config.theming.injectBody
  170. }
  171. if (page) {
  172. // -> EDIT MODE
  173. if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
  174. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  175. return res.render('unauthorized', { action: 'edit' })
  176. }
  177. // -> Get page tags
  178. await page.$relatedQuery('tags')
  179. page.tags = _.map(page.tags, 'tag')
  180. // Handle missing extra field
  181. page.extra = page.extra || { css: '', js: '' }
  182. // -> Beautify Script CSS
  183. if (!_.isEmpty(page.extra.css)) {
  184. page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
  185. }
  186. _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
  187. _.set(res.locals, 'pageMeta.description', page.description)
  188. page.mode = 'update'
  189. page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
  190. page.content = Buffer.from(page.content).toString('base64')
  191. } else {
  192. // -> CREATE MODE
  193. if (!effectivePermissions.pages.write) {
  194. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  195. return res.render('unauthorized', { action: 'create' })
  196. }
  197. _.set(res.locals, 'pageMeta.title', `New Page`)
  198. page = {
  199. path: pageArgs.path,
  200. localeCode: pageArgs.locale,
  201. editorKey: null,
  202. mode: 'create',
  203. content: null,
  204. title: null,
  205. description: null,
  206. updatedAt: new Date().toISOString(),
  207. extra: {
  208. css: '',
  209. js: ''
  210. }
  211. }
  212. // -> From Template
  213. if (req.query.from && tmplCreateRegex.test(req.query.from)) {
  214. let tmplPageId = 0
  215. let tmplVersionId = 0
  216. if (req.query.from.indexOf(',')) {
  217. const q = req.query.from.split(',')
  218. tmplPageId = _.toSafeInteger(q[0])
  219. tmplVersionId = _.toSafeInteger(q[1])
  220. } else {
  221. tmplPageId = _.toSafeInteger(req.query.from)
  222. }
  223. if (tmplVersionId > 0) {
  224. // -> From Page Version
  225. const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: tmplPageId, versionId: tmplVersionId })
  226. if (!pageVersion) {
  227. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  228. return res.status(404).render('notfound', { action: 'template' })
  229. }
  230. if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
  231. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  232. return res.render('unauthorized', { action: 'sourceVersion' })
  233. }
  234. page.content = Buffer.from(pageVersion.content).toString('base64')
  235. page.editorKey = pageVersion.editor
  236. page.title = pageVersion.title
  237. page.description = pageVersion.description
  238. } else {
  239. // -> From Page Live
  240. const pageOriginal = await WIKI.db.pages.query().findById(tmplPageId)
  241. if (!pageOriginal) {
  242. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  243. return res.status(404).render('notfound', { action: 'template' })
  244. }
  245. if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
  246. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  247. return res.render('unauthorized', { action: 'source' })
  248. }
  249. page.content = Buffer.from(pageOriginal.content).toString('base64')
  250. page.editorKey = pageOriginal.editorKey
  251. page.title = pageOriginal.title
  252. page.description = pageOriginal.description
  253. }
  254. }
  255. }
  256. res.render('editor', { page, injectCode, effectivePermissions })
  257. })
  258. /**
  259. * History
  260. */
  261. router.get(['/h', '/h/*'], async (req, res, next) => {
  262. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  263. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  264. return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
  265. }
  266. req.i18n.changeLanguage(pageArgs.locale)
  267. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  268. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  269. const page = await WIKI.db.pages.getPageFromDb({
  270. path: pageArgs.path,
  271. locale: pageArgs.locale,
  272. userId: req.user.id,
  273. isPrivate: false
  274. })
  275. if (!page) {
  276. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  277. return res.status(404).render('notfound', { action: 'history' })
  278. }
  279. pageArgs.tags = _.get(page, 'tags', [])
  280. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  281. if (!effectivePermissions.history.read) {
  282. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  283. return res.render('unauthorized', { action: 'history' })
  284. }
  285. if (page) {
  286. _.set(res.locals, 'pageMeta.title', page.title)
  287. _.set(res.locals, 'pageMeta.description', page.description)
  288. res.render('history', { page, effectivePermissions })
  289. } else {
  290. res.redirect(`/${pageArgs.path}`)
  291. }
  292. })
  293. /**
  294. * Page ID redirection
  295. */
  296. router.get(['/i', '/i/:id'], async (req, res, next) => {
  297. const pageId = _.toSafeInteger(req.params.id)
  298. if (pageId <= 0) {
  299. return res.redirect('/')
  300. }
  301. const page = await WIKI.db.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
  302. if (!page) {
  303. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  304. return res.status(404).render('notfound', { action: 'view' })
  305. }
  306. if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
  307. locale: page.localeCode,
  308. path: page.path,
  309. private: page.isPrivate,
  310. privateNS: page.privateNS,
  311. explicitLocale: false,
  312. tags: page.tags
  313. })) {
  314. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  315. return res.render('unauthorized', { action: 'view' })
  316. }
  317. if (WIKI.config.lang.namespacing) {
  318. return res.redirect(`/${page.localeCode}/${page.path}`)
  319. } else {
  320. return res.redirect(`/${page.path}`)
  321. }
  322. })
  323. /**
  324. * Source
  325. */
  326. router.get(['/s', '/s/*'], async (req, res, next) => {
  327. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  328. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  329. const page = await WIKI.db.pages.getPageFromDb({
  330. path: pageArgs.path,
  331. locale: pageArgs.locale,
  332. userId: req.user.id,
  333. isPrivate: false
  334. })
  335. pageArgs.tags = _.get(page, 'tags', [])
  336. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  337. return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
  338. }
  339. // -> Effective Permissions
  340. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  341. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  342. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  343. if (versionId > 0) {
  344. if (!effectivePermissions.history.read) {
  345. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  346. return res.render('unauthorized', { action: 'sourceVersion' })
  347. }
  348. } else {
  349. if (!effectivePermissions.source.read) {
  350. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  351. return res.render('unauthorized', { action: 'source' })
  352. }
  353. }
  354. if (page) {
  355. if (versionId > 0) {
  356. const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
  357. _.set(res.locals, 'pageMeta.title', pageVersion.title)
  358. _.set(res.locals, 'pageMeta.description', pageVersion.description)
  359. res.render('source', {
  360. page: {
  361. ...page,
  362. ...pageVersion
  363. },
  364. effectivePermissions
  365. })
  366. } else {
  367. _.set(res.locals, 'pageMeta.title', page.title)
  368. _.set(res.locals, 'pageMeta.description', page.description)
  369. res.render('source', { page, effectivePermissions })
  370. }
  371. } else {
  372. res.redirect(`/${pageArgs.path}`)
  373. }
  374. })
  375. /**
  376. * Tags
  377. */
  378. router.get(['/t', '/t/*'], (req, res, next) => {
  379. _.set(res.locals, 'pageMeta.title', 'Tags')
  380. res.render('tags')
  381. })
  382. /**
  383. * User Avatar
  384. */
  385. router.get('/_userav/:uid', async (req, res, next) => {
  386. if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
  387. return res.sendStatus(403)
  388. }
  389. const av = await WIKI.db.users.getUserAvatarData(req.params.uid)
  390. if (av) {
  391. res.set('Content-Type', 'image/jpeg')
  392. res.send(av)
  393. }
  394. return res.sendStatus(404)
  395. })
  396. /**
  397. * View document / asset
  398. */
  399. router.get('/*', async (req, res, next) => {
  400. const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
  401. const pageArgs = pageHelper.parsePath(req.path, { stripExt })
  402. const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
  403. if (isPage) {
  404. // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  405. // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
  406. // }
  407. // req.i18n.changeLanguage(pageArgs.locale)
  408. try {
  409. // -> Get Page from cache
  410. const page = await WIKI.db.pages.getPage({
  411. path: pageArgs.path,
  412. locale: pageArgs.locale,
  413. userId: req.user.id
  414. })
  415. pageArgs.tags = _.get(page, 'tags', [])
  416. // -> Effective Permissions
  417. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  418. // -> Check User Access
  419. if (!effectivePermissions.pages.read) {
  420. if (req.user.id === WIKI.auth.guest.id) {
  421. res.cookie('loginRedirect', req.path, {
  422. maxAge: 15 * 60 * 1000
  423. })
  424. }
  425. if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) {
  426. return res.redirect('/login')
  427. }
  428. return res.redirect(`/_error/unauthorized?from=${req.path}`)
  429. }
  430. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  431. // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  432. if (page) {
  433. _.set(res.locals, 'pageMeta.title', page.title)
  434. _.set(res.locals, 'pageMeta.description', page.description)
  435. // -> Check Publishing State
  436. let pageIsPublished = page.isPublished
  437. if (pageIsPublished && !_.isEmpty(page.publishStartDate)) {
  438. pageIsPublished = moment(page.publishStartDate).isSameOrBefore()
  439. }
  440. if (pageIsPublished && !_.isEmpty(page.publishEndDate)) {
  441. pageIsPublished = moment(page.publishEndDate).isSameOrAfter()
  442. }
  443. if (!pageIsPublished && !effectivePermissions.pages.write) {
  444. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  445. return res.status(403).render('unauthorized', {
  446. action: 'view'
  447. })
  448. }
  449. // -> Build sidebar navigation
  450. let sdi = 1
  451. const sidebar = (await WIKI.db.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({
  452. i: `sdi-${sdi++}`,
  453. k: n.kind,
  454. l: n.label,
  455. c: n.icon,
  456. y: n.targetType,
  457. t: n.target
  458. }))
  459. // -> Build theme code injection
  460. const injectCode = {
  461. css: '', // WIKI.config.theming.injectCSS,
  462. head: '', // WIKI.config.theming.injectHead,
  463. body: '' // WIKI.config.theming.injectBody
  464. }
  465. // Handle missing extra field
  466. page.extra = page.extra || { css: '', js: '' }
  467. if (!_.isEmpty(page.extra.css)) {
  468. injectCode.css = `${injectCode.css}\n${page.extra.css}`
  469. }
  470. if (!_.isEmpty(page.extra.js)) {
  471. injectCode.body = `${injectCode.body}\n${page.extra.js}`
  472. }
  473. // -> Convert page TOC
  474. if (!_.isString(page.toc)) {
  475. page.toc = JSON.stringify(page.toc)
  476. }
  477. // -> Inject comments variables
  478. const commentTmpl = {
  479. codeTemplate: '', // WIKI.data.commentProvider.codeTemplate,
  480. head: '', // WIKI.data.commentProvider.head,
  481. body: '', // WIKI.data.commentProvider.body,
  482. main: '' // WIKI.data.commentProvider.main
  483. }
  484. if (false && WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
  485. [
  486. { key: 'pageUrl', value: `${WIKI.config.host}/i/${page.id}` },
  487. { key: 'pageId', value: page.id }
  488. ].forEach((cfg) => {
  489. commentTmpl.head = _.replace(commentTmpl.head, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  490. commentTmpl.body = _.replace(commentTmpl.body, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  491. commentTmpl.main = _.replace(commentTmpl.main, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  492. })
  493. }
  494. // -> Render view
  495. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
  496. // res.render('page', {
  497. // page,
  498. // sidebar,
  499. // injectCode,
  500. // comments: commentTmpl,
  501. // effectivePermissions
  502. // })
  503. } else if (pageArgs.path === 'home') {
  504. res.redirect('/_welcome')
  505. } else {
  506. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  507. if (effectivePermissions.pages.write) {
  508. res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
  509. } else {
  510. res.status(404).render('notfound', { action: 'view' })
  511. }
  512. }
  513. } catch (err) {
  514. next(err)
  515. }
  516. } else {
  517. if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
  518. return res.sendStatus(403)
  519. }
  520. await WIKI.db.assets.getAsset(pageArgs.path, res)
  521. }
  522. })
  523. module.exports = router