From 4889e9732d2458d60e2a079d3e54e128f6ce1b53 Mon Sep 17 00:00:00 2001 From: BoHong Li Date: Wed, 8 Mar 2017 18:45:51 +0800 Subject: Use JavaScript Standard Style Introduce JavaScript Standard Style as project style rule, and fixed all fail on backend code. --- lib/models/author.js | 74 ++-- lib/models/index.js | 68 ++-- lib/models/note.js | 1011 ++++++++++++++++++++++++------------------------ lib/models/revision.js | 580 +++++++++++++-------------- lib/models/temp.js | 32 +- lib/models/user.js | 278 +++++++------ 6 files changed, 1010 insertions(+), 1033 deletions(-) (limited to 'lib/models') diff --git a/lib/models/author.js b/lib/models/author.js index 0b0f149d..5e39c347 100644 --- a/lib/models/author.js +++ b/lib/models/author.js @@ -1,43 +1,37 @@ -"use strict"; - // external modules -var Sequelize = require("sequelize"); - -// core -var logger = require("../logger.js"); +var Sequelize = require('sequelize') module.exports = function (sequelize, DataTypes) { - var Author = sequelize.define("Author", { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true - }, - color: { - type: DataTypes.STRING - } - }, { - indexes: [ - { - unique: true, - fields: ['noteId', 'userId'] - } - ], - classMethods: { - associate: function (models) { - Author.belongsTo(models.Note, { - foreignKey: "noteId", - as: "note", - constraints: false - }); - Author.belongsTo(models.User, { - foreignKey: "userId", - as: "user", - constraints: false - }); - } - } - }); - - return Author; -}; \ No newline at end of file + var Author = sequelize.define('Author', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + color: { + type: DataTypes.STRING + } + }, { + indexes: [ + { + unique: true, + fields: ['noteId', 'userId'] + } + ], + classMethods: { + associate: function (models) { + Author.belongsTo(models.Note, { + foreignKey: 'noteId', + as: 'note', + constraints: false + }) + Author.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user', + constraints: false + }) + } + } + }) + return Author +} diff --git a/lib/models/index.js b/lib/models/index.js index e83956e5..96babc2a 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -1,57 +1,55 @@ -"use strict"; - // external modules -var fs = require("fs"); -var path = require("path"); -var Sequelize = require("sequelize"); +var fs = require('fs') +var path = require('path') +var Sequelize = require('sequelize') // core -var config = require('../config.js'); -var logger = require("../logger.js"); +var config = require('../config.js') +var logger = require('../logger.js') -var dbconfig = config.db; -dbconfig.logging = config.debug ? logger.info : false; +var dbconfig = config.db +dbconfig.logging = config.debug ? logger.info : false -var sequelize = null; +var sequelize = null // Heroku specific -if (config.dburl) - sequelize = new Sequelize(config.dburl, dbconfig); -else - sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig); +if (config.dburl) { + sequelize = new Sequelize(config.dburl, dbconfig) +} else { + sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig) +} // [Postgres] Handling NULL bytes // https://github.com/sequelize/sequelize/issues/6485 -function stripNullByte(value) { - return value ? value.replace(/\u0000/g, "") : value; +function stripNullByte (value) { + return value ? value.replace(/\u0000/g, '') : value } -sequelize.stripNullByte = stripNullByte; +sequelize.stripNullByte = stripNullByte -function processData(data, _default, process) { - if (data === undefined) return data; - else return data === null ? _default : (process ? process(data) : data); +function processData (data, _default, process) { + if (data === undefined) return data + else return data === null ? _default : (process ? process(data) : data) } -sequelize.processData = processData; +sequelize.processData = processData -var db = {}; +var db = {} -fs - .readdirSync(__dirname) +fs.readdirSync(__dirname) .filter(function (file) { - return (file.indexOf(".") !== 0) && (file !== "index.js"); + return (file.indexOf('.') !== 0) && (file !== 'index.js') }) .forEach(function (file) { - var model = sequelize.import(path.join(__dirname, file)); - db[model.name] = model; - }); + 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); - } -}); + if ('associate' in db[modelName]) { + db[modelName].associate(db) + } +}) -db.sequelize = sequelize; -db.Sequelize = Sequelize; +db.sequelize = sequelize +db.Sequelize = Sequelize -module.exports = db; +module.exports = db diff --git a/lib/models/note.js b/lib/models/note.js index 8b38d3f9..bef9ee21 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -1,535 +1,524 @@ -"use strict"; - // external modules -var fs = require('fs'); -var path = require('path'); -var LZString = require('lz-string'); -var md = require('markdown-it')(); -var metaMarked = require('meta-marked'); -var cheerio = require('cheerio'); -var shortId = require('shortid'); -var Sequelize = require("sequelize"); -var async = require('async'); -var moment = require('moment'); -var DiffMatchPatch = require('diff-match-patch'); -var dmp = new DiffMatchPatch(); -var S = require('string'); +var fs = require('fs') +var path = require('path') +var LZString = require('lz-string') +var md = require('markdown-it')() +var metaMarked = require('meta-marked') +var cheerio = require('cheerio') +var shortId = require('shortid') +var Sequelize = require('sequelize') +var async = require('async') +var moment = require('moment') +var DiffMatchPatch = require('diff-match-patch') +var dmp = new DiffMatchPatch() +var S = require('string') // core -var config = require("../config.js"); -var logger = require("../logger.js"); +var config = require('../config.js') +var logger = require('../logger.js') -//ot -var ot = require("../ot/index.js"); +// ot +var ot = require('../ot/index.js') // permission types -var permissionTypes = ["freely", "editable", "limited", "locked", "protected", "private"]; +var permissionTypes = ['freely', 'editable', 'limited', 'locked', 'protected', '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, - get: function () { - return sequelize.processData(this.getDataValue('title'), ""); - }, - set: function (value) { - this.setDataValue('title', sequelize.stripNullByte(value)); - } - }, - content: { - type: DataTypes.TEXT, - get: function () { - return sequelize.processData(this.getDataValue('content'), ""); - }, - set: function (value) { - this.setDataValue('content', sequelize.stripNullByte(value)); - } - }, - authorship: { - type: DataTypes.TEXT, - get: function () { - return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse); - }, - set: function (value) { - this.setDataValue('authorship', JSON.stringify(value)); - } - }, - lastchangeAt: { - type: DataTypes.DATE - }, - savedAt: { - type: DataTypes.DATE + 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, + get: function () { + return sequelize.processData(this.getDataValue('title'), '') + }, + set: function (value) { + this.setDataValue('title', sequelize.stripNullByte(value)) + } + }, + content: { + type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('content'), '') + }, + set: function (value) { + this.setDataValue('content', sequelize.stripNullByte(value)) + } + }, + authorship: { + type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse) + }, + set: function (value) { + this.setDataValue('authorship', JSON.stringify(value)) + } + }, + lastchangeAt: { + type: DataTypes.DATE + }, + savedAt: { + type: DataTypes.DATE + } + }, { + paranoid: true, + classMethods: { + associate: function (models) { + Note.belongsTo(models.User, { + foreignKey: 'ownerId', + as: 'owner', + constraints: false + }) + Note.belongsTo(models.User, { + foreignKey: 'lastchangeuserId', + as: 'lastchangeuser', + constraints: false + }) + Note.hasMany(models.Revision, { + foreignKey: 'noteId', + constraints: false + }) + Note.hasMany(models.Author, { + foreignKey: 'noteId', + as: 'authors', + constraints: false + }) + }, + checkFileExist: function (filePath) { + try { + return fs.statSync(filePath).isFile() + } catch (err) { + return false } - }, { - paranoid: true, - classMethods: { - associate: function (models) { - Note.belongsTo(models.User, { - foreignKey: "ownerId", - as: "owner", - constraints: false - }); - Note.belongsTo(models.User, { - foreignKey: "lastchangeuserId", - as: "lastchangeuser", - constraints: false - }); - Note.hasMany(models.Revision, { - foreignKey: "noteId", - constraints: false - }); - Note.hasMany(models.Author, { - foreignKey: "noteId", - as: "authors", - 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 - } + }, + 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) { + let filePath = path.join(config.docspath, noteId + '.md') + if (Note.checkFileExist(filePath)) { + // if doc in filesystem have newer modified time than last change time + // then will update the doc in db + var fsModifiedTime = moment(fs.statSync(filePath).mtime) + var dbModifiedTime = moment(note.lastchangeAt || note.createdAt) + var body = fs.readFileSync(filePath, 'utf8') + var contentLength = body.length + var title = Note.parseNoteTitle(body) + if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) { + note.update({ + title: title, + content: body, + lastchangeAt: fsModifiedTime + }).then(function (note) { + sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { + if (err) return _callback(err, null) + // update authorship on after making revision of docs + var patch = dmp.patch_fromText(revision.patch) + var operations = Note.transformPatchToOperations(patch, contentLength) + var authorship = note.authorship + for (let i = 0; i < operations.length; i++) { + authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship) + } + note.update({ + authorship: JSON.stringify(authorship) }).then(function (note) { - if (note) { - var filePath = path.join(config.docspath, noteId + '.md'); - if (Note.checkFileExist(filePath)) { - // if doc in filesystem have newer modified time than last change time - // then will update the doc in db - var fsModifiedTime = moment(fs.statSync(filePath).mtime); - var dbModifiedTime = moment(note.lastchangeAt || note.createdAt); - var body = fs.readFileSync(filePath, 'utf8'); - var contentLength = body.length; - var title = Note.parseNoteTitle(body); - if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) { - note.update({ - title: title, - content: body, - lastchangeAt: fsModifiedTime - }).then(function (note) { - sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { - if (err) return _callback(err, null); - // update authorship on after making revision of docs - var patch = dmp.patch_fromText(revision.patch); - var operations = Note.transformPatchToOperations(patch, contentLength); - var authorship = note.authorship; - for (var i = 0; i < operations.length; i++) { - authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship); - } - note.update({ - authorship: JSON.stringify(authorship) - }).then(function (note) { - return callback(null, note.id); - }).catch(function (err) { - return _callback(err, null); - }); - }); - }).catch(function (err) { - return _callback(err, null); - }); - } else { - return callback(null, note.id); - } - } else { - 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); - } - } + return callback(null, note.id) }).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); - }); - }, - parseNoteInfo: function (body) { - var parsed = Note.extractMeta(body); - var $ = cheerio.load(md.render(parsed.markdown)); - return { - title: Note.extractNoteTitle(parsed.meta, $), - tags: Note.extractNoteTags(parsed.meta, $) - }; - }, - parseNoteTitle: function (body) { - var parsed = Note.extractMeta(body); - var $ = cheerio.load(md.render(parsed.markdown)); - return Note.extractNoteTitle(parsed.meta, $); - }, - extractNoteTitle: function (meta, $) { - var title = ""; - if (meta.title && (typeof meta.title == "string" || typeof meta.title == "number")) { - title = meta.title; + return _callback(err, null) + }) + }) + }).catch(function (err) { + return _callback(err, null) + }) + } else { + return callback(null, note.id) + } } else { - var h1s = $("h1"); - if (h1s.length > 0 && h1s.first().text().split('\n').length == 1) - title = S(h1s.first().text()).stripTags().s; + return callback(null, note.id) } - if (!title) title = "Untitled"; - return title; - }, - generateDescription: function (markdown) { - return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' '); - }, - decodeTitle: function (title) { - return title ? title : 'Untitled'; - }, - generateWebTitle: function (title) { - title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD"; - return title; - }, - extractNoteTags: function (meta, $) { - var tags = []; - var rawtags = []; - if (meta.tags && (typeof meta.tags == "string" || typeof meta.tags == "number")) { - var metaTags = ('' + meta.tags).split(','); - for (var i = 0; i < metaTags.length; i++) { - var text = metaTags[i].trim(); - if (text) rawtags.push(text); - } + } 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 { - var h6s = $("h6"); - h6s.each(function (key, value) { - if (/^tags/gmi.test($(value).text())) { - var codes = $(value).find("code"); - for (var i = 0; i < codes.length; i++) { - var text = S($(codes[i]).text().trim()).stripTags().s; - if (text) rawtags.push(text); - } - } - }); - } - for (var i = 0; i < rawtags.length; i++) { - var found = false; - for (var j = 0; j < tags.length; j++) { - if (tags[j] == rawtags[i]) { - found = true; - break; - } - } - if (!found) - tags.push(rawtags[i]); - } - return tags; - }, - extractMeta: function (content) { - try { - var obj = metaMarked(content); - if (!obj.markdown) obj.markdown = ""; - if (!obj.meta) obj.meta = {}; - } catch (err) { - var obj = { - markdown: content, - meta: {} - }; - } - return obj; - }, - parseMeta: function (meta) { - var _meta = {}; - if (meta) { - if (meta.title && (typeof meta.title == "string" || typeof meta.title == "number")) - _meta.title = meta.title; - if (meta.description && (typeof meta.description == "string" || typeof meta.description == "number")) - _meta.description = meta.description; - if (meta.robots && (typeof meta.robots == "string" || typeof meta.robots == "number")) - _meta.robots = meta.robots; - if (meta.GA && (typeof meta.GA == "string" || typeof meta.GA == "number")) - _meta.GA = meta.GA; - if (meta.disqus && (typeof meta.disqus == "string" || typeof meta.disqus == "number")) - _meta.disqus = meta.disqus; - if (meta.slideOptions && (typeof meta.slideOptions == "object")) - _meta.slideOptions = meta.slideOptions; + return _callback(null, null) } - return _meta; - }, - updateAuthorshipByOperation: function (operation, userId, authorships) { - var index = 0; - var timestamp = Date.now(); - for (var i = 0; i < operation.length; i++) { - var op = operation[i]; - if (ot.TextOperation.isRetain(op)) { - index += op; - } else if (ot.TextOperation.isInsert(op)) { - var opStart = index; - var opEnd = index + op.length; - var inserted = false; - // authorship format: [userId, startPos, endPos, createdAt, updatedAt] - if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]); - else { - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - if (!inserted) { - var nextAuthorship = authorships[j + 1] || -1; - if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) { - if (authorship[1] < opStart && authorship[2] > opStart) { - // divide - var postLength = authorship[2] - opStart; - authorship[2] = opStart; - authorship[4] = timestamp; - authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]); - authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]); - j += 2; - inserted = true; - } else if (authorship[1] >= opStart) { - authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]); - j += 1; - inserted = true; - } else if (authorship[2] <= opStart) { - authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]); - j += 1; - inserted = true; - } - } - } - if (authorship[1] >= opStart) { - authorship[1] += op.length; - authorship[2] += op.length; - } - } - } - index += op.length; - } else if (ot.TextOperation.isDelete(op)) { - var opStart = index; - var opEnd = index - op; - if (operation.length == 1) { - authorships = []; - } else if (authorships.length > 0) { - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) { - authorships.splice(j, 1); - j -= 1; - } else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) { - authorship[2] += op; - authorship[4] = timestamp; - } else if (authorship[2] >= opStart && authorship[2] <= opEnd) { - authorship[2] = opStart; - authorship[4] = timestamp; - } else if (authorship[1] >= opStart && authorship[1] <= opEnd) { - authorship[1] = opEnd; - authorship[4] = timestamp; - } - if (authorship[1] >= opEnd) { - authorship[1] += op; - authorship[2] += op; - } - } - } - index += op; - } - } - // merge - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - for (var k = j + 1; k < authorships.length; k++) { - var nextAuthorship = authorships[k]; - if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) { - var minTimestamp = Math.min(authorship[3], nextAuthorship[3]); - var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]); - authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]); - authorships.splice(k, 1); - j -= 1; - break; - } - } - } - // clear - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - if (!authorship[0]) { - authorships.splice(j, 1); - j -= 1; + } + }).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) + }) + }, + parseNoteInfo: function (body) { + var parsed = Note.extractMeta(body) + var $ = cheerio.load(md.render(parsed.markdown)) + return { + title: Note.extractNoteTitle(parsed.meta, $), + tags: Note.extractNoteTags(parsed.meta, $) + } + }, + parseNoteTitle: function (body) { + var parsed = Note.extractMeta(body) + var $ = cheerio.load(md.render(parsed.markdown)) + return Note.extractNoteTitle(parsed.meta, $) + }, + extractNoteTitle: function (meta, $) { + var title = '' + if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { + title = meta.title + } else { + var h1s = $('h1') + if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) { title = S(h1s.first().text()).stripTags().s } + } + if (!title) title = 'Untitled' + return title + }, + generateDescription: function (markdown) { + return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ') + }, + decodeTitle: function (title) { + return title || 'Untitled' + }, + generateWebTitle: function (title) { + title = !title || title === 'Untitled' ? 'HackMD - Collaborative markdown notes' : title + ' - HackMD' + return title + }, + extractNoteTags: function (meta, $) { + var tags = [] + var rawtags = [] + if (meta.tags && (typeof meta.tags === 'string' || typeof meta.tags === 'number')) { + var metaTags = ('' + meta.tags).split(',') + for (let i = 0; i < metaTags.length; i++) { + var text = metaTags[i].trim() + if (text) rawtags.push(text) + } + } else { + var h6s = $('h6') + h6s.each(function (key, value) { + if (/^tags/gmi.test($(value).text())) { + var codes = $(value).find('code') + for (let i = 0; i < codes.length; i++) { + var text = S($(codes[i]).text().trim()).stripTags().s + if (text) rawtags.push(text) + } + } + }) + } + for (let i = 0; i < rawtags.length; i++) { + var found = false + for (let j = 0; j < tags.length; j++) { + if (tags[j] === rawtags[i]) { + found = true + break + } + } + if (!found) { tags.push(rawtags[i]) } + } + return tags + }, + extractMeta: function (content) { + var obj = null + try { + obj = metaMarked(content) + if (!obj.markdown) obj.markdown = '' + if (!obj.meta) obj.meta = {} + } catch (err) { + obj = { + markdown: content, + meta: {} + } + } + return obj + }, + parseMeta: function (meta) { + var _meta = {} + if (meta) { + if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { _meta.title = meta.title } + if (meta.description && (typeof meta.description === 'string' || typeof meta.description === 'number')) { _meta.description = meta.description } + if (meta.robots && (typeof meta.robots === 'string' || typeof meta.robots === 'number')) { _meta.robots = meta.robots } + if (meta.GA && (typeof meta.GA === 'string' || typeof meta.GA === 'number')) { _meta.GA = meta.GA } + if (meta.disqus && (typeof meta.disqus === 'string' || typeof meta.disqus === 'number')) { _meta.disqus = meta.disqus } + if (meta.slideOptions && (typeof meta.slideOptions === 'object')) { _meta.slideOptions = meta.slideOptions } + } + return _meta + }, + updateAuthorshipByOperation: function (operation, userId, authorships) { + var index = 0 + var timestamp = Date.now() + for (let i = 0; i < operation.length; i++) { + var op = operation[i] + if (ot.TextOperation.isRetain(op)) { + index += op + } else if (ot.TextOperation.isInsert(op)) { + let opStart = index + let opEnd = index + op.length + var inserted = false + // authorship format: [userId, startPos, endPos, createdAt, updatedAt] + if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]) + else { + for (let j = 0; j < authorships.length; j++) { + let authorship = authorships[j] + if (!inserted) { + let nextAuthorship = authorships[j + 1] || -1 + if ((nextAuthorship !== -1 && nextAuthorship[1] >= opEnd) || j >= authorships.length - 1) { + if (authorship[1] < opStart && authorship[2] > opStart) { + // divide + let postLength = authorship[2] - opStart + authorship[2] = opStart + authorship[4] = timestamp + authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]) + authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]) + j += 2 + inserted = true + } else if (authorship[1] >= opStart) { + authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]) + j += 1 + inserted = true + } else if (authorship[2] <= opStart) { + authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]) + j += 1 + inserted = true } + } } - return authorships; - }, - transformPatchToOperations: function (patch, contentLength) { - var operations = []; - if (patch.length > 0) { - // calculate original content length - for (var j = patch.length - 1; j >= 0; j--) { - var p = patch[j]; - for (var i = 0; i < p.diffs.length; i++) { - var diff = p.diffs[i]; - switch(diff[0]) { - case 1: // insert - contentLength -= diff[1].length; - break; - case -1: // delete - contentLength += diff[1].length; - break; - } - } - } - // generate operations - var bias = 0; - var lengthBias = 0; - for (var j = 0; j < patch.length; j++) { - var operation = []; - var p = patch[j]; - var currIndex = p.start1; - var currLength = contentLength - bias; - for (var i = 0; i < p.diffs.length; i++) { - var diff = p.diffs[i]; - switch(diff[0]) { - case 0: // retain - if (i == 0) // first - operation.push(currIndex + diff[1].length); - else if (i != p.diffs.length - 1) // mid - operation.push(diff[1].length); - else // last - operation.push(currLength + lengthBias - currIndex); - currIndex += diff[1].length; - break; - case 1: // insert - operation.push(diff[1]); - lengthBias += diff[1].length; - currIndex += diff[1].length; - break; - case -1: // delete - operation.push(-diff[1].length); - bias += diff[1].length; - currIndex += diff[1].length; - break; - } - } - operations.push(operation); - } + if (authorship[1] >= opStart) { + authorship[1] += op.length + authorship[2] += op.length } - return operations; + } } - }, - 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)) { - var fsCreatedTime = moment(fs.statSync(filePath).ctime); - body = fs.readFileSync(filePath, 'utf8'); - note.title = Note.parseNoteTitle(body); - note.content = body; - if (filePath !== config.defaultnotepath) { - note.createdAt = fsCreatedTime; - } - } + index += op.length + } else if (ot.TextOperation.isDelete(op)) { + let opStart = index + let opEnd = index - op + if (operation.length === 1) { + authorships = [] + } else if (authorships.length > 0) { + for (let j = 0; j < authorships.length; j++) { + let authorship = authorships[j] + if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) { + authorships.splice(j, 1) + j -= 1 + } else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) { + authorship[2] += op + authorship[4] = timestamp + } else if (authorship[2] >= opStart && authorship[2] <= opEnd) { + authorship[2] = opStart + authorship[4] = timestamp + } else if (authorship[1] >= opStart && authorship[1] <= opEnd) { + authorship[1] = opEnd + authorship[4] = timestamp } - // if no permission specified and have owner then give default permission in config, else default permission is freely - if (!note.permission) { - if (note.ownerId) { - note.permission = config.defaultpermission; - } else { - note.permission = "freely"; - } + if (authorship[1] >= opEnd) { + authorship[1] += op + authorship[2] += op } - return callback(null, note); - }, - afterCreate: function (note, options, callback) { - sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { - callback(err, note); - }); + } + } + index += op + } + } + // merge + for (let j = 0; j < authorships.length; j++) { + let authorship = authorships[j] + for (let k = j + 1; k < authorships.length; k++) { + let nextAuthorship = authorships[k] + if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) { + let minTimestamp = Math.min(authorship[3], nextAuthorship[3]) + let maxTimestamp = Math.max(authorship[3], nextAuthorship[3]) + authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]) + authorships.splice(k, 1) + j -= 1 + break + } + } + } + // clear + for (let j = 0; j < authorships.length; j++) { + let authorship = authorships[j] + if (!authorship[0]) { + authorships.splice(j, 1) + j -= 1 + } + } + return authorships + }, + transformPatchToOperations: function (patch, contentLength) { + var operations = [] + if (patch.length > 0) { + // calculate original content length + for (let j = patch.length - 1; j >= 0; j--) { + var p = patch[j] + for (let i = 0; i < p.diffs.length; i++) { + var diff = p.diffs[i] + switch (diff[0]) { + case 1: // insert + contentLength -= diff[1].length + break + case -1: // delete + contentLength += diff[1].length + break + } + } + } + // generate operations + var bias = 0 + var lengthBias = 0 + for (let j = 0; j < patch.length; j++) { + var operation = [] + let p = patch[j] + var currIndex = p.start1 + var currLength = contentLength - bias + for (let i = 0; i < p.diffs.length; i++) { + let diff = p.diffs[i] + switch (diff[0]) { + case 0: // retain + if (i === 0) { + // first + operation.push(currIndex + diff[1].length) + } else if (i !== p.diffs.length - 1) { + // mid + operation.push(diff[1].length) + } else { + // last + operation.push(currLength + lengthBias - currIndex) + } + currIndex += diff[1].length + break + case 1: // insert + operation.push(diff[1]) + lengthBias += diff[1].length + currIndex += diff[1].length + break + case -1: // delete + operation.push(-diff[1].length) + bias += diff[1].length + currIndex += diff[1].length + break + } } + operations.push(operation) + } + } + return operations + } + }, + hooks: { + beforeCreate: function (note, options, callback) { + // if no content specified then use default note + if (!note.content) { + var body = null + let filePath = null + if (!note.alias) { + filePath = config.defaultnotepath + } else { + filePath = path.join(config.docspath, note.alias + '.md') + } + if (Note.checkFileExist(filePath)) { + var fsCreatedTime = moment(fs.statSync(filePath).ctime) + body = fs.readFileSync(filePath, 'utf8') + note.title = Note.parseNoteTitle(body) + note.content = body + if (filePath !== config.defaultnotepath) { + note.createdAt = fsCreatedTime + } + } + } + // if no permission specified and have owner then give default permission in config, else default permission is freely + if (!note.permission) { + if (note.ownerId) { + note.permission = config.defaultpermission + } else { + note.permission = 'freely' + } } - }); + return callback(null, note) + }, + afterCreate: function (note, options, callback) { + sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { + callback(err, note) + }) + } + } + }) - return Note; -}; + return Note +} diff --git a/lib/models/revision.js b/lib/models/revision.js index c7360fed..d8dab30a 100644 --- a/lib/models/revision.js +++ b/lib/models/revision.js @@ -1,306 +1,306 @@ -"use strict"; - // external modules -var Sequelize = require("sequelize"); -var async = require('async'); -var moment = require('moment'); -var childProcess = require('child_process'); -var shortId = require('shortid'); +var Sequelize = require('sequelize') +var async = require('async') +var moment = require('moment') +var childProcess = require('child_process') +var shortId = require('shortid') // core -var config = require("../config.js"); -var logger = require("../logger.js"); +var config = require('../config.js') +var logger = require('../logger.js') -var dmpWorker = createDmpWorker(); -var dmpCallbackCache = {}; +var dmpWorker = createDmpWorker() +var dmpCallbackCache = {} -function createDmpWorker() { - var worker = childProcess.fork("./lib/workers/dmpWorker.js", { - stdio: 'ignore' - }); - if (config.debug) logger.info('dmp worker process started'); - worker.on('message', function (data) { - if (!data || !data.msg || !data.cacheKey) { - return logger.error('dmp worker error: not enough data on message'); - } - var cacheKey = data.cacheKey; - switch(data.msg) { - case 'error': - dmpCallbackCache[cacheKey](data.error, null); - break; - case 'check': - dmpCallbackCache[cacheKey](null, data.result); - break; - } - delete dmpCallbackCache[cacheKey]; - }); - worker.on('close', function (code) { - dmpWorker = null; - if (config.debug) logger.info('dmp worker process exited with code ' + code); - }); - return worker; +function createDmpWorker () { + var worker = childProcess.fork('./lib/workers/dmpWorker.js', { + stdio: 'ignore' + }) + if (config.debug) logger.info('dmp worker process started') + worker.on('message', function (data) { + if (!data || !data.msg || !data.cacheKey) { + return logger.error('dmp worker error: not enough data on message') + } + var cacheKey = data.cacheKey + switch (data.msg) { + case 'error': + dmpCallbackCache[cacheKey](data.error, null) + break + case 'check': + dmpCallbackCache[cacheKey](null, data.result) + break + } + delete dmpCallbackCache[cacheKey] + }) + worker.on('close', function (code) { + dmpWorker = null + if (config.debug) logger.info('dmp worker process exited with code ' + code) + }) + return worker } -function sendDmpWorker(data, callback) { - if (!dmpWorker) dmpWorker = createDmpWorker(); - var cacheKey = Date.now() + '_' + shortId.generate(); - dmpCallbackCache[cacheKey] = callback; - data = Object.assign(data, { - cacheKey: cacheKey - }); - dmpWorker.send(data); +function sendDmpWorker (data, callback) { + if (!dmpWorker) dmpWorker = createDmpWorker() + var cacheKey = Date.now() + '_' + shortId.generate() + dmpCallbackCache[cacheKey] = callback + data = Object.assign(data, { + cacheKey: cacheKey + }) + dmpWorker.send(data) } module.exports = function (sequelize, DataTypes) { - var Revision = sequelize.define("Revision", { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: Sequelize.UUIDV4 - }, - patch: { - type: DataTypes.TEXT, - get: function () { - return sequelize.processData(this.getDataValue('patch'), ""); - }, - set: function (value) { - this.setDataValue('patch', sequelize.stripNullByte(value)); - } - }, - lastContent: { - type: DataTypes.TEXT, - get: function () { - return sequelize.processData(this.getDataValue('lastContent'), ""); + var Revision = sequelize.define('Revision', { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: Sequelize.UUIDV4 + }, + patch: { + type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('patch'), '') + }, + set: function (value) { + this.setDataValue('patch', sequelize.stripNullByte(value)) + } + }, + lastContent: { + type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('lastContent'), '') + }, + set: function (value) { + this.setDataValue('lastContent', sequelize.stripNullByte(value)) + } + }, + content: { + type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('content'), '') + }, + set: function (value) { + this.setDataValue('content', sequelize.stripNullByte(value)) + } + }, + length: { + type: DataTypes.INTEGER + }, + authorship: { + type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse) + }, + set: function (value) { + this.setDataValue('authorship', value ? JSON.stringify(value) : value) + } + } + }, { + classMethods: { + associate: function (models) { + Revision.belongsTo(models.Note, { + foreignKey: 'noteId', + as: 'note', + constraints: false + }) + }, + getNoteRevisions: function (note, callback) { + Revision.findAll({ + where: { + noteId: note.id + }, + order: '"createdAt" DESC' + }).then(function (revisions) { + var data = [] + for (var i = 0, l = revisions.length; i < l; i++) { + var revision = revisions[i] + data.push({ + time: moment(revision.createdAt).valueOf(), + length: revision.length + }) + } + callback(null, data) + }).catch(function (err) { + callback(err, null) + }) + }, + getPatchedNoteRevisionByTime: function (note, time, callback) { + // find all revisions to prepare for all possible calculation + Revision.findAll({ + where: { + noteId: note.id + }, + order: '"createdAt" DESC' + }).then(function (revisions) { + if (revisions.length <= 0) return callback(null, null) + // measure target revision position + Revision.count({ + where: { + noteId: note.id, + createdAt: { + $gte: time + } }, - set: function (value) { - this.setDataValue('lastContent', sequelize.stripNullByte(value)); + order: '"createdAt" DESC' + }).then(function (count) { + if (count <= 0) return callback(null, null) + sendDmpWorker({ + msg: 'get revision', + revisions: revisions, + count: count + }, callback) + }).catch(function (err) { + return callback(err, null) + }) + }).catch(function (err) { + return callback(err, null) + }) + }, + checkAllNotesRevision: function (callback) { + Revision.saveAllNotesRevision(function (err, notes) { + if (err) return callback(err, null) + if (!notes || notes.length <= 0) { + return callback(null, notes) + } else { + Revision.checkAllNotesRevision(callback) + } + }) + }, + saveAllNotesRevision: function (callback) { + sequelize.models.Note.findAll({ + // query all notes that need to save for revision + where: { + $and: [ + { + lastchangeAt: { + $or: { + $eq: null, + $and: { + $ne: null, + $gt: sequelize.col('createdAt') + } + } + } + }, + { + savedAt: { + $or: { + $eq: null, + $lt: sequelize.col('lastchangeAt') + } + } + } + ] + } + }).then(function (notes) { + if (notes.length <= 0) return callback(null, notes) + var savedNotes = [] + async.each(notes, function (note, _callback) { + // revision saving policy: note not been modified for 5 mins or not save for 10 mins + if (note.lastchangeAt && note.savedAt) { + var lastchangeAt = moment(note.lastchangeAt) + var savedAt = moment(note.savedAt) + if (moment().isAfter(lastchangeAt.add(5, 'minutes'))) { + savedNotes.push(note) + Revision.saveNoteRevision(note, _callback) + } else if (lastchangeAt.isAfter(savedAt.add(10, 'minutes'))) { + savedNotes.push(note) + Revision.saveNoteRevision(note, _callback) + } else { + return _callback(null, null) + } + } else { + savedNotes.push(note) + Revision.saveNoteRevision(note, _callback) } - }, - content: { - type: DataTypes.TEXT, - get: function () { - return sequelize.processData(this.getDataValue('content'), ""); - }, - set: function (value) { - this.setDataValue('content', sequelize.stripNullByte(value)); + }, function (err) { + if (err) { + return callback(err, null) } - }, - length: { - type: DataTypes.INTEGER - }, - authorship: { - type: DataTypes.TEXT, - get: function () { - return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse); - }, - set: function (value) { - this.setDataValue('authorship', value ? JSON.stringify(value) : value); - } - } - }, { - classMethods: { - associate: function (models) { - Revision.belongsTo(models.Note, { - foreignKey: "noteId", - as: "note", - constraints: false - }); - }, - getNoteRevisions: function (note, callback) { - Revision.findAll({ - where: { - noteId: note.id - }, - order: '"createdAt" DESC' - }).then(function (revisions) { - var data = []; - for (var i = 0, l = revisions.length; i < l; i++) { - var revision = revisions[i]; - data.push({ - time: moment(revision.createdAt).valueOf(), - length: revision.length - }); - } - callback(null, data); - }).catch(function (err) { - callback(err, null); - }); - }, - getPatchedNoteRevisionByTime: function (note, time, callback) { - // find all revisions to prepare for all possible calculation - Revision.findAll({ - where: { - noteId: note.id - }, - order: '"createdAt" DESC' - }).then(function (revisions) { - if (revisions.length <= 0) return callback(null, null); - // measure target revision position - Revision.count({ - where: { - noteId: note.id, - createdAt: { - $gte: time - } - }, - order: '"createdAt" DESC' - }).then(function (count) { - if (count <= 0) return callback(null, null); - sendDmpWorker({ - msg: 'get revision', - revisions: revisions, - count: count - }, callback); - }).catch(function (err) { - return callback(err, null); - }); + // return null when no notes need saving at this moment but have delayed tasks to be done + var result = ((savedNotes.length === 0) && (notes.length > savedNotes.length)) ? null : savedNotes + return callback(null, result) + }) + }).catch(function (err) { + return callback(err, null) + }) + }, + saveNoteRevision: function (note, callback) { + Revision.findAll({ + where: { + noteId: note.id + }, + order: '"createdAt" DESC' + }).then(function (revisions) { + if (revisions.length <= 0) { + // if no revision available + Revision.create({ + noteId: note.id, + lastContent: note.content, + length: note.content.length, + authorship: note.authorship + }).then(function (revision) { + Revision.finishSaveNoteRevision(note, revision, callback) + }).catch(function (err) { + return callback(err, null) + }) + } else { + var latestRevision = revisions[0] + var lastContent = latestRevision.content || latestRevision.lastContent + var content = note.content + sendDmpWorker({ + msg: 'create patch', + lastDoc: lastContent, + currDoc: content + }, function (err, patch) { + if (err) logger.error('save note revision error', err) + if (!patch) { + // if patch is empty (means no difference) then just update the latest revision updated time + latestRevision.changed('updatedAt', true) + latestRevision.update({ + updatedAt: Date.now() + }).then(function (revision) { + Revision.finishSaveNoteRevision(note, revision, callback) }).catch(function (err) { - return callback(err, null); - }); - }, - checkAllNotesRevision: function (callback) { - Revision.saveAllNotesRevision(function (err, notes) { - if (err) return callback(err, null); - if (!notes || notes.length <= 0) { - return callback(null, notes); - } else { - Revision.checkAllNotesRevision(callback); - } - }); - }, - saveAllNotesRevision: function (callback) { - sequelize.models.Note.findAll({ - // query all notes that need to save for revision - where: { - $and: [ - { - lastchangeAt: { - $or: { - $eq: null, - $and: { - $ne: null, - $gt: sequelize.col('createdAt') - } - } - } - }, - { - savedAt: { - $or: { - $eq: null, - $lt: sequelize.col('lastchangeAt') - } - } - } - ] - } - }).then(function (notes) { - if (notes.length <= 0) return callback(null, notes); - var savedNotes = []; - async.each(notes, function (note, _callback) { - // revision saving policy: note not been modified for 5 mins or not save for 10 mins - if (note.lastchangeAt && note.savedAt) { - var lastchangeAt = moment(note.lastchangeAt); - var savedAt = moment(note.savedAt); - if (moment().isAfter(lastchangeAt.add(5, 'minutes'))) { - savedNotes.push(note); - Revision.saveNoteRevision(note, _callback); - } else if (lastchangeAt.isAfter(savedAt.add(10, 'minutes'))) { - savedNotes.push(note); - Revision.saveNoteRevision(note, _callback); - } else { - return _callback(null, null); - } - } else { - savedNotes.push(note); - Revision.saveNoteRevision(note, _callback); - } - }, function (err) { - if (err) return callback(err, null); - // return null when no notes need saving at this moment but have delayed tasks to be done - var result = ((savedNotes.length == 0) && (notes.length > savedNotes.length)) ? null : savedNotes; - return callback(null, result); - }); + return callback(err, null) + }) + } else { + Revision.create({ + noteId: note.id, + patch: patch, + content: note.content, + length: note.content.length, + authorship: note.authorship + }).then(function (revision) { + // clear last revision content to reduce db size + latestRevision.update({ + content: null + }).then(function () { + Revision.finishSaveNoteRevision(note, revision, callback) + }).catch(function (err) { + return callback(err, null) + }) }).catch(function (err) { - return callback(err, null); - }); - }, - saveNoteRevision: function (note, callback) { - Revision.findAll({ - where: { - noteId: note.id - }, - order: '"createdAt" DESC' - }).then(function (revisions) { - if (revisions.length <= 0) { - // if no revision available - Revision.create({ - noteId: note.id, - lastContent: note.content, - length: note.content.length, - authorship: note.authorship - }).then(function (revision) { - Revision.finishSaveNoteRevision(note, revision, callback); - }).catch(function (err) { - return callback(err, null); - }); - } else { - var latestRevision = revisions[0]; - var lastContent = latestRevision.content || latestRevision.lastContent; - var content = note.content; - sendDmpWorker({ - msg: 'create patch', - lastDoc: lastContent, - currDoc: content, - }, function (err, patch) { - if (err) logger.error('save note revision error', err); - if (!patch) { - // if patch is empty (means no difference) then just update the latest revision updated time - latestRevision.changed('updatedAt', true); - latestRevision.update({ - updatedAt: Date.now() - }).then(function (revision) { - Revision.finishSaveNoteRevision(note, revision, callback); - }).catch(function (err) { - return callback(err, null); - }); - } else { - Revision.create({ - noteId: note.id, - patch: patch, - content: note.content, - length: note.content.length, - authorship: note.authorship - }).then(function (revision) { - // clear last revision content to reduce db size - latestRevision.update({ - content: null - }).then(function () { - Revision.finishSaveNoteRevision(note, revision, callback); - }).catch(function (err) { - return callback(err, null); - }); - }).catch(function (err) { - return callback(err, null); - }); - } - }); - } - }).catch(function (err) { - return callback(err, null); - }); - }, - finishSaveNoteRevision: function (note, revision, callback) { - note.update({ - savedAt: revision.updatedAt - }).then(function () { - return callback(null, revision); - }).catch(function (err) { - return callback(err, null); - }); - } - } - }); + return callback(err, null) + }) + } + }) + } + }).catch(function (err) { + return callback(err, null) + }) + }, + finishSaveNoteRevision: function (note, revision, callback) { + note.update({ + savedAt: revision.updatedAt + }).then(function () { + return callback(null, revision) + }).catch(function (err) { + return callback(err, null) + }) + } + } + }) - return Revision; -}; \ No newline at end of file + return Revision +} diff --git a/lib/models/temp.js b/lib/models/temp.js index 6eeff153..e770bb3a 100644 --- a/lib/models/temp.js +++ b/lib/models/temp.js @@ -1,19 +1,17 @@ -"use strict"; - -//external modules -var shortId = require('shortid'); +// 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 + var Temp = sequelize.define('Temp', { + id: { + type: DataTypes.STRING, + primaryKey: true, + defaultValue: shortId.generate + }, + data: { + type: DataTypes.TEXT + } + }) + + return Temp +} diff --git a/lib/models/user.js b/lib/models/user.js index dd93bf78..f7e533b7 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -1,149 +1,147 @@ -"use strict"; - // external modules -var md5 = require("blueimp-md5"); -var Sequelize = require("sequelize"); -var scrypt = require('scrypt'); +var md5 = require('blueimp-md5') +var Sequelize = require('sequelize') +var scrypt = require('scrypt') // core -var logger = require("../logger.js"); -var letterAvatars = require('../letter-avatars.js'); +var logger = require('../logger.js') +var letterAvatars = require('../letter-avatars.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 - }, - accessToken: { - type: DataTypes.STRING - }, - refreshToken: { - type: DataTypes.STRING - }, - email: { - type: Sequelize.TEXT, - validate: { - isEmail: true - } - }, - password: { - type: Sequelize.TEXT, - set: function(value) { - var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString("hex"); - this.setDataValue('password', hash); - } + 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 + }, + accessToken: { + type: DataTypes.STRING + }, + refreshToken: { + type: DataTypes.STRING + }, + email: { + type: Sequelize.TEXT, + validate: { + isEmail: true + } + }, + password: { + type: Sequelize.TEXT, + set: function (value) { + var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString('hex') + this.setDataValue('password', hash) + } + } + }, { + instanceMethods: { + verifyPassword: function (attempt) { + if (scrypt.verifyKdfSync(new Buffer(this.password, 'hex'), attempt)) { + return this + } else { + return false } - }, { - instanceMethods: { - verifyPassword: function(attempt) { - if (scrypt.verifyKdfSync(new Buffer(this.password, "hex"), attempt)) { - return this; - } else { - return false; - } - } - }, - classMethods: { - associate: function (models) { - User.hasMany(models.Note, { - foreignKey: "ownerId", - constraints: false - }); - User.hasMany(models.Note, { - foreignKey: "lastchangeuserId", - constraints: false - }); - }, - getProfile: function (user) { - return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null); - }, - 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), - biggerphoto: User.parsePhotoByProfile(profile, true) - } - } - return profile; - }, - parsePhotoByProfile: function (profile, bigger) { - var photo = null; - switch (profile.provider) { - case "facebook": - photo = 'https://graph.facebook.com/' + profile.id + '/picture'; - if (bigger) photo += '?width=400'; - else photo += '?width=96'; - break; - case "twitter": - photo = 'https://twitter.com/' + profile.username + '/profile_image'; - if (bigger) photo += '?size=original'; - else photo += '?size=bigger'; - break; - case "github": - photo = 'https://avatars.githubusercontent.com/u/' + profile.id; - if (bigger) photo += '?s=400'; - else photo += '?s=96'; - break; - case "gitlab": - photo = profile.avatarUrl; - if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400'); - else photo = photo.replace(/(\?s=)\d*$/i, '$196'); - break; - case "dropbox": - //no image api provided, use gravatar - photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value); - if (bigger) photo += '?s=400'; - else photo += '?s=96'; - break; - case "google": - photo = profile.photos[0].value; - if (bigger) photo = photo.replace(/(\?sz=)\d*$/i, '$1400'); - else photo = photo.replace(/(\?sz=)\d*$/i, '$196'); - break; - case "ldap": - //no image api provided, - //use gravatar if email exists, - //otherwise generate a letter avatar - if (profile.emails[0]) { - photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0]); - if (bigger) photo += '?s=400'; - else photo += '?s=96'; - } else { - photo = letterAvatars(profile.username); - } - break; - } - return photo; - }, - parseProfileByEmail: function (email) { - var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email); - return { - name: email.substring(0, email.lastIndexOf("@")), - photo: photoUrl += '?s=96', - biggerphoto: photoUrl += '?s=400' - }; + } + }, + classMethods: { + associate: function (models) { + User.hasMany(models.Note, { + foreignKey: 'ownerId', + constraints: false + }) + User.hasMany(models.Note, { + foreignKey: 'lastchangeuserId', + constraints: false + }) + }, + getProfile: function (user) { + return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null) + }, + parseProfile: function (profile) { + try { + profile = JSON.parse(profile) + } catch (err) { + logger.error(err) + profile = null + } + if (profile) { + profile = { + name: profile.displayName || profile.username, + photo: User.parsePhotoByProfile(profile), + biggerphoto: User.parsePhotoByProfile(profile, true) + } + } + return profile + }, + parsePhotoByProfile: function (profile, bigger) { + var photo = null + switch (profile.provider) { + case 'facebook': + photo = 'https://graph.facebook.com/' + profile.id + '/picture' + if (bigger) photo += '?width=400' + else photo += '?width=96' + break + case 'twitter': + photo = 'https://twitter.com/' + profile.username + '/profile_image' + if (bigger) photo += '?size=original' + else photo += '?size=bigger' + break + case 'github': + photo = 'https://avatars.githubusercontent.com/u/' + profile.id + if (bigger) photo += '?s=400' + else photo += '?s=96' + break + case 'gitlab': + photo = profile.avatarUrl + if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400') + else photo = photo.replace(/(\?s=)\d*$/i, '$196') + break + case 'dropbox': + // no image api provided, use gravatar + photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value) + if (bigger) photo += '?s=400' + else photo += '?s=96' + break + case 'google': + photo = profile.photos[0].value + if (bigger) photo = photo.replace(/(\?sz=)\d*$/i, '$1400') + else photo = photo.replace(/(\?sz=)\d*$/i, '$196') + break + case 'ldap': + // no image api provided, + // use gravatar if email exists, + // otherwise generate a letter avatar + if (profile.emails[0]) { + photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0]) + if (bigger) photo += '?s=400' + else photo += '?s=96' + } else { + photo = letterAvatars(profile.username) } + break + } + return photo + }, + parseProfileByEmail: function (email) { + var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email) + return { + name: email.substring(0, email.lastIndexOf('@')), + photo: photoUrl + '?s=96', + biggerphoto: photoUrl + '?s=400' } - }); + } + } + }) - return User; -}; \ No newline at end of file + return User +} -- cgit v1.2.3