From 49b51e478fa75b8d5254662de3265edcf8906004 Mon Sep 17 00:00:00 2001 From: Cheng-Han, Wu Date: Wed, 20 Apr 2016 18:03:55 +0800 Subject: Refactor server with Sequelize ORM, refactor server configs, now will show note status (created or updated) and support docs (note alias) --- lib/auth.js | 76 ++++-- lib/config.js | 112 ++++++++ lib/db.js | 151 ----------- lib/models/index.js | 37 +++ lib/models/note.js | 208 ++++++++++++++ lib/models/temp.js | 19 ++ lib/models/user.js | 77 ++++++ lib/note.js | 237 ---------------- lib/ot/server.js | 2 +- lib/realtime.js | 675 ++++++++++++++++++++------------------------- lib/response.js | 766 ++++++++++++++++++++-------------------------------- lib/temp.js | 84 ------ lib/user.js | 110 -------- 13 files changed, 1097 insertions(+), 1457 deletions(-) create mode 100644 lib/config.js delete mode 100644 lib/db.js create mode 100644 lib/models/index.js create mode 100644 lib/models/note.js create mode 100644 lib/models/temp.js create mode 100644 lib/models/user.js delete mode 100644 lib/note.js delete mode 100644 lib/temp.js delete mode 100644 lib/user.js (limited to 'lib') diff --git a/lib/auth.js b/lib/auth.js index dc8b94ca..af3e8d1d 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -7,44 +7,60 @@ var GithubStrategy = require('passport-github').Strategy; var DropboxStrategy = require('passport-dropbox-oauth2').Strategy; //core -var User = require('./user.js'); -var config = require('../config.js'); +var config = require('./config.js'); var logger = require("./logger.js"); +var models = require("./models"); function callback(accessToken, refreshToken, profile, done) { //logger.info(profile.displayName || profile.username); - User.findOrNewUser(profile.id, profile, function (err, user) { - if (err || user == null) { - logger.error('auth callback failed: ' + err); - } else { - if (config.debug && user) - logger.info('user login: ' + user._id); - done(null, user); + models.User.findOrCreate({ + where: { + profileid: profile.id.toString() + }, + defaults: { + profile: JSON.stringify(profile) } - }); + }).spread(function(user, created) { + if (user) { + if (config.debug) + logger.info('user login: ' + user.id); + return done(null, user); + } + }).catch(function(err) { + logger.error('auth callback failed: ' + err); + return done(err, null); + }) } //facebook -module.exports = passport.use(new FacebookStrategy({ - clientID: config.facebook.clientID, - clientSecret: config.facebook.clientSecret, - callbackURL: config.getserverurl() + config.facebook.callbackPath -}, callback)); +if (config.facebook) { + module.exports = passport.use(new FacebookStrategy({ + clientID: config.facebook.clientID, + clientSecret: config.facebook.clientSecret, + callbackURL: config.serverurl + '/auth/facebook/callback' + }, callback)); +} //twitter -passport.use(new TwitterStrategy({ - consumerKey: config.twitter.consumerKey, - consumerSecret: config.twitter.consumerSecret, - callbackURL: config.getserverurl() + config.twitter.callbackPath -}, callback)); +if (config.twitter) { + passport.use(new TwitterStrategy({ + consumerKey: config.twitter.consumerKey, + consumerSecret: config.twitter.consumerSecret, + callbackURL: config.serverurl + '/auth/twitter/callback' + }, callback)); +} //github -passport.use(new GithubStrategy({ - clientID: config.github.clientID, - clientSecret: config.github.clientSecret, - callbackURL: config.getserverurl() + config.github.callbackPath -}, callback)); +if (config.github) { + passport.use(new GithubStrategy({ + clientID: config.github.clientID, + clientSecret: config.github.clientSecret, + callbackURL: config.serverurl + '/auth/github/callback' + }, callback)); +} //dropbox -passport.use(new DropboxStrategy({ - clientID: config.dropbox.clientID, - clientSecret: config.dropbox.clientSecret, - callbackURL: config.getserverurl() + config.dropbox.callbackPath -}, callback)); \ No newline at end of file +if (config.dropbox) { + passport.use(new DropboxStrategy({ + clientID: config.dropbox.clientID, + clientSecret: config.dropbox.clientSecret, + callbackURL: config.serverurl + '/auth/dropbox/callback' + }, callback)); +} \ No newline at end of file diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 00000000..386b0885 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,112 @@ +// external modules +var path = require('path'); + +// configs +var env = process.env.NODE_ENV || 'development'; +var config = require(path.join(__dirname, '..', 'config.json'))[env]; +var debug = process.env.DEBUG ? (process.env.DEBUG === 'true') : ((typeof config.debug === 'boolean') ? config.debug : (env === 'development')); + +// url +var domain = config.domain || 'localhost'; +var urlpath = config.urlpath || ''; +var port = process.env.PORT || config.port || 3000; +var alloworigin = config.alloworigin || ['localhost']; + +var usessl = !!config.usessl; +var protocolusessl = (config.usessl === true && typeof config.protocolusessl === 'undefined') ? true : !!config.protocolusessl; +var urladdport = !!config.urladdport; + +var usecdn = !!config.usecdn; + +// db +var db = config.db || { + dialect: 'sqlite', + storage: './db.hackmd.sqlite' +}; + +// ssl path +var sslkeypath = config.sslkeypath || '' +var sslcertpath = config.sslcertpath || ''; +var sslcapath = config.sslcapath || ''; +var dhparampath = config.dhparampath || ''; + +// other path +var tmppath = config.tmppath || './tmp'; +var defaultnotepath = config.defaultnotepath || './public/default.md'; +var docspath = config.docspath || './public/docs'; +var indexpath = config.indexpath || './public/views/index.ejs'; +var hackmdpath = config.hackmdpath || './public/views/hackmd.ejs'; +var errorpath = config.errorpath || './public/views/error.ejs'; +var prettypath = config.prettypath || './public/views/pretty.ejs'; +var slidepath = config.slidepath || './public/views/slide.hbs'; + +// session +var sessionname = config.sessionname || 'connect.sid'; +var sessionsecret = config.sessionsecret || 'secret'; +var sessionlife = config.sessionlife || 14 * 24 * 60 * 60 * 1000; //14 days + +// static files +var staticcachetime = config.staticcachetime || 1 * 24 * 60 * 60 * 1000; // 1 day + +// socket.io +var heartbeatinterval = config.heartbeatinterval || 5000; +var heartbeattimeout = config.heartbeattimeout || 10000; + +// document +var documentmaxlength = config.documentmaxlength || 100000; + +// auth +var facebook = config.facebook || false; +var twitter = config.twitter || false; +var github = config.github || false; +var dropbox = config.dropbox || false; +var imgur = config.imgur || false; + +function getserverurl() { + var protocol = protocolusessl ? 'https://' : 'http://'; + var url = protocol + domain; + if (urladdport && ((usessl && port != 443) || (!usessl && port != 80))) + url += ':' + port; + if (urlpath) + url += '/' + urlpath; + return url; +} + +var version = '0.4.0'; +var cwd = path.join(__dirname, '..'); + +module.exports = { + version: version, + debug: debug, + urlpath: urlpath, + port: port, + alloworigin: alloworigin, + usessl: usessl, + serverurl: getserverurl(), + usecdn: usecdn, + db: db, + sslkeypath: path.join(cwd, sslkeypath), + sslcertpath: path.join(cwd, sslcertpath), + sslcapath: path.join(cwd, sslcapath), + dhparampath: path.join(cwd, dhparampath), + tmppath: path.join(cwd, tmppath), + defaultnotepath: path.join(cwd, defaultnotepath), + docspath: path.join(cwd, docspath), + indexpath: path.join(cwd, indexpath), + hackmdpath: path.join(cwd, hackmdpath), + errorpath: path.join(cwd, errorpath), + prettypath: path.join(cwd, prettypath), + slidepath: path.join(cwd, slidepath), + sessionname: sessionname, + sessionsecret: sessionsecret, + sessionlife: sessionlife, + staticcachetime: staticcachetime, + heartbeatinterval: heartbeatinterval, + heartbeattimeout: heartbeattimeout, + documentmaxlength: documentmaxlength, + facebook: facebook, + twitter: twitter, + github: github, + dropbox: dropbox, + imgur: imgur +}; \ No newline at end of file diff --git a/lib/db.js b/lib/db.js deleted file mode 100644 index 1ac6f0e1..00000000 --- a/lib/db.js +++ /dev/null @@ -1,151 +0,0 @@ -//db -//external modules -var pg = require('pg'); -var fs = require('fs'); -var util = require('util'); - -//core -var config = require("../config.js"); -var logger = require("./logger.js"); - -//public -var db = { - readFromFile: readFromDB, - saveToFile: saveToFile, - newToDB: newToDB, - readFromDB: readFromDB, - saveToDB: saveToDB, - countFromDB: countFromDB -}; - -function getDBClient() { - return new pg.Client(process.env.DATABASE_URL || config.postgresqlstring); -} - -function readFromFile(callback) { - fs.readFile('hackmd', 'utf8', function (err, data) { - if (err) throw err; - callback(data); - }); -} - -function saveToFile(doc) { - fs.writeFile('hackmd', doc, function (err) { - if (err) throw err; - }); -} - -var updatequery = "UPDATE notes SET title='%s', content='%s', update_time=NOW() WHERE id='%s';"; -var insertquery = "INSERT INTO notes (id, owner, content) VALUES ('%s', '%s', '%s');"; -var insertifnotexistquery = "INSERT INTO notes (id, owner, content) \ -SELECT '%s', '%s', '%s' \ -WHERE NOT EXISTS (SELECT 1 FROM notes WHERE id='%s') RETURNING *;"; -var selectquery = "SELECT * FROM notes WHERE id='%s';"; -var countquery = "SELECT count(*) FROM notes;"; - -function newToDB(id, owner, body, callback) { - var client = getDBClient(); - client.connect(function (err) { - if (err) { - client.end(); - callback(err, null); - return logger.error('could not connect to postgres', err); - } - var newnotequery = util.format(insertquery, id, owner, body); - //logger.info(newnotequery); - client.query(newnotequery, function (err, result) { - client.end(); - if (err) { - callback(err, null); - return logger.error("new note to db failed: " + err); - } else { - if (config.debug) - logger.info("new note to db success"); - callback(null, result); - } - }); - }); -} - -function readFromDB(id, callback) { - var client = getDBClient(); - client.connect(function (err) { - if (err) { - client.end(); - callback(err, null); - return logger.error('could not connect to postgres', err); - } - var readquery = util.format(selectquery, id); - //logger.info(readquery); - client.query(readquery, function (err, result) { - client.end(); - if (err) { - callback(err, null); - return logger.error("read from db failed: " + err); - } else { - //logger.info(result.rows); - if (result.rows.length <= 0) { - callback("not found note in db", null); - return logger.error("not found note in db: " + id, err); - } else { - if(config.debug) - logger.info("read from db success"); - callback(null, result); - } - } - }); - }); -} - -function saveToDB(id, title, data, callback) { - var client = getDBClient(); - client.connect(function (err) { - if (err) { - client.end(); - callback(err, null); - return logger.error('could not connect to postgres', err); - } - var savequery = util.format(updatequery, title, data, id); - //logger.info(savequery); - client.query(savequery, function (err, result) { - client.end(); - if (err) { - callback(err, null); - return logger.error("save to db failed: " + err); - } else { - if (config.debug) - logger.info("save to db success"); - callback(null, result); - } - }); - }); -} - -function countFromDB(callback) { - var client = getDBClient(); - client.connect(function (err) { - if (err) { - client.end(); - callback(err, null); - return logger.error('could not connect to postgres', err); - } - client.query(countquery, function (err, result) { - client.end(); - if (err) { - callback(err, null); - return logger.error("count from db failed: " + err); - } else { - //logger.info(result.rows); - if (result.rows.length <= 0) { - callback("not found note in db", null); - } else { - if(config.debug) - logger.info("count from db success"); - callback(null, result); - } - } - }); - }); -} - -module.exports = db; \ No newline at end of file diff --git a/lib/models/index.js b/lib/models/index.js new file mode 100644 index 00000000..3b49d459 --- /dev/null +++ b/lib/models/index.js @@ -0,0 +1,37 @@ +"use strict"; + +// external modules +var fs = require("fs"); +var path = require("path"); +var Sequelize = require("sequelize"); + +// core +var config = require('../config.js'); +var logger = require("../logger.js"); + +var dbconfig = config.db; +dbconfig.logging = config.debug ? logger.info : false; +var sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig); + +var db = {}; + +fs + .readdirSync(__dirname) + .filter(function (file) { + return (file.indexOf(".") !== 0) && (file !== "index.js"); + }) + .forEach(function (file) { + var model = sequelize.import(path.join(__dirname, file)); + db[model.name] = model; + }); + +Object.keys(db).forEach(function (modelName) { + if ("associate" in db[modelName]) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +module.exports = db; \ No newline at end of file diff --git a/lib/models/note.js b/lib/models/note.js new file mode 100644 index 00000000..96043b75 --- /dev/null +++ b/lib/models/note.js @@ -0,0 +1,208 @@ +"use strict"; + +// external modules +var fs = require('fs'); +var path = require('path'); +var LZString = require('lz-string'); +var marked = require('marked'); +var cheerio = require('cheerio'); +var shortId = require('shortid'); +var Sequelize = require("sequelize"); +var async = require('async'); + +// core +var config = require("../config.js"); +var logger = require("../logger.js"); + +// permission types +var permissionTypes = ["freely", "editable", "locked", "private"]; + +module.exports = function (sequelize, DataTypes) { + var Note = sequelize.define("Note", { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: Sequelize.UUIDV4 + }, + shortid: { + type: DataTypes.STRING, + unique: true, + allowNull: false, + defaultValue: shortId.generate + }, + alias: { + type: DataTypes.STRING, + unique: true + }, + permission: { + type: DataTypes.ENUM, + values: permissionTypes + }, + viewcount: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + title: { + type: DataTypes.TEXT + }, + content: { + type: DataTypes.TEXT + }, + lastchangeAt: { + type: DataTypes.DATE + } + }, { + classMethods: { + associate: function (models) { + Note.belongsTo(models.User, { + foreignKey: "ownerId", + as: "owner", + constraints: false + }); + Note.belongsTo(models.User, { + foreignKey: "lastchangeuserId", + as: "lastchangeuser", + constraints: false + }); + }, + checkFileExist: function (filePath) { + try { + return fs.statSync(filePath).isFile(); + } catch (err) { + return false; + } + }, + checkNoteIdValid: function (id) { + var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + var result = id.match(uuidRegex); + if (result && result.length == 1) + return true; + else + return false; + }, + parseNoteId: function (noteId, callback) { + async.series({ + parseNoteIdByAlias: function (_callback) { + // try to parse note id by alias (e.g. doc) + Note.findOne({ + where: { + alias: noteId + } + }).then(function (note) { + if (note) { + return callback(null, note.id); + } else { + var filePath = path.join(config.docspath, noteId + '.md'); + if (Note.checkFileExist(filePath)) { + Note.create({ + alias: noteId, + owner: null, + permission: 'locked' + }).then(function (note) { + return callback(null, note.id); + }).catch(function (err) { + return _callback(err, null); + }); + } else { + return _callback(null, null); + } + } + }).catch(function (err) { + return _callback(err, null); + }); + }, + parseNoteIdByLZString: function (_callback) { + // try to parse note id by LZString Base64 + try { + var id = LZString.decompressFromBase64(noteId); + if (id && Note.checkNoteIdValid(id)) + return callback(null, id); + else + return _callback(null, null); + } catch (err) { + return _callback(err, null); + } + }, + parseNoteIdByShortId: function (_callback) { + // try to parse note id by shortId + try { + if (shortId.isValid(noteId)) { + Note.findOne({ + where: { + shortid: noteId + } + }).then(function (note) { + if (!note) return _callback(null, null); + return callback(null, note.id); + }).catch(function (err) { + return _callback(err, null); + }); + } else { + return _callback(null, null); + } + } catch (err) { + return _callback(err, null); + } + } + }, function (err, result) { + if (err) { + logger.error(err); + return callback(err, null); + } + return callback(null, null); + }); + }, + parseNoteTitle: function (body) { + var $ = cheerio.load(marked(body)); + var h1s = $("h1"); + var title = ""; + if (h1s.length > 0 && h1s.first().text().split('\n').length == 1) + title = h1s.first().text(); + else + title = "Untitled"; + return title; + }, + decodeTitle: function (title) { + var decodedTitle = LZString.decompressFromBase64(title); + if (decodedTitle) title = decodedTitle; + else title = 'Untitled'; + return title; + }, + generateWebTitle: function (title) { + title = !title || title == "Untitled" ? "HackMD - Collaborative notes" : title + " - HackMD"; + return title; + } + }, + hooks: { + beforeCreate: function (note, options, callback) { + // if no content specified then use default note + if (!note.content) { + var body = null; + var filePath = null; + if (!note.alias) { + filePath = config.defaultnotepath; + } else { + filePath = path.join(config.docspath, note.alias + '.md'); + } + if (Note.checkFileExist(filePath)) { + body = fs.readFileSync(filePath, 'utf8'); + note.title = LZString.compressToBase64(Note.parseNoteTitle(body)); + note.content = LZString.compressToBase64(body); + } + } + // if no permission specified and have owner then give editable permission, else default permission is freely + if (!note.permission) { + if (note.ownerId) { + note.permission = "editable"; + } else { + note.permission = "freely"; + } + } + return callback(null, note); + } + } + }); + + return Note; +}; \ No newline at end of file diff --git a/lib/models/temp.js b/lib/models/temp.js new file mode 100644 index 00000000..6eeff153 --- /dev/null +++ b/lib/models/temp.js @@ -0,0 +1,19 @@ +"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; +}; \ No newline at end of file diff --git a/lib/models/user.js b/lib/models/user.js new file mode 100644 index 00000000..e1a373d6 --- /dev/null +++ b/lib/models/user.js @@ -0,0 +1,77 @@ +"use strict"; + +// external modules +var md5 = require("blueimp-md5"); +var Sequelize = require("sequelize"); + +// core +var logger = require("../logger.js"); + +module.exports = function (sequelize, DataTypes) { + var User = sequelize.define("User", { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: Sequelize.UUIDV4 + }, + profileid: { + type: DataTypes.STRING, + unique: true + }, + profile: { + type: DataTypes.TEXT + }, + history: { + type: DataTypes.TEXT + } + }, { + classMethods: { + associate: function (models) { + User.hasMany(models.Note, { + foreignKey: "ownerId", + constraints: false + }); + User.hasMany(models.Note, { + foreignKey: "lastchangeuserId", + constraints: false + }); + }, + parseProfile: function (profile) { + try { + var profile = JSON.parse(profile); + } catch (err) { + logger.error(err); + profile = null; + } + if (profile) { + profile = { + name: profile.displayName || profile.username, + photo: User.parsePhotoByProfile(profile) + } + } + return profile; + }, + parsePhotoByProfile: function (profile) { + var photo = null; + switch (profile.provider) { + case "facebook": + photo = 'https://graph.facebook.com/' + profile.id + '/picture'; + break; + case "twitter": + photo = profile.photos[0].value; + break; + case "github": + photo = 'https://avatars.githubusercontent.com/u/' + profile.id + '?s=48'; + break; + case "dropbox": + //no image api provided, use gravatar + photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value); + break; + } + return photo; + } + } + }); + + return User; +}; \ No newline at end of file diff --git a/lib/note.js b/lib/note.js deleted file mode 100644 index 0965b23e..00000000 --- a/lib/note.js +++ /dev/null @@ -1,237 +0,0 @@ -//note -//external modules -var mongoose = require('mongoose'); -var Schema = mongoose.Schema; -var LZString = require('lz-string'); -var marked = require('marked'); -var cheerio = require('cheerio'); -var shortId = require('shortid'); - -//others -var db = require("./db.js"); -var logger = require("./logger.js"); - -//permission types -permissionTypes = ["freely", "editable", "locked", "private"]; - -// create a note model -var model = mongoose.model('note', { - id: String, - shortid: { - type: String, - unique: true, - default: shortId.generate - }, - permission: { - type: String, - enum: permissionTypes - }, - lastchangeuser: { - type: Schema.Types.ObjectId, - ref: 'user' - }, - viewcount: { - type: Number, - default: 0 - }, - updated: Date, - created: Date -}); - -//public -var note = { - model: model, - findNote: findNote, - newNote: newNote, - findOrNewNote: findOrNewNote, - checkNoteIdValid: checkNoteIdValid, - checkNoteExist: checkNoteExist, - getNoteTitle: getNoteTitle, - decodeTitle: decodeTitle, - generateWebTitle: generateWebTitle, - increaseViewCount: increaseViewCount, - updatePermission: updatePermission, - updateLastChangeUser: updateLastChangeUser -}; - -function checkNoteIdValid(noteId) { - try { - //logger.info(noteId); - var id = LZString.decompressFromBase64(noteId); - if (!id) return false; - var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - var result = id.match(uuidRegex); - if (result && result.length == 1) - return true; - else - return false; - } catch (err) { - logger.error(err); - return false; - } -} - -function checkNoteExist(noteId) { - try { - //logger.info(noteId); - var id = LZString.decompressFromBase64(noteId); - db.readFromDB(id, function (err, result) { - if (err) return false; - return true; - }); - } catch (err) { - logger.error(err); - return false; - } -} - -//get title -function getNoteTitle(body) { - var $ = cheerio.load(marked(body)); - var h1s = $("h1"); - var title = ""; - if (h1s.length > 0 && h1s.first().text().split('\n').length == 1) - title = h1s.first().text(); - else - title = "Untitled"; - return title; -} - -// decode title -function decodeTitle(title) { - var decodedTitle = LZString.decompressFromBase64(title); - if (decodedTitle) title = decodedTitle; - else title = 'Untitled'; - return title; -} - -//generate note web page title -function generateWebTitle(title) { - title = !title || title == "Untitled" ? "HackMD - Collaborative notes" : title + " - HackMD"; - return title; -} - -function findNote(id, callback) { - model.findOne({ - $or: [ - { - id: id - }, - { - shortid: id - } - ] - }, function (err, note) { - if (err) { - logger.error('find note failed: ' + err); - callback(err, null); - } - if (!err && note) { - callback(null, note); - } else { - logger.error('find note failed: ' + err); - callback(err, null); - }; - }); -} - -function newNote(id, owner, callback) { - var permission = "freely"; - if (owner && owner != "null") { - permission = "editable"; - } - var note = new model({ - id: id, - permission: permission, - updated: Date.now(), - created: Date.now() - }); - note.save(function (err) { - if (err) { - logger.error('new note failed: ' + err); - callback(err, null); - } else { - logger.info("new note success: " + note.id); - callback(null, note); - }; - }); -} - -function findOrNewNote(id, owner, callback) { - findNote(id, function (err, note) { - if (err || !note) { - newNote(id, owner, function (err, note) { - if (err) { - logger.error('find or new note failed: ' + err); - callback(err, null); - } else { - callback(null, note); - } - }); - } else { - if (!note.permission) { - var permission = "freely"; - if (owner && owner != "null") { - permission = "editable"; - } - note.permission = permission; - note.updated = Date.now(); - note.save(function (err) { - if (err) { - logger.error('add note permission failed: ' + err); - callback(err, null); - } else { - logger.info("add note permission success: " + note.id); - callback(null, note); - }; - }); - } else { - callback(null, note); - } - } - }); -} - -function increaseViewCount(note, callback) { - note.viewcount++; - note.updated = Date.now(); - note.save(function (err) { - if (err) { - logger.error('increase note viewcount failed: ' + err); - callback(err, null); - } else { - logger.info("increase note viewcount success: " + note.id); - callback(null, note); - }; - }); -} - -function updatePermission(note, permission, callback) { - note.permission = permission; - note.updated = Date.now(); - note.save(function (err) { - if (err) { - logger.error('update note permission failed: ' + err); - callback(err, null); - } else { - logger.info("update note permission success: " + note.id); - callback(null, note); - }; - }); -} - -function updateLastChangeUser(note, lastchangeuser, callback) { - note.lastchangeuser = lastchangeuser; - note.updated = Date.now(); - note.save(function (err) { - if (err) { - logger.error('update note lastchangeuser failed: ' + err); - callback(err, null); - } else { - logger.info("update note lastchangeuser success: " + note.id); - callback(null, note); - }; - }); -} - -module.exports = note; \ No newline at end of file diff --git a/lib/ot/server.js b/lib/ot/server.js index b6559291..227eba25 100644 --- a/lib/ot/server.js +++ b/lib/ot/server.js @@ -1,4 +1,4 @@ -var config = require('../../config'); +var config = require('../config'); if (typeof ot === 'undefined') { var ot = {}; diff --git a/lib/realtime.js b/lib/realtime.js index 081d401e..a8bef97a 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -5,24 +5,19 @@ var cookieParser = require('cookie-parser'); var url = require('url'); var async = require('async'); var LZString = require('lz-string'); -var shortId = require('shortid'); var randomcolor = require("randomcolor"); var Chance = require('chance'), chance = new Chance(); var moment = require('moment'); //core -var config = require("../config.js"); +var config = require("./config.js"); var logger = require("./logger.js"); +var models = require("./models"); //ot var ot = require("./ot/index.js"); -//others -var db = require("./db.js"); -var Note = require("./note.js"); -var User = require("./user.js"); - //public var realtime = { io: null, @@ -72,12 +67,6 @@ function emitCheck(note) { lastchangeuserprofile: note.lastchangeuserprofile }; realtime.io.to(note.id).emit('check', out); - /* - for (var i = 0, l = note.socks.length; i < l; i++) { - var sock = note.socks[i]; - sock.emit('check', out); - }; - */ } //actions @@ -88,70 +77,82 @@ var updater = setInterval(function () { async.each(Object.keys(notes), function (key, callback) { var note = notes[key]; if (note.server.isDirty) { - if (config.debug) - logger.info("updater found dirty note: " + key); - updaterUpdateMongo(note, function(err, result) { - if (err) return callback(err, null); - updaterUpdatePostgres(note, function(err, result) { - if (err) return callback(err, null); - callback(null, null); - }); + if (config.debug) logger.info("updater found dirty note: " + key); + updateNote(note, function(err, _note) { + if (!_note) { + realtime.io.to(note.id).emit('info', { + code: 404 + }); + logger.error('note not found: ', note.id); + } + if (err || !_note) { + for (var i = 0, l = note.socks.length; i < l; i++) { + var sock = note.socks[i]; + sock.disconnect(true); + } + return callback(err, null); + } + note.server.isDirty = false; + note.updatetime = moment(_note.lastchangeAt).valueOf(); + emitCheck(note); + return callback(null, null); }); } else { - callback(null, null); + return callback(null, null); } }, function (err) { if (err) return logger.error('updater error', err); }); }, 1000); -function updaterUpdateMongo(note, callback) { - Note.findNote(note.id, function (err, _note) { - if (err || !_note) return callback(err, null); +function updateNote(note, callback) { + models.Note.findOne({ + where: { + id: note.id + } + }).then(function (_note) { + if (!_note) return callback(null, null); if (note.lastchangeuser) { - if (_note.lastchangeuser != note.lastchangeuser) { - var lastchangeuser = note.lastchangeuser; - var lastchangeuserprofile = null; - User.findUser(lastchangeuser, function (err, user) { - if (err) return callback(err, null); - if (user && user.profile) { - var profile = JSON.parse(user.profile); - if (profile) { - lastchangeuserprofile = { - name: profile.displayName || profile.username, - photo: User.parsePhotoByProfile(profile) - } - _note.lastchangeuser = lastchangeuser; - note.lastchangeuserprofile = lastchangeuserprofile; - Note.updateLastChangeUser(_note, lastchangeuser, function (err, result) { - if (err) return callback(err, null); - callback(null, null); - }); - } + if (_note.lastchangeuserId != note.lastchangeuser) { + models.User.findOne({ + where: { + id: note.lastchangeuser } + }).then(function (user) { + if (!user) return callback(null, null); + note.lastchangeuserprofile = models.User.parseProfile(user.profile); + return finishUpdateNote(note, _note, callback); + }).catch(function (err) { + logger.error(err); + return callback(err, null); }); + } else { + return finishUpdateNote(note, _note, callback); } } else { - _note.lastchangeuser = null; note.lastchangeuserprofile = null; - Note.updateLastChangeUser(_note, null, function (err, result) { - if (err) return callback(err, null); - callback(null, null); - }); + return finishUpdateNote(note, _note, callback); } + }).catch(function (err) { + logger.error(err); + return callback(err, null); }); } -function updaterUpdatePostgres(note, callback) { - //postgres update +function finishUpdateNote(note, _note, callback) { var body = note.server.document; - var title = Note.getNoteTitle(body); + var title = models.Note.parseNoteTitle(body); title = LZString.compressToBase64(title); body = LZString.compressToBase64(body); - db.saveToDB(note.id, title, body, function (err, result) { - if (err) return callback(err, null); - note.server.isDirty = false; - note.updatetime = Date.now(); - emitCheck(note); - callback(null, null); + var values = { + title: title, + content: body, + lastchangeuserId: note.lastchangeuser, + lastchangeAt: Date.now() + }; + _note.update(values).then(function (_note) { + return callback(null, _note); + }).catch(function (err) { + logger.error(err); + return callback(err, null); }); } //clean when user not in any rooms or user not in connected list @@ -170,15 +171,14 @@ var cleaner = setInterval(function () { disconnectSocketQueue.push(socket); disconnect(socket); } - callback(null, null); + return callback(null, null); }, function (err) { if (err) return logger.error('cleaner error', err); }); }, 60000); function getStatus(callback) { - db.countFromDB(function (err, data) { - if (err) return logger.info(err); + models.Note.count().then(function (notecount) { var distinctaddresses = []; var regaddresses = []; var distinctregaddresses = []; @@ -208,58 +208,58 @@ function getStatus(callback) { } } }); - User.getUserCount(function (err, regcount) { - if (err) { - logger.error('get status failed: ' + err); - return; - } - if (callback) - callback({ - onlineNotes: Object.keys(notes).length, - onlineUsers: Object.keys(users).length, - distinctOnlineUsers: distinctaddresses.length, - notesCount: data.rows[0].count, - registeredUsers: regcount, - onlineRegisteredUsers: regaddresses.length, - distinctOnlineRegisteredUsers: distinctregaddresses.length, - isConnectionBusy: isConnectionBusy, - connectionSocketQueueLength: connectionSocketQueue.length, - isDisconnectBusy: isDisconnectBusy, - disconnectSocketQueueLength: disconnectSocketQueue.length - }); + models.User.count().then(function (regcount) { + 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: isConnectionBusy, + connectionSocketQueueLength: connectionSocketQueue.length, + isDisconnectBusy: isDisconnectBusy, + disconnectSocketQueueLength: disconnectSocketQueue.length + }) : null; + }).catch(function (err) { + return logger.error('count user failed: ' + err); }); + }).catch(function (err) { + return logger.error('count note failed: ' + err); }); } -function getNotenameFromSocket(socket) { +function extractNoteIdFromSocket(socket) { if (!socket || !socket.handshake || !socket.handshake.headers) { - return; + return false; } var referer = socket.handshake.headers.referer; if (!referer) { - return socket.disconnect(true); + return false; } var hostUrl = url.parse(referer); - var notename = config.urlpath ? hostUrl.pathname.slice(config.urlpath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1]; - if (notename == config.featuresnotename) { - return notename; - } - if (!Note.checkNoteIdValid(notename)) { - socket.emit('info', { - code: 404 - }); - return socket.disconnect(true); + var noteId = config.urlpath ? hostUrl.pathname.slice(config.urlpath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1]; + return noteId; +} + +function parseNoteIdFromSocket(socket, callback) { + var noteId = extractNoteIdFromSocket(socket); + if (!noteId) { + return callback(null, null); } - notename = LZString.decompressFromBase64(notename); - return notename; + models.Note.parseNoteId(noteId, function (err, id) { + if (err || !id) return callback(err, id); + return callback(null, id); + }); } function emitOnlineUsers(socket) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; var users = []; - Object.keys(notes[notename].users).forEach(function (key) { - var user = notes[notename].users[key]; + Object.keys(notes[noteId].users).forEach(function (key) { + var user = notes[noteId].users[key]; if (user) users.push(buildUserOutData(user)); }); @@ -267,35 +267,20 @@ function emitOnlineUsers(socket) { users: users }; out = LZString.compressToUTF16(JSON.stringify(out)); - realtime.io.to(notename).emit('online users', out); - /* - for (var i = 0, l = notes[notename].socks.length; i < l; i++) { - var sock = notes[notename].socks[i]; - if (sock && out) - sock.emit('online users', out); - }; - */ + realtime.io.to(noteId).emit('online users', out); } function emitUserStatus(socket) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; var out = buildUserOutData(users[socket.id]); - socket.broadcast.to(notename).emit('user status', out); - /* - for (var i = 0, l = notes[notename].socks.length; i < l; i++) { - var sock = notes[notename].socks[i]; - if (sock != socket) { - sock.emit('user status', out); - } - }; - */ + socket.broadcast.to(noteId).emit('user status', out); } function emitRefresh(socket) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; - var note = notes[notename]; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; + var note = notes[noteId]; socket.emit('refresh', { docmaxlength: config.documentmaxlength, owner: note.owner, @@ -326,15 +311,10 @@ function finishConnection(socket, note, user) { if (!socket || !note || !user) return; //check view permission if (note.permission == 'private') { - if (socket.request.user && socket.request.user.logged_in && socket.request.user._id == note.owner) { + if (socket.request.user && socket.request.user.logged_in && socket.request.user.id == note.owner) { //na } else { - socket.emit('info', { - code: 403 - }); - clearSocketQueue(connectionSocketQueue, socket); - isConnectionBusy = false; - return socket.disconnect(true); + return failConnection(403, 'connection forbidden', socket); } } note.users[socket.id] = user; @@ -354,8 +334,8 @@ function finishConnection(socket, note, user) { startConnection(connectionSocketQueue[0]); if (config.debug) { - var notename = getNotenameFromSocket(socket); - logger.info('SERVER connected a client to [' + notename + ']:'); + var noteId = socket.noteId; + logger.info('SERVER connected a client to [' + noteId + ']:'); logger.info(JSON.stringify(user)); //logger.info(notes); getStatus(function (data) { @@ -367,117 +347,76 @@ function finishConnection(socket, note, user) { function startConnection(socket) { if (isConnectionBusy) return; isConnectionBusy = true; + + var noteId = socket.noteId; + if (!noteId) { + return failConnection(404, 'note id not found', socket); + } - var notename = getNotenameFromSocket(socket); - if (!notename) { - clearSocketQueue(connectionSocketQueue, socket); - isConnectionBusy = false; - return; - } - - if (!notes[notename]) { - db.readFromDB(notename, function (err, data) { - if (err) { - socket.emit('info', { - code: 404 - }); - socket.disconnect(true); - //clear err socket in queue - clearSocketQueue(connectionSocketQueue, socket); - isConnectionBusy = false; - return logger.error(err); + if (!notes[noteId]) { + var include = [{ + model: models.User, + as: "owner" + }, { + model: models.User, + as: "lastchangeuser" + }]; + + models.Note.findOne({ + where: { + id: noteId + }, + include: include + }).then(function (note) { + if (!note) { + return failConnection(404, 'note not found', socket); } - - var owner = data.rows[0].owner; - var ownerprofile = null; - - //find or new note - Note.findOrNewNote(notename, owner, function (err, note) { - if (err) { - socket.emit('info', { - code: 404 - }); - socket.disconnect(true); - clearSocketQueue(connectionSocketQueue, socket); - isConnectionBusy = false; - return logger.error(err); - } - - var body = LZString.decompressFromBase64(data.rows[0].content); - //body = LZString.compressToUTF16(body); - var createtime = data.rows[0].create_time; - var updatetime = data.rows[0].update_time; - var server = new ot.EditorSocketIOServer(body, [], notename, ifMayEdit); - - var lastchangeuser = note.lastchangeuser || null; - var lastchangeuserprofile = null; - - notes[notename] = { - id: notename, - owner: owner, - ownerprofile: ownerprofile, - permission: note.permission, - lastchangeuser: lastchangeuser, - lastchangeuserprofile: lastchangeuserprofile, - socks: [], - users: {}, - createtime: moment(createtime).valueOf(), - updatetime: moment(updatetime).valueOf(), - server: server - }; - - async.parallel([ - function getlastchangeuser(callback) { - if (lastchangeuser) { - //find last change user profile if lastchangeuser exists - User.findUser(lastchangeuser, function (err, user) { - if (!err && user && user.profile) { - var profile = JSON.parse(user.profile); - if (profile) { - lastchangeuserprofile = { - name: profile.displayName || profile.username, - photo: User.parsePhotoByProfile(profile) - } - notes[notename].lastchangeuserprofile = lastchangeuserprofile; - } - } - callback(null, null); - }); - } else { - callback(null, null); - } - }, - function getowner(callback) { - if (owner && owner != "null") { - //find owner profile if owner exists - User.findUser(owner, function (err, user) { - if (!err && user && user.profile) { - var profile = JSON.parse(user.profile); - if (profile) { - ownerprofile = { - name: profile.displayName || profile.username, - photo: User.parsePhotoByProfile(profile) - } - notes[notename].ownerprofile = ownerprofile; - } - } - callback(null, null); - }); - } else { - callback(null, null); - } - } - ], function(err, results){ - if (err) return; - finishConnection(socket, notes[notename], users[socket.id]); - }); - }); + var owner = note.ownerId; + var ownerprofile = note.owner ? models.User.parseProfile(note.owner.profile) : null; + + var lastchangeuser = note.lastchangeuserId; + var lastchangeuserprofile = note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null; + + var body = LZString.decompressFromBase64(note.content); + var createtime = note.createdAt; + var updatetime = note.lastchangeAt; + var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit); + + notes[noteId] = { + id: noteId, + owner: owner, + ownerprofile: ownerprofile, + permission: note.permission, + lastchangeuser: lastchangeuser, + lastchangeuserprofile: lastchangeuserprofile, + socks: [], + users: {}, + createtime: moment(createtime).valueOf(), + updatetime: moment(updatetime).valueOf(), + server: server + }; + + return finishConnection(socket, notes[noteId], users[socket.id]); + }).catch(function (err) { + return failConnection(500, err, socket); }); } else { - finishConnection(socket, notes[notename], users[socket.id]); + return finishConnection(socket, notes[noteId], users[socket.id]); } } +function failConnection(code, err, socket) { + logger.error(err); + // clear error socket in queue + clearSocketQueue(connectionSocketQueue, socket); + isConnectionBusy = false; + // emit error info + socket.emit('info', { + code: code + }); + return socket.disconnect(true); +} + function disconnect(socket) { if (isDisconnectBusy) return; isDisconnectBusy = true; @@ -490,8 +429,8 @@ function disconnect(socket) { if (users[socket.id]) { delete users[socket.id]; } - var notename = getNotenameFromSocket(socket); - var note = notes[notename]; + var noteId = socket.noteId; + var note = notes[noteId]; if (note) { delete note.users[socket.id]; do { @@ -502,22 +441,18 @@ function disconnect(socket) { } while (index != -1); if (Object.keys(note.users).length <= 0) { if (note.server.isDirty) { - var body = note.server.document; - var title = Note.getNoteTitle(body); - title = LZString.compressToBase64(title); - body = LZString.compressToBase64(body); - db.saveToDB(notename, title, body, - function (err, result) { - delete notes[notename]; - if (config.debug) { - //logger.info(notes); - getStatus(function (data) { - logger.info(JSON.stringify(data)); - }); - } - }); + updateNote(note, function (err, _note) { + if (err) return logger.error('disconnect note failed: ' + err); + delete notes[noteId]; + if (config.debug) { + //logger.info(notes); + getStatus(function (data) { + logger.info(JSON.stringify(data)); + }); + } + }); } else { - delete notes[notename]; + delete notes[noteId]; } } } @@ -556,10 +491,10 @@ function buildUserOutData(user) { function updateUserData(socket, user) { //retrieve user data from passport if (socket.request.user && socket.request.user.logged_in) { - var profile = JSON.parse(socket.request.user.profile); - user.photo = User.parsePhotoByProfile(profile); - user.name = profile.displayName || profile.username; - user.userid = socket.request.user._id; + var profile = models.User.parseProfile(socket.request.user.profile); + user.photo = profile.photo; + user.name = profile.name; + user.userid = socket.request.user.id; user.login = true; } else { user.userid = null; @@ -569,9 +504,9 @@ function updateUserData(socket, user) { } function ifMayEdit(socket, callback) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; - var note = notes[notename]; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; + var note = notes[noteId]; var mayEdit = true; switch (note.permission) { case "freely": @@ -584,69 +519,78 @@ function ifMayEdit(socket, callback) { break; case "locked": case "private": //only owner can change - if (note.owner != socket.request.user._id) + if (note.owner != socket.request.user.id) mayEdit = false; break; } //if user may edit and this note have owner (not anonymous usage) - if (socket.origin == 'operation' && mayEdit && note.owner && note.owner != "null") { + if (socket.origin == 'operation' && mayEdit && note.owner) { //save for the last change user id if (socket.request.user && socket.request.user.logged_in) { - note.lastchangeuser = socket.request.user._id; + note.lastchangeuser = socket.request.user.id; } else { note.lastchangeuser = null; } } - callback(mayEdit); + return callback(mayEdit); } function connection(socket) { - //split notename from socket - var notename = getNotenameFromSocket(socket); - - //initialize user data - //random color - var color = randomcolor({ - luminosity: 'light' - }); - //make sure color not duplicated or reach max random count - if (notename && notes[notename]) { - var randomcount = 0; - var maxrandomcount = 5; - var found = false; - do { - Object.keys(notes[notename].users).forEach(function (user) { - if (user.color == color) { - found = true; - return; - } - }); - if (found) { - color = randomcolor({ - luminosity: 'light' + parseNoteIdFromSocket(socket, function (err, noteId) { + if (err) { + return failConnection(500, err, socket); + } + if (!noteId) { + return failConnection(404, 'note id not found', socket); + } + + // store noteId in this socket session + socket.noteId = noteId; + + //initialize user data + //random color + var color = randomcolor({ + luminosity: 'light' + }); + //make sure color not duplicated or reach max random count + if (notes[noteId]) { + var randomcount = 0; + var maxrandomcount = 5; + var found = false; + do { + Object.keys(notes[noteId].users).forEach(function (user) { + if (user.color == color) { + found = true; + return; + } }); - randomcount++; - } - } while (found && randomcount < maxrandomcount); - } - //create user data - users[socket.id] = { - id: socket.id, - address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address, - 'user-agent': socket.handshake.headers['user-agent'], - color: color, - cursor: null, - login: false, - userid: null, - name: null, - idle: false, - type: null - }; - updateUserData(socket, users[socket.id]); + if (found) { + color = randomcolor({ + luminosity: 'light' + }); + randomcount++; + } + } while (found && randomcount < maxrandomcount); + } + //create user data + users[socket.id] = { + id: socket.id, + address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address, + 'user-agent': socket.handshake.headers['user-agent'], + color: color, + cursor: null, + login: false, + userid: null, + name: null, + idle: false, + type: null + }; + updateUserData(socket, users[socket.id]); - //start connection - connectionSocketQueue.push(socket); - startConnection(socket); + //start connection + connectionSocketQueue.push(socket); + startConnection(socket); + }); //received client refresh request socket.on('refresh', function () { @@ -655,10 +599,10 @@ function connection(socket) { //received user status socket.on('user status', function (data) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; if (config.debug) - logger.info('SERVER received [' + notename + '] user status from [' + socket.id + ']: ' + JSON.stringify(data)); + logger.info('SERVER received [' + noteId + '] user status from [' + socket.id + ']: ' + JSON.stringify(data)); if (data) { var user = users[socket.id]; user.idle = data.idle; @@ -671,41 +615,44 @@ function connection(socket) { socket.on('permission', function (permission) { //need login to do more actions if (socket.request.user && socket.request.user.logged_in) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; - var note = notes[notename]; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; + var note = notes[noteId]; //Only owner can change permission - if (note.owner == socket.request.user._id) { + if (note.owner == socket.request.user.id) { note.permission = permission; - Note.findNote(notename, function (err, _note) { - if (err || !_note) { + models.Note.update({ + permission: permission + }, { + where: { + id: noteId + } + }).then(function (count) { + if (!count) { return; } - Note.updatePermission(_note, permission, function (err, _note) { - if (err || !_note) { - return; - } - var out = { - permission: permission - }; - realtime.io.to(note.id).emit('permission', out); - for (var i = 0, l = note.socks.length; i < l; i++) { - var sock = note.socks[i]; - if (typeof sock !== 'undefined' && sock) { - //check view permission - if (permission == 'private') { - if (sock.request.user && sock.request.user.logged_in && sock.request.user._id == note.owner) { - //na - } else { - sock.emit('info', { - code: 403 - }); - return sock.disconnect(true); - } + var out = { + permission: permission + }; + realtime.io.to(note.id).emit('permission', out); + for (var i = 0, l = note.socks.length; i < l; i++) { + var sock = note.socks[i]; + if (typeof sock !== 'undefined' && sock) { + //check view permission + if (permission == 'private') { + if (sock.request.user && sock.request.user.logged_in && sock.request.user.id == note.owner) { + //na + } else { + sock.emit('info', { + code: 403 + }); + return sock.disconnect(true); } } } - }); + } + }).catch(function (err) { + return logger.error('update note permission failed: ' + err); }); } } @@ -714,19 +661,19 @@ function connection(socket) { //reveiced when user logout or changed socket.on('user changed', function () { logger.info('user changed'); - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; - updateUserData(socket, notes[notename].users[socket.id]); + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; + updateUserData(socket, notes[noteId].users[socket.id]); emitOnlineUsers(socket); }); //received sync of online users request socket.on('online users', function () { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; var users = []; - Object.keys(notes[notename].users).forEach(function (key) { - var user = notes[notename].users[key]; + Object.keys(notes[noteId].users).forEach(function (key) { + var user = notes[noteId].users[key]; if (user) users.push(buildUserOutData(user)); }); @@ -744,55 +691,31 @@ function connection(socket) { //received cursor focus socket.on('cursor focus', function (data) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; users[socket.id].cursor = data; var out = buildUserOutData(users[socket.id]); - socket.broadcast.to(notename).emit('cursor focus', out); - /* - for (var i = 0, l = notes[notename].socks.length; i < l; i++) { - var sock = notes[notename].socks[i]; - if (sock != socket) { - sock.emit('cursor focus', out); - } - }; - */ + socket.broadcast.to(noteId).emit('cursor focus', out); }); //received cursor activity socket.on('cursor activity', function (data) { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; users[socket.id].cursor = data; var out = buildUserOutData(users[socket.id]); - socket.broadcast.to(notename).emit('cursor activity', out); - /* - for (var i = 0, l = notes[notename].socks.length; i < l; i++) { - var sock = notes[notename].socks[i]; - if (sock != socket) { - sock.emit('cursor activity', out); - } - }; - */ + socket.broadcast.to(noteId).emit('cursor activity', out); }); //received cursor blur socket.on('cursor blur', function () { - var notename = getNotenameFromSocket(socket); - if (!notename || !notes[notename]) return; + var noteId = socket.noteId; + if (!noteId || !notes[noteId]) return; users[socket.id].cursor = null; var out = { id: socket.id }; - socket.broadcast.to(notename).emit('cursor blur', out); - /* - for (var i = 0, l = notes[notename].socks.length; i < l; i++) { - var sock = notes[notename].socks[i]; - if (sock != socket) { - sock.emit('cursor blur', out); - } - }; - */ + socket.broadcast.to(noteId).emit('cursor blur', out); }); //when a new client disconnect diff --git a/lib/response.js b/lib/response.js index 4e5fac3b..7a75e234 100644 --- a/lib/response.js +++ b/lib/response.js @@ -3,7 +3,6 @@ var ejs = require('ejs'); var fs = require('fs'); var path = require('path'); -var uuid = require('node-uuid'); var markdownpdf = require("markdown-pdf"); var LZString = require('lz-string'); var S = require('string'); @@ -13,12 +12,9 @@ var querystring = require('querystring'); var request = require('request'); //core -var config = require("../config.js"); - -//others -var db = require("./db.js"); -var Note = require("./note.js"); -var User = require("./user.js"); +var config = require("./config.js"); +var logger = require("./logger.js"); +var models = require("./models"); //slides var md = require('reveal.js/plugin/markdown/markdown'); @@ -26,10 +22,7 @@ var Mustache = require('mustache'); //reveal.js var opts = { - userBasePath: process.cwd(), - revealBasePath: path.resolve(require.resolve('reveal.js'), '..', '..'), - template: fs.readFileSync(path.join('.', '/public/views/slide', 'reveal.hbs')).toString(), - templateListing: fs.readFileSync(path.join('.', '/public/views/slide', 'listing.hbs')).toString(), + template: fs.readFileSync(config.slidepath).toString(), theme: 'css/theme/black.css', highlightTheme: 'zenburn', separator: '^(\r\n?|\n)---(\r\n?|\n)$', @@ -52,7 +45,6 @@ var response = { res.status(503).send("I'm busy right now, try again later."); }, newNote: newNote, - showFeatures: showFeatures, showNote: showNote, showPublishNote: showPublishNote, showPublishSlide: showPublishSlide, @@ -67,8 +59,13 @@ function responseError(res, code, detail, msg) { 'Content-Type': 'text/html' }); var template = config.errorpath; - var content = ejs.render(fs.readFileSync(template, 'utf8'), { - url: config.getserverurl(), + var options = { + cache: !config.debug, + filename: template + }; + var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); + var content = compiled({ + url: config.serverurl, title: code + ' ' + detail + ' ' + msg, cache: !config.debug, filename: template, @@ -86,193 +83,163 @@ function showIndex(req, res, next) { 'Content-Type': 'text/html' }); var template = config.indexpath; - var content = ejs.render(fs.readFileSync(template, 'utf8'), { - url: config.getserverurl(), - useCDN: config.usecdn + var options = { + cache: !config.debug, + filename: template + }; + var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); + var content = compiled({ + url: config.serverurl, + useCDN: config.usecdn, + facebook: config.facebook, + twitter: config.twitter, + github: config.github, + dropbox: config.dropbox, }); res.write(content); res.end(); } -function responseHackMD(res, noteId) { - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - var body = LZString.decompressFromBase64(notedata.content); - var meta = null; - try { - meta = metaMarked(body).meta; - } catch(err) { - //na - } - var title = Note.decodeTitle(notedata.title); - title = Note.generateWebTitle(title); - var template = config.hackmdpath; - var options = { - cache: !config.debug, - filename: template - }; - var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); - var html = compiled({ - url: config.getserverurl(), - title: title, - useCDN: config.usecdn, - robots: (meta && meta.robots) || false //default allow robots - }); - var buf = html; - res.writeHead(200, { - 'Content-Type': 'text/html; charset=UTF-8', - 'Cache-Control': 'private', - 'Content-Length': buf.length - }); - res.end(buf); +function responseHackMD(res, note) { + var body = LZString.decompressFromBase64(note.content); + var meta = null; + try { + meta = metaMarked(body).meta; + } catch(err) { + //na + } + var title = models.Note.decodeTitle(note.title); + title = models.Note.generateWebTitle(title); + var template = config.hackmdpath; + var options = { + cache: !config.debug, + filename: template + }; + var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); + var html = compiled({ + url: config.serverurl, + title: title, + useCDN: config.usecdn, + robots: (meta && meta.robots) || false, //default allow robots + facebook: config.facebook, + twitter: config.twitter, + github: config.github, + dropbox: config.dropbox, }); + var buf = html; + res.writeHead(200, { + 'Content-Type': 'text/html; charset=UTF-8', + 'Cache-Control': 'private', + 'Content-Length': buf.length + }); + res.end(buf); } function newNote(req, res, next) { - var newId = uuid.v4(); - var body = fs.readFileSync(config.defaultnotepath, 'utf8'); - body = LZString.compressToBase64(body); var owner = null; if (req.isAuthenticated()) { - owner = req.user._id; + owner = req.user.id; } - db.newToDB(newId, owner, body, function (err, result) { - if (err) { - return response.errorInternalError(res); - } - Note.newNote(newId, owner, function(err, result) { - if (err) { - return response.errorInternalError(res); - } - res.redirect(config.getserverurl() + "/" + LZString.compressToBase64(newId)); - }); + models.Note.create({ + ownerId: owner + }).then(function (note) { + return res.redirect(config.serverurl + "/" + LZString.compressToBase64(note.id)); + }).catch(function (err) { + logger.error(err); + return response.errorInternalError(res); }); } -function showFeatures(req, res, next) { - db.readFromDB(config.featuresnotename, function (err, data) { - if (err) { - var body = fs.readFileSync(config.defaultfeaturespath, 'utf8'); - body = LZString.compressToBase64(body); - db.newToDB(config.featuresnotename, null, body, function (err, result) { - if (err) { - return response.errorInternalError(res); - } - responseHackMD(res, config.featuresnotename); - }); - } else { - responseHackMD(res, config.featuresnotename); - } - }); +function checkViewPermission(req, note) { + if (note.permission == 'private') { + if (!req.isAuthenticated() || note.ownerId != req.user.id) + return false; + else + return true; + } else { + return true; + } } -function showNote(req, res, next) { - var noteId = req.params.noteId; - if (noteId != config.featuresnotename) { - if (!Note.checkNoteIdValid(noteId)) { - return response.errorNotFound(res); - } - noteId = LZString.decompressFromBase64(noteId); - if (!noteId) { - return response.errorNotFound(res); - } - } - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - Note.findOrNewNote(noteId, notedata.owner, function (err, note) { - if (err || !note) { +function findNote(req, res, callback, include) { + var id = req.params.noteId || req.params.shortid; + models.Note.parseNoteId(id, function (err, _id) { + models.Note.findOne({ + where: { + id: _id + }, + include: include || null + }).then(function (note) { + if (!note) { return response.errorNotFound(res); } - //check view permission - if (note.permission == 'private') { - if (!req.isAuthenticated() || notedata.owner != req.user._id) - return response.errorForbidden(res); + if (!checkViewPermission(req, note)) { + return response.errorForbidden(res); + } else { + return callback(note); } - responseHackMD(res, noteId); + }).catch(function (err) { + logger.error(err); + return response.errorInternalError(res); }); }); } +function showNote(req, res, next) { + findNote(req, res, function (note) { + return responseHackMD(res, note); + }); +} + function showPublishNote(req, res, next) { - var shortid = req.params.shortid; - if (shortId.isValid(shortid)) { - Note.findNote(shortid, function (err, note) { - if (err || !note) { + var include = [{ + model: models.User, + as: "owner" + }, { + model: models.User, + as: "lastchangeuser" + }]; + findNote(req, res, function (note) { + note.increment('viewcount').then(function (note) { + if (!note) { return response.errorNotFound(res); } - db.readFromDB(note.id, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - //check view permission - if (note.permission == 'private') { - if (!req.isAuthenticated() || notedata.owner != req.user._id) - return response.errorForbidden(res); - } - //increase note viewcount - Note.increaseViewCount(note, function (err, note) { - if (err || !note) { - return response.errorNotFound(res); - } - var body = LZString.decompressFromBase64(notedata.content); - var meta = null; - try { - meta = metaMarked(body).meta; - } catch(err) { - //na - } - var updatetime = notedata.update_time; - var text = S(body).escapeHTML().s; - var title = Note.decodeTitle(notedata.title); - title = Note.generateWebTitle(title); - var origin = config.getserverurl(); - var data = { - title: title, - viewcount: note.viewcount, - updatetime: updatetime, - url: origin, - body: text, - useCDN: config.usecdn, - lastchangeuserprofile: null, - robots: (meta && meta.robots) || false //default allow robots - }; - if (note.lastchangeuser) { - //find last change user profile if lastchangeuser exists - User.findUser(note.lastchangeuser, function (err, user) { - if (!err && user && user.profile) { - var profile = JSON.parse(user.profile); - if (profile) { - data.lastchangeuserprofile = { - name: profile.displayName || profile.username, - photo: User.parsePhotoByProfile(profile) - } - renderPublish(data, res); - } - } - }); - } else { - renderPublish(data, res); - } - - }); - }); + var body = LZString.decompressFromBase64(note.content); + var meta = null; + try { + meta = metaMarked(body).meta; + } catch(err) { + //na + } + var createtime = note.createdAt; + var updatetime = note.lastchangeAt; + var text = S(body).escapeHTML().s; + var title = models.Note.decodeTitle(note.title); + title = models.Note.generateWebTitle(title); + var origin = config.serverurl; + var data = { + title: title, + viewcount: note.viewcount, + createtime: createtime, + updatetime: updatetime, + url: origin, + body: text, + useCDN: config.usecdn, + lastchangeuserprofile: note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null, + robots: (meta && meta.robots) || false //default allow robots + }; + return renderPublish(data, res); + }).catch(function (err) { + logger.error(err); + return response.errorInternalError(res); }); - } else { - return response.errorNotFound(res); - } + }, include); } function renderPublish(data, res) { var template = config.prettypath; var options = { - url: config.getserverurl(), + url: config.serverurl, cache: !config.debug, filename: template }; @@ -287,343 +254,206 @@ function renderPublish(data, res) { res.end(buf); } -function actionPublish(req, res, noteId) { - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var owner = data.rows[0].owner; - Note.findOrNewNote(noteId, owner, function (err, note) { - if (err) { - return response.errorNotFound(res); - } - res.redirect(config.getserverurl() + "/s/" + note.shortid); - }); - }); +function actionPublish(req, res, note) { + res.redirect(config.serverurl + "/s/" + (note.alias || note.shortid)); } -function actionSlide(req, res, noteId) { - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var owner = data.rows[0].owner; - Note.findOrNewNote(noteId, owner, function (err, note) { - if (err) { - return response.errorNotFound(res); - } - res.redirect(config.getserverurl() + "/p/" + note.shortid); - }); - }); +function actionSlide(req, res, note) { + res.redirect(config.serverurl + "/p/" + (note.alias || note.shortid)); } -function actionDownload(req, res, noteId) { - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - var body = LZString.decompressFromBase64(notedata.content); - var title = Note.decodeTitle(notedata.title); - var filename = title; - filename = encodeURIComponent(filename); - res.writeHead(200, { - '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', - 'Content-Length': body.length, - 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling - }); - res.end(body); +function actionDownload(req, res, note) { + var body = LZString.decompressFromBase64(note.content); + var title = models.Note.decodeTitle(note.title); + var filename = title; + filename = encodeURIComponent(filename); + res.writeHead(200, { + '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', + 'Content-Length': body.length, + 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling }); + res.end(body); } -function actionPDF(req, res, noteId) { - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - var body = LZString.decompressFromBase64(notedata.content); - try { - body = metaMarked(body).markdown; - } catch(err) { - //na - } - var title = Note.decodeTitle(notedata.title); +function actionPDF(req, res, note) { + var body = LZString.decompressFromBase64(note.content); + try { + body = metaMarked(body).markdown; + } catch(err) { + //na + } + var title = models.Note.decodeTitle(note.title); - if (!fs.existsSync(config.tmppath)) { - fs.mkdirSync(config.tmppath); - } - var path = config.tmppath + Date.now() + '.pdf'; - markdownpdf().from.string(body).to(path, function () { - var stream = fs.createReadStream(path); - var filename = title; - // Be careful of special characters - filename = encodeURIComponent(filename); - // Ideally this should strip them - res.setHeader('Content-disposition', 'attachment; filename="' + filename + '.pdf"'); - res.setHeader('Cache-Control', 'private'); - res.setHeader('Content-Type', 'application/pdf; charset=UTF-8'); - res.setHeader('X-Robots-Tag', 'noindex, nofollow'); // prevent crawling - stream.pipe(res); - fs.unlink(path); - }); + if (!fs.existsSync(config.tmppath)) { + fs.mkdirSync(config.tmppath); + } + var path = config.tmppath + Date.now() + '.pdf'; + markdownpdf().from.string(body).to(path, function () { + var stream = fs.createReadStream(path); + var filename = title; + // Be careful of special characters + filename = encodeURIComponent(filename); + // Ideally this should strip them + res.setHeader('Content-disposition', 'attachment; filename="' + filename + '.pdf"'); + res.setHeader('Cache-Control', 'private'); + res.setHeader('Content-Type', 'application/pdf; charset=UTF-8'); + res.setHeader('X-Robots-Tag', 'noindex, nofollow'); // prevent crawling + stream.pipe(res); + fs.unlink(path); }); } -function actionGist(req, res, noteId) { - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var owner = data.rows[0].owner; - Note.findOrNewNote(noteId, owner, function (err, note) { - if (err) { - return response.errorNotFound(res); - } - var data = { - client_id: config.github.clientID, - redirect_uri: config.getserverurl() + '/auth/github/callback/' + LZString.compressToBase64(noteId) + '/gist', - scope: "gist", - state: shortId.generate() - }; - var query = querystring.stringify(data); - res.redirect("https://github.com/login/oauth/authorize?" + query); - }); - }); +function actionGist(req, res, note) { + var data = { + client_id: config.github.clientID, + redirect_uri: config.serverurl + '/auth/github/callback/' + LZString.compressToBase64(note.id) + '/gist', + scope: "gist", + state: shortId.generate() + }; + var query = querystring.stringify(data); + res.redirect("https://github.com/login/oauth/authorize?" + query); } function noteActions(req, res, next) { var noteId = req.params.noteId; - if (noteId != config.featuresnotename) { - if (!Note.checkNoteIdValid(noteId)) { - return response.errorNotFound(res); + 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 "pdf": + actionPDF(req, res, note); + break; + case "gist": + actionGist(req, res, note); + break; + default: + return res.redirect(config.serverurl + '/' + noteId); + break; } - noteId = LZString.decompressFromBase64(noteId); - if (!noteId) { - return response.errorNotFound(res); - } - } - Note.findNote(noteId, function (err, note) { - if (err || !note) { - return response.errorNotFound(res); - } - db.readFromDB(note.id, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - //check view permission - if (note.permission == 'private') { - if (!req.isAuthenticated() || notedata.owner != req.user._id) - return response.errorForbidden(res); - } - var action = req.params.action; - switch (action) { - case "publish": - case "pretty": //pretty deprecated - actionPublish(req, res, noteId); - break; - case "slide": - actionSlide(req, res, noteId); - break; - case "download": - actionDownload(req, res, noteId); - break; - case "pdf": - actionPDF(req, res, noteId); - break; - case "gist": - actionGist(req, res, noteId); - break; - default: - if (noteId != config.featuresnotename) - res.redirect(config.getserverurl() + '/' + LZString.compressToBase64(noteId)); - else - res.redirect(config.getserverurl() + '/' + noteId); - break; - } - }); }); } function publishNoteActions(req, res, next) { - var shortid = req.params.shortid; - if (shortId.isValid(shortid)) { - Note.findNote(shortid, function (err, note) { - if (err || !note) { - return response.errorNotFound(res); - } - db.readFromDB(note.id, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - //check view permission - if (note.permission == 'private') { - if (!req.isAuthenticated() || notedata.owner != req.user._id) - return response.errorForbidden(res); - } - var action = req.params.action; - switch (action) { - case "edit": - if (note.id != config.featuresnotename) - res.redirect(config.getserverurl() + '/' + LZString.compressToBase64(note.id)); - else - res.redirect(config.getserverurl() + '/' + note.id); - break; - } - }); - }); - } + findNote(req, res, function (note) { + var action = req.params.action; + switch (action) { + case "edit": + res.redirect(config.serverurl + '/' + (note.alias ? note.alias : LZString.compressToBase64(note.id))); + break; + default: + res.redirect(config.serverurl + '/s/' + note.shortid); + break; + } + }); } function githubActions(req, res, next) { var noteId = req.params.noteId; - if (noteId != config.featuresnotename) { - if (!Note.checkNoteIdValid(noteId)) { - return response.errorNotFound(res); - } - noteId = LZString.decompressFromBase64(noteId); - if (!noteId) { - return response.errorNotFound(res); + findNote(req, res, function (note) { + var action = req.params.action; + switch (action) { + case "gist": + githubActionGist(req, res, note); + break; + default: + res.redirect(config.serverurl + '/' + noteId); + break; } - } - Note.findNote(noteId, function (err, note) { - if (err || !note) { - return response.errorNotFound(res); - } - db.readFromDB(note.id, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - //check view permission - if (note.permission == 'private') { - if (!req.isAuthenticated() || notedata.owner != req.user._id) - return response.errorForbidden(res); - } - var action = req.params.action; - switch (action) { - case "gist": - githubActionGist(req, res, noteId); - break; - default: - if (noteId != config.featuresnotename) - res.redirect(config.getserverurl() + '/' + LZString.compressToBase64(noteId)); - else - res.redirect(config.getserverurl() + '/' + noteId); - break; - } - }); }); } -function githubActionGist(req, res, noteId) { - db.readFromDB(noteId, function (err, data) { - if (err) { - return response.errorNotFound(res); +function githubActionGist(req, res, note) { + var code = req.query.code; + var state = req.query.state; + if (!code || !state) { + return response.errorForbidden(res); + } else { + var data = { + client_id: config.github.clientID, + client_secret: config.github.clientSecret, + code: code, + state: state } - var notedata = data.rows[0]; - var code = req.query.code; - var state = req.query.state; - if (!code || !state) { - return response.errorForbidden(res); - } else { - var data = { - client_id: config.github.clientID, - client_secret: config.github.clientSecret, - code: code, - state: state - } - var auth_url = 'https://github.com/login/oauth/access_token'; - request({ - url: auth_url, - method: "POST", - json: data - }, function (error, httpResponse, body) { - if (!error && httpResponse.statusCode == 200) { - var access_token = body.access_token; - if (access_token) { - var content = LZString.decompressFromBase64(notedata.content); - var title = Note.decodeTitle(notedata.title); - var filename = title.replace('/', ' ') + '.md'; - var gist = { - "files": {} - }; - gist.files[filename] = { - "content": content - }; - var gist_url = "https://api.github.com/gists"; - request({ - url: gist_url, - headers: { - 'User-Agent': 'HackMD', - 'Authorization': 'token ' + access_token - }, - method: "POST", - json: gist - }, function (error, httpResponse, body) { - if (!error && httpResponse.statusCode == 201) { - res.setHeader('referer', ''); - res.redirect(body.html_url); - } else { - return response.errorForbidden(res); - } - }); - } else { - return response.errorForbidden(res); - } + var auth_url = 'https://github.com/login/oauth/access_token'; + request({ + url: auth_url, + method: "POST", + json: data + }, function (error, httpResponse, body) { + if (!error && httpResponse.statusCode == 200) { + var access_token = body.access_token; + if (access_token) { + var content = LZString.decompressFromBase64(note.content); + var title = models.Note.decodeTitle(note.title); + var filename = title.replace('/', ' ') + '.md'; + var gist = { + "files": {} + }; + gist.files[filename] = { + "content": content + }; + var gist_url = "https://api.github.com/gists"; + request({ + url: gist_url, + headers: { + 'User-Agent': 'HackMD', + 'Authorization': 'token ' + access_token + }, + method: "POST", + json: gist + }, function (error, httpResponse, body) { + if (!error && httpResponse.statusCode == 201) { + res.setHeader('referer', ''); + res.redirect(body.html_url); + } else { + return response.errorForbidden(res); + } + }); } else { return response.errorForbidden(res); } - }) - } - }); + } else { + return response.errorForbidden(res); + } + }) + } } function showPublishSlide(req, res, next) { - var shortid = req.params.shortid; - if (shortId.isValid(shortid)) { - Note.findNote(shortid, function (err, note) { - if (err || !note) { + findNote(req, res, function (note) { + note.increment('viewcount').then(function (note) { + if (!note) { return response.errorNotFound(res); } - db.readFromDB(note.id, function (err, data) { - if (err) { - return response.errorNotFound(res); - } - var notedata = data.rows[0]; - //check view permission - if (note.permission == 'private') { - if (!req.isAuthenticated() || notedata.owner != req.user._id) - return response.errorForbidden(res); - } - //increase note viewcount - Note.increaseViewCount(note, function (err, note) { - if (err || !note) { - return response.errorNotFound(res); - } - var body = LZString.decompressFromBase64(notedata.content); - try { - body = metaMarked(body).markdown; - } catch(err) { - //na - } - var title = Note.decodeTitle(notedata.title); - title = Note.generateWebTitle(title); - var text = S(body).escapeHTML().s; - render(res, title, text); - }); - }); + var body = LZString.decompressFromBase64(note.content); + try { + body = metaMarked(body).markdown; + } catch(err) { + //na + } + var title = models.Note.decodeTitle(note.title); + title = models.Note.generateWebTitle(title); + var text = S(body).escapeHTML().s; + render(res, title, text); + }).catch(function (err) { + logger.error(err); + return response.errorInternalError(res); }); - } else { - return response.errorNotFound(res); - } + }); } //reveal.js render @@ -631,7 +461,7 @@ var render = function (res, title, markdown) { var slides = md.slidify(markdown, opts); res.end(Mustache.to_html(opts.template, { - url: config.getserverurl(), + url: config.serverurl, title: title, theme: opts.theme, highlightTheme: opts.highlightTheme, diff --git a/lib/temp.js b/lib/temp.js deleted file mode 100644 index b635644d..00000000 --- a/lib/temp.js +++ /dev/null @@ -1,84 +0,0 @@ -//temp -//external modules -var mongoose = require('mongoose'); - -//core -var config = require("../config.js"); -var logger = require("./logger.js"); - -// create a temp model -var model = mongoose.model('temp', { - id: String, - data: String, - created: Date -}); - -//public -var temp = { - model: model, - findTemp: findTemp, - newTemp: newTemp, - removeTemp: removeTemp, - getTempCount: getTempCount -}; - -function getTempCount(callback) { - model.count(function(err, count){ - if(err) callback(err, null); - else callback(null, count); - }); -} - -function findTemp(id, callback) { - model.findOne({ - id: id - }, function (err, temp) { - if (err) { - logger.error('find temp failed: ' + err); - callback(err, null); - } - if (!err && temp) { - callback(null, temp); - } else { - logger.error('find temp failed: ' + err); - callback(err, null); - }; - }); -} - -function newTemp(id, data, callback) { - var temp = new model({ - id: id, - data: data, - created: Date.now() - }); - temp.save(function (err) { - if (err) { - logger.error('new temp failed: ' + err); - callback(err, null); - } else { - logger.info("new temp success: " + temp.id); - callback(null, temp); - }; - }); -} - -function removeTemp(id, callback) { - findTemp(id, function(err, temp) { - if(!err && temp) { - temp.remove(function(err) { - if(err) { - logger.error('remove temp failed: ' + err); - callback(err, null); - } else { - callback(null, null); - } - }); - } else { - logger.error('remove temp failed: ' + err); - callback(err, null); - } - }); -} - -module.exports = temp; \ No newline at end of file diff --git a/lib/user.js b/lib/user.js deleted file mode 100644 index 639d66c3..00000000 --- a/lib/user.js +++ /dev/null @@ -1,110 +0,0 @@ -//user -//external modules -var mongoose = require('mongoose'); -var md5 = require("md5"); - -//core -var config = require("../config.js"); -var logger = require("./logger.js"); - -// create a user model -var model = mongoose.model('user', { - id: String, - profile: String, - history: String, - created: Date -}); - -//public -var user = { - model: model, - findUser: findUser, - newUser: newUser, - findOrNewUser: findOrNewUser, - getUserCount: getUserCount, - parsePhotoByProfile: parsePhotoByProfile -}; - -function parsePhotoByProfile(profile) { - var photo = null; - switch (profile.provider) { - case "facebook": - photo = 'https://graph.facebook.com/' + profile.id + '/picture'; - break; - case "twitter": - photo = profile.photos[0].value; - break; - case "github": - photo = 'https://avatars.githubusercontent.com/u/' + profile.id + '?s=48'; - break; - case "dropbox": - //no image api provided, use gravatar - photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value); - break; - } - return photo; -} - -function getUserCount(callback) { - model.count(function(err, count){ - if(err) callback(err, null); - else callback(null, count); - }); -} - -function findUser(id, callback) { - var rule = {}; - var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$"); - if (checkForHexRegExp.test(id)) - rule._id = id; - else - rule.id = id; - model.findOne(rule, function (err, user) { - if (err) { - logger.error('find user failed: ' + err); - callback(err, null); - } - if (!err && user) { - callback(null, user); - } else { - logger.error('find user failed: ' + err); - callback(err, null); - }; - }); -} - -function newUser(id, profile, callback) { - var user = new model({ - id: id, - profile: JSON.stringify(profile), - created: Date.now() - }); - user.save(function (err) { - if (err) { - logger.error('new user failed: ' + err); - callback(err, null); - } else { - logger.info("new user success: " + user.id); - callback(null, user); - }; - }); -} - -function findOrNewUser(id, profile, callback) { - findUser(id, function(err, user) { - if(err || !user) { - newUser(id, profile, function(err, user) { - if(err) { - logger.error('find or new user failed: ' + err); - callback(err, null); - } else { - callback(null, user); - } - }); - } else { - callback(null, user); - } - }); -} - -module.exports = user; \ No newline at end of file -- cgit v1.2.3