Browse Source

nice preprocessing things, logging in works nicely now

master
mort 8 years ago
parent
commit
42c1fdc5d1

+ 3
- 2
conf.json.example View File

@@ -5,7 +5,7 @@
"host": "localhost",
"user": "dbuser",
"password": "dbpass",
"database": "mimg"
"database": "spus"
},
"use_https": false,
"https": {
@@ -20,5 +20,6 @@
"session_timeout": 1800000,
"dir": {
"imgs": "imgs"
}
},
"debug": false
}

+ 0
- 0
imgs/.placeholder View File


+ 27
- 3
lib/context.js View File

@@ -1,9 +1,17 @@
var formidable = require("formidable");
var crypto = require("crypto");
var preprocess = require("./preprocess.js");

var sessions = {};

function templatify(str, args) {
function templatify(str, args, ctx) {
str = preprocess(str, {
session: ctx.session,
template: function(key) {
return ctx.template(key);
}
});

if (args == undefined)
return str;

@@ -46,11 +54,16 @@ module.exports = function(options) {
setTimeout(function() {
delete sessions[key];
}, this.conf.session_timeout);

this.session = sessions[key];
}
}

module.exports.prototype = {
end: function(str) {
if (this.statusCode)
this.res.writeHead(this.statusCode);

this.res.end(str);
},

@@ -58,10 +71,17 @@ module.exports.prototype = {
obj = obj || {};
obj.success = true;

this.res.setHeader("Content-Type", "application/json");

this.end(JSON.stringify(obj));
},

fail: function(err) {
console.log("Sending error to client:");
console.trace(err);

this.res.setHeader("Content-Type", "application/json");

obj = {};
obj.success = false;
obj.error = err.toString();
@@ -73,7 +93,7 @@ module.exports.prototype = {
if (!str)
throw new Error("No such template: "+name);

return templatify(str, args);
return templatify(str, args, this);
},

view: function(name, args) {
@@ -81,7 +101,7 @@ module.exports.prototype = {
if (!str)
throw new Error("No such view: "+name);

return templatify(str, args);
return templatify(str, args, this);
},

getPostData: function(cb) {
@@ -102,5 +122,9 @@ module.exports.prototype = {

cb(null, data, files);
}.bind(this));
},

setStatus: function(code) {
this.statusCode = code;
}
}

+ 15
- 23
lib/includeHtml.js View File

@@ -1,32 +1,24 @@
var fs = require("fs");
var minify = require("./minify");
var minify = require("./minify.js");
var preprocess = require("./preprocess.js");

var globalRegex = /{{([^}]+)#([^}]+)}}/g;
var localRegex = /{{([^}]+)#([^}]+)}}/;
var cache = {};

module.exports = function load(path, conf) {
var html = fs.readFileSync(path, "utf8");

var placeholders = html.match(globalRegex);
if (!placeholders)
return minify.html(html);

placeholders.forEach(function(p) {
var parts = html.match(localRegex);

var s = parts[0];
var ns = parts[1];
var key = parts[2];

switch (ns) {
case "conf":
html = html.replace(s, conf[key]);
break;
case "template":
html = html.replace(s, load("templates/"+key+".html", conf));
break;
var env = {
conf: conf.web,
template: function(key) {
var str = load("templates/"+key+".html", conf);
return str;
}
});
}

html = preprocess(html, env);

return minify.html(html);
if (conf.minify)
return minify.html(html);
else
return html;
}

+ 2
- 2
lib/loader.js View File

@@ -54,14 +54,14 @@ exports.load = function(endpoints, conf) {
var templates = {};
fs.readdirSync("templates").forEach(function(f) {
var name = f.replace(/\.html$/, "");
res.templates[name] = includeHtml("templates/"+f, conf.web);
res.templates[name] = includeHtml("templates/"+f, conf);
});

//Prepare all views
var views = {};
fs.readdirSync("views").forEach(function(f) {
var name = f.replace(/\.html$/, "");
res.views[name] = includeHtml("views/"+f, conf.web);
res.views[name] = includeHtml("views/"+f, conf);
});

return res;

+ 68
- 0
lib/preprocess.js View File

@@ -0,0 +1,68 @@
var valueRegex = "([a-zA-Z0-9_\\-]+)#([a-zA-Z0-9_\\-]+)";

var regexStr =
"{{"+ //{{
valueRegex+ //foo#bar - $1#$2
"(?:"+ //<optional>
" \\? "+ //?
valueRegex+ //foo#bar - $3#$4
" : "+ //:
valueRegex+ //foo#bar - $5#$6
")?"+ //</optional>
"}}"; //}}

var localRegex = new RegExp(regexStr);
var globalRegex = new RegExp(regexStr, "g");

function getVal(ns, key, env) {
var n = env[ns];

if (typeof n === "function")
return n(key);
else if (n)
return n[key];
else
throw new Error("Namespace "+ns+" doesn't exist.");
}

module.exports = function(str, env) {
var placeholders = str.match(globalRegex);

if (!placeholders)
return str;

placeholders.forEach(function(p) {
var parts = p.match(localRegex);
var s = parts[0];

//Ternary
if (parts[6]) {
try {
var cond = getVal(parts[1], parts[2], env);
var val1 = getVal(parts[3], parts[4], env);
var val2 = getVal(parts[5], parts[6], env);
} catch (err) {
return;
}

if (cond === true)
str = str.replace(s, val1);
else
str = str.replace(s, val2);
}

//Direct value
else {
try {
var val = getVal(parts[1], parts[2], env);
} catch (err) {
return;
}

if (val !== undefined && val !== null)
str = str.replace(s, val);
}
});

return str;
}

+ 1
- 0
package.json View File

@@ -19,6 +19,7 @@
"formidable": "^1.0.17",
"html-minifier": "^0.7.2",
"pg": "^4.4.0",
"scrypt": "^4.0.7",
"uglify-js": "^2.4.24",
"uglifycss": "0.0.15"
}

+ 10
- 8
scripts/reset.js View File

@@ -3,15 +3,15 @@ var pg = require("pg");

var conf = JSON.parse(fs.readFileSync("./conf.json"));

var sql = fs.readFileSync("sql/reset.sql", "utf8");
var sql = fs.readFileSync("scripts/sql/reset.sql", "utf8");

var client = new pg.Client(
"postgres://"+
conf.db.user+":"+
conf.db.pass+"@"+
conf.db.host+"/"+
conf.db.database
);
var client = new pg.Client(conf.db);
function deleteFiles(dir) {
fs.readdirSync(dir).forEach(function(f) {
fs.unlinkSync(dir+"/"+f);
});
}

client.connect(function(err) {
if (err) {
@@ -25,6 +25,8 @@ client.connect(function(err) {
} else {
console.log("Database reset.");
}

deleteFiles(conf.dir.imgs);
process.exit();
});
});

+ 2
- 8
scripts/setup.js View File

@@ -3,15 +3,9 @@ var pg = require("pg");

var conf = JSON.parse(fs.readFileSync("./conf.json"));

var sql = fs.readFileSync("sql/setup.sql", "utf8");
var sql = fs.readFileSync("scripts/sql/setup.sql", "utf8");

var client = new pg.Client(
"postgres://"+
conf.db.user+":"+
conf.db.pass+"@"+
conf.db.host+"/"+
conf.db.database
);
var client = new pg.Client(conf.db);

client.connect(function(err) {
if (err) {

sql/reset.sql → scripts/sql/reset.sql View File


sql/setup.sql → scripts/sql/setup.sql View File

@@ -1,13 +1,14 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
pass_hash CHAR(128) NOT NULL,
pass_hash VARCHAR(256) NOT NULL,
date_created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);

CREATE TABLE collections (
id SERIAL PRIMARY KEY,
name VARCHAR(64),
date_created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),

user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

+ 25
- 18
server.js View File

@@ -29,8 +29,11 @@ var endpoints = {
"/i": "i/index.node.js",

//API files
"/api/template": "api/template.node.js",
"/api/image_create": "api/image_create.node.js",
"/api/collection_create": "api/collection_create.node.js"
"/api/collection_create": "api/collection_create.node.js",
"/api/account_create": "api/account_create.node.js",
"/api/account_login": "api/account_login.node.js"
}

var loaded = loader.load(endpoints, conf);
@@ -41,26 +44,28 @@ var db = new pg.Client(conf.db);
function onRequest(req, res) {
console.log("Request for "+req.url);

var ctx = new Context({
req: req,
res: res,
templates: loaded.templates,
views: loaded.views,
db: db,
conf: conf
});

var ep = loaded.endpoints[req.url.split("?")[0]];

//If the file doesn't exist, we 404.
if (!ep) {
ep = loaded.endpoints["/404"];
res.writeHead(404);
ctx.setStatus(404);
}

//Execute if it's a .node.js, or just respond with the contents of the file
if (typeof ep == "function") {
ep(new Context({
req: req,
res: res,
templates: loaded.templates,
views: loaded.views,
db: db,
conf: conf
}));
ep(ctx);
} else {
res.end(ep);
ctx.end(ep);
}
}

@@ -80,10 +85,12 @@ db.connect(function() {
});

//We don't want to crash even if something throws an uncaught exception.
var d = domain.create();
d.on("error", function(err) {
console.trace(err);
});
process.on("uncaughtException", function(err) {
console.trace(err);
});
if (!conf.debug) {
var d = domain.create();
d.on("error", function(err) {
console.trace(err);
});
process.on("uncaughtException", function(err) {
console.trace(err);
});
}

+ 2
- 2
templates/global.html View File

@@ -2,8 +2,8 @@
<div class="navbar-inner">
<div class="container-fluid">
<a class="navbar-brand" href="/">{{conf#title}}</a>
<ul class="nav navbar-nav navbar-right">
{{profile}}
<ul id="navbar-profile-container" class="nav navbar-nav navbar-right">
{{session#loggedIn ? template#navbar-loggedin : template#navbar-login}}
</ul>
</div>
</div>

+ 3
- 0
templates/navbar-loggedin.html View File

@@ -0,0 +1,3 @@
<li>
<a href="/profile?{{session#userId}}">{{session#username}}</a>
</li>

templates/navbar-profile-login.html → templates/navbar-login.html View File

@@ -7,16 +7,17 @@
<form id="login-form">
<div class="form-group">
<label>Username<br>
<input type="text">
<input type="text" id="login-username">
</label>
</div>
<div class="form-group">
<label>Password<br>
<input type="password">
<input type="password" id="login-password">
</label>
</div>
<div class="submit-container">
<button type="submit" class="btn btn-default">Log In</button>
<a class="btn btn-default register" href="/register">Register</a>
</div>
</form>
</li></ul>

+ 38
- 0
web/api/account_create.node.js View File

@@ -0,0 +1,38 @@
var scrypt = require("scrypt");

module.exports = function(ctx) {
ctx.getPostData(function(err, data) {
if (err)
return ctx.fail(err);

if (!data.username || !data.password)
return ctx.fail("You must provide a username and a password.");

var params = scrypt.params(1);
scrypt.hash(new Buffer(data.password), params, function(err, hash) {
if (err)
return ctx.fail(err);

ctx.db.query(
"INSERT INTO users (username, pass_hash) "+
"VALUES ($1, $2 )"+
"RETURNING id",
[data.username, hash.toString("hex")],
queryCallback
);
});
});

function queryCallback(err, res) {
if (err)
return ctx.fail(err);

ctx.session.loggedIn = true;
ctx.session.userId = res.rows[0].id;
ctx.session.username = ctx.postData.username;

ctx.succeed({
id: res.rows[0].id
});
}
}

+ 47
- 0
web/api/account_login.node.js View File

@@ -0,0 +1,47 @@
var scrypt = require("scrypt");

module.exports = function(ctx) {
ctx.getPostData(function(err, data) {
if (err)
return ctx.fail(err);

if (!data.username || !data.password)
return ctx.fail("You must provide a username and a password.");

ctx.db.query(
"SELECT id, username, pass_hash "+
"FROM users "+
"WHERE username=$1",
[data.username],
queryCallback
);
});

function queryCallback(err, res) {
if (err)
return ctx.fail(err);

var user = res.rows[0];

ctx.session.loggedIn = true;
ctx.session.userId = user.id;
ctx.session.username = user.username;

if (!user)
return ctx.fail("Wrong username or password.");

scrypt.verify(
new Buffer(user.pass_hash, "hex"),
new Buffer(ctx.postData.data.password),
function(err, success) {
if (success) {
ctx.succeed({
id: user.id
})
} else {
ctx.fail("Wrong username or password.");
}
}
);
}
}

+ 4
- 2
web/api/collection_create.node.js View File

@@ -1,6 +1,7 @@
module.exports = function(ctx) {
ctx.getPostData(function(err, data) {
if (err) return ctx.fail(err);
if (err)
return ctx.fail(err);

ctx.db.query(
"INSERT INTO collections (name) "+
@@ -12,7 +13,8 @@ module.exports = function(ctx) {
});

function queryCallback(err, res) {
if (err) return ctx.fail(err);
if (err)
return ctx.fail(err);

ctx.session.collectionId = res.rows[0].id;


+ 18
- 0
web/api/template.node.js View File

@@ -0,0 +1,18 @@
module.exports = function(ctx) {
var name = ctx.req.url.split("?")[1];
if (!name)
return ctx.fail("You must supply a template name.");

ctx.getPostData(function(err, data) {
if (err)
return ctx.fail(err);

try {
ctx.succeed({
html: ctx.template(name, data)
});
} catch (err) {
ctx.fail(err);
}
});
}

+ 6
- 2
web/global.css View File

@@ -18,6 +18,9 @@
#login-dropdown .submit-container {
text-align: right;
}
#login-dropdown .submit-container .btn {
width: 75px;
}

#notify-box {
transition: max-height 0.2s;
@@ -27,10 +30,10 @@
border: 1px solid #E7E7E7;

overflow: hidden;
line-height: 48px;
position: fixed;
bottom: 0px;
width: 100%;
box-sizing: content-box;
}

#notify-box .close {
@@ -47,15 +50,16 @@
}
#notify-box .title,
#notify-box .body {
line-height: 48px;
margin-left: 12px;
width: calc(100% - 42px);
margin-top: 16px;
}
#notify-box .title {
font-weight: bold;
}

#notify-box.active {
min-height: 48px;
max-height: 48px;
}
#notify-box.active:hover {

+ 35
- 11
web/global.js View File

@@ -16,16 +16,10 @@
$("#notify-box").on("mouseenter", function() {
clearTimeout(util.notify.timeout);
});

$("#login-form").on("submit", function(evt) {
evt.stopPropagation();
evt.preventDefault();
util.notify("Feature Not Implemented", "This feature is not implemented.");
});
});

util.error = function(body) {
util.notify("An error occurred.", body);
util.notify("Error: "+body);
}

util.htmlEntities = function(str) {
@@ -59,11 +53,10 @@
}).done(function(res) {
console.log("response from "+name+":");
console.log(res);
var obj = JSON.parse(res);
if (obj.success)
cb(null, obj);
if (res.success)
cb(null, res);
else
cb(obj.error);
cb(res.error);
});
}

@@ -86,4 +79,35 @@
n -= 1;
}
}

window.display = {};

window.display.loggedIn = function() {
util.api("template?navbar-loggedin", {}, function(err, res) {
if (err)
return util.error(err);

$("#navbar-profile-container").html(res.html);
});
}

$(document).ready(function() {
$("#login-form").on("submit", function(evt) {
evt.stopPropagation();
evt.preventDefault();

var username = $("#login-username").val();
var password = $("#login-password").val();

util.api("account_login", {
username: username,
password: password
}, function(err, res) {
if (err)
util.error(err);
else
display.loggedIn();
});
});
});
})();

+ 1
- 3
web/index/index.node.js View File

@@ -1,5 +1,3 @@
module.exports = function(ctx) {
ctx.end(ctx.view("index", {
profile: ctx.template("navbar-profile-login")
}));
ctx.end(ctx.view("index"));
}

+ 4
- 2
web/index/script.js View File

@@ -68,7 +68,8 @@
util.api("collection_create", {
name: "New Collection"
}, function(err, res) {
if (err) return util.error(err);
if (err)
return util.error(err);

var collectionId = res.id;

@@ -106,7 +107,8 @@
collectionId: collectionId,
file: f
}, function(err, res) {
if (err) return util.error(err);
if (err)
return util.error(err);

a();
}, getXhr);

+ 0
- 1
web/view/index.node.js View File

@@ -27,7 +27,6 @@ module.exports = function(ctx) {
});

ctx.end(ctx.view("view", {
profile: ctx.template("navbar-profile-login"),
images: images
}));
}

Loading…
Cancel
Save