From 68fcf35c70651d5c2fb4480d213447ebdd33533f Mon Sep 17 00:00:00 2001 From: BoHong Li Date: Sun, 5 Jan 2020 05:59:57 +0800 Subject: [PATCH] feat: merge all route to single file Signed-off-by: BoHong Li --- app.js | 8 +- lib/realtime.js | 93 ++++++++-------- lib/response.js | 4 +- lib/web/baseRouter.js | 22 ---- lib/web/errorPage/index.js | 21 ++++ lib/{history.js => web/history/index.js} | 8 +- lib/web/historyRouter.js | 18 ---- lib/web/homepage/index.js | 38 +++++++ lib/web/noteRouter.js | 28 ----- lib/web/routes.js | 80 ++++++++++++++ lib/web/status/index.js | 35 +++++++ lib/web/statusRouter.js | 45 -------- lib/web/user/index.js | 121 +++++++++++++++++++++ lib/web/userRouter.js | 128 ----------------------- lib/web/utils.js | 2 + 15 files changed, 353 insertions(+), 298 deletions(-) delete mode 100644 lib/web/baseRouter.js create mode 100644 lib/web/errorPage/index.js rename lib/{history.js => web/history/index.js} (97%) delete mode 100644 lib/web/historyRouter.js create mode 100644 lib/web/homepage/index.js delete mode 100644 lib/web/noteRouter.js create mode 100644 lib/web/routes.js create mode 100644 lib/web/status/index.js delete mode 100644 lib/web/statusRouter.js create mode 100644 lib/web/user/index.js delete mode 100644 lib/web/userRouter.js diff --git a/app.js b/app.js index 9acd94ca..25fc46d1 100644 --- a/app.js +++ b/app.js @@ -206,13 +206,7 @@ app.locals.enableDropBoxSave = config.isDropboxEnable app.locals.enableGitHubGist = config.isGitHubEnable app.locals.enableGitlabSnippets = config.isGitlabSnippetsEnable -app.use(require('./lib/web/baseRouter')) -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')) +app.use(require('./lib/web/routes').router) // response not found if no any route matxches app.get('*', function (req, res) { diff --git a/lib/realtime.js b/lib/realtime.js index fe16c277..6b9e4e14 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -14,7 +14,7 @@ const get = require('lodash/get') // core const config = require('./config') const logger = require('./logger') -const history = require('./history') +const history = require('./web/history') const models = require('./models') // ot @@ -247,59 +247,62 @@ async function _updateNoteAsync (note) { } // TODO: test it -function getStatus (callback) { - models.Note.count().then(function (notecount) { - var distinctaddresses = [] - var regaddresses = [] - var distinctregaddresses = [] - Object.keys(users).forEach(function (key) { - var user = users[key] - if (!user) return - let found = false - for (let i = 0; i < distinctaddresses.length; i++) { - if (user.address === distinctaddresses[i]) { - found = true - break - } - } - if (!found) { - distinctaddresses.push(user.address) - } - if (user.login) { - regaddresses.push(user.address) +function getStatus () { + return models.Note.count() + .then(function (notecount) { + var distinctaddresses = [] + var regaddresses = [] + var distinctregaddresses = [] + Object.keys(users).forEach(function (key) { + var user = users[key] + if (!user) return let found = false - for (let i = 0; i < distinctregaddresses.length; i++) { - if (user.address === distinctregaddresses[i]) { + for (let i = 0; i < distinctaddresses.length; i++) { + if (user.address === distinctaddresses[i]) { found = true break } } if (!found) { - distinctregaddresses.push(user.address) + distinctaddresses.push(user.address) } - } - }) - models.User.count().then(function (regcount) { - // eslint-disable-next-line standard/no-callback-literal - return callback ? callback({ - onlineNotes: Object.keys(notes).length, - onlineUsers: Object.keys(users).length, - distinctOnlineUsers: distinctaddresses.length, - notesCount: notecount, - registeredUsers: regcount, - onlineRegisteredUsers: regaddresses.length, - distinctOnlineRegisteredUsers: distinctregaddresses.length, - isConnectionBusy: connectProcessQueue.lock, - connectionSocketQueueLength: connectProcessQueue.queue.length, - isDisconnectBusy: disconnectProcessQueue.lock, - disconnectSocketQueueLength: disconnectProcessQueue.queue.length - }) : null + if (user.login) { + regaddresses.push(user.address) + let found = false + for (let i = 0; i < distinctregaddresses.length; i++) { + if (user.address === distinctregaddresses[i]) { + found = true + break + } + } + if (!found) { + distinctregaddresses.push(user.address) + } + } + }) + + return models.User.count() + .then(function (regcount) { + return { + onlineNotes: Object.keys(notes).length, + onlineUsers: Object.keys(users).length, + distinctOnlineUsers: distinctaddresses.length, + notesCount: notecount, + registeredUsers: regcount, + onlineRegisteredUsers: regaddresses.length, + distinctOnlineRegisteredUsers: distinctregaddresses.length, + isConnectionBusy: connectProcessQueue.lock, + connectionSocketQueueLength: connectProcessQueue.queue.length, + isDisconnectBusy: disconnectProcessQueue.lock, + disconnectSocketQueueLength: disconnectProcessQueue.queue.length + } + }) + .catch(function (err) { + return logger.error('count user failed: ' + err) + }) }).catch(function (err) { - return logger.error('count user failed: ' + err) + return logger.error('count note failed: ' + err) }) - }).catch(function (err) { - return logger.error('count note failed: ' + err) - }) } // TODO: test it diff --git a/lib/response.js b/lib/response.js index ce7e4fc3..d835d7ec 100644 --- a/lib/response.js +++ b/lib/response.js @@ -14,9 +14,10 @@ const config = require('./config') const logger = require('./logger') const models = require('./models') const utils = require('./utils') -const history = require('./history') +const history = require('./web/history') // public +exports.responseError = responseError exports.errorForbidden = errorForbidden exports.errorNotFound = errorNotFound exports.errorBadRequest = errorBadRequest @@ -33,6 +34,7 @@ exports.publishNoteActions = publishNoteActions exports.publishSlideActions = publishSlideActions exports.githubActions = githubActions exports.gitlabActions = gitlabActions +exports.checkViewPermission = checkViewPermission function errorForbidden (res) { const { req } = res diff --git a/lib/web/baseRouter.js b/lib/web/baseRouter.js deleted file mode 100644 index b918ce75..00000000 --- a/lib/web/baseRouter.js +++ /dev/null @@ -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) -}) diff --git a/lib/web/errorPage/index.js b/lib/web/errorPage/index.js new file mode 100644 index 00000000..1bdb3135 --- /dev/null +++ b/lib/web/errorPage/index.js @@ -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.') +} diff --git a/lib/history.js b/lib/web/history/index.js similarity index 97% rename from lib/history.js rename to lib/web/history/index.js index d4485591..ddab8dce 100644 --- a/lib/history.js +++ b/lib/web/history/index.js @@ -4,10 +4,10 @@ var LZString = require('@hackmd/lz-string') // core -var config = require('./config') -var logger = require('./logger') -var response = require('./response') -var models = require('./models') +var config = require('../../config') +var logger = require('../../logger') +var response = require('../../response') +var models = require('../../models') function getHistory (userid, callback) { models.User.findOne({ diff --git a/lib/web/historyRouter.js b/lib/web/historyRouter.js deleted file mode 100644 index fa426bbb..00000000 --- a/lib/web/historyRouter.js +++ /dev/null @@ -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) diff --git a/lib/web/homepage/index.js b/lib/web/homepage/index.js new file mode 100644 index 00000000..0111f5bf --- /dev/null +++ b/lib/web/homepage/index.js @@ -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) +} diff --git a/lib/web/noteRouter.js b/lib/web/noteRouter.js deleted file mode 100644 index bac2cf88..00000000 --- a/lib/web/noteRouter.js +++ /dev/null @@ -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) diff --git a/lib/web/routes.js b/lib/web/routes.js new file mode 100644 index 00000000..ba39e5d1 --- /dev/null +++ b/lib/web/routes.js @@ -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 diff --git a/lib/web/status/index.js b/lib/web/status/index.js new file mode 100644 index 00000000..2644a76b --- /dev/null +++ b/lib/web/status/index.js @@ -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) +} diff --git a/lib/web/statusRouter.js b/lib/web/statusRouter.js deleted file mode 100644 index e2a7119b..00000000 --- a/lib/web/statusRouter.js +++ /dev/null @@ -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) -}) diff --git a/lib/web/user/index.js b/lib/web/user/index.js new file mode 100644 index 00000000..3535e875 --- /dev/null +++ b/lib/web/user/index.js @@ -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)) +} diff --git a/lib/web/userRouter.js b/lib/web/userRouter.js deleted file mode 100644 index e1e2770f..00000000 --- a/lib/web/userRouter.js +++ /dev/null @@ -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)) -}) diff --git a/lib/web/utils.js b/lib/web/utils.js index d58294ad..a60f4d75 100644 --- a/lib/web/utils.js +++ b/lib/web/utils.js @@ -2,6 +2,8 @@ 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 exports.urlencodedParser = bodyParser.urlencoded({ extended: false,