//history //external modules var async = require('async'); var moment = require('moment'); //core var config = require("./config.js"); var logger = require("./logger.js"); var response = require("./response.js"); var models = require("./models"); //public var History = { historyGet: historyGet, historyPost: historyPost, historyDelete: historyDelete, isReady: isReady, updateHistory: updateHistory }; var caches = {}; //update when the history is dirty var updater = setInterval(function () { var deleted = []; async.each(Object.keys(caches), function (key, callback) { var cache = caches[key]; if (cache.isDirty) { if (config.debug) logger.info("history updater found dirty history: " + key); var history = parseHistoryToArray(cache.history); cache.isDirty = false; finishUpdateHistory(key, history, function (err, count) { if (err) return callback(err, null); if (!count) return callback(null, null); cache.updateAt = Date.now(); return callback(null, null); }); } else { if (moment().isAfter(moment(cache.updateAt).add(5, 'minutes'))) { deleted.push(key); } return callback(null, null); } }, function (err) { if (err) return logger.error('history updater error', err); }); // delete specified caches for (var i = 0, l = deleted.length; i < l; i++) { caches[deleted[i]].history = {}; delete caches[deleted[i]]; } }, 1000); function finishUpdateHistory(userid, history, callback) { models.User.update({ history: JSON.stringify(history) }, { where: { id: userid } }).then(function (count) { return callback(null, count); }).catch(function (err) { return callback(err, null); }); } function isReady() { var dirtyCount = 0; async.each(Object.keys(caches), function (key, callback) { if (caches[key].isDirty) dirtyCount++; return callback(null, null); }, function (err) { if (err) return logger.error('history ready check error', err); }); return dirtyCount > 0 ? false : true; } function getHistory(userid, callback) { if (caches[userid]) { return callback(null, caches[userid].history); } else { models.User.findOne({ where: { id: userid } }).then(function (user) { if (!user) return callback(null, null); var history = []; if (user.history) history = JSON.parse(user.history); if (config.debug) logger.info('read history success: ' + user.id); setHistory(userid, history); return callback(null, history); }).catch(function (err) { logger.error('read history failed: ' + err); return callback(err, null); }); } } function setHistory(userid, history) { if (Array.isArray(history)) history = parseHistoryToObject(history); if (!caches[userid]) { caches[userid] = { history: {}, isDirty: false, updateAt: Date.now() }; } caches[userid].history = history; } function updateHistory(userid, noteId, document) { if (userid && noteId && typeof document !== 'undefined') { getHistory(userid, function (err, history) { if (err || !history) return; if (!caches[userid].history[noteId]) { caches[userid].history[noteId] = {}; } var noteHistory = caches[userid].history[noteId]; var noteInfo = models.Note.parseNoteInfo(document); noteHistory.id = noteId; noteHistory.text = noteInfo.title; noteHistory.time = moment().valueOf(); noteHistory.tags = noteInfo.tags; caches[userid].isDirty = true; }); } } function parseHistoryToArray(history) { var _history = []; Object.keys(history).forEach(function (key) { var item = history[key]; _history.push(item); }); return _history; } function parseHistoryToObject(history) { var _history = {}; for (var i = 0, l = history.length; i < l; i++) { var item = history[i]; _history[item.id] = item; } return _history; } function historyGet(req, res) { if (req.isAuthenticated()) { getHistory(req.user.id, function (err, history) { if (err) return response.errorInternalError(res); if (!history) return response.errorNotFound(res); res.send({ history: parseHistoryToArray(history) }); }); } else { return response.errorForbidden(res); } } function historyPost(req, res) { if (req.isAuthenticated()) { var noteId = req.params.noteId; if (!noteId) { if (typeof req.body['history'] === 'undefined') return response.errorBadRequest(res); if (config.debug) logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history); try { var history = JSON.parse(req.body.history); } catch (err) { return response.errorBadRequest(res); } if (Array.isArray(history)) { setHistory(req.user.id, history); caches[req.user.id].isDirty = true; res.end(); } else { return response.errorBadRequest(res); } } else { if (typeof req.body['pinned'] === 'undefined') return response.errorBadRequest(res); getHistory(req.user.id, function (err, history) { if (err) return response.errorInternalError(res); if (!history) return response.errorNotFound(res); if (!caches[req.user.id].history[noteId]) return response.errorNotFound(res); if (req.body.pinned === 'true' || req.body.pinned === 'false') { caches[req.user.id].history[noteId].pinned = (req.body.pinned === 'true'); caches[req.user.id].isDirty = true; res.end(); } else { return response.errorBadRequest(res); } }); } } else { return response.errorForbidden(res); } } function historyDelete(req, res) { if (req.isAuthenticated()) { var noteId = req.params.noteId; if (!noteId) { setHistory(req.user.id, []); caches[req.user.id].isDirty = true; res.end(); } else { getHistory(req.user.id, function (err, history) { if (err) return response.errorInternalError(res); if (!history) return response.errorNotFound(res); delete caches[req.user.id].history[noteId]; caches[req.user.id].isDirty = true; res.end(); }); } } else { return response.errorForbidden(res); } } module.exports = History;