@@ -4,21 +4,18 @@ var preprocess = require("./preprocess.js"); | |||
var sessions = {}; | |||
function templatify(str, args, ctx) { | |||
function templatify(str, args, ctx, env) { | |||
env.url = ctx.req.url; | |||
str = preprocess(str, { | |||
session: ctx.session, | |||
arg: args, | |||
env: env, | |||
template: function(key) { | |||
return ctx.template(key); | |||
} | |||
}); | |||
if (args == undefined) | |||
return str; | |||
for (var i in args) { | |||
str = str.split("{{"+i+"}}").join(args[i]); | |||
} | |||
return str; | |||
} | |||
@@ -29,6 +26,9 @@ module.exports = function(options) { | |||
this.views = options.views; | |||
this.db = options.db; | |||
this.conf = options.conf; | |||
this.query = this.req.url.split("?")[1] || ""; | |||
if (this.conf.debug) | |||
this.startTime = new Date(); | |||
//Handle cookies | |||
this.cookies = {}; | |||
@@ -61,6 +61,17 @@ module.exports = function(options) { | |||
module.exports.prototype = { | |||
end: function(str) { | |||
if (this.conf.debug) { | |||
var ms = (new Date().getTime() - this.startTime.getTime()); | |||
console.log( | |||
ms+" millisecond(s)\t"+ | |||
(this.statusCode || 200)+"\t"+ | |||
this.req.url | |||
); | |||
} else { | |||
conole.log(this.req.url); | |||
} | |||
if (this.statusCode) | |||
this.res.writeHead(this.statusCode); | |||
@@ -93,7 +104,7 @@ module.exports.prototype = { | |||
if (!str) | |||
throw new Error("No such template: "+name); | |||
return templatify(str, args, this); | |||
return templatify(str, args, this, {template: name}); | |||
}, | |||
view: function(name, args) { | |||
@@ -101,7 +112,7 @@ module.exports.prototype = { | |||
if (!str) | |||
throw new Error("No such view: "+name); | |||
return templatify(str, args, this); | |||
return templatify(str, args, this, {view: name}); | |||
}, | |||
getPostData: function(cb) { | |||
@@ -138,5 +149,29 @@ module.exports.prototype = { | |||
this.session.loggedIn = false; | |||
delete this.session.username; | |||
delete this.session.userId; | |||
}, | |||
async: function(n, cb) { | |||
if (typeof n !== "number") | |||
throw new Error("Expected number, got "+typeof n); | |||
if (n < 1) | |||
return cb(); | |||
var res = {}; | |||
var errs = {}; | |||
var errnum = 0; | |||
return function(key, val, err) { | |||
if (key) | |||
res[key] = val; | |||
if (err) | |||
errs[key] = err; | |||
if (n === 1) | |||
cb((errnum ? errs : null), res); | |||
else | |||
n -= 1; | |||
} | |||
} | |||
} |
@@ -26,6 +26,11 @@ var endpoints = { | |||
"/register/style.css": "register/style.css", | |||
"/register/script.js": "register/script.js", | |||
//Profile | |||
"/profile": "profile/index.node.js", | |||
"/profile/style.css": "profile/style.css", | |||
"/profile/script.js": "profile/script.js", | |||
//Viewer | |||
"/view": "view/index.node.js", | |||
"/view/style.css": "view/style.css", | |||
@@ -48,8 +53,6 @@ var db = new pg.Client(conf.db); | |||
//Function to run on each request | |||
function onRequest(req, res) { | |||
console.log("Request for "+req.url); | |||
var ctx = new Context({ | |||
req: req, | |||
res: res, | |||
@@ -100,3 +103,40 @@ if (!conf.debug) { | |||
console.trace(err); | |||
}); | |||
} | |||
function command(tokens) { | |||
switch(tokens[0]) { | |||
//Reload configuration | |||
case "reload-conf": | |||
var c = JSON.parse(fs.readFileSync("conf.json")); | |||
for (var i in c) | |||
conf[i] = c[i]; | |||
break; | |||
//Reload HTML | |||
case "reload-html": | |||
var l = loader.load(endpoints, conf); | |||
for (var i in l) | |||
loaded[i] = l[i]; | |||
break; | |||
//Reload everything | |||
case "reload": | |||
command(["reload-conf"]); | |||
command(["reload-html"]); | |||
break; | |||
default: return false; | |||
} | |||
return true; | |||
} | |||
process.stdin.on("data", function(line) { | |||
var tokens = line.toString().split(/\s+/); | |||
if (command(tokens)) { | |||
return console.log(tokens[0]+" completed successfully."); | |||
} else { | |||
return console.log("Command not found: "+tokens[0]); | |||
} | |||
}); |
@@ -0,0 +1,4 @@ | |||
<div class="collection"> | |||
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a> | |||
<span class="date-created">{{arg#date_created}}</span> | |||
</div> |
@@ -1,8 +1,12 @@ | |||
<title>{{conf#title}}</title> | |||
<meta charset="utf-8"> | |||
<meta name="viewport" content="width=device-width"> | |||
<link rel="stylesheet" href="/global.css"> | |||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> | |||
<link rel="stylesheet" href="/{{env#view}}/style.css"> | |||
<script src="https://code.jquery.com/jquery-2.1.4.js"></script> | |||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> | |||
<script src="/global.js"></script> | |||
<script src="/{{env#view}}/script.js"></script> |
@@ -1,6 +1,6 @@ | |||
<div class="image"> | |||
<div class="title">{{title}}</div> | |||
<img class="img-rounded" src="/i?{{id}}.{{extension}}"> | |||
<div class="description">{{description}}</div> | |||
<input class="url" type="text" value="{{conf#base_url}}/i?{{id}}.{{extension}}" onclick="select()"> | |||
<div class="title">{{arg#title}}</div> | |||
<img class="img-rounded" src="/i?{{arg#id}}.{{arg#extension}}"> | |||
<div class="description">{{arg#description}}</div> | |||
<input class="url" type="text" value="{{conf#base_url}}/i?{{arg#id}}.{{arg#extension}}" onclick="select()"> | |||
</div> |
@@ -1,3 +1,3 @@ | |||
<li> | |||
<a href="/profile?{{session#userId}}">{{session#username}}</a> | |||
<a id="navbar-button" href="/profile?{{session#userId}}">{{session#username}}</a> | |||
</li> |
@@ -1,5 +1,5 @@ | |||
<li class="dropdown" id="login-dropdown"> | |||
<a href="#" data-toggle="dropdown" class="dropdown-toggle" style="float: right"> | |||
<a href="#" data-toggle="dropdown" class="dropdown-toggle" id="navbar-button"> | |||
Log In | |||
<b class="caret"></b> | |||
</a> |
@@ -4,6 +4,7 @@ | |||
<meta charset="utf-8"> | |||
</head> | |||
<body> | |||
404, file not found. | |||
404, file not found.<br> | |||
{{env#url}} | |||
</body> | |||
</html> |
@@ -2,10 +2,9 @@ | |||
<html> | |||
<head> | |||
{{template#head}} | |||
<link rel="stylesheet" href="/index/style.css"> | |||
</head> | |||
<body> | |||
{{template#global}} | |||
{{template#body}} | |||
<div id="uploader" class="container"> | |||
<input type="file" accept="image/*" id="uploader-input" class="hidden" multiple> | |||
@@ -19,7 +18,5 @@ | |||
<ul class="list-group" id="uploader-list"></ul> | |||
</div> | |||
<script src="/index/script.js"></script> | |||
</body> | |||
</html> |
@@ -0,0 +1,16 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
{{template#head}} | |||
</head> | |||
<body> | |||
{{template#body}} | |||
<div id="profile" class="container"> | |||
<div id="collections" class="container"> | |||
<div class="name">{{arg#username}}</div> | |||
{{arg#collections}} | |||
</div> | |||
</div> | |||
</body> | |||
</html> |
@@ -2,10 +2,9 @@ | |||
<html> | |||
<head> | |||
{{template#head}} | |||
<link rel="stylesheet" href="/register/style.css"> | |||
</head> | |||
<body> | |||
{{template#global}} | |||
{{template#body}} | |||
<div class="container" id="register"> | |||
<form id="register-form"> | |||
@@ -21,7 +20,7 @@ | |||
</div> | |||
<div class="form-group"> | |||
<label>Repeat Password<br> | |||
<input type="psasword" id="register-password-repeat"> | |||
<input type="password" id="register-password-repeat"> | |||
</label> | |||
</div> | |||
<div class="submit-container"> | |||
@@ -29,7 +28,5 @@ | |||
</div> | |||
</form> | |||
</div> | |||
<script src="/index/script.js"></script> | |||
</body> | |||
</html> |
@@ -2,15 +2,12 @@ | |||
<html> | |||
<head> | |||
{{template#head}} | |||
<link rel="stylesheet" href="/view/style.css"> | |||
</head> | |||
<body> | |||
{{template#global}} | |||
{{template#body}} | |||
<div id="viewer" class="container"> | |||
{{images}} | |||
{{arg#images}} | |||
</div> | |||
<script src="/index/script.js"></script> | |||
</body> | |||
</html> |
@@ -27,7 +27,7 @@ module.exports = function(ctx) { | |||
if (err) | |||
return ctx.fail(err); | |||
ctx.login(ctx.postData.username, res.rows[0].id); | |||
ctx.login(ctx.postData.data.username, res.rows[0].id); | |||
ctx.succeed({ | |||
id: res.rows[0].id |
@@ -23,11 +23,11 @@ module.exports = function(ctx) { | |||
var user = res.rows[0]; | |||
ctx.login(user.username, user.id); | |||
if (!user) | |||
return ctx.fail("Wrong username or password."); | |||
ctx.login(user.username, user.id); | |||
scrypt.verify( | |||
new Buffer(user.pass_hash, "hex"), | |||
new Buffer(ctx.postData.data.password), |
@@ -4,10 +4,10 @@ module.exports = function(ctx) { | |||
return ctx.fail(err); | |||
ctx.db.query( | |||
"INSERT INTO collections (name) "+ | |||
"VALUES ($1) "+ | |||
"INSERT INTO collections (name, user_id) "+ | |||
"VALUES ($1, $2) "+ | |||
"RETURNING id", | |||
[data.name], | |||
[data.name, ctx.session.userId], | |||
queryCallback | |||
); | |||
}); |
@@ -1,5 +1,5 @@ | |||
module.exports = function(ctx) { | |||
var name = ctx.req.url.split("?")[1]; | |||
var name = ctx.query; | |||
if (!name) | |||
return ctx.fail("You must supply a template name."); | |||
@@ -10,6 +10,10 @@ | |||
padding: 10px; | |||
} | |||
#navbar-button { | |||
float: right; | |||
} | |||
#login-dropdown label, | |||
#login-dropdown input { | |||
width: 100%; |
@@ -1,4 +1,19 @@ | |||
(function() { | |||
var months = [ | |||
"January", | |||
"February", | |||
"March", | |||
"April", | |||
"May", | |||
"June", | |||
"July", | |||
"August", | |||
"September", | |||
"October", | |||
"November", | |||
"December" | |||
] | |||
window.util = {}; | |||
util.notify = function notify(title, body) { | |||
@@ -69,8 +84,8 @@ | |||
var res = {}; | |||
return function(key, val) { | |||
if (key !== undefined) | |||
return function(key, val, err) { | |||
if (key) | |||
res[key] = val; | |||
if (n === 1) | |||
@@ -80,6 +95,25 @@ | |||
} | |||
} | |||
util.pad = function(str, length, padChar) { | |||
var missing = (length - str.length) + 1; | |||
if (missing <= 0) | |||
return str; | |||
return new Array(missing).join(padChar) + str; | |||
} | |||
util.dateToString = function(date) { | |||
var day = util.pad(date.getDate().toString(), 2, "0"); | |||
var month = months[date.getMonth()]; | |||
return day+". of "+month+" "+ | |||
date.getFullYear()+", "+ | |||
util.pad(date.getHours().toString(), 2, "0")+":"+ | |||
util.pad(date.getMinutes().toString(), 2, "0"); | |||
} | |||
window.display = {}; | |||
window.display.loggedIn = function() { | |||
@@ -88,6 +122,8 @@ | |||
return util.error(err); | |||
$("#navbar-profile-container").html(res.html); | |||
util.notify("Logged In", "You are now logged in."); | |||
}); | |||
} | |||
@@ -1,12 +1,9 @@ | |||
var fs = require("fs"); | |||
module.exports = function(ctx) { | |||
var id; | |||
try { | |||
id = ctx.req.url.split("?")[1].replace(/\..*/, ""); | |||
} catch (err) { | |||
var id = ctx.query.replace(/\..*/, ""); | |||
if (!id) | |||
return ctx.end(ctx.view("404")); | |||
} | |||
var readStream = fs.createReadStream(ctx.conf.dir.imgs+"/"+id); | |||
readStream.pipe(ctx.res); |
@@ -1,4 +1,4 @@ | |||
(function() { | |||
$(document).on("ready", function() { | |||
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { | |||
notify("Your Browser Sucks."); | |||
} | |||
@@ -21,9 +21,10 @@ | |||
var files = []; | |||
$("#uploader-input").on("change", function(evt) { | |||
console.log(evt); | |||
//Enable upload button | |||
$("#uploader-upload").removeAttr("disabled") | |||
$("#uploader-upload").removeAttr("disabled"); | |||
var inputFiles = evt.target.files; | |||
@@ -115,4 +116,4 @@ | |||
}); | |||
}); | |||
}); | |||
})(); | |||
}); |
@@ -0,0 +1,44 @@ | |||
module.exports = function(ctx) { | |||
var id = ctx.query; | |||
ctx.db.query( | |||
"SELECT name, date_created, id "+ | |||
"FROM collections "+ | |||
"WHERE user_id = $1", | |||
[id], | |||
function(err, res) { a("collections", res.rows, err) } | |||
); | |||
ctx.db.query( | |||
"SELECT username "+ | |||
"FROM users "+ | |||
"WHERE id = $1", | |||
[id], | |||
function(err, res) { a("users", res.rows, err) } | |||
); | |||
var a = ctx.async(2, function(err, res) { | |||
if (err) | |||
return ctx.fail(err); | |||
var user = res.users[0]; | |||
if (!user) | |||
return ctx.end(ctx.view("404")); | |||
var collections = ""; | |||
res.collections.forEach(function(row) { | |||
var d = new Date(row.date_created); | |||
collections += ctx.template("collection", { | |||
name: row.name, | |||
date_created: d.toString(), | |||
id: row.id | |||
}); | |||
}); | |||
ctx.end(ctx.view("profile", { | |||
username: user.username, | |||
collections: collections | |||
})); | |||
}); | |||
} |
@@ -0,0 +1,5 @@ | |||
$(document).on("ready", function() { | |||
$("#collections .date-created").each(function() { | |||
this.innerHTML = util.dateToString(new Date(this.innerHTML)); | |||
}); | |||
}); |
@@ -0,0 +1,9 @@ | |||
#profile { | |||
text-align: center; | |||
} | |||
#collections { | |||
text-align: left; | |||
width: auto; | |||
display: inline-block; | |||
} |
@@ -0,0 +1,34 @@ | |||
$(document).on("ready", function() { | |||
$("#register-form").on("submit", function(evt) { | |||
console.log(evt); | |||
evt.preventDefault(); | |||
evt.stopPropagation(); | |||
var username = $("#register-username").val(); | |||
var password = $("#register-password").val(); | |||
var password2 = $("#register-password-repeat").val(); | |||
if (password !== password2) | |||
return util.error("Paswords don't match."); | |||
if (!username) | |||
return util.error("You must supply a username."); | |||
if (!password) | |||
return util.error("You must supply a password."); | |||
util.api("account_create", { | |||
username: username, | |||
password: password | |||
}, function(err, res) { | |||
if (err) | |||
return util.error(err); | |||
display.loggedIn(); | |||
setTimeout(function() { | |||
location.href = "/profile?"+res.id; | |||
}, 1000); | |||
}); | |||
}); | |||
}); |
@@ -1,5 +1,5 @@ | |||
module.exports = function(ctx) { | |||
var id = parseInt(ctx.req.url.split("?")[1]); | |||
var id = parseInt(ctx.query); | |||
if (isNaN(id)) | |||
return ctx.end(ctx.view("404")); | |||
@@ -16,6 +16,9 @@ module.exports = function(ctx) { | |||
if (err) | |||
return ctx.fail(err); | |||
if (!res.rows[0]) | |||
return ctx.end(ctx.view("404")); | |||
var images = ""; | |||
res.rows.forEach(function(row) { | |||
images += ctx.template("image", { |