var sessions = {}; | var sessions = {}; | ||||
function templatify(str, args, ctx) { | |||||
function templatify(str, args, ctx, env) { | |||||
env.url = ctx.req.url; | |||||
str = preprocess(str, { | str = preprocess(str, { | ||||
session: ctx.session, | session: ctx.session, | ||||
arg: args, | |||||
env: env, | |||||
template: function(key) { | template: function(key) { | ||||
return ctx.template(key); | return ctx.template(key); | ||||
} | } | ||||
}); | }); | ||||
if (args == undefined) | |||||
return str; | |||||
for (var i in args) { | |||||
str = str.split("{{"+i+"}}").join(args[i]); | |||||
} | |||||
return str; | return str; | ||||
} | } | ||||
this.views = options.views; | this.views = options.views; | ||||
this.db = options.db; | this.db = options.db; | ||||
this.conf = options.conf; | this.conf = options.conf; | ||||
this.query = this.req.url.split("?")[1] || ""; | |||||
if (this.conf.debug) | |||||
this.startTime = new Date(); | |||||
//Handle cookies | //Handle cookies | ||||
this.cookies = {}; | this.cookies = {}; | ||||
module.exports.prototype = { | module.exports.prototype = { | ||||
end: function(str) { | 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) | if (this.statusCode) | ||||
this.res.writeHead(this.statusCode); | this.res.writeHead(this.statusCode); | ||||
if (!str) | if (!str) | ||||
throw new Error("No such template: "+name); | throw new Error("No such template: "+name); | ||||
return templatify(str, args, this); | |||||
return templatify(str, args, this, {template: name}); | |||||
}, | }, | ||||
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, this); | |||||
return templatify(str, args, this, {view: name}); | |||||
}, | }, | ||||
getPostData: function(cb) { | getPostData: function(cb) { | ||||
this.session.loggedIn = false; | this.session.loggedIn = false; | ||||
delete this.session.username; | delete this.session.username; | ||||
delete this.session.userId; | 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; | |||||
} | |||||
} | } | ||||
} | } |
"/register/style.css": "register/style.css", | "/register/style.css": "register/style.css", | ||||
"/register/script.js": "register/script.js", | "/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 | //Viewer | ||||
"/view": "view/index.node.js", | "/view": "view/index.node.js", | ||||
"/view/style.css": "view/style.css", | "/view/style.css": "view/style.css", | ||||
//Function to run on each request | //Function to run on each request | ||||
function onRequest(req, res) { | function onRequest(req, res) { | ||||
console.log("Request for "+req.url); | |||||
var ctx = new Context({ | var ctx = new Context({ | ||||
req: req, | req: req, | ||||
res: res, | res: res, | ||||
console.trace(err); | 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]); | |||||
} | |||||
}); |
<div class="collection"> | |||||
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a> | |||||
<span class="date-created">{{arg#date_created}}</span> | |||||
</div> |
<title>{{conf#title}}</title> | <title>{{conf#title}}</title> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<meta name="viewport" content="width=device-width"> | <meta name="viewport" content="width=device-width"> | ||||
<link rel="stylesheet" href="/global.css"> | <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="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://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="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> | ||||
<script src="/global.js"></script> | <script src="/global.js"></script> | ||||
<script src="/{{env#view}}/script.js"></script> |
<div class="image"> | <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> | </div> |
<li> | <li> | ||||
<a href="/profile?{{session#userId}}">{{session#username}}</a> | |||||
<a id="navbar-button" href="/profile?{{session#userId}}">{{session#username}}</a> | |||||
</li> | </li> |
<li class="dropdown" id="login-dropdown"> | <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 | Log In | ||||
<b class="caret"></b> | <b class="caret"></b> | ||||
</a> | </a> |
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
</head> | </head> | ||||
<body> | <body> | ||||
404, file not found. | |||||
404, file not found.<br> | |||||
{{env#url}} | |||||
</body> | </body> | ||||
</html> | </html> |
<html> | <html> | ||||
<head> | <head> | ||||
{{template#head}} | {{template#head}} | ||||
<link rel="stylesheet" href="/index/style.css"> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
{{template#global}} | |||||
{{template#body}} | |||||
<div id="uploader" class="container"> | <div id="uploader" class="container"> | ||||
<input type="file" accept="image/*" id="uploader-input" class="hidden" multiple> | <input type="file" accept="image/*" id="uploader-input" class="hidden" multiple> | ||||
<ul class="list-group" id="uploader-list"></ul> | <ul class="list-group" id="uploader-list"></ul> | ||||
</div> | </div> | ||||
<script src="/index/script.js"></script> | |||||
</body> | </body> | ||||
</html> | </html> |
<!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> |
<html> | <html> | ||||
<head> | <head> | ||||
{{template#head}} | {{template#head}} | ||||
<link rel="stylesheet" href="/register/style.css"> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
{{template#global}} | |||||
{{template#body}} | |||||
<div class="container" id="register"> | <div class="container" id="register"> | ||||
<form id="register-form"> | <form id="register-form"> | ||||
</div> | </div> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<label>Repeat Password<br> | <label>Repeat Password<br> | ||||
<input type="psasword" id="register-password-repeat"> | |||||
<input type="password" id="register-password-repeat"> | |||||
</label> | </label> | ||||
</div> | </div> | ||||
<div class="submit-container"> | <div class="submit-container"> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
</div> | </div> | ||||
<script src="/index/script.js"></script> | |||||
</body> | </body> | ||||
</html> | </html> |
<html> | <html> | ||||
<head> | <head> | ||||
{{template#head}} | {{template#head}} | ||||
<link rel="stylesheet" href="/view/style.css"> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
{{template#global}} | |||||
{{template#body}} | |||||
<div id="viewer" class="container"> | <div id="viewer" class="container"> | ||||
{{images}} | |||||
{{arg#images}} | |||||
</div> | </div> | ||||
<script src="/index/script.js"></script> | |||||
</body> | </body> | ||||
</html> | </html> |
if (err) | if (err) | ||||
return ctx.fail(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({ | ctx.succeed({ | ||||
id: res.rows[0].id | id: res.rows[0].id |
var user = res.rows[0]; | var user = res.rows[0]; | ||||
ctx.login(user.username, user.id); | |||||
if (!user) | if (!user) | ||||
return ctx.fail("Wrong username or password."); | return ctx.fail("Wrong username or password."); | ||||
ctx.login(user.username, user.id); | |||||
scrypt.verify( | scrypt.verify( | ||||
new Buffer(user.pass_hash, "hex"), | new Buffer(user.pass_hash, "hex"), | ||||
new Buffer(ctx.postData.data.password), | new Buffer(ctx.postData.data.password), |
return ctx.fail(err); | return ctx.fail(err); | ||||
ctx.db.query( | ctx.db.query( | ||||
"INSERT INTO collections (name) "+ | |||||
"VALUES ($1) "+ | |||||
"INSERT INTO collections (name, user_id) "+ | |||||
"VALUES ($1, $2) "+ | |||||
"RETURNING id", | "RETURNING id", | ||||
[data.name], | |||||
[data.name, ctx.session.userId], | |||||
queryCallback | queryCallback | ||||
); | ); | ||||
}); | }); |
module.exports = function(ctx) { | module.exports = function(ctx) { | ||||
var name = ctx.req.url.split("?")[1]; | |||||
var name = ctx.query; | |||||
if (!name) | if (!name) | ||||
return ctx.fail("You must supply a template name."); | return ctx.fail("You must supply a template name."); | ||||
padding: 10px; | padding: 10px; | ||||
} | } | ||||
#navbar-button { | |||||
float: right; | |||||
} | |||||
#login-dropdown label, | #login-dropdown label, | ||||
#login-dropdown input { | #login-dropdown input { | ||||
width: 100%; | width: 100%; |
(function() { | (function() { | ||||
var months = [ | |||||
"January", | |||||
"February", | |||||
"March", | |||||
"April", | |||||
"May", | |||||
"June", | |||||
"July", | |||||
"August", | |||||
"September", | |||||
"October", | |||||
"November", | |||||
"December" | |||||
] | |||||
window.util = {}; | window.util = {}; | ||||
util.notify = function notify(title, body) { | util.notify = function notify(title, body) { | ||||
var res = {}; | var res = {}; | ||||
return function(key, val) { | |||||
if (key !== undefined) | |||||
return function(key, val, err) { | |||||
if (key) | |||||
res[key] = val; | res[key] = val; | ||||
if (n === 1) | if (n === 1) | ||||
} | } | ||||
} | } | ||||
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 = {}; | ||||
window.display.loggedIn = function() { | window.display.loggedIn = function() { | ||||
return util.error(err); | return util.error(err); | ||||
$("#navbar-profile-container").html(res.html); | $("#navbar-profile-container").html(res.html); | ||||
util.notify("Logged In", "You are now logged in."); | |||||
}); | }); | ||||
} | } | ||||
var fs = require("fs"); | var fs = require("fs"); | ||||
module.exports = function(ctx) { | 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")); | return ctx.end(ctx.view("404")); | ||||
} | |||||
var readStream = fs.createReadStream(ctx.conf.dir.imgs+"/"+id); | var readStream = fs.createReadStream(ctx.conf.dir.imgs+"/"+id); | ||||
readStream.pipe(ctx.res); | readStream.pipe(ctx.res); |
(function() { | |||||
$(document).on("ready", function() { | |||||
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { | if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { | ||||
notify("Your Browser Sucks."); | notify("Your Browser Sucks."); | ||||
} | } | ||||
var files = []; | var files = []; | ||||
$("#uploader-input").on("change", function(evt) { | $("#uploader-input").on("change", function(evt) { | ||||
console.log(evt); | |||||
//Enable upload button | //Enable upload button | ||||
$("#uploader-upload").removeAttr("disabled") | |||||
$("#uploader-upload").removeAttr("disabled"); | |||||
var inputFiles = evt.target.files; | var inputFiles = evt.target.files; | ||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
})(); | |||||
}); |
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 | |||||
})); | |||||
}); | |||||
} |
$(document).on("ready", function() { | |||||
$("#collections .date-created").each(function() { | |||||
this.innerHTML = util.dateToString(new Date(this.innerHTML)); | |||||
}); | |||||
}); |
#profile { | |||||
text-align: center; | |||||
} | |||||
#collections { | |||||
text-align: left; | |||||
width: auto; | |||||
display: inline-block; | |||||
} |
$(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); | |||||
}); | |||||
}); | |||||
}); |
module.exports = function(ctx) { | module.exports = function(ctx) { | ||||
var id = parseInt(ctx.req.url.split("?")[1]); | |||||
var id = parseInt(ctx.query); | |||||
if (isNaN(id)) | if (isNaN(id)) | ||||
return ctx.end(ctx.view("404")); | return ctx.end(ctx.view("404")); | ||||
if (err) | if (err) | ||||
return ctx.fail(err); | return ctx.fail(err); | ||||
if (!res.rows[0]) | |||||
return ctx.end(ctx.view("404")); | |||||
var images = ""; | var images = ""; | ||||
res.rows.forEach(function(row) { | res.rows.forEach(function(row) { | ||||
images += ctx.template("image", { | images += ctx.template("image", { |