From 8ccf7d5a71d1d1aa6ad1c3367b01dceda35d0453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Mon, 21 Mar 2016 11:16:35 +0100 Subject: [PATCH] Note deletion feature added --- package.json | 14 +- resources/edit.html | 3 +- resources/public/index.html | 1 + resources/public/js/publishing.js | 15 +- server.js | 232 +++++++++++++++--------------- src/storage.js | 73 +++++----- 6 files changed, 177 insertions(+), 161 deletions(-) diff --git a/package.json b/package.json index c5109e5..d3b6d1b 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,15 @@ }, "homepage": "https://github.com/chmllr/NoteHub", "dependencies": { - "body-parser": "^1.14.1", - "express": "^4.13.3", - "lru-cache": "^2.6.5", + "body-parser": "^1.15.0", + "express": "^4.13.4", + "lru-cache": "^4.0.0", "marked": "^0.3.5", - "md5": "^2.0.0", - "sequelize": "^3.8.0", - "sqlite3": "^3.1.0" + "md5": "^2.1.0", + "sequelize": "^3.19.3", + "sqlite3": "*" }, "devDependencies": { - "nodemon": "^1.7.3" + "nodemon": "^1.9.1" } } diff --git a/resources/edit.html b/resources/edit.html index ea1d91e..59ad251 100644 --- a/resources/edit.html +++ b/resources/edit.html @@ -22,7 +22,8 @@
  - + + 0 words diff --git a/resources/public/index.html b/resources/public/index.html index 34fceb0..eb34795 100644 --- a/resources/public/index.html +++ b/resources/public/index.html @@ -18,6 +18,7 @@

Changelog

    +
  • 2016-03: Note deletion feature added.
  • 2015-10: NoteHub rewritten in Node.js.
  • 2015-10: NoteHub API and note styling discontinued due to low adoption by the user base.
  • 2014-09: text size setting added
  • diff --git a/resources/public/js/publishing.js b/resources/public/js/publishing.js index ac4cf32..df0bddb 100644 --- a/resources/public/js/publishing.js +++ b/resources/public/js/publishing.js @@ -4,8 +4,7 @@ var $ = function(id) { var iosDetected = navigator.userAgent.match("(iPad|iPod|iPhone)"); var timer = null; var timerDelay = iosDetected ? 800 : 400; -var $note, $action, $preview, $plain_password, - updatePreview, $tableau; +var $note, $action, $preview, $plain_password, $tableau; var backendTimer; function md2html(input) { @@ -25,7 +24,7 @@ function onLoad() { $preview = $("draft"); $tableau = $("tableau"); $plain_password = $("plain-password"); - updatePreview = function() { + var updatePreview = function() { clearTimeout(timer); var content = $note.value; var delay = Math.min(timerDelay, timerDelay * (content.length / 400)); @@ -36,6 +35,7 @@ function onLoad() { }; if ($action == "UPDATE") updatePreview(); else { + $("delete-button").style.display = "none"; $note.value = ""; var draft = localStorage.getItem("draft"); if (draft) { @@ -44,14 +44,13 @@ function onLoad() { } } $note.onkeyup = updatePreview; - $("publish-button").onclick = function(e) { + $("delete-button").onclick = $("publish-button").onclick = function(e) { localStorage.removeItem("draft"); - self.onbeforeunload = null;; + self.onbeforeunload = null; if ($plain_password.value != "") $("password").value = md5($plain_password.value); $plain_password.value = null; - $("signature").value = md5($("session").value + - $note.value.replace(/[\n\r]/g, "")); - } + $("signature").value = md5($("session").value + $note.value.replace(/[\n\r]/g, "")); + }; if (iosDetected) $note.className += " ui-border"; else $note.focus(); self.onbeforeunload = saveDraft; diff --git a/server.js b/server.js index 7947f3a..2030997 100644 --- a/server.js +++ b/server.js @@ -11,145 +11,153 @@ app.use(bodyParser.urlencoded({ extended: true })); var MODELS = {}; var CACHE = new LRU({ - max: 50, - dispose: key => { - log("disposing", key, "from cache"); - var model = MODELS[key]; - model && model.save(); - delete MODELS[key]; - } + max: 50, + dispose: key => { + log("disposing", key, "from cache"); + var model = MODELS[key]; + model && model.save(); + delete MODELS[key]; + } }); var getTimeStamp = () => { - var timestamp = new Date().getTime(); - timestamp = Math.floor(timestamp / 10000000); - return (timestamp).toString(16) + var timestamp = new Date().getTime(); + timestamp = Math.floor(timestamp / 10000000); + return (timestamp).toString(16) } app.use(express.static(__dirname + '/resources/public')); -var log = function () { - var date = new Date(); - var timestamp = date.getDate() + "/" + date.getMonth() + " " + date.getHours() + ":" + - date.getMinutes() + ":" + date.getSeconds() + "." + date.getMilliseconds(); - var message = Array.prototype.slice.call(arguments); - message.unshift("--"); - message.unshift(timestamp); - console.log.apply(console, message); -} - -app.get('/new', function (req, res) { - log(req.ip, "opens /new"); - res.send(view.newNotePage(getTimeStamp() + md5(Math.random()))); -}); +var log = function() { + var date = new Date(); + var timestamp = date.getDate() + "/" + date.getMonth() + " " + date.getHours() + ":" + + date.getMinutes() + ":" + date.getSeconds() + "." + date.getMilliseconds(); + var message = Array.prototype.slice.call(arguments); + message.unshift("--"); + message.unshift(timestamp); + console.log.apply(console, message); +} -app.post('/note', function (req, res) { - var body = req.body, - session = body.session, - note = body.note, - password = body.password, - action = body.action, - id = body.id; - log(req.ip, "calls /note to", action, id); - var goToNote = note => res.redirect("/" + note.id); - if (session.indexOf(getTimeStamp()) != 0) - return sendResponse(res, 400, "Session expired"); - var expectedSignature = md5(session + note.replace(/[\n\r]/g, "")); - if (expectedSignature != body.signature) - return sendResponse(res, 400, "Signature mismatch"); - if (action == "POST") - storage.addNote(note, password).then(goToNote); - else { - CACHE.del(id); - storage.updateNote(id, password, note).then(goToNote, - error => sendResponse(res, 403, error.message)); - } +app.get('/new', function(req, res) { + log(req.ip, "opens /new"); + res.send(view.newNotePage(getTimeStamp() + md5(Math.random()))); }); -app.get("/:year/:month/:day/:title", function (req, res) { - var P = req.params, url = P.year + "/" + P.month + "/" + P.day + "/" + P.title; - log(req.ip, "resolves deprecated id", url); - if (CACHE.has(url)) { - log(url, "is cached!"); - var id = CACHE.get(url); - if (id) res.redirect("/" + id); - else notFound(res); - } else storage.getNoteId(url).then(note => { - log(url, "is not cached, resolving..."); - if (note) { - CACHE.set(url, note.id); - res.redirect("/" + note.id) - } else { - CACHE.set(url, null); - notFound(res); +app.post('/note', function(req, res) { + var body = req.body, + session = body.session, + note = body.note, + password = body.password, + action = body.action, + id = body.id; + log(req.ip, "calls /note to", action, id); + var goToNote = note => res.redirect("/" + note.id); + if (session.indexOf(getTimeStamp()) != 0) + return sendResponse(res, 400, "Session expired"); + var expectedSignature = md5(session + note.replace(/[\n\r]/g, "")); + if (expectedSignature != body.signature) + return sendResponse(res, 400, "Signature mismatch"); + if (action == "POST") + storage.addNote(note, password).then(goToNote); + else { + CACHE.del(id); + if (body.button == "Delete") { + log("deleting note", id); + storage.deleteNote(id, password).then( + () => sendResponse(res, 200, "Note deleted"), + error => sendResponse(res, 403, error.message)); + } else { + log("updating note", id); + storage.updateNote(id, password, note).then(goToNote, + error => sendResponse(res, 403, error.message)); + } } - }); }); -app.get(/\/([a-z0-9]+\/edit)/, function (req, res) { - var link = req.params["0"].replace("/edit", ""); - log(req.ip, "calls /edit on", link); - storage.getNote(link).then(note => res.send(note - ? view.editNotePage(getTimeStamp() + md5(Math.random()), note) - : notFound(res))); +app.get("/:year/:month/:day/:title", function(req, res) { + var P = req.params, url = P.year + "/" + P.month + "/" + P.day + "/" + P.title; + log(req.ip, "resolves deprecated id", url); + if (CACHE.has(url)) { + log(url, "is cached!"); + var id = CACHE.get(url); + if (id) res.redirect("/" + id); + else notFound(res); + } else storage.getNoteId(url).then(note => { + log(url, "is not cached, resolving..."); + if (note) { + CACHE.set(url, note.id); + res.redirect("/" + note.id) + } else { + CACHE.set(url, null); + notFound(res); + } + }); }); -app.get(/\/([a-z0-9]+\/export)/, function (req, res) { - var link = req.params["0"].replace("/export", ""); - log(req.ip, "calls /export on", link); - res.set({ 'Content-Type': 'text/plain', 'Charset': 'utf-8' }); - storage.getNote(link).then(note => note - ? res.send(note.text) - : notFound(res)); +app.get(/\/([a-z0-9]+\/edit)/, function(req, res) { + var link = req.params["0"].replace("/edit", ""); + log(req.ip, "calls /edit on", link); + storage.getNote(link).then(note => res.send(note + ? view.editNotePage(getTimeStamp() + md5(Math.random()), note) + : notFound(res))); }); -app.get(/\/([a-z0-9]+\/stats)/, function (req, res) { - var link = req.params["0"].replace("/stats", ""); - log(req.ip, "calls /stats on", link); - var promise = link in MODELS - ? new Promise(resolve => resolve(MODELS[link])) - : storage.getNote(link); - promise.then(note => note - ? res.send(view.renderStats(note)) - : notFound(res)); +app.get(/\/([a-z0-9]+\/export)/, function(req, res) { + var link = req.params["0"].replace("/export", ""); + log(req.ip, "calls /export on", link); + res.set({ 'Content-Type': 'text/plain', 'Charset': 'utf-8' }); + storage.getNote(link).then(note => note + ? res.send(note.text) + : notFound(res)); }); -app.get(/\/([a-z0-9]+)/, function (req, res) { - var link = req.params["0"]; - log(req.ip, "open note", link, "from", req.get("Referer")); - if (CACHE.has(link)) { - log(link, "is cached!"); - var note = MODELS[link]; - if (!note) return notFound(res); - note.views++; - res.send(CACHE.get(link)); - } else storage.getNote(link).then(note => { - log(link, "is not cached, resolving..."); - if (!note) { - CACHE.set(link, null); - return notFound(res); - } - var content = view.renderNote(note); - CACHE.set(link, content); - MODELS[link] = note; - note.views++; - res.send(content); - }); +app.get(/\/([a-z0-9]+\/stats)/, function(req, res) { + var link = req.params["0"].replace("/stats", ""); + log(req.ip, "calls /stats on", link); + var promise = link in MODELS + ? new Promise(resolve => resolve(MODELS[link])) + : storage.getNote(link); + promise.then(note => note + ? res.send(view.renderStats(note)) + : notFound(res)); +}); + +app.get(/\/([a-z0-9]+)/, function(req, res) { + var link = req.params["0"]; + log(req.ip, "open note", link, "from", req.get("Referer")); + if (CACHE.has(link)) { + log(link, "is cached!"); + var note = MODELS[link]; + if (!note) return notFound(res); + note.views++; + res.send(CACHE.get(link)); + } else storage.getNote(link).then(note => { + log(link, "is not cached, resolving..."); + if (!note) { + CACHE.set(link, null); + return notFound(res); + } + var content = view.renderNote(note); + CACHE.set(link, content); + MODELS[link] = note; + note.views++; + res.send(content); + }); }); var sendResponse = (res, code, message) => { - log("sending response", code, message); - res.status(code).send(view.renderPage(message, "

    " + message + "

    ", "")); + log("sending response", code, message); + res.status(code).send(view.renderPage(message, "

    " + message + "

    ", "")); } var notFound = res => sendResponse(res, 404, "Not found"); -var server = app.listen(process.env.PORT || 3000, function () { - log('NoteHub server listening on port %s', server.address().port); +var server = app.listen(process.env.PORT || 3000, function() { + log('NoteHub server listening on port', server.address().port); }); setInterval(() => { - var keys = Object.keys(MODELS); - log("saving stats for", keys.length, "models..."); - keys.forEach(id => MODELS[id].save()) + var keys = Object.keys(MODELS); + log("saving stats for", keys.length, "models..."); + keys.forEach(id => MODELS[id].save()) }, 60 * 5 * 1000); \ No newline at end of file diff --git a/src/storage.js b/src/storage.js index aedeca3..1b73eac 100644 --- a/src/storage.js +++ b/src/storage.js @@ -1,22 +1,22 @@ var Sequelize = require('sequelize'); var sequelize = new Sequelize('database', null, null, { - dialect: 'sqlite', - pool: { - max: 5, - min: 0, - idle: 10000 - }, - storage: 'database.sqlite' + dialect: 'sqlite', + pool: { + max: 5, + min: 0, + idle: 10000 + }, + storage: 'database.sqlite' }); var Note = sequelize.define('Note', { - id: { type: Sequelize.STRING(6), unique: true, primaryKey: true }, - deprecatedId: Sequelize.TEXT, - text: Sequelize.TEXT, - published: { type: Sequelize.DATE, defaultValue: Sequelize.NOW }, - edited: { type: Sequelize.DATE, allowNull: true, defaultValue: null }, - password: Sequelize.STRING(16), - views: { type: Sequelize.INTEGER, defaultValue: 0 } + id: { type: Sequelize.STRING(6), unique: true, primaryKey: true }, + deprecatedId: Sequelize.TEXT, + text: Sequelize.TEXT, + published: { type: Sequelize.DATE, defaultValue: Sequelize.NOW }, + edited: { type: Sequelize.DATE, allowNull: true, defaultValue: null }, + password: Sequelize.STRING(16), + views: { type: Sequelize.INTEGER, defaultValue: 0 } }); sequelize.sync(); @@ -25,31 +25,38 @@ module.exports.getNote = id => Note.findById(id); module.exports.getNoteId = deprecatedId => Note.findOne({ where: { deprecatedId: deprecatedId } - }); +}); var generateId = () => [1, 1, 1, 1, 1] - .map(() => { - var code = Math.floor(Math.random() * 36); - return String.fromCharCode(code + (code < 10 ? 48 : 87)); - }) - .join(""); + .map(() => { + var code = Math.floor(Math.random() * 36); + return String.fromCharCode(code + (code < 10 ? 48 : 87)); + }) + .join(""); var getFreeId = () => { - var id = generateId(); - return Note.findById(id).then(result => result ? getFreeId() : id); + var id = generateId(); + return Note.findById(id).then(result => result ? getFreeId() : id); }; module.exports.addNote = (note, password) => getFreeId().then(id => Note.create({ - id: id, - text: note, - password: password + id: id, + text: note, + password: password })); -module.exports.updateNote = (id, password, text) => Note.findById(id).then(note => { - if (!note || note.password !== password) return new Promise((resolve, reject) => { - reject({ message: "Password is wrong" }); - }); - note.text = text; - note.edited = new Date(); - return note.save(); -}); \ No newline at end of file +var passwordCheck = (note, password, callback) => (!note || note.password !== password) + ? new Promise((resolve, reject) => reject({ message: "Password is wrong" })) + : callback(); + +module.exports.updateNote = (id, password, text) => + Note.findById(id).then(note => + passwordCheck(note, password, () => { + note.text = text; + note.edited = new Date(); + return note.save(); + })); + +module.exports.deleteNote = (id, password) => + Note.findById(id).then(note => + passwordCheck(note, password, () => note.destroy())); \ No newline at end of file