feat: merge all route to single file

Signed-off-by: BoHong Li <raccoon@hackmd.io>
This commit is contained in:
BoHong Li 2020-01-05 05:59:57 +08:00
parent 80859f6cf7
commit 68fcf35c70
No known key found for this signature in database
GPG Key ID: 06770355DC9ECD38
15 changed files with 353 additions and 298 deletions

8
app.js
View File

@ -206,13 +206,7 @@ app.locals.enableDropBoxSave = config.isDropboxEnable
app.locals.enableGitHubGist = config.isGitHubEnable app.locals.enableGitHubGist = config.isGitHubEnable
app.locals.enableGitlabSnippets = config.isGitlabSnippetsEnable app.locals.enableGitlabSnippets = config.isGitlabSnippetsEnable
app.use(require('./lib/web/baseRouter')) app.use(require('./lib/web/routes').router)
app.use(require('./lib/web/statusRouter'))
app.use(require('./lib/web/auth'))
app.use(require('./lib/web/historyRouter'))
app.use(require('./lib/web/userRouter'))
app.use(require('./lib/web/imageRouter'))
app.use(require('./lib/web/noteRouter'))
// response not found if no any route matxches // response not found if no any route matxches
app.get('*', function (req, res) { app.get('*', function (req, res) {

View File

@ -14,7 +14,7 @@ const get = require('lodash/get')
// core // core
const config = require('./config') const config = require('./config')
const logger = require('./logger') const logger = require('./logger')
const history = require('./history') const history = require('./web/history')
const models = require('./models') const models = require('./models')
// ot // ot
@ -247,8 +247,9 @@ async function _updateNoteAsync (note) {
} }
// TODO: test it // TODO: test it
function getStatus (callback) { function getStatus () {
models.Note.count().then(function (notecount) { return models.Note.count()
.then(function (notecount) {
var distinctaddresses = [] var distinctaddresses = []
var regaddresses = [] var regaddresses = []
var distinctregaddresses = [] var distinctregaddresses = []
@ -279,9 +280,10 @@ function getStatus (callback) {
} }
} }
}) })
models.User.count().then(function (regcount) {
// eslint-disable-next-line standard/no-callback-literal return models.User.count()
return callback ? callback({ .then(function (regcount) {
return {
onlineNotes: Object.keys(notes).length, onlineNotes: Object.keys(notes).length,
onlineUsers: Object.keys(users).length, onlineUsers: Object.keys(users).length,
distinctOnlineUsers: distinctaddresses.length, distinctOnlineUsers: distinctaddresses.length,
@ -293,8 +295,9 @@ function getStatus (callback) {
connectionSocketQueueLength: connectProcessQueue.queue.length, connectionSocketQueueLength: connectProcessQueue.queue.length,
isDisconnectBusy: disconnectProcessQueue.lock, isDisconnectBusy: disconnectProcessQueue.lock,
disconnectSocketQueueLength: disconnectProcessQueue.queue.length disconnectSocketQueueLength: disconnectProcessQueue.queue.length
}) : null }
}).catch(function (err) { })
.catch(function (err) {
return logger.error('count user failed: ' + err) return logger.error('count user failed: ' + err)
}) })
}).catch(function (err) { }).catch(function (err) {

View File

@ -14,9 +14,10 @@ const config = require('./config')
const logger = require('./logger') const logger = require('./logger')
const models = require('./models') const models = require('./models')
const utils = require('./utils') const utils = require('./utils')
const history = require('./history') const history = require('./web/history')
// public // public
exports.responseError = responseError
exports.errorForbidden = errorForbidden exports.errorForbidden = errorForbidden
exports.errorNotFound = errorNotFound exports.errorNotFound = errorNotFound
exports.errorBadRequest = errorBadRequest exports.errorBadRequest = errorBadRequest
@ -33,6 +34,7 @@ exports.publishNoteActions = publishNoteActions
exports.publishSlideActions = publishSlideActions exports.publishSlideActions = publishSlideActions
exports.githubActions = githubActions exports.githubActions = githubActions
exports.gitlabActions = gitlabActions exports.gitlabActions = gitlabActions
exports.checkViewPermission = checkViewPermission
function errorForbidden (res) { function errorForbidden (res) {
const { req } = res const { req } = res

View File

@ -1,22 +0,0 @@
'use strict'
const Router = require('express').Router
const response = require('../response')
const baseRouter = module.exports = Router()
// get index
baseRouter.get('/', response.showIndex)
// get 403 forbidden
baseRouter.get('/403', function (req, res) {
response.errorForbidden(res)
})
// get 404 not found
baseRouter.get('/404', function (req, res) {
response.errorNotFound(res)
})
// get 500 internal error
baseRouter.get('/500', function (req, res) {
response.errorInternalError(res)
})

View File

@ -0,0 +1,21 @@
'use strict'
const config = require('../../config')
const { responseError } = require('../../response')
exports.errorForbidden = (req, res) => {
if (req.user) {
return responseError(res, '403', 'Forbidden', 'oh no.')
}
req.flash('error', 'You are not allowed to access this page. Maybe try logging in?')
res.redirect(config.serverURL + '/')
}
exports.errorNotFound = (req, res) => {
responseError(res, '404', 'Not Found', 'oops.')
}
exports.errorInternalError = (req, res) => {
responseError(res, '500', 'Internal Error', 'wtf.')
}

View File

@ -4,10 +4,10 @@
var LZString = require('@hackmd/lz-string') var LZString = require('@hackmd/lz-string')
// core // core
var config = require('./config') var config = require('../../config')
var logger = require('./logger') var logger = require('../../logger')
var response = require('./response') var response = require('../../response')
var models = require('./models') var models = require('../../models')
function getHistory (userid, callback) { function getHistory (userid, callback) {
models.User.findOne({ models.User.findOne({

View File

@ -1,18 +0,0 @@
'use strict'
const Router = require('express').Router
const { urlencodedParser } = require('./utils')
const history = require('../history')
const historyRouter = module.exports = Router()
// get history
historyRouter.get('/history', history.historyGet)
// post history
historyRouter.post('/history', urlencodedParser, history.historyPost)
// post history by note id
historyRouter.post('/history/:noteId', urlencodedParser, history.historyPost)
// delete history
historyRouter.delete('/history', history.historyDelete)
// delete history by note id
historyRouter.delete('/history/:noteId', history.historyDelete)

38
lib/web/homepage/index.js Normal file
View File

@ -0,0 +1,38 @@
'use strict'
const fs = require('fs')
const path = require('path')
const config = require('../../config')
const { User } = require('../../models')
const logger = require('../../logger')
exports.showIndex = async (req, res) => {
const isLogin = req.isAuthenticated()
const deleteToken = ''
const data = {
signin: isLogin,
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 (!isLogin) {
return res.render('index.ejs', data)
}
const user = await User.findOne({
where: {
id: req.user.id
}
})
if (user) {
data.deleteToken = user.deleteToken
return res.render('index.ejs', data)
}
logger.error(`error: user not found with id ${req.user.id}`)
return res.render('index.ejs', data)
}

View File

@ -1,28 +0,0 @@
'use strict'
const Router = require('express').Router
const response = require('../response')
const { markdownParser } = require('./utils')
const noteRouter = module.exports = Router()
// get new note
noteRouter.get('/new', response.newNote)
// post new note with content
noteRouter.post('/new', markdownParser, response.newNote)
// get publish note
noteRouter.get('/s/:shortid', response.showPublishNote)
// publish note actions
noteRouter.get('/s/:shortid/:action', response.publishNoteActions)
// get publish slide
noteRouter.get('/p/:shortid', response.showPublishSlide)
// publish slide actions
noteRouter.get('/p/:shortid/:action', response.publishSlideActions)
// get note by id
noteRouter.get('/:noteId', response.showNote)
// note actions
noteRouter.get('/:noteId/:action', response.noteActions)
// note actions with action id
noteRouter.get('/:noteId/:action/:actionId', response.noteActions)

80
lib/web/routes.js Normal file
View File

@ -0,0 +1,80 @@
'use strict'
const { Router } = require('express')
const { wrap, urlencodedParser, markdownParser } = require('./utils')
// load controller
const indexController = require('./homepage')
const errorPageController = require('./errorPage')
const statusController = require('./status')
const historyController = require('./history')
const userController = require('./user')
const response = require('../response')
const appRouter = Router()
// register route
// get index
appRouter.get('/', wrap(indexController.showIndex))
// ----- error page -----
// get 403 forbidden
appRouter.get('/403', errorPageController.errorForbidden)
// get 404 not found
appRouter.get('/404', errorPageController.errorNotFound)
// get 500 internal error
appRouter.get('/500', errorPageController.errorInternalError)
appRouter.get('/status', wrap(statusController.getStatus))
appRouter.get('/config', statusController.getConfig)
// register auth module
appRouter.use(require('./auth'))
// get history
appRouter.get('/history', historyController.historyGet)
// post history
appRouter.post('/history', urlencodedParser, historyController.historyPost)
// post history by note id
appRouter.post('/history/:noteId', urlencodedParser, historyController.historyPost)
// delete history
appRouter.delete('/history', historyController.historyDelete)
// delete history by note id
appRouter.delete('/history/:noteId', historyController.historyDelete)
// user
// get me info
appRouter.get('/me', wrap(userController.getMe))
// delete the currently authenticated user
appRouter.get('/me/delete/:token?', wrap(userController.deleteUser))
// export the data of the authenticated user
appRouter.get('/me/export', userController.exportMyData)
appRouter.get('/user/:username/avatar.svg', userController.getMyAvatar)
// register image upload module
appRouter.use(require('./imageRouter'))
// get new note
appRouter.get('/new', response.newNote)
// post new note with content
appRouter.post('/new', markdownParser, response.newNote)
// get publish note
appRouter.get('/s/:shortid', response.showPublishNote)
// publish note actions
appRouter.get('/s/:shortid/:action', response.publishNoteActions)
// get publish slide
appRouter.get('/p/:shortid', response.showPublishSlide)
// publish slide actions
appRouter.get('/p/:shortid/:action', response.publishSlideActions)
// get note by id
appRouter.get('/:noteId', response.showNote)
// note actions
appRouter.get('/:noteId/:action', response.noteActions)
// note actions with action id
appRouter.get('/:noteId/:action/:actionId', response.noteActions)
exports.router = appRouter

35
lib/web/status/index.js Normal file
View File

@ -0,0 +1,35 @@
'use strict'
const realtime = require('../../realtime')
const config = require('../../config')
exports.getStatus = async (req, res) => {
const data = await realtime.getStatus()
res.set({
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
'Content-Type': 'application/json'
})
res.send(data)
}
exports.getConfig = (req, res) => {
const data = {
domain: config.domain,
urlpath: config.urlPath,
debug: config.debug,
version: config.fullversion,
plantumlServer: config.plantuml.server,
DROPBOX_APP_KEY: config.dropbox.appKey,
allowedUploadMimeTypes: config.allowedUploadMimeTypes,
defaultUseHardbreak: config.defaultUseHardbreak,
linkifyHeaderStyle: config.linkifyHeaderStyle
}
res.set({
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
'Content-Type': 'application/javascript'
})
res.render('../js/lib/common/constant.ejs', data)
}

View File

@ -1,45 +0,0 @@
'use strict'
const Router = require('express').Router
const response = require('../response')
const realtime = require('../realtime')
const config = require('../config')
const models = require('../models')
const logger = require('../logger')
const { urlencodedParser } = require('./utils')
const statusRouter = module.exports = Router()
// get status
statusRouter.get('/status', function (req, res, next) {
realtime.getStatus(function (data) {
res.set({
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
'Content-Type': 'application/json'
})
res.send(data)
})
})
statusRouter.get('/config', function (req, res) {
var data = {
domain: config.domain,
urlpath: config.urlPath,
debug: config.debug,
version: config.fullversion,
plantumlServer: config.plantuml.server,
DROPBOX_APP_KEY: config.dropbox.appKey,
allowedUploadMimeTypes: config.allowedUploadMimeTypes,
defaultUseHardbreak: config.defaultUseHardbreak,
linkifyHeaderStyle: config.linkifyHeaderStyle
}
res.set({
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
'Content-Type': 'application/javascript'
})
res.render('../js/lib/common/constant.ejs', data)
})

121
lib/web/user/index.js Normal file
View File

@ -0,0 +1,121 @@
'use strict'
const archiver = require('archiver')
const async = require('async')
const response = require('../../response')
const config = require('../../config')
const models = require('../../models')
const logger = require('../../logger')
const { generateAvatar } = require('../../letter-avatars')
exports.getMe = async (req, res) => {
if (!req.isAuthenticated()) {
res.status(401).send({
status: 'forbidden'
})
}
const user = await models.User.findOne({
where: {
id: req.user.id
}
})
if (!user) {
return response.errorNotFound(res)
}
const profile = models.User.getProfile(user)
res.send({
status: 'ok',
id: req.user.id,
name: profile.name,
photo: profile.photo
})
}
exports.deleteUser = async (req, res) => {
if (!req.isAuthenticated()) {
return response.errorForbidden(res)
}
const user = await models.User.findOne({
where: {
id: req.user.id
}
})
if (!user) {
return response.errorNotFound(res)
}
if (user.deleteToken !== req.params.token) {
return response.errorForbidden(res)
}
await user.destroy()
return res.redirect(config.serverURL + '/')
}
exports.exportMyData = (req, res) => {
if (!req.isAuthenticated()) {
return response.errorForbidden(res)
}
const archive = archiver('zip', {
zlib: { level: 3 } // Sets the compression level.
})
res.setHeader('Content-Type', 'application/zip')
res.attachment('archive.zip')
archive.pipe(res)
archive.on('error', function (err) {
logger.error('export user data failed: ' + err)
return response.errorInternalError(res)
})
models.User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
models.Note.findAll({
where: {
ownerId: user.id
}
}).then(function (notes) {
const filenames = {}
async.each(notes, function (note, callback) {
const basename = note.title.replace(/\//g, '-') // Prevent subdirectories
let filename
let suffix = 0
do {
const separator = suffix === 0 ? '' : '-'
filename = basename + separator + suffix + '.md'
suffix++
} while (filenames[filename])
filenames[filename] = true
logger.debug('Write: ' + filename)
archive.append(Buffer.from(note.content), { name: filename, date: note.lastchangeAt })
callback(null, null)
}, function (err) {
if (err) {
return response.errorInternalError(res)
}
archive.finalize()
})
})
}).catch(function (err) {
logger.error('export user data failed: ' + err)
return response.errorInternalError(res)
})
}
exports.getMyAvatar = (req, res) => {
res.setHeader('Content-Type', 'image/svg+xml')
res.setHeader('Cache-Control', 'public, max-age=86400')
res.send(generateAvatar(req.params.username))
}

View File

@ -1,128 +0,0 @@
'use strict'
const archiver = require('archiver')
const async = require('async')
const Router = require('express').Router
const response = require('../response')
const config = require('../config')
const models = require('../models')
const logger = require('../logger')
const { generateAvatar } = require('../letter-avatars')
const UserRouter = module.exports = Router()
// get me info
UserRouter.get('/me', function (req, res) {
if (req.isAuthenticated()) {
models.User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
if (!user) { return response.errorNotFound(res) }
var profile = models.User.getProfile(user)
res.send({
status: 'ok',
id: req.user.id,
name: profile.name,
photo: profile.photo
})
}).catch(function (err) {
logger.error('read me failed: ' + err)
return response.errorInternalError(res)
})
} else {
res.status(401).send({
status: 'forbidden'
})
}
})
// delete the currently authenticated user
UserRouter.get('/me/delete/:token?', function (req, res) {
if (req.isAuthenticated()) {
models.User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
if (!user) {
return response.errorNotFound(res)
}
if (user.deleteToken === req.params.token) {
user.destroy().then(function () {
res.redirect(config.serverURL + '/')
})
} else {
return response.errorForbidden(res)
}
}).catch(function (err) {
logger.error('delete user failed: ' + err)
return response.errorInternalError(res)
})
} else {
return response.errorForbidden(res)
}
})
// export the data of the authenticated user
UserRouter.get('/me/export', function (req, res) {
if (req.isAuthenticated()) {
const archive = archiver('zip', {
zlib: { level: 3 } // Sets the compression level.
})
res.setHeader('Content-Type', 'application/zip')
res.attachment('archive.zip')
archive.pipe(res)
archive.on('error', function (err) {
logger.error('export user data failed: ' + err)
return response.errorInternalError(res)
})
models.User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
models.Note.findAll({
where: {
ownerId: user.id
}
}).then(function (notes) {
const filenames = {}
async.each(notes, function (note, callback) {
const basename = note.title.replace(/\//g, '-') // Prevent subdirectories
let filename
let suffix = 0
do {
const separator = suffix === 0 ? '' : '-'
filename = basename + separator + suffix + '.md'
suffix++
} while (filenames[filename])
filenames[filename] = true
logger.debug('Write: ' + filename)
archive.append(Buffer.from(note.content), { name: filename, date: note.lastchangeAt })
callback(null, null)
}, function (err) {
if (err) {
return response.errorInternalError(res)
}
archive.finalize()
})
})
}).catch(function (err) {
logger.error('export user data failed: ' + err)
return response.errorInternalError(res)
})
} else {
return response.errorForbidden(res)
}
})
UserRouter.get('/user/:username/avatar.svg', function (req, res, next) {
res.setHeader('Content-Type', 'image/svg+xml')
res.setHeader('Cache-Control', 'public, max-age=86400')
res.send(generateAvatar(req.params.username))
})

View File

@ -2,6 +2,8 @@
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
exports.wrap = innerHandler => (req, res, next) => innerHandler(req, res).catch(err => next(err))
// create application/x-www-form-urlencoded parser // create application/x-www-form-urlencoded parser
exports.urlencodedParser = bodyParser.urlencoded({ exports.urlencodedParser = bodyParser.urlencoded({
extended: false, extended: false,