common.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  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. console.info(req.params)
  40. switch (req.params.resource) {
  41. case 'logo': {
  42. if (site.config.assets.logo) {
  43. // TODO: Fetch from db if not in disk cache
  44. res.sendFile(path.join(siteAssetsPath, `logo-${site.id}.${site.config.assets.logoExt}`))
  45. } else {
  46. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
  47. }
  48. break
  49. }
  50. case 'favicon': {
  51. if (site.config.assets.favicon) {
  52. // TODO: Fetch from db if not in disk cache
  53. res.sendFile(path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`))
  54. } else {
  55. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
  56. }
  57. break
  58. }
  59. case 'loginbg': {
  60. if (site.config.assets.loginBg) {
  61. // TODO: Fetch from db if not in disk cache
  62. res.sendFile(path.join(siteAssetsPath, `loginbg-${site.id}.jpg`))
  63. } else {
  64. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/bg/login.jpg'))
  65. }
  66. break
  67. }
  68. default: {
  69. return res.status(404).send('Invalid Site Resource')
  70. }
  71. }
  72. })
  73. /**
  74. * Asset Thumbnails / Download
  75. */
  76. router.get('/_asset/:siteId/:mode/*', async (req, res, next) => {
  77. const site = req.params.siteId ? WIKI.sites[req.params.siteId] : await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
  78. if (!site) {
  79. return res.status(404).send('Site Not Found')
  80. }
  81. const filePath = req.params[0]
  82. console.info(filePath)
  83. switch (req.params.mode) {
  84. case 'thumb': {
  85. try {
  86. } catch (err) {
  87. }
  88. break
  89. }
  90. case 'download': {
  91. break
  92. }
  93. default: {
  94. return res.status(404).send('Invalid Site Resource')
  95. }
  96. }
  97. return res.send('BOB').end()
  98. })
  99. /**
  100. * New v3 vue app
  101. */
  102. router.get([
  103. '/_admin',
  104. '/_admin/*',
  105. '/_profile',
  106. '/_profile/*',
  107. '/_error',
  108. '/_error/*',
  109. '/_welcome'
  110. ], (req, res, next) => {
  111. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
  112. })
  113. // router.get(['/_admin', '/_admin/*'], (req, res, next) => {
  114. // if (!WIKI.auth.checkAccess(req.user, [
  115. // 'manage:system',
  116. // 'write:users',
  117. // 'manage:users',
  118. // 'write:groups',
  119. // 'manage:groups',
  120. // 'manage:navigation',
  121. // 'manage:theme',
  122. // 'manage:api'
  123. // ])) {
  124. // _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  125. // return res.status(403).render('unauthorized', { action: 'view' })
  126. // }
  127. // _.set(res.locals, 'pageMeta.title', 'Admin')
  128. // res.render('admin')
  129. // })
  130. /**
  131. * Download Page / Version
  132. */
  133. router.get(['/d', '/d/*'], async (req, res, next) => {
  134. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  135. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  136. const page = await WIKI.db.pages.getPageFromDb({
  137. path: pageArgs.path,
  138. locale: pageArgs.locale,
  139. userId: req.user.id,
  140. isPrivate: false
  141. })
  142. pageArgs.tags = _.get(page, 'tags', [])
  143. if (versionId > 0) {
  144. if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
  145. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  146. return res.render('unauthorized', { action: 'downloadVersion' })
  147. }
  148. } else {
  149. if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
  150. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  151. return res.render('unauthorized', { action: 'download' })
  152. }
  153. }
  154. if (page) {
  155. const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType)
  156. res.attachment(fileName)
  157. if (versionId > 0) {
  158. const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
  159. res.send(pageHelper.injectPageMetadata(pageVersion))
  160. } else {
  161. res.send(pageHelper.injectPageMetadata(page))
  162. }
  163. } else {
  164. res.status(404).end()
  165. }
  166. })
  167. /**
  168. * Create/Edit document
  169. */
  170. router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
  171. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  172. const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
  173. if (!site) {
  174. throw new Error('INVALID_SITE')
  175. }
  176. if (pageArgs.path === '') {
  177. return res.redirect(`/_edit/home`)
  178. }
  179. // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  180. // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`)
  181. // }
  182. // req.i18n.changeLanguage(pageArgs.locale)
  183. // -> Set Editor Lang
  184. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  185. // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  186. // -> Check for reserved path
  187. if (pageHelper.isReservedPath(pageArgs.path)) {
  188. return next(new Error('Cannot create this page because it starts with a system reserved path.'))
  189. }
  190. // -> Get page data from DB
  191. let page = await WIKI.db.pages.getPageFromDb({
  192. siteId: site.id,
  193. path: pageArgs.path,
  194. locale: pageArgs.locale,
  195. userId: req.user.id
  196. })
  197. pageArgs.tags = _.get(page, 'tags', [])
  198. // -> Effective Permissions
  199. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  200. const injectCode = {
  201. css: '', // WIKI.config.theming.injectCSS,
  202. head: '', // WIKI.config.theming.injectHead,
  203. body: '' // WIKI.config.theming.injectBody
  204. }
  205. if (page) {
  206. // -> EDIT MODE
  207. if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
  208. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  209. return res.render('unauthorized', { action: 'edit' })
  210. }
  211. // -> Get page tags
  212. await page.$relatedQuery('tags')
  213. page.tags = _.map(page.tags, 'tag')
  214. // Handle missing extra field
  215. page.extra = page.extra || { css: '', js: '' }
  216. // -> Beautify Script CSS
  217. if (!_.isEmpty(page.extra.css)) {
  218. page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
  219. }
  220. _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
  221. _.set(res.locals, 'pageMeta.description', page.description)
  222. page.mode = 'update'
  223. page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
  224. page.content = Buffer.from(page.content).toString('base64')
  225. } else {
  226. // -> CREATE MODE
  227. if (!effectivePermissions.pages.write) {
  228. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  229. return res.render('unauthorized', { action: 'create' })
  230. }
  231. _.set(res.locals, 'pageMeta.title', `New Page`)
  232. page = {
  233. path: pageArgs.path,
  234. localeCode: pageArgs.locale,
  235. editorKey: null,
  236. mode: 'create',
  237. content: null,
  238. title: null,
  239. description: null,
  240. updatedAt: new Date().toISOString(),
  241. extra: {
  242. css: '',
  243. js: ''
  244. }
  245. }
  246. }
  247. res.render('editor', { page, injectCode, effectivePermissions })
  248. })
  249. /**
  250. * History
  251. */
  252. router.get(['/h', '/h/*'], async (req, res, next) => {
  253. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  254. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  255. return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
  256. }
  257. req.i18n.changeLanguage(pageArgs.locale)
  258. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  259. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  260. const page = await WIKI.db.pages.getPageFromDb({
  261. path: pageArgs.path,
  262. locale: pageArgs.locale,
  263. userId: req.user.id,
  264. isPrivate: false
  265. })
  266. if (!page) {
  267. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  268. return res.status(404).render('notfound', { action: 'history' })
  269. }
  270. pageArgs.tags = _.get(page, 'tags', [])
  271. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  272. if (!effectivePermissions.history.read) {
  273. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  274. return res.render('unauthorized', { action: 'history' })
  275. }
  276. if (page) {
  277. _.set(res.locals, 'pageMeta.title', page.title)
  278. _.set(res.locals, 'pageMeta.description', page.description)
  279. res.render('history', { page, effectivePermissions })
  280. } else {
  281. res.redirect(`/${pageArgs.path}`)
  282. }
  283. })
  284. /**
  285. * Page ID redirection
  286. */
  287. router.get(['/i', '/i/:id'], async (req, res, next) => {
  288. const pageId = _.toSafeInteger(req.params.id)
  289. if (pageId <= 0) {
  290. return res.redirect('/')
  291. }
  292. const page = await WIKI.db.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
  293. if (!page) {
  294. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  295. return res.status(404).render('notfound', { action: 'view' })
  296. }
  297. if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
  298. locale: page.localeCode,
  299. path: page.path,
  300. private: page.isPrivate,
  301. privateNS: page.privateNS,
  302. explicitLocale: false,
  303. tags: page.tags
  304. })) {
  305. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  306. return res.render('unauthorized', { action: 'view' })
  307. }
  308. if (WIKI.config.lang.namespacing) {
  309. return res.redirect(`/${page.localeCode}/${page.path}`)
  310. } else {
  311. return res.redirect(`/${page.path}`)
  312. }
  313. })
  314. /**
  315. * Source
  316. */
  317. router.get(['/s', '/s/*'], async (req, res, next) => {
  318. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  319. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  320. const page = await WIKI.db.pages.getPageFromDb({
  321. path: pageArgs.path,
  322. locale: pageArgs.locale,
  323. userId: req.user.id,
  324. isPrivate: false
  325. })
  326. pageArgs.tags = _.get(page, 'tags', [])
  327. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  328. return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
  329. }
  330. // -> Effective Permissions
  331. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  332. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  333. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  334. if (versionId > 0) {
  335. if (!effectivePermissions.history.read) {
  336. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  337. return res.render('unauthorized', { action: 'sourceVersion' })
  338. }
  339. } else {
  340. if (!effectivePermissions.source.read) {
  341. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  342. return res.render('unauthorized', { action: 'source' })
  343. }
  344. }
  345. if (page) {
  346. if (versionId > 0) {
  347. const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
  348. _.set(res.locals, 'pageMeta.title', pageVersion.title)
  349. _.set(res.locals, 'pageMeta.description', pageVersion.description)
  350. res.render('source', {
  351. page: {
  352. ...page,
  353. ...pageVersion
  354. },
  355. effectivePermissions
  356. })
  357. } else {
  358. _.set(res.locals, 'pageMeta.title', page.title)
  359. _.set(res.locals, 'pageMeta.description', page.description)
  360. res.render('source', { page, effectivePermissions })
  361. }
  362. } else {
  363. res.redirect(`/${pageArgs.path}`)
  364. }
  365. })
  366. /**
  367. * Tags
  368. */
  369. router.get(['/t', '/t/*'], (req, res, next) => {
  370. _.set(res.locals, 'pageMeta.title', 'Tags')
  371. res.render('tags')
  372. })
  373. /**
  374. * User Avatar
  375. */
  376. router.get('/_user/:uid/avatar', async (req, res, next) => {
  377. if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
  378. return res.sendStatus(403)
  379. }
  380. const av = await WIKI.db.users.getUserAvatarData(req.params.uid)
  381. if (av) {
  382. res.set('Content-Type', 'image/jpeg')
  383. return res.send(av)
  384. }
  385. return res.sendStatus(404)
  386. })
  387. /**
  388. * View document / asset
  389. */
  390. router.get('/*', async (req, res, next) => {
  391. const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
  392. const pageArgs = pageHelper.parsePath(req.path, { stripExt })
  393. const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
  394. const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
  395. if (!site) {
  396. throw new Error('INVALID_SITE')
  397. }
  398. if (isPage) {
  399. // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  400. // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
  401. // }
  402. // req.i18n.changeLanguage(pageArgs.locale)
  403. try {
  404. // -> Get Page from cache
  405. const page = await WIKI.db.pages.getPage({
  406. siteId: site.id,
  407. path: pageArgs.path,
  408. locale: pageArgs.locale,
  409. userId: req.user.id
  410. })
  411. pageArgs.tags = _.get(page, 'tags', [])
  412. // -> Effective Permissions
  413. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  414. // -> Check User Access
  415. if (!effectivePermissions.pages.read) {
  416. if (req.user.id === WIKI.auth.guest.id) {
  417. res.cookie('loginRedirect', req.path, {
  418. maxAge: 15 * 60 * 1000
  419. })
  420. }
  421. if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) {
  422. return res.redirect('/login')
  423. }
  424. return res.redirect(`/_error/unauthorized?from=${req.path}`)
  425. }
  426. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  427. // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  428. if (page) {
  429. _.set(res.locals, 'pageMeta.title', page.title)
  430. _.set(res.locals, 'pageMeta.description', page.description)
  431. // -> Check Publishing State
  432. let pageIsPublished = page.isPublished
  433. if (pageIsPublished && !_.isEmpty(page.publishStartDate)) {
  434. pageIsPublished = moment(page.publishStartDate).isSameOrBefore()
  435. }
  436. if (pageIsPublished && !_.isEmpty(page.publishEndDate)) {
  437. pageIsPublished = moment(page.publishEndDate).isSameOrAfter()
  438. }
  439. if (!pageIsPublished && !effectivePermissions.pages.write) {
  440. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  441. return res.status(403).render('unauthorized', {
  442. action: 'view'
  443. })
  444. }
  445. // -> Render view
  446. res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
  447. } else if (pageArgs.path === 'home') {
  448. res.redirect('/_welcome')
  449. } else {
  450. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  451. if (effectivePermissions.pages.write) {
  452. res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
  453. } else {
  454. res.status(404).render('notfound', { action: 'view' })
  455. }
  456. }
  457. } catch (err) {
  458. next(err)
  459. }
  460. } else {
  461. if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
  462. return res.sendStatus(403)
  463. }
  464. await WIKI.db.assets.getAsset(pageArgs.path, res)
  465. }
  466. })
  467. module.exports = router