浏览代码

nice preprocessing things, logging in works nicely now

master
mort 8 年前
父节点
当前提交
42c1fdc5d1

+ 3
- 2
conf.json.example 查看文件

"host": "localhost", "host": "localhost",
"user": "dbuser", "user": "dbuser",
"password": "dbpass", "password": "dbpass",
"database": "mimg"
"database": "spus"
}, },
"use_https": false, "use_https": false,
"https": { "https": {
"session_timeout": 1800000, "session_timeout": 1800000,
"dir": { "dir": {
"imgs": "imgs" "imgs": "imgs"
}
},
"debug": false
} }

+ 0
- 0
imgs/.placeholder 查看文件


+ 27
- 3
lib/context.js 查看文件

var formidable = require("formidable"); var formidable = require("formidable");
var crypto = require("crypto"); var crypto = require("crypto");
var preprocess = require("./preprocess.js");


var sessions = {}; 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) if (args == undefined)
return str; return str;


setTimeout(function() { setTimeout(function() {
delete sessions[key]; delete sessions[key];
}, this.conf.session_timeout); }, this.conf.session_timeout);

this.session = sessions[key];
} }
} }


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

this.res.end(str); this.res.end(str);
}, },


obj = obj || {}; obj = obj || {};
obj.success = true; obj.success = true;


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

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


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

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

obj = {}; obj = {};
obj.success = false; obj.success = false;
obj.error = err.toString(); obj.error = err.toString();
if (!str) if (!str)
throw new Error("No such template: "+name); throw new Error("No such template: "+name);


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


view: function(name, args) { view: function(name, args) {
if (!str) if (!str)
throw new Error("No such view: "+name); throw new Error("No such view: "+name);


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


getPostData: function(cb) { getPostData: function(cb) {


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

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

+ 15
- 23
lib/includeHtml.js 查看文件

var fs = require("fs"); 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) { module.exports = function load(path, conf) {
var html = fs.readFileSync(path, "utf8"); 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 查看文件

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


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


return res; return res;

+ 68
- 0
lib/preprocess.js 查看文件

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 查看文件

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

+ 10
- 8
scripts/reset.js 查看文件



var conf = JSON.parse(fs.readFileSync("./conf.json")); 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) { client.connect(function(err) {
if (err) { if (err) {
} else { } else {
console.log("Database reset."); console.log("Database reset.");
} }

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

+ 2
- 8
scripts/setup.js 查看文件



var conf = JSON.parse(fs.readFileSync("./conf.json")); 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) { client.connect(function(err) {
if (err) { if (err) {

sql/reset.sql → scripts/sql/reset.sql 查看文件


sql/setup.sql → scripts/sql/setup.sql 查看文件

CREATE TABLE users ( CREATE TABLE users (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL, 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() date_created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
); );


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


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

+ 25
- 18
server.js 查看文件

"/i": "i/index.node.js", "/i": "i/index.node.js",


//API files //API files
"/api/template": "api/template.node.js",
"/api/image_create": "api/image_create.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); var loaded = loader.load(endpoints, conf);
function onRequest(req, res) { function onRequest(req, res) {
console.log("Request for "+req.url); 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]]; var ep = loaded.endpoints[req.url.split("?")[0]];


//If the file doesn't exist, we 404. //If the file doesn't exist, we 404.
if (!ep) { if (!ep) {
ep = loaded.endpoints["/404"]; 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 //Execute if it's a .node.js, or just respond with the contents of the file
if (typeof ep == "function") { if (typeof ep == "function") {
ep(new Context({
req: req,
res: res,
templates: loaded.templates,
views: loaded.views,
db: db,
conf: conf
}));
ep(ctx);
} else { } else {
res.end(ep);
ctx.end(ep);
} }
} }


}); });


//We don't want to crash even if something throws an uncaught exception. //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 查看文件

<div class="navbar-inner"> <div class="navbar-inner">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="/">{{conf#title}}</a> <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> </ul>
</div> </div>
</div> </div>

+ 3
- 0
templates/navbar-loggedin.html 查看文件

<li>
<a href="/profile?{{session#userId}}">{{session#username}}</a>
</li>

templates/navbar-profile-login.html → templates/navbar-login.html 查看文件

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

+ 38
- 0
web/api/account_create.node.js 查看文件

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 查看文件

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 查看文件

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


ctx.db.query( ctx.db.query(
"INSERT INTO collections (name) "+ "INSERT INTO collections (name) "+
}); });


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


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



+ 18
- 0
web/api/template.node.js 查看文件

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 查看文件

#login-dropdown .submit-container { #login-dropdown .submit-container {
text-align: right; text-align: right;
} }
#login-dropdown .submit-container .btn {
width: 75px;
}


#notify-box { #notify-box {
transition: max-height 0.2s; transition: max-height 0.2s;
border: 1px solid #E7E7E7; border: 1px solid #E7E7E7;


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


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


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

+ 35
- 11
web/global.js 查看文件

$("#notify-box").on("mouseenter", function() { $("#notify-box").on("mouseenter", function() {
clearTimeout(util.notify.timeout); 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.error = function(body) {
util.notify("An error occurred.", body);
util.notify("Error: "+body);
} }


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


n -= 1; 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 查看文件

module.exports = function(ctx) { 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 查看文件

util.api("collection_create", { util.api("collection_create", {
name: "New Collection" name: "New Collection"
}, function(err, res) { }, function(err, res) {
if (err) return util.error(err);
if (err)
return util.error(err);


var collectionId = res.id; var collectionId = res.id;


collectionId: collectionId, collectionId: collectionId,
file: f file: f
}, function(err, res) { }, function(err, res) {
if (err) return util.error(err);
if (err)
return util.error(err);


a(); a();
}, getXhr); }, getXhr);

+ 0
- 1
web/view/index.node.js 查看文件

}); });


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

正在加载...
取消
保存