Skip to content

Instantly share code, notes, and snippets.

@jakubkulhan
Created July 22, 2012 22:09
Show Gist options
  • Save jakubkulhan/3161171 to your computer and use it in GitHub Desktop.
Save jakubkulhan/3161171 to your computer and use it in GitHub Desktop.
blog written using Jeph <https://github.com/jakubkulhan/jeph>

Blog example

This Gist contains (incomplete) sources of what an implementation of blog in Jeph might look like when Jeph is completed and supports all needed functions (think of this code as soon-not-to-be-failing test).

First look at main.js, it creates some example data and calls jeph() with database-based handler. index.js creates blog's property model and sets basic transformations. archive.js, comment_aggregate.js, and post.js define transformations for blog/* types.

Transformations

What is an transformation anyway? It takes an entity and if the entity matches given conditions, it adds some properties to it. A condition is any property of transformation that is not a function. true matches when property is present, false if it is not present. String and number match if property has given value. For example, { "jeph/type": true } matches an entity if it has property called jeph/type, { "jeph/type": "blog/post" } matches if the property's value is string blog/post. (Query uses same matching conditions).

A function property is a transformatter. It returns new value of property. The value can be result of some processing, or an Query object. If it's an Query, the query is executed everytime entity is retrieved from store.

Database-based handler

Database-based handler is a Jeph handler, that processes request according to data read from database. It strips req.url from req.basePath and query (?) arguments and then queries the database for { "jeph/path": strippedUrl, "jeph/handle": true }. Then it calls entities jeph/handle with req, and res arguments, and this is set to the entity object. It's as simple as that.

TODO

  • fs module, support for Node modules (_tpl.js)
  • Query.first() (archive.js), Query.all() instead of Query.fetch()
  • transformation returning query object (archive.js)
  • transformation that depends on DB (when transformatter accepts db as its first argument) (comment_aggregate.js)
  • transformation independent of DB (when transformatter does not have any parameters (.length === 0)) (post.js)
  • transformation condition as array (also should be possible in query) (main.js)
var fs = require("fs"),
jade = require("jade");
exports = function (t) {
var file = __dirname + "/" + t + ".jade",
template = jade.compile(fs.readFileSync(ret.file), { filename: ret.file });
return {
render: function (req, res, entity) {
var locals = { req: req, res: res };
locals[t] = entity;
var ret = template(locals);
res.writeHead(200, { "content-type": "text/html; charset=UTF-8",
"content-length": ret.length });
res.end(ret);
}
};
};
exports = function (db) {
db.transformation({
"jeph/type": "blog/archive",
"blog/archive/year": true,
"jeph/path/parent": function (db) {
return db.query({ "jeph/type": "blog/index" }).first().id;
},
"jeph/path/relative": function () {
return String(this["blog/archive/year"]);
},
"blog/archive/posts": function (db) {
return db.query({ "jeph/type": "blog/post", "jeph/path/parent": this.id })
.sort({ "blog/post/published_at": -1 });
}
});
};
exports = function (db) {
db.transformation({
"jeph/type": "blog/comment_aggregate",
"blog/comment_aggregate/post": true,
"jeph/path": function (db) {
return db.get(this["blog/comment_aggregate/post"])["jeph/path"] + "/comments.xml";
},
"blog/comment_aggregate/comments": function (db) {
return db.query({ "jeph/type": "blog/comment",
"blog/comment/post": this["blog/comment_aggregate/post"] })
.sort({ "blog/comment/created_at": -1 })
.take(10);
}
});
};
exports = function (db) {
db.namespace("blog", function (db) {
db.namespace("index", function (db) {
db.property("posts", function (p) { p.type = "blog/post" });
db.property("postcount", function (p) { p.type = "int" });
});
db.namespace("archive", function (db) {
db.property("year", function (p) { p.type = "int" });
db.property("posts", function (p) { p.type = "blog/post" });
});
db.namespace("post", function (db) {
db.property("title", function (p) { p.type = "string"; });
db.property("text", function (p) { p.type = "string"; });
db.property("created_at", function (p) { p.type = "string"; });
db.property("published_at", function (p) { p.type = "string"; });
db.property("comments", function (p) { p.type = "blog/comment"; });
});
db.namespace("comment", function (db) {
db.property("post", function (p) { p.type = "id"; });
db.property("email", function (p) { p.type = "string"; });
db.property("text", function (p) { p.type = "string"; });
db.property("created_at", function (p) { p.type = "string"; });
});
db.namespace("comment_aggregate", function (db) {
db.property("post", function (p) { p.type = "id"; });
db.property("comments", function (p) { p.type = "blog/comment"; });
});
});
db.transformation({
"jeph/type": "blog/index",
"blog/index/postcount": function (db) {
return db.query({ "jeph/type": "blog/post" }).count();
},
"blog/index/posts": function (db) {
return db.query({ "jeph/type": "blog/post" })
.sort({ "blog/post/created_at": -1 })
.take(5);
}
});
db.transformation({
"jeph/type": [ "blog/index", "blog/archive", "blog/post", "blog/comment_aggregate" ],
"jeph/handle": function () {
return function (req, res) {
require(__dirname + "/tpl" +
this["jeph/type"].substring(this["jeph/type"].indexOf("/") + 1) +
".js").render(req, res, this);
};
}
});
require("./archive.js")(db);
require("./post.js")(db);
require("./comment_aggregate.js")(db);
};
var MemoryStore = require("jephdb/stores/MemoryStore"),
jephdb = require("jephdb"),
db = new jephdb(new MemoryStore),
jeph = require("jeph"),
handler = require("jephdb/handler");
require("jeph/properties")(db);
require("./index")(db);
db.create({
"jeph/type": "blog/index",
"jeph/path": "/"
}).save();
var post = db.create({
"jeph/type": "blog/post",
"blog/post/title": "Hello, world!",
"blog/post/text": "Lorem ipsum dolor sit amet...",
"blog/post/created_at": "2012-09-22 22:19",
"blog/post/published_at": "2012-09-22 23:00"
}).save();
db.create({
"jeph/type": "blog/comment",
"blog/comment/post": post.id,
"blog/comment/email": "[email protected]",
"blog/comment/text": "Awesome post!",
"blod/comment/created_at": "2012-09-22 23:15"
}).save();
jeph(new handler(db));
exports = function (db) {
db.transformation({
"jeph/type": "blog/post",
"blog/post/title": true,
"jeph/path/parent": function (db) {
var archive = db.query({ "jeph/type": "blog/archive",
"blog/archive/year": new Date(this["blog/post/published_at"]).getFullYear() })
.first();
if (!archive) {
archive = db.create({ "jeph/type": "blog/archive",
"blog/archive/year": new Date(this["blost/post/published_at"]).getFullYear() }).save();
}
return archive.id;
},
"jeph/path/relative": function () {
return this["blog/post/title"].webalize();
},
"blog/post/comments": function (db) {
return db.query({ "jeph/type": "blog/comment",
"blog/comment/post": this.id })
.sort({ "blog/comment/created_at": 1 });
}
});
};
exports = require("./_tpl.js")("archive");
exports = {
render: function (req, res, entity) {
res.writeHead(200, { "content-type": "application/rss+xml" });
// ... generate RSS from entity["blog/comment_aggregate/comments"] ...
}
};
exports = require("./_tpl.js")("index");
//
// Gist says too many files, so here is tpl/post.js:
//
exports = require("./_tpl.js")("post");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment