all repos — litestore @ f1ce72ad5bbbfc6b51142ea46dbf05944dc14501

A minimalist nosql document store.

Implemented basic CRUD operations in the app.
h3rald h3rald@h3rald.com
Sat, 14 Mar 2015 23:04:45 +0100
commit

f1ce72ad5bbbfc6b51142ea46dbf05944dc14501

parent

49ecdd02163f833c379137fcfe0564e5b8fa3063

M app/js/app.jsapp/js/app.js

@@ -1,13 +1,16 @@

(function(){ 'use strict'; var app = window.LS || (window.LS = {}); + + app.flash = m.prop(); m.route.mode = "hash"; m.route(document.body, "/info", { '/info': app.info, "/tags/:id": app.tags, - "/document/:id...": app.document, - "/guide/:id": app.guide + "/document/:action/:id...": app.document, + "/guide/:id": app.guide, + "/new": app.create }); -}()); +}());
M app/js/components.jsapp/js/components.js

@@ -8,7 +8,7 @@ app.editor.config = function(obj){

return function(element, isInitialized, context){ var e = element; var setHeight = function(){ - e.style.height = (window.innerHeight-250)+"px"; + e.style.height = (window.innerHeight-210)+"px"; }; if (!isInitialized) {

@@ -17,11 +17,12 @@ obj.editor = editor;

e.style.position = "relative"; setHeight(); window.addEventListener("resize", setHeight); - editor.setReadOnly(true); + editor.setReadOnly(obj.readOnly); editor.setShowPrintMargin(false); editor.setTheme("ace/theme/github"); editor.getSession().setMode("ace/mode/"+obj.mode); editor.getSession().setUseWrapMode(true); + editor.getSession().setTabSize(2); } }; };

@@ -32,7 +33,6 @@ * - content The content of the editor

*/ app.editor.view = function(obj) { return m(".editor.panel.panal-default", {config: app.editor.config(obj)}, obj.content); - }; -}()); +}());
M app/js/models.jsapp/js/models.js

@@ -37,8 +37,15 @@ url: "/v1/docs/"+id+"?raw=true"

}).then(doc); }; - Doc.put = function(doc){ - xhrcfg = u.getContentType(doc); + Doc.delete = function(id){ + return m.request({ + method: "DELETE", + url: "/v1/docs/"+id + }); + }; + + Doc.put = function(doc, contentType){ + xhrcfg = u.setContentType(doc, contentType); return m.request({ method: "PUT", url: "/v1/docs/"+doc.id,

@@ -46,5 +53,5 @@ data: doc.data,

serialize: function(data){return data;}, config: xhrcfg }); - }; -}()); + }; +}());
M app/js/modules.jsapp/js/modules.js

@@ -9,7 +9,8 @@ app.info.vm.init = function() {

this.content = Info.get(); }; app.info.main = function(){ - var info = app.info.vm.content(); + var vm = app.info.vm; + var info = vm.content(); var infolist = m("dl", [ m("dt", "Version"), m("dd", info.version),

@@ -68,61 +69,120 @@ table

]); }; + // Document module app.document = {vm: {}}; app.document.vm.init = function() { var vm = this; - this.id = m.route.param("id"); - this.ext = this.id.match(/\.(.+)$/)[1]; - this.getDoc = function(cb){ - vm.doc = Doc.get(vm.id); + vm.id = m.prop(m.route.param("id")); + vm.action = m.route.param("action"); + vm.readOnly = true; + vm.contentType = m.prop(""); + vm.existingId = m.prop(); + vm.existingContentType = m.prop(); + vm.existingContent = m.prop(); + try { + vm.ext = vm.id().match(/\.(.+)$/)[1]; + } catch(e) { + vm.ext = ""; + } + vm.getDoc = function(cb){ + vm.doc = Doc.get(vm.id()); vm.doc.then(function(doc){ vm.content = doc.data; + vm.tags = doc.tags; }); }; - this.getDoc(); - this.state = "view"; - switch (this.ext){ + vm.tags = []; + switch (vm.action) { + case 'create': + vm.readOnly = false; + if (vm.existingId()) { + vm.id(vm.existingId()); + vm.contentType(vm.existingContentType()); + vm.content(vm.existingContent()); + vm.existingId = m.prop(); + vm.existingContentType = m.prop(); + vm.existingContent = m.prop(); + } + break; + case 'edit': + vm.getDoc(); + vm.readOnly = false; + break; + case 'view': + vm.getDoc(); + break; + } + switch (vm.ext){ case 'js': - this.mode = "javascript"; + vm.mode = "javascript"; break; case 'css': - this.mode = "css"; + vm.mode = "css"; break; case 'html': - this.mode = "html"; + vm.mode = "html"; break; case 'json': - this.mode = "json"; + vm.mode = "json"; break; case 'md': - this.mode = "markdown"; + vm.mode = "markdown"; break; default: - this.mode = "text"; + vm.mode = "text"; } - this.edit = function(){ - vm.state = "edit"; - vm.editor.setReadOnly(false); + vm.edit = function(){ + m.route("/document/edit/"+vm.id()); }; - this.save = function(){ + vm.save = function(){ var doc = {}; - doc.id = vm.doc().id; - doc.tags = vm.doc().tags; + doc.id = vm.id(); doc.data = vm.editor.getValue(); - Doc.put(doc).then(function(){ - m.route(m.route()); - }); + doc.tags = vm.tags; + var put = function(){ + Doc.put(doc, vm.contentType()).then(function(){ + LS.flash({type: "success", content: "Document saved successfully."}); + m.route("/document/view/"+vm.id()); + }); + }; + if (vm.action === "create") { + Doc.get(vm.id()) + .then(function(){ + LS.flash({type: "danger", content: "Document '"+vm.id()+"' already exists."}); + vm.existingContent(doc.data); + vm.existingContentType(vm.contentType()); + vm.existingId(vm.id()); + m.route(m.route()); + }, function(){put();}); + } else { + put(); + } }; - this.cancel = function(){ - vm.state = "view"; - vm.editor.setReadOnly(true); - m.route(m.route()); + vm.delete = function(){ + var msg = "Do you want to delete document '"+vm.id()+"'?"; + if (confirm(msg)) { + Doc.delete(vm.id()).then(function(){ + LS.flash({type: "success", content: "Document '"+vm.id()+"' deleted successfully."}); + m.route("/info"); + }); + } else { + m.route("/document/view/"+vm.id()); + } + }; + vm.cancel = function(){ + if (vm.action === "create"){ + m.route("/info"); + } else { + m.route("/document/view/"+vm.id()); + } }; - this.tools = function(){ - switch (vm.state){ + vm.tools = function(){ + switch (vm.action){ case "view": return [ - {title: "Edit", icon: "edit", action: vm.edit} + {title: "Edit", icon: "edit", action: vm.edit}, + {title: "Delete", icon: "trash", action: vm.delete} ]; default: return [

@@ -134,7 +194,23 @@ };

}; app.document.main = function(){ var vm = app.document.vm; - var title = m("span",[vm.id, m("span.pull-right", vm.doc().tags.map(function(t){return u.taglink(t);}))]); + var titleLeft = vm.id(); + var titleRight = m("span.pull-right", vm.tags.map(function(t){return u.taglink(t);})); + if (vm.action === "create"){ + titleLeft = m("input", { + placeholder: "Specify document ID...", + onchange: m.withAttr("value", vm.id), + size: 35, + value: vm.id() + }); + titleRight = m("span.pull-right", [m("input", { + placeholder: "Specify content type...", + onchange: m.withAttr("value", vm.contentType), + size: 20, + value: vm.contentType() + })]); + } + var title = m("span",[titleLeft, titleRight]); return m("div", [ m(".row", [u.toolbar({links: vm.tools()})]), m(".row", [u.panel({title: title, content:app.editor.view(vm)})])

@@ -158,4 +234,7 @@ u.layout(app.info);

u.layout(app.tags); u.layout(app.document); -}()); +}());tags); + u.layout(app.document); + +}());));
M app/js/navbar.jsapp/js/navbar.js

@@ -31,9 +31,12 @@ },

view: function(ctrl){ var vm = app.navlinks.vm; return m("ul.nav.navbar-nav", [ - m("li", {class: vm.activelink("info")}, [m("a", {href: "/info", config: m.route}, "Info")]), - u.dropdown({title: "Tags", links: vm.taglinks(vm.info()), active: vm.activelink("tags")}), - u.dropdown({title: "Guide", links: vm.guidelinks, active: vm.activelink("guide")}) + m("li", {class: vm.activelink("info")}, [m("a", {href: "/info", config: m.route}, + [m("i.fa.fa-info-circle"), " Info"])]), + u.dropdown({title: "Tags", icon:"fa-tag", links: vm.taglinks(vm.info()), active: vm.activelink("tags")}), + u.dropdown({title: "Guide", icon:"fa-book", links: vm.guidelinks, active: vm.activelink("guide")}), + m("li", {class: vm.activelink("new")}, [m("a", {href: "/document/create/", config: m.route}, + [m("i.fa.fa-plus-circle"), " New Document"])]) ]); } };

@@ -85,4 +88,4 @@ ])

]); } }; -}()); +}());
M app/js/utils.jsapp/js/utils.js

@@ -4,22 +4,24 @@ var app = window.LS || (window.LS = {});

var u = app.utils = {}; /** - * @param mod a module - * @param vm a view-model (with init function) - * @param main the main view to load + * mod object: + * @property vm a view-model (with init function) + * @property main the main view to load */ u.layout = function(mod) { mod.controller = mod.controller || function(){ this.navbar = new app.navbar.controller(); mod.vm.init(); + mod.vm.flash = m.prop(u.flash()); + LS.flash = m.prop(); }; mod.view = function(ctrl){ return m("div", [ m(".container", [ app.navbar.view(ctrl.navbar), - m("main", [mod.main()]) + m("main", [mod.vm.flash(), mod.main()]) ]) ]); };

@@ -37,12 +39,13 @@ ]);

}; u.dropdown = function(obj) { var el = "li.dropdown"; + var icon = (obj.icon) ? m("i.fa."+obj.icon) : ""; if (obj.active.length > 0) { el += "."+obj.active; } return m(el, [ m("a.dropdown-toggle[href='#'][data-toggle='dropdown'][role='button'][aria-expanded='false']", - [m("span", obj.title+" "), m("span.caret")]), + [icon, m("span", " "+obj.title+" "), m("span.caret")]), m("ul.dropdown-menu[role='menu']", obj.links.map(function(e){ return m("li",

@@ -55,7 +58,7 @@ return m("span.label.label-primary", [m("a", {href: "/tags/"+tag, config:m.route}, tag)]);

}; u.doclink = function(id) { - return m("a", {href: "/document/"+id, config: m.route}, id); + return m("a", {href: "/document/view/"+id, config: m.route}, id); }; u.date = function(date) {

@@ -72,18 +75,31 @@ return m("a.btn.btn-default", {onclick:l.action}, [m("i.fa.fa-"+l.icon), " "+l.title]);

}) ); }; + + u.flash = function(){ + if (LS.flash()){ + return m(".row.alert.alert-dismissible.alert-"+LS.flash().type, [ + m("button.close[data-dismiss='alert']", m.trust("×")), + LS.flash().content]); + } else { + return ""; + } + }; - u.getContentType = function(doc){ + u.setContentType = function(doc, contentType){ var type = ""; var subtype = ""; - doc.tags.forEach(function(tag){ - var t = tag.match(/^\$type:(.+)/); - var s = tag.match(/^\$subtype:(.+)/); - if (t) type = t[1]; - if (s) subtype = s[1]; - }); + if (doc.tags && doc.tags.length > 0) { + doc.tags.forEach(function(tag){ + var t = tag.match(/^\$type:(.+)/); + var s = tag.match(/^\$subtype:(.+)/); + if (t) type = t[1]; + if (s) subtype = s[1]; + }); + contentType = type+"/"+subtype; + } return function(xhr) { - xhr.setRequestHeader("Content-Type", type+"/"+subtype); + xhr.setRequestHeader("Content-Type", contentType); }; }; -}()); +}());
M app/styles/elements.lessapp/styles/elements.less

@@ -7,6 +7,10 @@

/* Blocks */ +.panel { + margin-bottom: 0; +} + blockquote { border-left: 3px solid #dedede; padding: 0px 10px;
M app/styles/litestore.cssapp/styles/litestore.css

@@ -1664,6 +1664,9 @@ margin: auto;

margin-top: 60px; } /* Blocks */ +.panel { + margin-bottom: 0; +} blockquote { border-left: 3px solid #dedede; padding: 0px 10px;
M lib/core.nimlib/core.nim

@@ -52,6 +52,46 @@

proc hasMirror(store: Datastore): bool = return store.mirror.len > 0 +# Manage Tags + +proc createTag*(store: Datastore, tagid, documentid: string, system=false) = + if tagid.match(PEG_USER_TAG) or system and tagid.match(PEG_TAG): + store.db.exec(SQL_INSERT_TAG, tagid, documentid) + else: + raise newException(EInvalidTag, "Invalid Tag: $1" % tagid) + +proc destroyTag*(store: Datastore, tagid, documentid: string, system=false): int64 = + if tagid.match(PEG_USER_TAG) or system and tagid.match(PEG_TAG): + return store.db.execAffectedRows(SQL_DELETE_TAG, tagid, documentid) + else: + raise newException(EInvalidTag, "Invalid Tag: $1" % tagid) + +proc retrieveTag*(store: Datastore, id: string, options: QueryOptions = newQueryOptions()): string = + var options = options + options.single = true + var query = prepareSelectTagsQuery(options) + var raw_tag = store.db.getRow(query.sql, id) + return $(%[("id", %raw_tag[0]), ("documents", %(raw_tag[1].parseInt))]) + +proc retrieveTags*(store: Datastore, options: QueryOptions = newQueryOptions()): string = + var query = prepareSelectTagsQuery(options) + var raw_tags = store.db.getAllRows(query.sql) + var tags = newSeq[JsonNode](0) + for tag in raw_tags: + tags.add(%[("id", %tag[0]), ("documents", %(tag[1].parseInt))]) + return $(%tags) + +proc countTags*(store: Datastore): int64 = + return store.db.getRow(SQL_COUNT_TAGS)[0].parseInt + +proc retrieveTagsWithTotals*(store: Datastore): JsonNode = + var data = store.db.getAllRows(SQL_SELECT_TAGS_WITH_TOTALS) + var tag_array = newSeq[JsonNode](0) + for row in data: + var obj = newJObject() + obj[row[0]] = %row[1].parseInt + tag_array.add(obj) + return %tag_array # Manage Documents

@@ -82,8 +122,10 @@ # Add to search index

store.db.exec(SQL_INSERT_SEARCHCONTENT, id, data) store.addDocumentSystemTags(id, contenttype) if store.hasMirror: + # Add dir tag + store.createTag("$dir:"&store.mirror, id, true) var filename = id.unixToNativePath - if fileExists(filename): + if not fileExists(filename): var file = filename.open(fmWrite) file.write(rawdata) else:

@@ -150,47 +192,6 @@ return %documents

proc countDocuments*(store: Datastore): int64 = return store.db.getRow(SQL_COUNT_DOCUMENTS)[0].parseInt - -# Manage Tags - -proc createTag*(store: Datastore, tagid, documentid: string, system=false) = - if tagid.match(PEG_USER_TAG) or system and tagid.match(PEG_TAG): - store.db.exec(SQL_INSERT_TAG, tagid, documentid) - else: - raise newException(EInvalidTag, "Invalid Tag: $1" % tagid) - -proc destroyTag*(store: Datastore, tagid, documentid: string, system=false): int64 = - if tagid.match(PEG_USER_TAG) or system and tagid.match(PEG_TAG): - return store.db.execAffectedRows(SQL_DELETE_TAG, tagid, documentid) - else: - raise newException(EInvalidTag, "Invalid Tag: $1" % tagid) - -proc retrieveTag*(store: Datastore, id: string, options: QueryOptions = newQueryOptions()): string = - var options = options - options.single = true - var query = prepareSelectTagsQuery(options) - var raw_tag = store.db.getRow(query.sql, id) - return $(%[("id", %raw_tag[0]), ("documents", %(raw_tag[1].parseInt))]) - -proc retrieveTags*(store: Datastore, options: QueryOptions = newQueryOptions()): string = - var query = prepareSelectTagsQuery(options) - var raw_tags = store.db.getAllRows(query.sql) - var tags = newSeq[JsonNode](0) - for tag in raw_tags: - tags.add(%[("id", %tag[0]), ("documents", %(tag[1].parseInt))]) - return $(%tags) - -proc countTags*(store: Datastore): int64 = - return store.db.getRow(SQL_COUNT_TAGS)[0].parseInt - -proc retrieveTagsWithTotals*(store: Datastore): JsonNode = - var data = store.db.getAllRows(SQL_SELECT_TAGS_WITH_TOTALS) - var tag_array = newSeq[JsonNode](0) - for row in data: - var obj = newJObject() - obj[row[0]] = %row[1].parseInt - tag_array.add(obj) - return %tag_array proc importDir*(store: Datastore, dir: string) = # TODO: Only allow directory names (not paths)?