Parcourir la source

lots of new things, including settings and some CSS

master
mort il y a 8 ans
Parent
révision
a5d83fe6ad

+ 3
- 0
conf.json.example Voir le fichier

"title": "Spus", "title": "Spus",
"base_url": "http://example.com" "base_url": "http://example.com"
}, },
"scrypt": {
"maxtime": 1
},
"minify": true, "minify": true,
"session_timeout": 1800000, "session_timeout": 1800000,
"dir": { "dir": {

+ 26
- 5
lib/context.js Voir le fichier



str = preprocess(str, { str = preprocess(str, {
session: ctx.session, session: ctx.session,
arg: args,
arg: function(key) {
return ctx.htmlEntities(args[key]);
},
noescape: args,
env: env, env: env,
template: function(key) { template: function(key) {
return ctx.template(key); return ctx.template(key);
}.bind(this)); }.bind(this));


//Handle sessions //Handle sessions
var key;
if (sessions[this.cookies.session]) { if (sessions[this.cookies.session]) {
this.session = sessions[this.cookies.session];
key = this.cookies.session;
} else { } else {
var key;
do { do {
key = crypto.randomBytes(64).toString("hex"); key = crypto.randomBytes(64).toString("hex");
} while (sessions[key]); } while (sessions[key]);
setTimeout(function() { setTimeout(function() {
delete sessions[key]; delete sessions[key];
}, this.conf.session_timeout); }, this.conf.session_timeout);

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

//Reset session delete timer
if (this.session._timeout)
clearTimeout(this.session._timeout);

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


module.exports.prototype = { module.exports.prototype = {
else else
n -= 1; n -= 1;
} }
},

htmlEntities: function(arg) {
if (typeof arg === "string") {
return arg.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
} else {
return arg;
}
} }
} }

+ 7
- 1
server.js Voir le fichier

"/profile/style.css": "profile/style.css", "/profile/style.css": "profile/style.css",
"/profile/script.js": "profile/script.js", "/profile/script.js": "profile/script.js",


//Settings
"/settings": "settings/index.node.js",
"/settings/style.css": "settings/style.css",
"/settings/script.js": "settings/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",
"/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_create": "api/account_create.node.js",
"/api/account_login": "api/account_login.node.js", "/api/account_login": "api/account_login.node.js",
"/api/account_logout": "api/account_logout.node.js"
"/api/account_logout": "api/account_logout.node.js",
"/api/account_change_password": "api/account_change_password.node.js"
} }


var loaded = loader.load(endpoints, conf); var loaded = loader.load(endpoints, conf);

+ 1
- 1
templates/collection.html Voir le fichier

<div class="collection"> <div class="collection">
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a>
<span class="date-created">{{arg#date_created}}</span> <span class="date-created">{{arg#date_created}}</span>
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a>
</div> </div>

+ 1
- 1
templates/navbar-loggedin.html Voir le fichier

<li> <li>
<a id="navbar-button" href="/profile?{{session#userId}}">{{session#username}}</a>
<a id="navbar-button" href="/settings">{{session#username}}</a>
</li> </li>

+ 6
- 3
views/404.html Voir le fichier

<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8">
{{template#head}}
</head> </head>
<body> <body>
404, file not found.<br>
{{env#url}}
{{template#body}}
<div class="container">
<div class="title">404 not found.</div>
{{env#url}}
</div>
</body> </body>
</html> </html>

+ 2
- 1
views/index.html Voir le fichier

<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>


<button class="btn btn-default" onclick="$('#uploader-input').click()">
<button class="btn btn-default" onclick="$('#uploader-input').click()" id="uploader-select-files">
Select Files Select Files
</button> </button>
<input type="text" id="uploader-collection-name" placeholder="Collection">
<button class="btn btn-default" id="uploader-upload" disabled> <button class="btn btn-default" id="uploader-upload" disabled>
Upload Upload
</button> </button>

+ 4
- 4
views/profile.html Voir le fichier

<body> <body>
{{template#body}} {{template#body}}


<div id="profile" class="container">
<div id="collections" class="container">
<div class="name">{{arg#username}}</div>
{{arg#collections}}
<div id="profile" class="container small-width-container">
<div id="collections" class="container small-width">
<div class="title">{{arg#username}}</div>
{{noescape#collections}}
</div> </div>
</div> </div>
</body> </body>

+ 2
- 2
views/register.html Voir le fichier

<body> <body>
{{template#body}} {{template#body}}


<div class="container" id="register">
<form id="register-form">
<div id="register" class="container small-width-container">
<form id="register-form" class="container small-width">
<div class="form-group"> <div class="form-group">
<label>Username<br> <label>Username<br>
<input type="text" id="register-username"> <input type="text" id="register-username">

+ 42
- 0
views/settings.html Voir le fichier

<!DOCTYPE html>
<html>
<head>
{{template#head}}
</head>
<body>
{{template#body}}

<div id="settings" class="container small-width-container">
<a class="title" href="/profile?{{session#userId}}">{{session#username}}</a>

<form id="password-form" class="container small-width">
<div class="title">Change Password</div>
<div class="form-group">
<label>Old Password<br>
<input type="password" id="password-old">
</label>
</div>
<div class="form-group">
<label>New Password<br>
<input type="password" id="password-new">
</label>
</div>
<div class="form-group">
<label>Repeat Password<br>
<input type="password" id="password-repeat">
</label>
</div>
<div class="submit-container">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>

<form id="logout-form" class="container small-width">
<div class="title">Log Out</div>
<div class="submit-container">
<button type="submit" class="btn btn-default">Log Out</button>
</div>
</form>
</div>
</body>
</html>

+ 1
- 1
views/view.html Voir le fichier

{{template#body}} {{template#body}}


<div id="viewer" class="container"> <div id="viewer" class="container">
{{arg#images}}
{{noescape#images}}
</div> </div>
</body> </body>
</html> </html>

+ 68
- 0
web/api/account_change_password.node.js Voir le fichier

var scrypt = require("scrypt");

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

if (!data.oldPassword || !data.newPassword)
return ctx.fail("You must provide passwords.");

if (!ctx.session.loggedIn)
return ctx.fail("You're not logged in.");

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

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

var user = res.rows[0];

if (!user)
return ctx.fail("User doesn't exist.");

scrypt.verify(
new Buffer(user.pass_hash, "hex"),
new Buffer(ctx.postData.data.oldPassword),
function(err, success) {
if (!success)
return ctx.fail("Wrong password.");

updatePassword();
}
);
}

function updatePassword() {
var params = scrypt.params(ctx.conf.scrypt.maxtime);
scrypt.hash(
new Buffer(ctx.postData.data.newPassword),
params,
function(err, hash) {
if (err)
return ctx.fail(err);

ctx.db.query(
"UPDATE users "+
"SET pass_hash = $1 "+
"WHERE id = $2",
[hash.toString("hex"), ctx.session.userId],
function(err, res) {
if (err)
return ctx.fail(err);

ctx.succeed();
}
);
}
);
}
}

+ 5
- 2
web/api/account_create.node.js Voir le fichier

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


var params = scrypt.params(1);
if (!/^[a-zA-Z0-9_\-]+$/.test(data.username))
return ctx.fail("Username contains illegal characters.");

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


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

+ 34
- 1
web/global.css Voir le fichier

margin-bottom: 0px; margin-bottom: 0px;
} }


.navbar form {
border: none;
}

#login-dropdown .dropdown-menu { #login-dropdown .dropdown-menu {
padding: 10px; padding: 10px;
} }
text-align: right; text-align: right;
} }
form .submit-container .btn { form .submit-container .btn {
width: 75px;
width: 100%;
}
form input[type="text"],
form input[type="password"] {
width: 100%;
}
form label {
width: 100%;
}
form.container {
border: 1px solid #CCC;
border-radius: 4px;
padding: 10px !important;
margin-bottom: 10px;
}

.title {
font-weight: bold;
font-size: 1.2em;
margin-bottom: 6px;
}

.small-width-container {
text-align: center !important;
}
.small-width {
width: auto !important;
text-align: left !important;
max-width: 400px !important;
width: 90% !important;
} }


#notify-box { #notify-box {

+ 29
- 5
web/global.js Voir le fichier

util.htmlEntities = function(str) { util.htmlEntities = function(str) {
return str.replace(/&/g, "&amp;") return str.replace(/&/g, "&amp;")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
.replace(/>/g, "&lt;")
.replace(/"/g, "&quot");
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
} }


util.api = function(name, data, cb, getXhr) { util.api = function(name, data, cb, getXhr) {
getXhr(xhr); getXhr(xhr);


return xhr; return xhr;
},
error: function(xhr, status, err) {
cb(err);
} }
}).done(function(res) { }).done(function(res) {
console.log("response from "+name+":"); console.log("response from "+name+":");
util.pad(date.getMinutes().toString(), 2, "0"); util.pad(date.getMinutes().toString(), 2, "0");
} }


util.prevent = function(evt) {
evt.preventDefault();
evt.stopPropagation();
}

util.redirect = function(url, timeout) {
setTimeout(function() {
location.href = url;
}, timeout || 1000);
}

window.display = {}; window.display = {};


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


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

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

util.notify("Logged Out", "You are now logged out.");
});
}

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


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

+ 9
- 6
web/index/script.js Voir le fichier



//First, create a collection //First, create a collection
util.api("collection_create", { util.api("collection_create", {
name: "New Collection"
name: ($("#uploader-collection-name").val() || "Collection")
}, function(err, res) { }, function(err, res) {
if (err) if (err)
return util.error(err); return util.error(err);
var collectionId = res.id; var collectionId = res.id;


//Go to collection once files are uploaded //Go to collection once files are uploaded
var a = util.async(files.length, function() {
setTimeout(function() {
location.href = "/view?"+collectionId;
}, 1000);
var a = util.async(files.length, function(res) {
if (res.error)
util.redirect("/", 5000);
else
util.redirect("/view?"+collectionId);
}); });


//Loop through files, uploading them //Loop through files, uploading them
collectionId: collectionId, collectionId: collectionId,
file: f file: f
}, function(err, res) { }, function(err, res) {
if (err)
if (err) {
a("error", true);
return util.error(err); return util.error(err);
}


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

+ 13
- 0
web/index/style.css Voir le fichier

#uploader-upload { #uploader-upload {
float: right; float: right;
} }

#uploader-select-files, #uploader-upload {
width: 100px;
}
#uploader-select-files {
float: left;
}
#uploader-collection-name {
width: calc(100% - 200px);
border: 1px solid #CCC;
border-radius: 4px;
padding: 6px 12px;
}

+ 7
- 4
web/profile/index.node.js Voir le fichier

"FROM collections "+ "FROM collections "+
"WHERE user_id = $1", "WHERE user_id = $1",
[id], [id],
function(err, res) { a("collections", res.rows, err) }
function(err, res) { a("collections", res, err) }
); );


ctx.db.query( ctx.db.query(
"FROM users "+ "FROM users "+
"WHERE id = $1", "WHERE id = $1",
[id], [id],
function(err, res) { a("users", res.rows, err) }
function(err, res) { a("users", res, err) }
); );


var a = ctx.async(2, function(err, res) { var a = ctx.async(2, function(err, res) {
if (err) if (err)
return ctx.fail(err); return ctx.fail(err);


var user = res.users[0];
if (!res.collections || !res.users)
return ctx.end(ctx.view("404"));

var user = res.users.rows[0];
if (!user) if (!user)
return ctx.end(ctx.view("404")); return ctx.end(ctx.view("404"));


var collections = ""; var collections = "";
res.collections.forEach(function(row) {
res.collections.rows.forEach(function(row) {
var d = new Date(row.date_created); var d = new Date(row.date_created);


collections += ctx.template("collection", { collections += ctx.template("collection", {

+ 0
- 9
web/profile/style.css Voir le fichier

#profile {
text-align: center;
}

#collections {
text-align: left;
width: auto;
display: inline-block;
}

+ 2
- 6
web/register/script.js Voir le fichier

$(document).on("ready", function() { $(document).on("ready", function() {
$("#register-form").on("submit", function(evt) { $("#register-form").on("submit", function(evt) {
console.log(evt);
evt.preventDefault();
evt.stopPropagation();
util.prevent(evt);


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


display.loggedIn(); display.loggedIn();


setTimeout(function() {
location.href = "/profile?"+res.id;
}, 1000);
util.redirect("/settings");
}); });
}); });
}); });

+ 0
- 8
web/register/style.css Voir le fichier

#register-form {
text-align: left;
display: inline-block;
}

#register {
text-align: center;
}

+ 6
- 0
web/settings/index.node.js Voir le fichier

module.exports = function(ctx) {
if (!ctx.session.loggedIn)
return ctx.end(ctx.view("404"));

ctx.end(ctx.view("settings"));
}

+ 36
- 0
web/settings/script.js Voir le fichier

$(document).on("ready", function() {
$("#password-form").on("submit", function(evt) {
util.prevent(evt);

var oldPass = $("#password-old").val();
var newPass = $("#password-new").val();
var repeatPass = $("#password-repeat").val();

if (newPass !== repeatPass)
return util.error("Passwords don't match.");

util.api("account_change_password", {
oldPassword: oldPass,
newPassword: newPass
}, function(err, res) {
if (err)
return util.error(err);

util.notify("Password changed!");
});
});

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

util.api("account_logout", {}, function(err, res) {
if (err)
return util.error(err);

display.logIn();

util.redirect("/");
});
});
});

+ 0
- 0
web/settings/style.css Voir le fichier


Chargement…
Annuler
Enregistrer