diff --git a/app.js b/app.js index 9acd94ca..c884a511 100644 --- a/app.js +++ b/app.js @@ -66,7 +66,7 @@ io.engine.ws = new (require('ws').Server)({ }) // others -var realtime = require('./lib/realtime.js') +var realtime = require('./lib/realtime/realtime.js') // assign socket io to realtime realtime.io = io @@ -153,7 +153,7 @@ server.on('resumeSession', function (id, cb) { }) // middleware which blocks requests when we're too busy -app.use(require('./lib/web/middleware/tooBusy')) +app.use(require('./lib/middleware/tooBusy')) app.use(flash()) @@ -162,10 +162,10 @@ app.use(passport.initialize()) app.use(passport.session()) // check uri is valid before going further -app.use(require('./lib/web/middleware/checkURIValid')) +app.use(require('./lib/middleware/checkURIValid')) // redirect url without trailing slashes -app.use(require('./lib/web/middleware/redirectWithoutTrailingSlashes')) -app.use(require('./lib/web/middleware/codiMDVersion')) +app.use(require('./lib/middleware/redirectWithoutTrailingSlashes')) +app.use(require('./lib/middleware/codiMDVersion')) // routes need sessions // template files @@ -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/routes').router) // response not found if no any route matxches app.get('*', function (req, res) { diff --git a/lib/web/auth/bitbucket/index.js b/lib/auth/bitbucket/index.js similarity index 95% rename from lib/web/auth/bitbucket/index.js rename to lib/auth/bitbucket/index.js index 5d8f1523..6cbdd9b4 100644 --- a/lib/web/auth/bitbucket/index.js +++ b/lib/auth/bitbucket/index.js @@ -3,7 +3,7 @@ const Router = require('express').Router const passport = require('passport') const BitbucketStrategy = require('passport-bitbucket-oauth2').Strategy -const config = require('../../../config') +const config = require('../../config') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const bitbucketAuth = module.exports = Router() diff --git a/lib/web/auth/dropbox/index.js b/lib/auth/dropbox/index.js similarity index 95% rename from lib/web/auth/dropbox/index.js rename to lib/auth/dropbox/index.js index 7d02275e..099e3724 100644 --- a/lib/web/auth/dropbox/index.js +++ b/lib/auth/dropbox/index.js @@ -3,7 +3,7 @@ const Router = require('express').Router const passport = require('passport') const DropboxStrategy = require('passport-dropbox-oauth2').Strategy -const config = require('../../../config') +const config = require('../../config') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const dropboxAuth = module.exports = Router() diff --git a/lib/web/auth/email/index.js b/lib/auth/email/index.js similarity index 93% rename from lib/web/auth/email/index.js rename to lib/auth/email/index.js index ce26931a..56ed17a1 100644 --- a/lib/web/auth/email/index.js +++ b/lib/auth/email/index.js @@ -4,12 +4,12 @@ const Router = require('express').Router const passport = require('passport') const validator = require('validator') const LocalStrategy = require('passport-local').Strategy -const config = require('../../../config') -const models = require('../../../models') -const logger = require('../../../logger') +const config = require('../../config') +const models = require('../../models') +const logger = require('../../logger') const { setReturnToFromReferer } = require('../utils') const { urlencodedParser } = require('../../utils') -const response = require('../../../response') +const response = require('../../response') const emailAuth = module.exports = Router() diff --git a/lib/web/auth/facebook/index.js b/lib/auth/facebook/index.js similarity index 95% rename from lib/web/auth/facebook/index.js rename to lib/auth/facebook/index.js index 86471535..201c82cf 100644 --- a/lib/web/auth/facebook/index.js +++ b/lib/auth/facebook/index.js @@ -4,7 +4,7 @@ const Router = require('express').Router const passport = require('passport') const FacebookStrategy = require('passport-facebook').Strategy -const config = require('../../../config') +const config = require('../../config') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const facebookAuth = module.exports = Router() diff --git a/lib/web/auth/github/index.js b/lib/auth/github/index.js similarity index 93% rename from lib/web/auth/github/index.js rename to lib/auth/github/index.js index 0609f6b3..8f05f122 100644 --- a/lib/web/auth/github/index.js +++ b/lib/auth/github/index.js @@ -3,8 +3,8 @@ const Router = require('express').Router const passport = require('passport') const GithubStrategy = require('passport-github').Strategy -const config = require('../../../config') -const response = require('../../../response') +const config = require('../../config') +const response = require('../../response') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const { URL } = require('url') diff --git a/lib/web/auth/gitlab/index.js b/lib/auth/gitlab/index.js similarity index 93% rename from lib/web/auth/gitlab/index.js rename to lib/auth/gitlab/index.js index f5f493de..72ab0cde 100644 --- a/lib/web/auth/gitlab/index.js +++ b/lib/auth/gitlab/index.js @@ -3,8 +3,8 @@ const Router = require('express').Router const passport = require('passport') const GitlabStrategy = require('passport-gitlab2').Strategy -const config = require('../../../config') -const response = require('../../../response') +const config = require('../../config') +const response = require('../../response') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const HttpsProxyAgent = require('https-proxy-agent') diff --git a/lib/web/auth/google/index.js b/lib/auth/google/index.js similarity index 95% rename from lib/web/auth/google/index.js rename to lib/auth/google/index.js index b9a8a142..59d1ca2c 100644 --- a/lib/web/auth/google/index.js +++ b/lib/auth/google/index.js @@ -3,7 +3,7 @@ const Router = require('express').Router const passport = require('passport') var GoogleStrategy = require('passport-google-oauth20').Strategy -const config = require('../../../config') +const config = require('../../config') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const googleAuth = module.exports = Router() diff --git a/lib/web/auth/index.js b/lib/auth/index.js similarity index 93% rename from lib/web/auth/index.js rename to lib/auth/index.js index b387b710..fcc39a2a 100644 --- a/lib/web/auth/index.js +++ b/lib/auth/index.js @@ -3,9 +3,9 @@ const Router = require('express').Router const passport = require('passport') -const config = require('../../config') -const logger = require('../../logger') -const models = require('../../models') +const config = require('../config') +const logger = require('../logger') +const models = require('../models') const authRouter = module.exports = Router() diff --git a/lib/web/auth/ldap/index.js b/lib/auth/ldap/index.js similarity index 94% rename from lib/web/auth/ldap/index.js rename to lib/auth/ldap/index.js index 1c24fd52..2f3b9a11 100644 --- a/lib/web/auth/ldap/index.js +++ b/lib/auth/ldap/index.js @@ -3,12 +3,12 @@ const Router = require('express').Router const passport = require('passport') const LDAPStrategy = require('passport-ldapauth') -const config = require('../../../config') -const models = require('../../../models') -const logger = require('../../../logger') +const config = require('../../config') +const models = require('../../models') +const logger = require('../../logger') const { setReturnToFromReferer } = require('../utils') const { urlencodedParser } = require('../../utils') -const response = require('../../../response') +const response = require('../../response') const ldapAuth = module.exports = Router() diff --git a/lib/web/auth/mattermost/index.js b/lib/auth/mattermost/index.js similarity index 97% rename from lib/web/auth/mattermost/index.js rename to lib/auth/mattermost/index.js index a0b440c7..ace631d0 100644 --- a/lib/web/auth/mattermost/index.js +++ b/lib/auth/mattermost/index.js @@ -5,7 +5,7 @@ const Router = require('express').Router const passport = require('passport') const MattermostClient = require('mattermost-redux/client/client4').default const OAuthStrategy = require('passport-oauth2').Strategy -const config = require('../../../config') +const config = require('../../config') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const mattermostAuth = module.exports = Router() diff --git a/lib/web/auth/oauth2/index.js b/lib/auth/oauth2/index.js similarity index 98% rename from lib/web/auth/oauth2/index.js rename to lib/auth/oauth2/index.js index ed04587f..cee4584f 100644 --- a/lib/web/auth/oauth2/index.js +++ b/lib/auth/oauth2/index.js @@ -3,7 +3,7 @@ const Router = require('express').Router const passport = require('passport') const { Strategy, InternalOAuthError } = require('passport-oauth2') -const config = require('../../../config') +const config = require('../../config') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const oauth2Auth = module.exports = Router() diff --git a/lib/web/auth/openid/index.js b/lib/auth/openid/index.js similarity index 92% rename from lib/web/auth/openid/index.js rename to lib/auth/openid/index.js index 9f2db3db..e51ce087 100644 --- a/lib/web/auth/openid/index.js +++ b/lib/auth/openid/index.js @@ -3,9 +3,9 @@ const Router = require('express').Router const passport = require('passport') const OpenIDStrategy = require('@passport-next/passport-openid').Strategy -const config = require('../../../config') -const models = require('../../../models') -const logger = require('../../../logger') +const config = require('../../config') +const models = require('../../models') +const logger = require('../../logger') const { urlencodedParser } = require('../../utils') const { setReturnToFromReferer } = require('../utils') diff --git a/lib/web/auth/saml/index.js b/lib/auth/saml/index.js similarity index 96% rename from lib/web/auth/saml/index.js rename to lib/auth/saml/index.js index df5f2835..ecf740a6 100644 --- a/lib/web/auth/saml/index.js +++ b/lib/auth/saml/index.js @@ -3,9 +3,9 @@ const Router = require('express').Router const passport = require('passport') const SamlStrategy = require('passport-saml').Strategy -const config = require('../../../config') -const models = require('../../../models') -const logger = require('../../../logger') +const config = require('../../config') +const models = require('../../models') +const logger = require('../../logger') const { urlencodedParser } = require('../../utils') const fs = require('fs') const intersection = function (array1, array2) { return array1.filter((n) => array2.includes(n)) } diff --git a/lib/web/auth/twitter/index.js b/lib/auth/twitter/index.js similarity index 95% rename from lib/web/auth/twitter/index.js rename to lib/auth/twitter/index.js index 144baccd..3c638067 100644 --- a/lib/web/auth/twitter/index.js +++ b/lib/auth/twitter/index.js @@ -4,7 +4,7 @@ const Router = require('express').Router const passport = require('passport') const TwitterStrategy = require('passport-twitter').Strategy -const config = require('../../../config') +const config = require('../../config') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils') const twitterAuth = module.exports = Router() diff --git a/lib/web/auth/utils.js b/lib/auth/utils.js similarity index 92% rename from lib/web/auth/utils.js rename to lib/auth/utils.js index ff7a1237..fe6295d2 100644 --- a/lib/web/auth/utils.js +++ b/lib/auth/utils.js @@ -1,8 +1,8 @@ 'use strict' -const models = require('../../models') -const config = require('../../config') -const logger = require('../../logger') +const models = require('../models') +const config = require('../config') +const logger = require('../logger') exports.setReturnToFromReferer = function setReturnToFromReferer (req) { var referer = req.get('referer') diff --git a/lib/errorPage/index.js b/lib/errorPage/index.js new file mode 100644 index 00000000..07633063 --- /dev/null +++ b/lib/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/history/index.js similarity index 97% rename from lib/history.js rename to lib/history/index.js index d4485591..19a2646a 100644 --- a/lib/history.js +++ b/lib/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/homepage/index.js b/lib/homepage/index.js new file mode 100644 index 00000000..d3ce9f05 --- /dev/null +++ b/lib/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/imageRouter/azure.js b/lib/imageRouter/azure.js similarity index 92% rename from lib/web/imageRouter/azure.js rename to lib/imageRouter/azure.js index cc98e5fc..1756517b 100644 --- a/lib/web/imageRouter/azure.js +++ b/lib/imageRouter/azure.js @@ -1,8 +1,8 @@ 'use strict' const path = require('path') -const config = require('../../config') -const logger = require('../../logger') +const config = require('../config') +const logger = require('../logger') const azure = require('azure-storage') diff --git a/lib/web/imageRouter/filesystem.js b/lib/imageRouter/filesystem.js similarity index 88% rename from lib/web/imageRouter/filesystem.js rename to lib/imageRouter/filesystem.js index a43a2782..53ecc9d4 100644 --- a/lib/web/imageRouter/filesystem.js +++ b/lib/imageRouter/filesystem.js @@ -2,8 +2,8 @@ const URL = require('url').URL const path = require('path') -const config = require('../../config') -const logger = require('../../logger') +const config = require('../config') +const logger = require('../logger') exports.uploadImage = function (imagePath, callback) { if (!imagePath || typeof imagePath !== 'string') { diff --git a/lib/web/imageRouter/imgur.js b/lib/imageRouter/imgur.js similarity index 90% rename from lib/web/imageRouter/imgur.js rename to lib/imageRouter/imgur.js index 11dac6ad..3ed014f5 100644 --- a/lib/web/imageRouter/imgur.js +++ b/lib/imageRouter/imgur.js @@ -1,6 +1,6 @@ 'use strict' -const config = require('../../config') -const logger = require('../../logger') +const config = require('../config') +const logger = require('../logger') const imgur = require('@hackmd/imgur') diff --git a/lib/web/imageRouter/index.js b/lib/imageRouter/index.js similarity index 89% rename from lib/web/imageRouter/index.js rename to lib/imageRouter/index.js index f3c2decf..c4ab548f 100644 --- a/lib/web/imageRouter/index.js +++ b/lib/imageRouter/index.js @@ -3,9 +3,9 @@ const Router = require('express').Router const formidable = require('formidable') -const config = require('../../config') -const logger = require('../../logger') -const response = require('../../response') +const config = require('../config') +const logger = require('../logger') +const response = require('../response') const imageRouter = module.exports = Router() diff --git a/lib/web/imageRouter/lutim.js b/lib/imageRouter/lutim.js similarity index 85% rename from lib/web/imageRouter/lutim.js rename to lib/imageRouter/lutim.js index 78b856c9..3e27fc42 100644 --- a/lib/web/imageRouter/lutim.js +++ b/lib/imageRouter/lutim.js @@ -1,8 +1,8 @@ 'use strict' -const config = require('../../config') -const logger = require('../../logger') +const config = require('../config') +const logger = require('../logger') -const lutim = require('lutim') +const lutim = require('lib/imageRouter/lutim') exports.uploadImage = function (imagePath, callback) { if (!imagePath || typeof imagePath !== 'string') { diff --git a/lib/web/imageRouter/minio.js b/lib/imageRouter/minio.js similarity index 88% rename from lib/web/imageRouter/minio.js rename to lib/imageRouter/minio.js index 3ced94e2..7112b306 100644 --- a/lib/web/imageRouter/minio.js +++ b/lib/imageRouter/minio.js @@ -2,11 +2,11 @@ const fs = require('fs') const path = require('path') -const config = require('../../config') -const { getImageMimeType } = require('../../utils') -const logger = require('../../logger') +const config = require('../config') +const { getImageMimeType } = require('../utils') +const logger = require('../logger') -const Minio = require('minio') +const Minio = require('lib/imageRouter/minio') const minioClient = new Minio.Client({ endPoint: config.minio.endPoint, port: config.minio.port, diff --git a/lib/web/imageRouter/s3.js b/lib/imageRouter/s3.js similarity index 91% rename from lib/web/imageRouter/s3.js rename to lib/imageRouter/s3.js index fbed1b0c..b4a7ecda 100644 --- a/lib/web/imageRouter/s3.js +++ b/lib/imageRouter/s3.js @@ -2,9 +2,9 @@ const fs = require('fs') const path = require('path') -const config = require('../../config') -const { getImageMimeType } = require('../../utils') -const logger = require('../../logger') +const config = require('../config') +const { getImageMimeType } = require('../utils') +const logger = require('../logger') const AWS = require('aws-sdk') const awsConfig = new AWS.Config(config.s3) diff --git a/lib/web/middleware/checkURIValid.js b/lib/middleware/checkURIValid.js similarity index 70% rename from lib/web/middleware/checkURIValid.js rename to lib/middleware/checkURIValid.js index 88065e79..edb61da2 100644 --- a/lib/web/middleware/checkURIValid.js +++ b/lib/middleware/checkURIValid.js @@ -1,7 +1,7 @@ 'use strict' -const logger = require('../../logger') -const response = require('../../response') +const logger = require('../logger') +const response = require('../response') module.exports = function (req, res, next) { try { diff --git a/lib/web/middleware/codiMDVersion.js b/lib/middleware/codiMDVersion.js similarity index 77% rename from lib/web/middleware/codiMDVersion.js rename to lib/middleware/codiMDVersion.js index e7775876..eb965793 100644 --- a/lib/web/middleware/codiMDVersion.js +++ b/lib/middleware/codiMDVersion.js @@ -1,6 +1,6 @@ 'use strict' -const config = require('../../config') +const config = require('../config') module.exports = function (req, res, next) { res.set({ diff --git a/lib/web/middleware/redirectWithoutTrailingSlashes.js b/lib/middleware/redirectWithoutTrailingSlashes.js similarity index 92% rename from lib/web/middleware/redirectWithoutTrailingSlashes.js rename to lib/middleware/redirectWithoutTrailingSlashes.js index 60ca04af..456e3e30 100644 --- a/lib/web/middleware/redirectWithoutTrailingSlashes.js +++ b/lib/middleware/redirectWithoutTrailingSlashes.js @@ -1,6 +1,6 @@ 'use strict' -const config = require('../../config') +const config = require('../config') module.exports = function (req, res, next) { if (req.method === 'GET' && req.path.substr(-1) === '/' && req.path.length > 1) { diff --git a/lib/web/middleware/tooBusy.js b/lib/middleware/tooBusy.js similarity index 73% rename from lib/web/middleware/tooBusy.js rename to lib/middleware/tooBusy.js index f7c0850f..13642c6c 100644 --- a/lib/web/middleware/tooBusy.js +++ b/lib/middleware/tooBusy.js @@ -2,8 +2,8 @@ const toobusy = require('toobusy-js') -const config = require('../../config') -const response = require('../../response') +const config = require('../config') +const response = require('../response') toobusy.maxLag(config.responseMaxLag) diff --git a/lib/migrations/20200104215332-remove-temp-table.js b/lib/migrations/20200104215332-remove-temp-table.js new file mode 100644 index 00000000..eb743337 --- /dev/null +++ b/lib/migrations/20200104215332-remove-temp-table.js @@ -0,0 +1,26 @@ +'use strict' + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Temp') + /* + Add altering commands here. + Return a promise to correctly handle asynchronicity. + + Example: + return queryInterface.createTable('users', { id: Sequelize.INTEGER }); + */ + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.createTable('Temp', { + id: { + type: Sequelize.STRING, + primaryKey: true + }, + date: Sequelize.TEXT, + createdAt: Sequelize.DATE, + updatedAt: Sequelize.DATE + }) + } +} diff --git a/lib/models/note.js b/lib/models/note.js index ffd2a85c..8030be00 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -186,6 +186,16 @@ module.exports = function (sequelize, DataTypes) { var result = id.match(uuidRegex) if (result && result.length === 1) { return true } else { return false } } + Note.parseNoteIdAsync = function (noteId) { + return new Promise((resolve, reject) => { + Note.parseNoteId(noteId, (err, id) => { + if (err) { + return reject(err) + } + resolve(id) + }) + }) + } Note.parseNoteId = function (noteId, callback) { async.series({ parseNoteIdByAlias: function (_callback) { diff --git a/lib/models/temp.js b/lib/models/temp.js deleted file mode 100644 index 2ad23fb5..00000000 --- a/lib/models/temp.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' -// external modules -var shortId = require('shortid') - -module.exports = function (sequelize, DataTypes) { - var Temp = sequelize.define('Temp', { - id: { - type: DataTypes.STRING, - primaryKey: true, - defaultValue: shortId.generate - }, - data: { - type: DataTypes.TEXT - } - }) - - return Temp -} diff --git a/lib/note/index.js b/lib/note/index.js new file mode 100644 index 00000000..54bf5fba --- /dev/null +++ b/lib/note/index.js @@ -0,0 +1,171 @@ +'use strict' + +const config = require('../config') +const logger = require('../logger') + +const { Note, User } = require('../models') + +const { newCheckViewPermission, errorForbidden, responseCodiMD, errorNotFound } = require('../response') +const { updateHistory } = require('../history') +const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision } = require('./noteActions') + +async function getNoteById (noteId, { includeUser } = { includeUser: false }) { + const id = await Note.parseNoteIdAsync(noteId) + + const includes = [] + + if (includeUser) { + includes.push({ + model: User, + as: 'owner' + }, { + model: User, + as: 'lastchangeuser' + }) + } + + const note = await Note.findOne({ + where: { + id: id + }, + include: includes + }) + return note +} + +async function createNote (userId, noteAlias) { + if (!config.allowAnonymous && !!userId) { + throw new Error('can not create note') + } + + const note = await Note.create({ + ownerId: userId, + alias: noteAlias + }) + + if (userId) { + updateHistory(userId, note) + } + + return note +} + +// controller +async function showNote (req, res) { + const noteId = req.params.noteId + const userId = req.user ? req.user.id : null + + let note = await getNoteById(noteId) + + if (!note) { + // if allow free url enable, auto create note + if (!config.allowFreeURL || config.forbiddenNoteIDs.includes(noteId)) { + return errorNotFound(res) + } + note = await createNote(userId, noteId) + } + + if (!newCheckViewPermission(note, req.isAuthenticated(), userId)) { + return errorForbidden(res) + } + + // force to use note id + const id = 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) +} + +async function showPublishNote (req, res) { + const shortid = req.params.shortid + + const note = await getNoteById(shortid, { + includeUser: true + }) + + if (!note) { + return errorNotFound(res) + } + + if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) { + return res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid)) + } + + await note.increment('viewcount') + + const body = note.content + const extracted = Note.extractMeta(body) + const markdown = extracted.markdown + const meta = Note.parseMeta(extracted.meta) + const createTime = note.createdAt + const updateTime = note.lastchangeAt + const title = Note.generateWebTitle(meta.title || Note.decodeTitle(note.title)) + + const data = { + title: title, + description: meta.description || (markdown ? Note.generateDescription(markdown) : null), + viewcount: note.viewcount, + createtime: createTime, + updatetime: updateTime, + body: body, + owner: note.owner ? note.owner.id : null, + ownerprofile: note.owner ? User.getProfile(note.owner) : null, + lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null, + lastchangeuserprofile: note.lastchangeuser ? User.getProfile(note.lastchangeuser) : null, + robots: meta.robots || false, // default allow robots + GA: meta.GA, + disqus: meta.disqus, + cspNonce: res.locals.nonce + } + + res.set({ + 'Cache-Control': 'private' // only cache by client + }) + + res.render('pretty.ejs', data) +} + +async function noteActions (req, res) { + const noteId = req.params.noteId + + const note = await getNoteById(noteId) + if (!note) { + return errorNotFound(res) + } + + const action = req.params.action + switch (action) { + case 'publish': + case 'pretty': // pretty deprecated + return actionPublish(req, res, note) + case 'slide': + return actionSlide(req, res, note) + 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') + errorForbidden(res) + } + break + case 'gist': + actionGist(req, res, note) + break + case 'revision': + actionRevision(req, res, note) + break + default: + return res.redirect(config.serverURL + '/' + noteId) + } +} + +exports.showNote = showNote +exports.showPublishNote = showPublishNote +exports.noteActions = noteActions diff --git a/lib/note/noteActions.js b/lib/note/noteActions.js new file mode 100644 index 00000000..c13fa258 --- /dev/null +++ b/lib/note/noteActions.js @@ -0,0 +1,164 @@ +'use strict' + +const fs = require('fs') +const path = require('path') +const markdownpdf = require('markdown-pdf') +const shortId = require('shortid') +const querystring = require('querystring') +const moment = require('moment') + +const config = require('../config') +const logger = require('../logger') +const { Note, Revision } = require('../models') +const { errorInternalError, errorNotFound } = require('../response') + +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) { + const body = note.content + const title = Note.decodeTitle(note.title) + const filename = encodeURIComponent(title) + 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) { + const body = note.content + const extracted = Note.extractMeta(body) + const markdown = extracted.markdown + const meta = Note.parseMeta(extracted.meta) + const createtime = note.createdAt + const updatetime = note.lastchangeAt + const title = Note.decodeTitle(note.title) + + const data = { + title: meta.title || title, + description: meta.description || (markdown ? 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) { + const url = config.serverURL || 'http://' + req.get('host') + const body = note.content + const extracted = Note.extractMeta(body) + let content = extracted.markdown + const title = Note.decodeTitle(note.title) + + const highlightCssPath = path.join(config.appRootPath, '/node_modules/highlight.js/styles/github-gist.css') + + if (!fs.existsSync(config.tmpPath)) { + fs.mkdirSync(config.tmpPath) + } + const pdfPath = config.tmpPath + '/' + Date.now() + '.pdf' + content = content.replace(/\]\(\//g, '](' + url + '/') + const markdownpdfOptions = { + highlightCssPath: highlightCssPath + } + markdownpdf(markdownpdfOptions).from.string(content).to(pdfPath, function () { + if (!fs.existsSync(pdfPath)) { + logger.error('PDF seems to not be generated as expected. File doesn\'t exist: ' + pdfPath) + return errorInternalError(res) + } + const stream = fs.createReadStream(pdfPath) + let 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.unlinkSync(pdfPath) + }) +} + +function actionGist (req, res, note) { + const data = { + client_id: config.github.clientID, + redirect_uri: config.serverURL + '/auth/github/callback/' + Note.encodeNoteId(note.id) + '/gist', + scope: 'gist', + state: shortId.generate() + } + const query = querystring.stringify(data) + res.redirect('https://github.com/login/oauth/authorize?' + query) +} + +function actionRevision (req, res, note) { + const actionId = req.params.actionId + if (actionId) { + const time = moment(parseInt(actionId)) + if (!time.isValid()) { + return errorNotFound(res) + } + Revision.getPatchedNoteRevisionByTime(note, time, function (err, content) { + if (err) { + logger.error(err) + return errorInternalError(res) + } + if (!content) { + return 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 { + Revision.getNoteRevisions(note, function (err, data) { + if (err) { + logger.error(err) + return errorInternalError(res) + } + const result = { + 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(result) + }) + } +} + +exports.actionPublish = actionPublish +exports.actionSlide = actionSlide +exports.actionDownload = actionDownload +exports.actionInfo = actionInfo +exports.actionPDF = actionPDF +exports.actionGist = actionGist +exports.actionRevision = actionRevision diff --git a/lib/processQueue.js b/lib/realtime/processQueue.js similarity index 100% rename from lib/processQueue.js rename to lib/realtime/processQueue.js diff --git a/lib/realtime.js b/lib/realtime/realtime.js similarity index 91% rename from lib/realtime.js rename to lib/realtime/realtime.js index fe16c277..6fce654f 100644 --- a/lib/realtime.js +++ b/lib/realtime/realtime.js @@ -12,13 +12,13 @@ const moment = require('moment') const get = require('lodash/get') // core -const config = require('./config') -const logger = require('./logger') -const history = require('./history') -const models = require('./models') +const config = require('../config') +const logger = require('../logger') +const history = require('../history') +const models = require('../models') // ot -const ot = require('./ot') +const ot = require('../ot') const { ProcessQueue } = require('./processQueue') const { RealtimeClientConnection } = require('./realtimeClientConnection') @@ -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/realtimeCleanDanglingUserJob.js b/lib/realtime/realtimeCleanDanglingUserJob.js similarity index 94% rename from lib/realtimeCleanDanglingUserJob.js rename to lib/realtime/realtimeCleanDanglingUserJob.js index 916e436c..7719d63e 100644 --- a/lib/realtimeCleanDanglingUserJob.js +++ b/lib/realtime/realtimeCleanDanglingUserJob.js @@ -1,8 +1,8 @@ 'use strict' const async = require('async') -const config = require('./config') -const logger = require('./logger') +const config = require('../config') +const logger = require('../logger') /** * clean when user not in any rooms or user not in connected list diff --git a/lib/realtimeClientConnection.js b/lib/realtime/realtimeClientConnection.js similarity index 98% rename from lib/realtimeClientConnection.js rename to lib/realtime/realtimeClientConnection.js index b1490791..b723bc26 100644 --- a/lib/realtimeClientConnection.js +++ b/lib/realtime/realtimeClientConnection.js @@ -2,9 +2,9 @@ const get = require('lodash/get') -const config = require('./config') -const models = require('./models') -const logger = require('./logger') +const config = require('../config') +const models = require('../models') +const logger = require('../logger') class RealtimeClientConnection { constructor (socket) { diff --git a/lib/realtimeSaveRevisionJob.js b/lib/realtime/realtimeSaveRevisionJob.js similarity index 92% rename from lib/realtimeSaveRevisionJob.js rename to lib/realtime/realtimeSaveRevisionJob.js index 08327499..967449d0 100644 --- a/lib/realtimeSaveRevisionJob.js +++ b/lib/realtime/realtimeSaveRevisionJob.js @@ -1,7 +1,7 @@ 'use strict' -const models = require('./models') -const logger = require('./logger') +const models = require('../models') +const logger = require('../logger') /** * clean when user not in any rooms or user not in connected list diff --git a/lib/realtimeUpdateDirtyNoteJob.js b/lib/realtime/realtimeUpdateDirtyNoteJob.js similarity index 96% rename from lib/realtimeUpdateDirtyNoteJob.js rename to lib/realtime/realtimeUpdateDirtyNoteJob.js index 5f8de295..c428ec53 100644 --- a/lib/realtimeUpdateDirtyNoteJob.js +++ b/lib/realtime/realtimeUpdateDirtyNoteJob.js @@ -1,7 +1,7 @@ 'use strict' -const config = require('./config') -const logger = require('./logger') +const config = require('../config') +const logger = require('../logger') const moment = require('moment') class UpdateDirtyNoteJob { diff --git a/lib/response.js b/lib/response.js index ce7e4fc3..4779afb8 100644 --- a/lib/response.js +++ b/lib/response.js @@ -1,13 +1,7 @@ 'use strict' // response // external modules -const fs = require('fs') -const path = require('path') -const markdownpdf = require('markdown-pdf') -const shortId = require('shortid') -const querystring = require('querystring') const request = require('request') -const moment = require('moment') // core const config = require('./config') @@ -17,6 +11,7 @@ const utils = require('./utils') const history = require('./history') // public +exports.responseError = responseError exports.errorForbidden = errorForbidden exports.errorNotFound = errorNotFound exports.errorBadRequest = errorBadRequest @@ -24,15 +19,14 @@ exports.errorTooLong = errorTooLong exports.errorInternalError = errorInternalError exports.errorServiceUnavailable = errorServiceUnavailable exports.newNote = newNote -exports.showNote = showNote -exports.showPublishNote = showPublishNote exports.showPublishSlide = showPublishSlide -exports.showIndex = showIndex -exports.noteActions = noteActions exports.publishNoteActions = publishNoteActions exports.publishSlideActions = publishSlideActions exports.githubActions = githubActions exports.gitlabActions = gitlabActions +exports.checkViewPermission = checkViewPermission +exports.newCheckViewPermission = newCheckViewPermission +exports.responseCodiMD = responseCodiMD function errorForbidden (res) { const { req } = res @@ -43,20 +37,25 @@ function errorForbidden (res) { res.redirect(config.serverURL + '/') } } + function errorNotFound (res) { responseError(res, '404', 'Not Found', 'oops.') } + function errorBadRequest (res) { responseError(res, '400', 'Bad Request', 'something not right.') } + function errorTooLong (res) { responseError(res, '413', 'Payload Too Large', 'Shorten your note!') } + function errorInternalError (res) { responseError(res, '500', 'Internal Error', 'wtf.') } + function errorServiceUnavailable (res) { - res.status(503).send("I'm busy right now, try again later.") + res.status(503).send('I\'m busy right now, try again later.') } function responseError (res, code, detail, msg) { @@ -68,35 +67,6 @@ function responseError (res, code, detail, 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) @@ -148,6 +118,16 @@ function newNote (req, res, next) { }) } +function newCheckViewPermission (note, isLogin, userId) { + if (note.permission === 'private') { + return note.ownerId === userId + } + if (note.permission === 'limited' || note.permission === 'protected') { + return isLogin + } + return true +} + function checkViewPermission (req, note) { if (note.permission === 'private') { if (!req.isAuthenticated() || note.ownerId !== req.user.id) { return false } else { return true } @@ -192,81 +172,6 @@ function findNote (req, res, callback, include) { }) } -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 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 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) @@ -284,162 +189,6 @@ function actionDownload (req, res, note) { 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) - - var highlightCssPath = path.join(config.appRootPath, '/node_modules/highlight.js/styles/github-gist.css') - - if (!fs.existsSync(config.tmpPath)) { - fs.mkdirSync(config.tmpPath) - } - var pdfPath = config.tmpPath + '/' + Date.now() + '.pdf' - content = content.replace(/\]\(\//g, '](' + url + '/') - var markdownpdfOptions = { - highlightCssPath: highlightCssPath - } - markdownpdf(markdownpdfOptions).from.string(content).to(pdfPath, function () { - if (!fs.existsSync(pdfPath)) { - logger.error('PDF seems to not be generated as expected. File doesn\'t exist: ' + pdfPath) - return errorInternalError(res) - } - var stream = fs.createReadStream(pdfPath) - 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.unlinkSync(pdfPath) - }) -} - -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 errorInternalError(res) - } - if (!content) { - return 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 errorNotFound(res) - } - } else { - models.Revision.getNoteRevisions(note, function (err, data) { - if (err) { - logger.error(err) - return 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') - 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 @@ -631,17 +380,13 @@ function showPublishSlide (req, res, next) { disqus: meta.disqus, cspNonce: res.locals.nonce } - return renderPublishSlide(data, res) + res.set({ + 'Cache-Control': 'private' // only cache by client + }) + res.render('slide.ejs', data) }).catch(function (err) { logger.error(err) return errorInternalError(res) }) }, include) } - -function renderPublishSlide (data, res) { - res.set({ - 'Cache-Control': 'private' // only cache by client - }) - res.render('slide.ejs', data) -} diff --git a/lib/routes.js b/lib/routes.js new file mode 100644 index 00000000..7a835698 --- /dev/null +++ b/lib/routes.js @@ -0,0 +1,81 @@ +'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 noteController = require('./note') +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', noteController.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', wrap(noteController.showNote)) +// note actions +appRouter.get('/:noteId/:action', noteController.noteActions) +// note actions with action id +appRouter.get('/:noteId/:action/:actionId', noteController.noteActions) + +exports.router = appRouter diff --git a/lib/status/index.js b/lib/status/index.js new file mode 100644 index 00000000..0a323d84 --- /dev/null +++ b/lib/status/index.js @@ -0,0 +1,35 @@ +'use strict' + +const realtime = require('../realtime/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/user/index.js b/lib/user/index.js new file mode 100644 index 00000000..74649463 --- /dev/null +++ b/lib/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/utils.js b/lib/utils.js index b46c4884..4180e202 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,7 @@ 'use strict' const fs = require('fs') const path = require('path') +const bodyParser = require('body-parser') exports.isSQLite = function isSQLite (sequelize) { return sequelize.options.dialect === 'sqlite' @@ -32,3 +33,18 @@ exports.isRevealTheme = function isRevealTheme (theme) { } return undefined } + +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, + limit: 1024 * 1024 * 10 // 10 mb +}) + +// create text/markdown parser +exports.markdownParser = bodyParser.text({ + inflate: true, + type: ['text/plain', 'text/markdown'], + limit: 1024 * 1024 * 10 // 10 mb +}) 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/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/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/statusRouter.js b/lib/web/statusRouter.js deleted file mode 100644 index ae891ad6..00000000 --- a/lib/web/statusRouter.js +++ /dev/null @@ -1,112 +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) - }) -}) -// get status -statusRouter.get('/temp', function (req, res) { - var host = req.get('host') - if (config.allowOrigin.indexOf(host) === -1) { - response.errorForbidden(res) - } else { - var tempid = req.query.tempid - if (!tempid) { - response.errorForbidden(res) - } else { - models.Temp.findOne({ - where: { - id: tempid - } - }).then(function (temp) { - if (!temp) { - response.errorNotFound(res) - } else { - res.header('Access-Control-Allow-Origin', '*') - res.send({ - temp: temp.data - }) - temp.destroy().catch(function (err) { - if (err) { - logger.error('remove temp failed: ' + err) - } - }) - } - }).catch(function (err) { - logger.error(err) - return response.errorInternalError(res) - }) - } - } -}) -// post status -statusRouter.post('/temp', urlencodedParser, function (req, res) { - var host = req.get('host') - if (config.allowOrigin.indexOf(host) === -1) { - response.errorForbidden(res) - } else { - var data = req.body.data - if (!data) { - response.errorForbidden(res) - } else { - if (config.debug) { - logger.info('SERVER received temp from [' + host + ']: ' + req.body.data) - } - models.Temp.create({ - data: data - }).then(function (temp) { - if (temp) { - res.header('Access-Control-Allow-Origin', '*') - res.send({ - status: 'ok', - id: temp.id - }) - } else { - response.errorInternalError(res) - } - }).catch(function (err) { - logger.error(err) - return response.errorInternalError(res) - }) - } - } -}) - -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/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 deleted file mode 100644 index d58294ad..00000000 --- a/lib/web/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const bodyParser = require('body-parser') - -// create application/x-www-form-urlencoded parser -exports.urlencodedParser = bodyParser.urlencoded({ - extended: false, - limit: 1024 * 1024 * 10 // 10 mb -}) - -// create text/markdown parser -exports.markdownParser = bodyParser.text({ - inflate: true, - type: ['text/plain', 'text/markdown'], - limit: 1024 * 1024 * 10 // 10 mb -}) diff --git a/package.json b/package.json index ff679bcb..f1048da1 100644 --- a/package.json +++ b/package.json @@ -131,8 +131,8 @@ "reveal.js": "~3.7.0", "scrypt": "~6.0.3", "select2": "~3.5.2-browserify", - "sequelize": "5.15.1", - "sequelize-cli": "~5.4.0", + "sequelize": "5.21.3", + "sequelize-cli": "~5.5.1", "shortid": "~2.2.14", "socket.io": "~2.2.0", "socket.io-client": "~2.2.0", diff --git a/public/js/history.js b/public/js/history.js index d1c7f25f..da39f095 100644 --- a/public/js/history.js +++ b/public/js/history.js @@ -6,8 +6,6 @@ import LZString from '@hackmd/lz-string' import escapeHTML from 'lodash/escape' -import wurl from 'wurl' - import { checkNoteIdValid, encodeNoteId @@ -19,38 +17,6 @@ import { urlpath } from './lib/config' window.migrateHistoryFromTempCallback = null -migrateHistoryFromTemp() - -function migrateHistoryFromTemp () { - if (wurl('#tempid')) { - $.get(`${serverurl}/temp`, { - tempid: wurl('#tempid') - }) - .done(data => { - if (data && data.temp) { - getStorageHistory(olddata => { - if (!olddata || olddata.length === 0) { - saveHistoryToStorage(JSON.parse(data.temp)) - } - }) - } - }) - .always(() => { - let hash = location.hash.split('#')[1] - hash = hash.split('&') - for (let i = 0; i < hash.length; i++) { - if (hash[i].indexOf('tempid') === 0) { - hash.splice(i, 1) - i-- - } - } - hash = hash.join('&') - location.hash = hash - if (window.migrateHistoryFromTempCallback) { window.migrateHistoryFromTempCallback() } - }) - } -} - export function saveHistory (notehistory) { checkIfAuth( () => { diff --git a/test/connectionQueue.test.js b/test/connectionQueue.test.js index 3bf7c734..326b5110 100644 --- a/test/connectionQueue.test.js +++ b/test/connectionQueue.test.js @@ -4,7 +4,7 @@ const assert = require('assert') const sinon = require('sinon') -const { ProcessQueue } = require('../lib/processQueue') +const { ProcessQueue } = require('../lib/realtime/processQueue') describe('ProcessQueue', function () { let clock diff --git a/test/realtime/cleanDanglingUser.test.js b/test/realtime/cleanDanglingUser.test.js index 012195ca..576ee711 100644 --- a/test/realtime/cleanDanglingUser.test.js +++ b/test/realtime/cleanDanglingUser.test.js @@ -31,13 +31,13 @@ describe('cleanDanglingUser', function () { afterEach(() => { clock.restore() - removeModuleFromRequireCache('../../lib/realtime') + removeModuleFromRequireCache('../../lib/realtime/realtime') mock.stopAll() sinon.restore() }) it('should call queueForDisconnectSpy when user is dangling', (done) => { - const realtime = require('../../lib/realtime') + const realtime = require('../../lib/realtime/realtime') const queueForDisconnectSpy = sinon.spy(realtime, 'queueForDisconnect') realtime.io = { to: sinon.stub().callsFake(function () { diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index 3baa9c10..bba201ca 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -31,7 +31,7 @@ describe('realtime#connection', function () { mock('../../lib/realtimeCleanDanglingUserJob', realtimeJobStub) mock('../../lib/realtimeSaveRevisionJob', realtimeJobStub) mock('../../lib/ot', require('../testDoubles/otFake')) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') }) afterEach(() => { diff --git a/test/realtime/dirtyNoteUpdate.test.js b/test/realtime/dirtyNoteUpdate.test.js index be2a7c70..0ba65131 100644 --- a/test/realtime/dirtyNoteUpdate.test.js +++ b/test/realtime/dirtyNoteUpdate.test.js @@ -27,7 +27,7 @@ describe('realtime#update note is dirty timer', function () { } }) mock('../../lib/config', {}) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') realtime.io = { to: sinon.stub().callsFake(function () { @@ -39,8 +39,8 @@ describe('realtime#update note is dirty timer', function () { }) afterEach(() => { - removeModuleFromRequireCache('../../lib/realtimeUpdateDirtyNoteJob') - removeModuleFromRequireCache('../../lib/realtime') + removeModuleFromRequireCache('../../lib/realtime/realtimeUpdateDirtyNoteJob') + removeModuleFromRequireCache('../../lib/realtime/realtime') mock.stopAll() clock.restore() }) diff --git a/test/realtime/disconnect-process.test.js b/test/realtime/disconnect-process.test.js index 4f8a5b89..8a04d894 100644 --- a/test/realtime/disconnect-process.test.js +++ b/test/realtime/disconnect-process.test.js @@ -28,7 +28,7 @@ describe('realtime#disconnect', function () { }) mock('../../lib/config', {}) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') updateNoteStub = sinon.stub(realtime, 'updateNote').callsFake((note, callback) => { callback(null, note) }) @@ -60,7 +60,7 @@ describe('realtime#disconnect', function () { }) afterEach(() => { - removeModuleFromRequireCache('../../lib/realtime') + removeModuleFromRequireCache('../../lib/realtime/realtime') mock.stopAll() sinon.restore() }) diff --git a/test/realtime/extractNoteIdFromSocket.test.js b/test/realtime/extractNoteIdFromSocket.test.js index 55a09d98..99b2c927 100644 --- a/test/realtime/extractNoteIdFromSocket.test.js +++ b/test/realtime/extractNoteIdFromSocket.test.js @@ -14,14 +14,14 @@ describe('realtime#extractNoteIdFromSocket', function () { }) afterEach(() => { - delete require.cache[require.resolve('../../lib/realtime')] + delete require.cache[require.resolve('../../lib/realtime/realtime')] mock.stopAll() }) describe('urlPath not set', function () { beforeEach(() => { mock('../../lib/config', {}) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') }) let realtime @@ -76,7 +76,7 @@ describe('realtime#extractNoteIdFromSocket', function () { mock('../../lib/config', { urlPath: urlPath }) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') const incomingNoteId = 'myNoteId' const incomingSocket = makeMockSocket({ referer: `https://localhost:3000/${urlPath}/${incomingNoteId}` diff --git a/test/realtime/ifMayEdit.test.js b/test/realtime/ifMayEdit.test.js index 88c11661..957a0e65 100644 --- a/test/realtime/ifMayEdit.test.js +++ b/test/realtime/ifMayEdit.test.js @@ -81,7 +81,7 @@ describe('realtime#ifMayEdit', function () { client.request.user.id = noteOwnerId } client.noteId = noteId - const realtime = require('../../lib/realtime') + const realtime = require('../../lib/realtime/realtime') realtime.getNotePool()[noteId] = note const callback = sinon.stub() realtime.ifMayEdit(client, callback) @@ -98,7 +98,7 @@ describe('realtime#ifMayEdit', function () { client.noteId = noteId const callback = sinon.stub() client.origin = 'operation' - const realtime = require('../../lib/realtime') + const realtime = require('../../lib/realtime/realtime') realtime.getNotePool()[noteId] = note realtime.ifMayEdit(client, callback) assert(callback.calledOnce) @@ -116,7 +116,7 @@ describe('realtime#ifMayEdit', function () { client.request.user.id = loggedInUserId const callback = sinon.stub() client.origin = 'operation' - const realtime = require('../../lib/realtime') + const realtime = require('../../lib/realtime/realtime') realtime.getNotePool()[noteId] = note realtime.ifMayEdit(client, callback) assert(callback.calledOnce) diff --git a/test/realtime/parseNoteIdFromSocket.test.js b/test/realtime/parseNoteIdFromSocket.test.js index 15e28c34..6ed7b129 100644 --- a/test/realtime/parseNoteIdFromSocket.test.js +++ b/test/realtime/parseNoteIdFromSocket.test.js @@ -23,12 +23,12 @@ describe('realtime#parseNoteIdFromSocketAsync', function () { }) afterEach(() => { - removeModuleFromRequireCache('../../lib/realtime') + removeModuleFromRequireCache('../../lib/realtime/realtime') mock.stopAll() }) it('should return null when socket not send noteId', async function () { - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') const mockSocket = makeMockSocket() try { const notes = await realtime.parseNoteIdFromSocketAsync(mockSocket) @@ -49,12 +49,12 @@ describe('realtime#parseNoteIdFromSocketAsync', function () { }) }) it('should return noteId when noteId exists', async function () { - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') const noteId = '123456' const mockSocket = makeMockSocket(undefined, { noteId: noteId }) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') let parsedNoteId try { parsedNoteId = await realtime.parseNoteIdFromSocketAsync(mockSocket) @@ -76,12 +76,12 @@ describe('realtime#parseNoteIdFromSocketAsync', function () { }) }) it('should return null when noteId not exists', async function () { - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') const noteId = '123456' const mockSocket = makeMockSocket(undefined, { noteId: noteId }) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') const parsedNoteId = await realtime.parseNoteIdFromSocketAsync(mockSocket) assert(parsedNoteId === null) }) @@ -99,12 +99,12 @@ describe('realtime#parseNoteIdFromSocketAsync', function () { }) }) it('should return error when noteId parse error', async function () { - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') const noteId = '123456' const mockSocket = makeMockSocket(undefined, { noteId: noteId }) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') try { await realtime.parseNoteIdFromSocketAsync(mockSocket) } catch (err) { diff --git a/test/realtime/realtime.test.js b/test/realtime/realtime.test.js index 8891b872..b4c713b4 100644 --- a/test/realtime/realtime.test.js +++ b/test/realtime/realtime.test.js @@ -50,7 +50,7 @@ describe('realtime', function () { } }) mock('../../lib/config', {}) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') }) Object.keys(viewPermission).forEach(function (permission) { diff --git a/test/realtime/saveRevisionJob.test.js b/test/realtime/saveRevisionJob.test.js index 7594842c..0a5bab0b 100644 --- a/test/realtime/saveRevisionJob.test.js +++ b/test/realtime/saveRevisionJob.test.js @@ -34,8 +34,8 @@ describe('save revision job', function () { afterEach(() => { clock.restore() - removeModuleFromRequireCache('../../lib/realtime') - removeModuleFromRequireCache('../../lib/realtimeSaveRevisionJob') + removeModuleFromRequireCache('../../lib/realtime/realtime') + removeModuleFromRequireCache('../../lib/realtime/realtimeSaveRevisionJob') mock.stopAll() sinon.restore() }) @@ -44,7 +44,7 @@ describe('save revision job', function () { mockModels.Revision.saveAllNotesRevision.callsFake((callback) => { callback(null, []) }) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') clock.tick(5 * 60 * 1000) clock.restore() setTimeout(() => { @@ -58,7 +58,7 @@ describe('save revision job', function () { mockModels.Revision.saveAllNotesRevision.callsFake((callback) => { callback(null, [1]) }) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') clock.tick(5 * 60 * 1000) clock.restore() setTimeout(() => { diff --git a/test/realtime/socket-events.test.js b/test/realtime/socket-events.test.js index 0015d179..4c577c60 100644 --- a/test/realtime/socket-events.test.js +++ b/test/realtime/socket-events.test.js @@ -76,7 +76,7 @@ describe('realtime#socket event', function () { mock('../../lib/models', modelsMock) mock('../../lib/config', configMock) mock('../../lib/ot', require('../testDoubles/otFake')) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') // get all socket event handler clientSocket = makeMockSocket(null, { @@ -122,8 +122,8 @@ describe('realtime#socket event', function () { }) afterEach(function () { - removeModuleFromRequireCache('../../lib/realtime') - removeModuleFromRequireCache('../../lib/realtimeClientConnection') + removeModuleFromRequireCache('../../lib/realtime/realtime') + removeModuleFromRequireCache('../../lib/realtime/realtimeClientConnection') mock.stopAll() sinon.restore() clock.restore() diff --git a/test/realtime/updateNote.test.js b/test/realtime/updateNote.test.js index f2291cdf..95b3721e 100644 --- a/test/realtime/updateNote.test.js +++ b/test/realtime/updateNote.test.js @@ -47,7 +47,7 @@ describe('realtime#updateNote', function () { it('should save history to each edited user', function (done) { modelsStub.Note.findOne.returns(Promise.resolve({})) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') const updateHistoryStub = sinon.stub(realtime, 'updateHistory') const callback = sinon.stub() @@ -86,7 +86,7 @@ describe('realtime#updateNote', function () { name: 'User 01' }) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') realtime.updateNote(note, callback) clock.restore() @@ -125,7 +125,7 @@ describe('realtime#updateNote', function () { }) clock.tick(1000) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') realtime.updateNote(note, callback) setTimeout(() => { assert(note.lastchangeuserprofile.name === 'User 01') @@ -159,7 +159,7 @@ describe('realtime#updateNote', function () { }) clock.tick(1000) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') realtime.updateNote(note, callback) setTimeout(() => { assert(modelsStub.User.findOne.callCount === 0) @@ -196,7 +196,7 @@ describe('realtime#updateNote', function () { }) clock.tick(1000) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') realtime.updateNote(note, callback) setTimeout(() => { assert(modelsStub.User.findOne.callCount === 0) @@ -233,7 +233,7 @@ describe('realtime#updateNote', function () { }) clock.tick(1000) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') realtime.updateNote(note, callback) setTimeout(() => { assert(modelsStub.User.findOne.called) @@ -272,7 +272,7 @@ describe('realtime#updateNote', function () { }) clock.tick(1000) - realtime = require('../../lib/realtime') + realtime = require('../../lib/realtime/realtime') realtime.updateNote(note, callback) setTimeout(() => { assert(note.lastchangeuserprofile.name === 'User 01') diff --git a/yarn.lock b/yarn.lock index 7274a3bb..d9853d23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11048,7 +11048,7 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry-as-promised@^3.1.0: +retry-as-promised@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-3.2.0.tgz#769f63d536bec4783549db0777cb56dadd9d8543" integrity sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg== @@ -11246,7 +11246,7 @@ semver@4.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= -semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -11275,10 +11275,10 @@ seq-queue@^0.0.5: resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" integrity sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4= -sequelize-cli@~5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/sequelize-cli/-/sequelize-cli-5.4.0.tgz#6a2c2af331466414d8b2ecb6912e24d2de0d04b5" - integrity sha512-4Gvl0yH0T3hhSdiiOci3+IKIfVG9x2os0hGWsbfa8QuyGgk9mZOqgTBnSCRtuxsdAyzUix9kfcTnfNolVNtprg== +sequelize-cli@~5.5.1: + version "5.5.1" + resolved "https://registry.yarnpkg.com/sequelize-cli/-/sequelize-cli-5.5.1.tgz#0b9c2fc04d082cc8ae0a8fe270b96bb606152bab" + integrity sha512-ZM4kUZvY3y14y+Rq3cYxGH7YDJz11jWHcN2p2x7rhAIemouu4CEXr5ebw30lzTBtyXV4j2kTO+nUjZOqzG7k+Q== dependencies: bluebird "^3.5.3" cli-color "^1.4.0" @@ -11287,33 +11287,33 @@ sequelize-cli@~5.4.0: lodash "^4.17.5" resolve "^1.5.0" umzug "^2.1.0" - yargs "^12.0.5" + yargs "^13.1.0" sequelize-pool@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-2.3.0.tgz#64f1fe8744228172c474f530604b6133be64993d" integrity sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA== -sequelize@5.15.1: - version "5.15.1" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.15.1.tgz#f130ded17e74395ae7f5e265277c99577e895afb" - integrity sha512-DCzzJYvJLMKnyf8G3at2A+yM9M2fSQmTmuOYIpCWM8Gjqx3XfgNTd1NkuyPWFoi1/d1AXQsN2VDPXkPczida8A== +sequelize@5.21.3: + version "5.21.3" + resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.21.3.tgz#f8a6fa0245f8995d70849e4da00c2c7c9aa9f569" + integrity sha512-ptdeAxwTY0zbj7AK8m+SH3z52uHVrt/qmOTSIGo/kyfnSp3h5HeKlywkJf5GEk09kuRrPHfWARVSXH1W3IGU7g== dependencies: bluebird "^3.5.0" cls-bluebird "^2.1.0" debug "^4.1.1" dottie "^2.0.0" inflection "1.12.0" - lodash "^4.17.11" + lodash "^4.17.15" moment "^2.24.0" moment-timezone "^0.5.21" - retry-as-promised "^3.1.0" - semver "^6.1.1" + retry-as-promised "^3.2.0" + semver "^6.3.0" sequelize-pool "^2.3.0" toposort-class "^1.0.1" - uuid "^3.2.1" + uuid "^3.3.3" validator "^10.11.0" - wkx "^0.4.6" + wkx "^0.4.8" serialize-error@2.1.0: version "2.1.0" @@ -12848,11 +12848,16 @@ utils-merge@1.0.1, utils-merge@1.x.x: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@3.3.2, uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@~3.3.2: +uuid@3.3.2, uuid@^3.0.0, uuid@^3.1.0, uuid@^3.3.2, uuid@~3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" @@ -13586,7 +13591,7 @@ winston@~3.2.1: triple-beam "^1.3.0" winston-transport "^4.3.0" -wkx@^0.4.6: +wkx@^0.4.8: version "0.4.8" resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.8.tgz#a092cf088d112683fdc7182fd31493b2c5820003" integrity sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ== @@ -13831,7 +13836,7 @@ yargs@13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" -yargs@^12.0.2, yargs@^12.0.5: +yargs@^12.0.2: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== @@ -13849,7 +13854,7 @@ yargs@^12.0.2, yargs@^12.0.5: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yargs@^13.2.2, yargs@~13.3.0: +yargs@^13.1.0, yargs@^13.2.2, yargs@~13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==