| @@ -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", { | |||