summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/auth.js76
-rw-r--r--lib/config.js112
-rw-r--r--lib/db.js151
-rw-r--r--lib/models/index.js37
-rw-r--r--lib/models/note.js208
-rw-r--r--lib/models/temp.js19
-rw-r--r--lib/models/user.js77
-rw-r--r--lib/note.js237
-rw-r--r--lib/ot/server.js2
-rw-r--r--lib/realtime.js675
-rw-r--r--lib/response.js766
-rw-r--r--lib/temp.js84
-rw-r--r--lib/user.js110
13 files changed, 1097 insertions, 1457 deletions
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