Add a configuration setting to "hard"-disable creation of notes as set by the configuration value. This defaults to `['robots.txt', 'favicon.ico']`, because these files are often accidentally created by bots and browsers. This commit fixes #1052. Signed-off-by: Daan Sprenkels <hello@dsprenkels.com>
		
			
				
	
	
		
			629 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			629 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict'
 | |
| // response
 | |
| // external modules
 | |
| var fs = require('fs')
 | |
| var path = require('path')
 | |
| var markdownpdf = require('markdown-pdf')
 | |
| var shortId = require('shortid')
 | |
| var querystring = require('querystring')
 | |
| var request = require('request')
 | |
| var moment = require('moment')
 | |
| 
 | |
| // core
 | |
| var config = require('./config')
 | |
| var logger = require('./logger')
 | |
| var models = require('./models')
 | |
| var utils = require('./utils')
 | |
| 
 | |
| // public
 | |
| var response = {
 | |
|   errorForbidden: function (res) {
 | |
|     const {req} = res
 | |
|     if (req.user) {
 | |
|       responseError(res, '403', 'Forbidden', 'oh no.')
 | |
|     } else {
 | |
|       req.flash('error', 'You are not allowed to access this page. Maybe try logging in?')
 | |
|       res.redirect(config.serverURL)
 | |
|     }
 | |
|   },
 | |
|   errorNotFound: function (res) {
 | |
|     responseError(res, '404', 'Not Found', 'oops.')
 | |
|   },
 | |
|   errorBadRequest: function (res) {
 | |
|     responseError(res, '400', 'Bad Request', 'something not right.')
 | |
|   },
 | |
|   errorTooLong: function (res) {
 | |
|     responseError(res, '413', 'Payload Too Large', 'Shorten your note!')
 | |
|   },
 | |
|   errorInternalError: function (res) {
 | |
|     responseError(res, '500', 'Internal Error', 'wtf.')
 | |
|   },
 | |
|   errorServiceUnavailable: function (res) {
 | |
|     res.status(503).send("I'm busy right now, try again later.")
 | |
|   },
 | |
|   newNote: newNote,
 | |
|   showNote: showNote,
 | |
|   showPublishNote: showPublishNote,
 | |
|   showPublishSlide: showPublishSlide,
 | |
|   showIndex: showIndex,
 | |
|   noteActions: noteActions,
 | |
|   publishNoteActions: publishNoteActions,
 | |
|   publishSlideActions: publishSlideActions,
 | |
|   githubActions: githubActions,
 | |
|   gitlabActions: gitlabActions
 | |
| }
 | |
| 
 | |
| function responseError (res, code, detail, msg) {
 | |
|   res.status(code).render('error.ejs', {
 | |
|     title: code + ' ' + detail + ' ' + msg,
 | |
|     code: code,
 | |
|     detail: detail,
 | |
|     msg: msg
 | |
|   })
 | |
| }
 | |
| 
 | |
| function showIndex (req, res, next) {
 | |
|   var authStatus = req.isAuthenticated()
 | |
|   var deleteToken = ''
 | |
| 
 | |
|   var data = {
 | |
|     signin: authStatus,
 | |
|     infoMessage: req.flash('info'),
 | |
|     errorMessage: req.flash('error'),
 | |
|     privacyStatement: fs.existsSync(path.join(config.docsPath, 'privacy.md')),
 | |
|     termsOfUse: fs.existsSync(path.join(config.docsPath, 'terms-of-use.md')),
 | |
|     deleteToken: deleteToken
 | |
|   }
 | |
| 
 | |
|   if (authStatus) {
 | |
|     models.User.findOne({
 | |
|       where: {
 | |
|         id: req.user.id
 | |
|       }
 | |
|     }).then(function (user) {
 | |
|       if (user) {
 | |
|         data.deleteToken = user.deleteToken
 | |
|         res.render('index.ejs', data)
 | |
|       }
 | |
|     })
 | |
|   } else {
 | |
|     res.render('index.ejs', data)
 | |
|   }
 | |
| }
 | |
| 
 | |
| function responseCodiMD (res, note) {
 | |
|   var body = note.content
 | |
|   var extracted = models.Note.extractMeta(body)
 | |
|   var meta = models.Note.parseMeta(extracted.meta)
 | |
|   var title = models.Note.decodeTitle(note.title)
 | |
|   title = models.Note.generateWebTitle(meta.title || title)
 | |
|   res.set({
 | |
|     'Cache-Control': 'private', // only cache by client
 | |
|     'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
 | |
|   })
 | |
|   res.render('codimd.ejs', {
 | |
|     title: title
 | |
|   })
 | |
| }
 | |
| 
 | |
| function newNote (req, res, next) {
 | |
|   var owner = null
 | |
|   var body = ''
 | |
|   if (req.body && req.body.length > config.documentMaxLength) {
 | |
|     return response.errorTooLong(res)
 | |
|   } else if (req.body) {
 | |
|     body = req.body
 | |
|   }
 | |
|   body = body.replace(/[\r]/g, '')
 | |
|   if (req.isAuthenticated()) {
 | |
|     owner = req.user.id
 | |
|   } else if (!config.allowAnonymous) {
 | |
|     return response.errorForbidden(res)
 | |
|   }
 | |
|   models.Note.create({
 | |
|     ownerId: owner,
 | |
|     alias: req.alias ? req.alias : null,
 | |
|     content: body
 | |
|   }).then(function (note) {
 | |
|     return res.redirect(config.serverURL + '/' + models.Note.encodeNoteId(note.id))
 | |
|   }).catch(function (err) {
 | |
|     logger.error(err)
 | |
|     return response.errorInternalError(res)
 | |
|   })
 | |
| }
 | |
| 
 | |
| function checkViewPermission (req, note) {
 | |
|   if (note.permission === 'private') {
 | |
|     if (!req.isAuthenticated() || note.ownerId !== req.user.id) { return false } else { return true }
 | |
|   } else if (note.permission === 'limited' || note.permission === 'protected') {
 | |
|     if (!req.isAuthenticated()) { return false } else { return true }
 | |
|   } else {
 | |
|     return true
 | |
|   }
 | |
| }
 | |
| 
 | |
| function findNote (req, res, callback, include) {
 | |
|   var noteId = req.params.noteId
 | |
|   var id = req.params.noteId || req.params.shortid
 | |
|   models.Note.parseNoteId(id, function (err, _id) {
 | |
|     if (err) {
 | |
|       logger.error(err)
 | |
|       return response.errorInternalError(res)
 | |
|     }
 | |
|     models.Note.findOne({
 | |
|       where: {
 | |
|         id: _id
 | |
|       },
 | |
|       include: include || null
 | |
|     }).then(function (note) {
 | |
|       if (!note) {
 | |
|         if (config.allowFreeURL && noteId && !config.forbiddenNoteIDs.includes(noteId)) {
 | |
|           req.alias = noteId
 | |
|           return newNote(req, res)
 | |
|         } else {
 | |
|           return response.errorNotFound(res)
 | |
|         }
 | |
|       }
 | |
|       if (!checkViewPermission(req, note)) {
 | |
|         return response.errorForbidden(res)
 | |
|       } else {
 | |
|         return callback(note)
 | |
|       }
 | |
|     }).catch(function (err) {
 | |
|       logger.error(err)
 | |
|       return response.errorInternalError(res)
 | |
|     })
 | |
|   })
 | |
| }
 | |
| 
 | |
| function showNote (req, res, next) {
 | |
|   findNote(req, res, function (note) {
 | |
|     // force to use note id
 | |
|     var noteId = req.params.noteId
 | |
|     var id = models.Note.encodeNoteId(note.id)
 | |
|     if ((note.alias && noteId !== note.alias) || (!note.alias && noteId !== id)) { return res.redirect(config.serverURL + '/' + (note.alias || id)) }
 | |
|     return responseCodiMD(res, note)
 | |
|   })
 | |
| }
 | |
| 
 | |
| function showPublishNote (req, res, next) {
 | |
|   var include = [{
 | |
|     model: models.User,
 | |
|     as: 'owner'
 | |
|   }, {
 | |
|     model: models.User,
 | |
|     as: 'lastchangeuser'
 | |
|   }]
 | |
|   findNote(req, res, function (note) {
 | |
|     // force to use short id
 | |
|     var shortid = req.params.shortid
 | |
|     if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
 | |
|       return res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid))
 | |
|     }
 | |
|     note.increment('viewcount').then(function (note) {
 | |
|       if (!note) {
 | |
|         return response.errorNotFound(res)
 | |
|       }
 | |
|       var body = note.content
 | |
|       var extracted = models.Note.extractMeta(body)
 | |
|       var markdown = extracted.markdown
 | |
|       var meta = models.Note.parseMeta(extracted.meta)
 | |
|       var createtime = note.createdAt
 | |
|       var updatetime = note.lastchangeAt
 | |
|       var title = models.Note.decodeTitle(note.title)
 | |
|       title = models.Note.generateWebTitle(meta.title || title)
 | |
|       var data = {
 | |
|         title: title,
 | |
|         description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
 | |
|         viewcount: note.viewcount,
 | |
|         createtime: createtime,
 | |
|         updatetime: updatetime,
 | |
|         body: body,
 | |
|         owner: note.owner ? note.owner.id : null,
 | |
|         ownerprofile: note.owner ? models.User.getProfile(note.owner) : null,
 | |
|         lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
 | |
|         lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
 | |
|         robots: meta.robots || false, // default allow robots
 | |
|         GA: meta.GA,
 | |
|         disqus: meta.disqus,
 | |
|         cspNonce: res.locals.nonce
 | |
|       }
 | |
|       return renderPublish(data, res)
 | |
|     }).catch(function (err) {
 | |
|       logger.error(err)
 | |
|       return response.errorInternalError(res)
 | |
|     })
 | |
|   }, include)
 | |
| }
 | |
| 
 | |
| function renderPublish (data, res) {
 | |
|   res.set({
 | |
|     'Cache-Control': 'private' // only cache by client
 | |
|   })
 | |
|   res.render('pretty.ejs', data)
 | |
| }
 | |
| 
 | |
| function actionPublish (req, res, note) {
 | |
|   res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid))
 | |
| }
 | |
| 
 | |
| function actionSlide (req, res, note) {
 | |
|   res.redirect(config.serverURL + '/p/' + (note.alias || note.shortid))
 | |
| }
 | |
| 
 | |
| function actionDownload (req, res, note) {
 | |
|   var body = note.content
 | |
|   var title = models.Note.decodeTitle(note.title)
 | |
|   var filename = title
 | |
|   filename = encodeURIComponent(filename)
 | |
|   res.set({
 | |
|     'Access-Control-Allow-Origin': '*', // allow CORS as API
 | |
|     'Access-Control-Allow-Headers': 'Range',
 | |
|     'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
 | |
|     'Content-Type': 'text/markdown; charset=UTF-8',
 | |
|     'Cache-Control': 'private',
 | |
|     'Content-disposition': 'attachment; filename=' + filename + '.md',
 | |
|     'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
 | |
|   })
 | |
|   res.send(body)
 | |
| }
 | |
| 
 | |
| function actionInfo (req, res, note) {
 | |
|   var body = note.content
 | |
|   var extracted = models.Note.extractMeta(body)
 | |
|   var markdown = extracted.markdown
 | |
|   var meta = models.Note.parseMeta(extracted.meta)
 | |
|   var createtime = note.createdAt
 | |
|   var updatetime = note.lastchangeAt
 | |
|   var title = models.Note.decodeTitle(note.title)
 | |
|   var data = {
 | |
|     title: meta.title || title,
 | |
|     description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
 | |
|     viewcount: note.viewcount,
 | |
|     createtime: createtime,
 | |
|     updatetime: updatetime
 | |
|   }
 | |
|   res.set({
 | |
|     'Access-Control-Allow-Origin': '*', // allow CORS as API
 | |
|     'Access-Control-Allow-Headers': 'Range',
 | |
|     'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
 | |
|     'Cache-Control': 'private', // only cache by client
 | |
|     'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
 | |
|   })
 | |
|   res.send(data)
 | |
| }
 | |
| 
 | |
| function actionPDF (req, res, note) {
 | |
|   var url = config.serverURL || 'http://' + req.get('host')
 | |
|   var body = note.content
 | |
|   var extracted = models.Note.extractMeta(body)
 | |
|   var content = extracted.markdown
 | |
|   var title = models.Note.decodeTitle(note.title)
 | |
| 
 | |
|   if (!fs.existsSync(config.tmpPath)) {
 | |
|     fs.mkdirSync(config.tmpPath)
 | |
|   }
 | |
|   var path = config.tmpPath + '/' + Date.now() + '.pdf'
 | |
|   content = content.replace(/\]\(\//g, '](' + url + '/')
 | |
|   markdownpdf().from.string(content).to(path, function () {
 | |
|     if (!fs.existsSync(path)) {
 | |
|       logger.error('PDF seems to not be generated as expected. File doesn\'t exist: ' + path)
 | |
|       return response.errorInternalError(res)
 | |
|     }
 | |
|     var stream = fs.createReadStream(path)
 | |
|     var filename = title
 | |
|     // Be careful of special characters
 | |
|     filename = encodeURIComponent(filename)
 | |
|     // Ideally this should strip them
 | |
|     res.setHeader('Content-disposition', 'attachment; filename="' + filename + '.pdf"')
 | |
|     res.setHeader('Cache-Control', 'private')
 | |
|     res.setHeader('Content-Type', 'application/pdf; charset=UTF-8')
 | |
|     res.setHeader('X-Robots-Tag', 'noindex, nofollow') // prevent crawling
 | |
|     stream.pipe(res)
 | |
|     fs.unlink(path)
 | |
|   })
 | |
| }
 | |
| 
 | |
| function actionGist (req, res, note) {
 | |
|   var data = {
 | |
|     client_id: config.github.clientID,
 | |
|     redirect_uri: config.serverURL + '/auth/github/callback/' + models.Note.encodeNoteId(note.id) + '/gist',
 | |
|     scope: 'gist',
 | |
|     state: shortId.generate()
 | |
|   }
 | |
|   var query = querystring.stringify(data)
 | |
|   res.redirect('https://github.com/login/oauth/authorize?' + query)
 | |
| }
 | |
| 
 | |
| function actionRevision (req, res, note) {
 | |
|   var actionId = req.params.actionId
 | |
|   if (actionId) {
 | |
|     var time = moment(parseInt(actionId))
 | |
|     if (time.isValid()) {
 | |
|       models.Revision.getPatchedNoteRevisionByTime(note, time, function (err, content) {
 | |
|         if (err) {
 | |
|           logger.error(err)
 | |
|           return response.errorInternalError(res)
 | |
|         }
 | |
|         if (!content) {
 | |
|           return response.errorNotFound(res)
 | |
|         }
 | |
|         res.set({
 | |
|           'Access-Control-Allow-Origin': '*', // allow CORS as API
 | |
|           'Access-Control-Allow-Headers': 'Range',
 | |
|           'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
 | |
|           'Cache-Control': 'private', // only cache by client
 | |
|           'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
 | |
|         })
 | |
|         res.send(content)
 | |
|       })
 | |
|     } else {
 | |
|       return response.errorNotFound(res)
 | |
|     }
 | |
|   } else {
 | |
|     models.Revision.getNoteRevisions(note, function (err, data) {
 | |
|       if (err) {
 | |
|         logger.error(err)
 | |
|         return response.errorInternalError(res)
 | |
|       }
 | |
|       var out = {
 | |
|         revision: data
 | |
|       }
 | |
|       res.set({
 | |
|         'Access-Control-Allow-Origin': '*', // allow CORS as API
 | |
|         'Access-Control-Allow-Headers': 'Range',
 | |
|         'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
 | |
|         'Cache-Control': 'private', // only cache by client
 | |
|         'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
 | |
|       })
 | |
|       res.send(out)
 | |
|     })
 | |
|   }
 | |
| }
 | |
| 
 | |
| function noteActions (req, res, next) {
 | |
|   var noteId = req.params.noteId
 | |
|   findNote(req, res, function (note) {
 | |
|     var action = req.params.action
 | |
|     switch (action) {
 | |
|       case 'publish':
 | |
|       case 'pretty': // pretty deprecated
 | |
|         actionPublish(req, res, note)
 | |
|         break
 | |
|       case 'slide':
 | |
|         actionSlide(req, res, note)
 | |
|         break
 | |
|       case 'download':
 | |
|         actionDownload(req, res, note)
 | |
|         break
 | |
|       case 'info':
 | |
|         actionInfo(req, res, note)
 | |
|         break
 | |
|       case 'pdf':
 | |
|         if (config.allowPDFExport) {
 | |
|           actionPDF(req, res, note)
 | |
|         } else {
 | |
|           logger.error('PDF export failed: Disabled by config. Set "allowPDFExport: true" to enable. Check the documentation for details')
 | |
|           response.errorForbidden(res)
 | |
|         }
 | |
|         break
 | |
|       case 'gist':
 | |
|         actionGist(req, res, note)
 | |
|         break
 | |
|       case 'revision':
 | |
|         actionRevision(req, res, note)
 | |
|         break
 | |
|       default:
 | |
|         return res.redirect(config.serverURL + '/' + noteId)
 | |
|     }
 | |
|   })
 | |
| }
 | |
| 
 | |
| function publishNoteActions (req, res, next) {
 | |
|   findNote(req, res, function (note) {
 | |
|     var action = req.params.action
 | |
|     switch (action) {
 | |
|       case 'download':
 | |
|         actionDownload(req, res, note)
 | |
|         break
 | |
|       case 'edit':
 | |
|         res.redirect(config.serverURL + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)))
 | |
|         break
 | |
|       default:
 | |
|         res.redirect(config.serverURL + '/s/' + note.shortid)
 | |
|         break
 | |
|     }
 | |
|   })
 | |
| }
 | |
| 
 | |
| function publishSlideActions (req, res, next) {
 | |
|   findNote(req, res, function (note) {
 | |
|     var action = req.params.action
 | |
|     switch (action) {
 | |
|       case 'edit':
 | |
|         res.redirect(config.serverURL + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)))
 | |
|         break
 | |
|       default:
 | |
|         res.redirect(config.serverURL + '/p/' + note.shortid)
 | |
|         break
 | |
|     }
 | |
|   })
 | |
| }
 | |
| 
 | |
| function githubActions (req, res, next) {
 | |
|   var noteId = req.params.noteId
 | |
|   findNote(req, res, function (note) {
 | |
|     var action = req.params.action
 | |
|     switch (action) {
 | |
|       case 'gist':
 | |
|         githubActionGist(req, res, note)
 | |
|         break
 | |
|       default:
 | |
|         res.redirect(config.serverURL + '/' + noteId)
 | |
|         break
 | |
|     }
 | |
|   })
 | |
| }
 | |
| 
 | |
| function githubActionGist (req, res, note) {
 | |
|   var code = req.query.code
 | |
|   var state = req.query.state
 | |
|   if (!code || !state) {
 | |
|     return response.errorForbidden(res)
 | |
|   } else {
 | |
|     var data = {
 | |
|       client_id: config.github.clientID,
 | |
|       client_secret: config.github.clientSecret,
 | |
|       code: code,
 | |
|       state: state
 | |
|     }
 | |
|     var authUrl = 'https://github.com/login/oauth/access_token'
 | |
|     request({
 | |
|       url: authUrl,
 | |
|       method: 'POST',
 | |
|       json: data
 | |
|     }, function (error, httpResponse, body) {
 | |
|       if (!error && httpResponse.statusCode === 200) {
 | |
|         var accessToken = body.access_token
 | |
|         if (accessToken) {
 | |
|           var content = note.content
 | |
|           var title = models.Note.decodeTitle(note.title)
 | |
|           var filename = title.replace('/', ' ') + '.md'
 | |
|           var gist = {
 | |
|             'files': {}
 | |
|           }
 | |
|           gist.files[filename] = {
 | |
|             'content': content
 | |
|           }
 | |
|           var gistUrl = 'https://api.github.com/gists'
 | |
|           request({
 | |
|             url: gistUrl,
 | |
|             headers: {
 | |
|               'User-Agent': 'CodiMD',
 | |
|               'Authorization': 'token ' + accessToken
 | |
|             },
 | |
|             method: 'POST',
 | |
|             json: gist
 | |
|           }, function (error, httpResponse, body) {
 | |
|             if (!error && httpResponse.statusCode === 201) {
 | |
|               res.setHeader('referer', '')
 | |
|               res.redirect(body.html_url)
 | |
|             } else {
 | |
|               return response.errorForbidden(res)
 | |
|             }
 | |
|           })
 | |
|         } else {
 | |
|           return response.errorForbidden(res)
 | |
|         }
 | |
|       } else {
 | |
|         return response.errorForbidden(res)
 | |
|       }
 | |
|     })
 | |
|   }
 | |
| }
 | |
| 
 | |
| function gitlabActions (req, res, next) {
 | |
|   var noteId = req.params.noteId
 | |
|   findNote(req, res, function (note) {
 | |
|     var action = req.params.action
 | |
|     switch (action) {
 | |
|       case 'projects':
 | |
|         gitlabActionProjects(req, res, note)
 | |
|         break
 | |
|       default:
 | |
|         res.redirect(config.serverURL + '/' + noteId)
 | |
|         break
 | |
|     }
 | |
|   })
 | |
| }
 | |
| 
 | |
| function gitlabActionProjects (req, res, note) {
 | |
|   if (req.isAuthenticated()) {
 | |
|     models.User.findOne({
 | |
|       where: {
 | |
|         id: req.user.id
 | |
|       }
 | |
|     }).then(function (user) {
 | |
|       if (!user) { return response.errorNotFound(res) }
 | |
|       var ret = { baseURL: config.gitlab.baseURL, version: config.gitlab.version }
 | |
|       ret.accesstoken = user.accessToken
 | |
|       ret.profileid = user.profileid
 | |
|       request(
 | |
|                 config.gitlab.baseURL + '/api/' + config.gitlab.version + '/projects?membership=yes&per_page=100&access_token=' + user.accessToken,
 | |
|                 function (error, httpResponse, body) {
 | |
|                   if (!error && httpResponse.statusCode === 200) {
 | |
|                     ret.projects = JSON.parse(body)
 | |
|                     return res.send(ret)
 | |
|                   } else {
 | |
|                     return res.send(ret)
 | |
|                   }
 | |
|                 }
 | |
|             )
 | |
|     }).catch(function (err) {
 | |
|       logger.error('gitlab action projects failed: ' + err)
 | |
|       return response.errorInternalError(res)
 | |
|     })
 | |
|   } else {
 | |
|     return response.errorForbidden(res)
 | |
|   }
 | |
| }
 | |
| 
 | |
| function showPublishSlide (req, res, next) {
 | |
|   var include = [{
 | |
|     model: models.User,
 | |
|     as: 'owner'
 | |
|   }, {
 | |
|     model: models.User,
 | |
|     as: 'lastchangeuser'
 | |
|   }]
 | |
|   findNote(req, res, function (note) {
 | |
|     // force to use short id
 | |
|     var shortid = req.params.shortid
 | |
|     if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) { return res.redirect(config.serverURL + '/p/' + (note.alias || note.shortid)) }
 | |
|     note.increment('viewcount').then(function (note) {
 | |
|       if (!note) {
 | |
|         return response.errorNotFound(res)
 | |
|       }
 | |
|       var body = note.content
 | |
|       var extracted = models.Note.extractMeta(body)
 | |
|       var markdown = extracted.markdown
 | |
|       var meta = models.Note.parseMeta(extracted.meta)
 | |
|       var createtime = note.createdAt
 | |
|       var updatetime = note.lastchangeAt
 | |
|       var title = models.Note.decodeTitle(note.title)
 | |
|       title = models.Note.generateWebTitle(meta.title || title)
 | |
|       var data = {
 | |
|         title: title,
 | |
|         description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
 | |
|         viewcount: note.viewcount,
 | |
|         createtime: createtime,
 | |
|         updatetime: updatetime,
 | |
|         body: markdown,
 | |
|         theme: meta.slideOptions && utils.isRevealTheme(meta.slideOptions.theme),
 | |
|         meta: JSON.stringify(extracted.meta),
 | |
|         owner: note.owner ? note.owner.id : null,
 | |
|         ownerprofile: note.owner ? models.User.getProfile(note.owner) : null,
 | |
|         lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
 | |
|         lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
 | |
|         robots: meta.robots || false, // default allow robots
 | |
|         GA: meta.GA,
 | |
|         disqus: meta.disqus,
 | |
|         cspNonce: res.locals.nonce
 | |
|       }
 | |
|       return renderPublishSlide(data, res)
 | |
|     }).catch(function (err) {
 | |
|       logger.error(err)
 | |
|       return response.errorInternalError(res)
 | |
|     })
 | |
|   }, include)
 | |
| }
 | |
| 
 | |
| function renderPublishSlide (data, res) {
 | |
|   res.set({
 | |
|     'Cache-Control': 'private' // only cache by client
 | |
|   })
 | |
|   res.render('slide.ejs', data)
 | |
| }
 | |
| 
 | |
| module.exports = response
 |