Support show last change user with profile and support YAML config inside the note with robots, lang, dir, breaks options

This commit is contained in:
Wu Cheng-Han 2016-01-12 08:01:42 -06:00
parent 1672df3dce
commit 2ecec3b59a
18 changed files with 546 additions and 167 deletions

View file

@ -26,6 +26,10 @@ var model = mongoose.model('note', {
type: String,
enum: permissionTypes
},
lastchangeuser: {
type: Schema.Types.ObjectId,
ref: 'user'
},
viewcount: {
type: Number,
default: 0
@ -45,7 +49,8 @@ var note = {
getNoteTitle: getNoteTitle,
generateWebTitle: generateWebTitle,
increaseViewCount: increaseViewCount,
updatePermission: updatePermission
updatePermission: updatePermission,
updateLastChangeUser: updateLastChangeUser
};
function checkNoteIdValid(noteId) {
@ -198,4 +203,18 @@ function updatePermission(note, permission, callback) {
});
}
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;

View file

@ -9,7 +9,6 @@ var shortId = require('shortid');
var randomcolor = require("randomcolor");
var Chance = require('chance'),
chance = new Chance();
var md5 = require("blueimp-md5").md5;
var moment = require('moment');
//core
@ -68,7 +67,9 @@ function secure(socket, next) {
function emitCheck(note) {
var out = {
updatetime: note.updatetime
updatetime: note.updatetime,
lastchangeuser: note.lastchangeuser,
lastchangeuserprofile: note.lastchangeuserprofile
};
realtime.io.to(note.id).emit('check', out);
/*
@ -89,18 +90,52 @@ var updater = setInterval(function () {
if (note.server.isDirty) {
if (config.debug)
logger.info("updater found dirty note: " + key);
var body = note.server.document;
var title = Note.getNoteTitle(body);
title = LZString.compressToBase64(title);
body = LZString.compressToBase64(body);
db.saveToDB(key, title, body, function (err, result) {
if (err) return;
note.server.isDirty = false;
note.updatetime = Date.now();
emitCheck(note);
Note.findNote(note.id, function (err, _note) {
if (err || !_note) return callback(err, null);
//mongo update
if (note.lastchangeuser && _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);
});
}
}
});
} else {
note.lastchangeuser = null;
note.lastchangeuserprofile = null;
Note.updateLastChangeUser(_note, null, function (err, result) {
if (err) return callback(err, null);
});
}
//postgres update
var body = note.server.document;
var title = Note.getNoteTitle(body);
title = LZString.compressToBase64(title);
body = LZString.compressToBase64(body);
db.saveToDB(key, title, body, function (err, result) {
if (err) return callback(err, null);
note.server.isDirty = false;
note.updatetime = Date.now();
emitCheck(note);
callback(null, null);
});
});
} else {
callback(null, null);
}
callback();
}, function (err) {
if (err) return logger.error('updater error', err);
});
@ -121,7 +156,7 @@ var cleaner = setInterval(function () {
disconnectSocketQueue.push(socket);
disconnect(socket);
}
callback();
callback(null, null);
}, function (err) {
if (err) return logger.error('cleaner error', err);
});
@ -250,7 +285,11 @@ function emitRefresh(socket) {
socket.emit('refresh', {
docmaxlength: config.documentmaxlength,
owner: note.owner,
ownerprofile: note.ownerprofile,
lastchangeuser: note.lastchangeuser,
lastchangeuserprofile: note.lastchangeuserprofile,
permission: note.permission,
createtime: note.createtime,
updatetime: note.updatetime
});
}
@ -321,11 +360,15 @@ function startConnection(socket) {
isConnectionBusy = false;
return logger.error(err);
}
var owner = data.rows[0].owner;
var ownerprofile = null;
var permission = "freely";
if (owner && owner != "null") {
permission = "editable";
}
//find or new note
Note.findOrNewNote(notename, permission, function (err, note) {
if (err) {
responseError(res, "404", "Not Found", "oops.");
@ -333,20 +376,64 @@ function startConnection(socket) {
isConnectionBusy = false;
return;
}
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
};
finishConnection(socket, notes[notename], users[socket.id]);
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;
}
}
});
}
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;
}
}
finishConnection(socket, notes[notename], users[socket.id]);
});
} else {
finishConnection(socket, notes[notename], users[socket.id]);
}
});
});
} else {
@ -433,23 +520,7 @@ 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);
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;
}
user.photo = photo;
user.photo = User.parsePhotoByProfile(profile);
user.name = profile.displayName || profile.username;
user.userid = socket.request.user._id;
user.login = true;
@ -466,19 +537,28 @@ function ifMayEdit(socket, callback) {
var note = notes[notename];
var mayEdit = true;
switch (note.permission) {
case "freely":
//not blocking anyone
break;
case "editable":
//only login user can change
if (!socket.request.user || !socket.request.user.logged_in)
mayEdit = false;
break;
case "locked":
//only owner can change
if (note.owner != socket.request.user._id)
mayEdit = false;
break;
case "freely":
//not blocking anyone
break;
case "editable":
//only login user can change
if (!socket.request.user || !socket.request.user.logged_in)
mayEdit = false;
break;
case "locked":
//only owner can change
if (note.owner != socket.request.user._id)
mayEdit = false;
break;
}
//if user may edit and this note have owner (not anonymous usage)
if (mayEdit && note.owner && note.owner != "null") {
//save for the last change user id
if (socket.request.user && socket.request.user.logged_in) {
note.lastchangeuser = socket.request.user._id;
} else {
note.lastchangeuser = null;
}
}
callback(mayEdit);
}

View file

@ -8,6 +8,7 @@ var markdownpdf = require("markdown-pdf");
var LZString = require('lz-string');
var S = require('string');
var shortId = require('shortid');
var metaMarked = require('meta-marked');
//core
var config = require("../config.js");
@ -15,6 +16,7 @@ var config = require("../config.js");
//others
var db = require("./db.js");
var Note = require("./note.js");
var User = require("./user.js");
//slides
var md = require('reveal.js/plugin/markdown/markdown');
@ -104,6 +106,13 @@ function responseHackMD(res, noteId) {
responseError(res, "404", "Not Found", "oops.");
return;
}
var body = LZString.decompressFromBase64(data.rows[0].content);
var meta = null;
try {
meta = metaMarked(body).meta;
} catch(err) {
//na
}
var title = data.rows[0].title;
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;
@ -116,7 +125,8 @@ function responseHackMD(res, noteId) {
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
var html = compiled({
title: title,
useCDN: config.usecdn
useCDN: config.usecdn,
robots: (meta && meta.robots) || false //default allow robots
});
var buf = html;
res.writeHead(200, {
@ -192,34 +202,47 @@ function showPublishNote(req, res, next) {
return;
}
var body = LZString.decompressFromBase64(data.rows[0].content);
var meta = null;
try {
meta = metaMarked(body).meta;
} catch(err) {
//na
}
var updatetime = data.rows[0].update_time;
var text = S(body).escapeHTML().s;
var title = data.rows[0].title;
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;
title = Note.generateWebTitle(title);
var template = config.prettypath;
var options = {
cache: !config.debug,
filename: template
};
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
var origin = config.getserverurl();
var html = compiled({
var data = {
title: title,
viewcount: note.viewcount,
updatetime: updatetime,
url: origin,
body: text,
useCDN: config.usecdn
});
var buf = html;
res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8',
'Cache-Control': 'private',
'Content-Length': buf.length
});
res.end(buf);
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);
}
});
});
});
@ -228,6 +251,23 @@ function showPublishNote(req, res, next) {
}
}
function renderPublish(data, res) {
var template = config.prettypath;
var options = {
cache: !config.debug,
filename: template
};
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
var html = compiled(data);
var buf = html;
res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8',
'Cache-Control': 'private',
'Content-Length': buf.length
});
res.end(buf);
}
function actionPublish(req, res, noteId) {
db.readFromDB(noteId, function (err, data) {
if (err) {
@ -269,36 +309,6 @@ function actionSlide(req, res, noteId) {
});
});
}
//pretty api is deprecated
function actionPretty(req, res, noteId) {
db.readFromDB(noteId, function (err, data) {
if (err) {
responseError(res, "404", "Not Found", "oops.");
return;
}
var body = LZString.decompressFromBase64(data.rows[0].content);
var text = S(body).escapeHTML().s;
var title = data.rows[0].title;
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;
title = Note.generateWebTitle(title);
var template = config.prettypath;
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'));
var origin = config.getserverurl();
var html = compiled({
title: title,
url: origin,
body: text
});
var buf = html;
res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8',
'Cache-Control': 'private',
'Content-Length': buf.length
});
res.end(buf);
});
}
function actionDownload(req, res, noteId) {
db.readFromDB(noteId, function (err, data) {
@ -325,6 +335,11 @@ function actionPDF(req, res, noteId) {
return;
}
var body = LZString.decompressFromBase64(data.rows[0].content);
try {
body = metaMarked(body).markdown;
} catch(err) {
//na
}
var title = Note.getNoteTitle(body);
if (!fs.existsSync(config.tmppath)) {
@ -361,46 +376,46 @@ function noteActions(req, res, next) {
}
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;
default:
if (noteId != config.featuresnotename)
res.redirect('/' + LZString.compressToBase64(noteId));
else
res.redirect('/' + noteId);
break;
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;
default:
if (noteId != config.featuresnotename)
res.redirect('/' + LZString.compressToBase64(noteId));
else
res.redirect('/' + noteId);
break;
}
}
function publishNoteActions(req, res, next) {
var action = req.params.action;
switch (action) {
case "edit":
var shortid = req.params.shortid;
if (shortId.isValid(shortid)) {
Note.findNote(shortid, function (err, note) {
if (err || !note) {
responseError(res, "404", "Not Found", "oops.");
return;
}
if (note.id != config.featuresnotename)
res.redirect('/' + LZString.compressToBase64(note.id));
else
res.redirect('/' + note.id);
});
}
break;
case "edit":
var shortid = req.params.shortid;
if (shortId.isValid(shortid)) {
Note.findNote(shortid, function (err, note) {
if (err || !note) {
responseError(res, "404", "Not Found", "oops.");
return;
}
if (note.id != config.featuresnotename)
res.redirect('/' + LZString.compressToBase64(note.id));
else
res.redirect('/' + note.id);
});
}
break;
}
}
@ -424,6 +439,11 @@ function showPublishSlide(req, res, next) {
return;
}
var body = LZString.decompressFromBase64(data.rows[0].content);
try {
body = metaMarked(body).markdown;
} catch(err) {
//na
}
var title = data.rows[0].title;
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;

View file

@ -1,6 +1,7 @@
//user
//external modules
var mongoose = require('mongoose');
var md5 = require("blueimp-md5").md5;
//core
var config = require("../config.js");
@ -20,9 +21,30 @@ var user = {
findUser: findUser,
newUser: newUser,
findOrNewUser: findOrNewUser,
getUserCount: getUserCount
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);
@ -31,9 +53,13 @@ function getUserCount(callback) {
}
function findUser(id, callback) {
model.findOne({
id: id
}, function (err, user) {
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);