imgs | imgs | ||||
!imgs/.placeholder | !imgs/.placeholder | ||||
.currentRun | .currentRun | ||||
.sessions | |||||
favicon.ico | favicon.ico |
var formidable = require("formidable"); | var formidable = require("formidable"); | ||||
var crypto = require("crypto"); | var crypto = require("crypto"); | ||||
var zlib = require("zlib"); | var zlib = require("zlib"); | ||||
var fs = require("fs"); | |||||
var preprocess = require("./preprocess.js"); | var preprocess = require("./preprocess.js"); | ||||
var sessions = {}; | var sessions = {}; | ||||
var sessionTimeouts = {}; | |||||
if (fs.existsSync(".sessions")) { | |||||
sessions = JSON.parse(fs.readFileSync(".sessions", "utf8")); | |||||
//Set appropriate timeouts. | |||||
Object.keys(sessions).forEach(function(i) { | |||||
sessionTimeouts[i] = setTimeout(function() { | |||||
delete sessions[i]; | |||||
}, 1000*60*10); | |||||
}); | |||||
} | |||||
function saveSessions() { | |||||
fs.writeFileSync(".sessions", JSON.stringify(sessions)); | |||||
process.exit(); | |||||
} | |||||
process.on("SIGINT", saveSessions); | |||||
process.on("SIGTERM", saveSessions); | |||||
function templatify(str, args, ctx, env) { | function templatify(str, args, ctx, env) { | ||||
env.url = ctx.req.url; | env.url = ctx.req.url; | ||||
str = preprocess(str, { | |||||
var env = { | |||||
session: function(key) { | session: function(key) { | ||||
ctx.cachable = false; | ctx.cachable = false; | ||||
return ctx.session[key]; | return ctx.session[key]; | ||||
noescape: args, | noescape: args, | ||||
env: env, | env: env, | ||||
template: function(key) { | template: function(key) { | ||||
return ctx.template(key); | |||||
return ctx.template(key, args); | |||||
} | } | ||||
}); | |||||
} | |||||
str = preprocess(str, env); | |||||
return str; | return str; | ||||
} | } | ||||
sessions[key] = {}; | sessions[key] = {}; | ||||
this.res.setHeader("Set-Cookie", "session="+key); | this.res.setHeader("Set-Cookie", "session="+key); | ||||
//Delete session after a while | |||||
setTimeout(function() { | |||||
delete sessions[key]; | |||||
}, this.conf.session_timeout); | |||||
} | } | ||||
this.session = sessions[key]; | this.session = sessions[key]; | ||||
//Reset session delete timer | //Reset session delete timer | ||||
if (this.session._timeout) | |||||
clearTimeout(this.session._timeout); | |||||
if (sessionTimeouts[key]) | |||||
clearTimeout(sessionTimeouts[key]); | |||||
this.session._timeout = setTimeout(function() { | |||||
sessionTimeouts[key] = setTimeout(function() { | |||||
delete sessions[key]; | delete sessions[key]; | ||||
}, this.conf.session_timeout); | }, this.conf.session_timeout); | ||||
} | } |
"pg": "^4.4.0", | "pg": "^4.4.0", | ||||
"scrypt": "^4.0.7", | "scrypt": "^4.0.7", | ||||
"uglify-js": "^2.4.24", | "uglify-js": "^2.4.24", | ||||
"uglifycss": "0.0.15" | |||||
"uglifycss": "0.0.15", | |||||
"wrench": "^1.5.8" | |||||
} | } | ||||
} | } |
var fs = require("fs"); | var fs = require("fs"); | ||||
var domain = require("domain"); | var domain = require("domain"); | ||||
var zlib = require("zlib"); | var zlib = require("zlib"); | ||||
var wrench = require("wrench"); | |||||
var loader = require("./lib/loader.js"); | var loader = require("./lib/loader.js"); | ||||
var pg = require("pg"); | var pg = require("pg"); | ||||
var Context = require("./lib/context.js"); | var Context = require("./lib/context.js"); | ||||
"/api/template": "api/template.node.js", | "/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/collection_delete": "api/collection_delete.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", | ||||
db.query( | db.query( | ||||
"DELETE FROM collections "+ | "DELETE FROM collections "+ | ||||
"WHERE user_id IS NULL "+ | "WHERE user_id IS NULL "+ | ||||
"AND date_created < NOW() - INTERVAL '"+timeout+"'", | |||||
"AND date_created < NOW() - INTERVAL '"+timeout+"' "+ | |||||
"RETURNING id", | |||||
function(err, res) { | function(err, res) { | ||||
if (err) | if (err) | ||||
throw err; | throw err; | ||||
res.rows.forEach(function(row) { | |||||
wrench.rmdirSyncRecursive( | |||||
conf.dir.imgs+"/"+ | |||||
row.id | |||||
); | |||||
}); | |||||
if (res.rowCount > 0) { | if (res.rowCount > 0) { | ||||
console.log( | console.log( | ||||
"Deleted "+res.rowCount+" collections "+ | "Deleted "+res.rowCount+" collections "+ |
<div class="collection"> | |||||
<div class="collection" data-id="{{arg#id}}"> | |||||
<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> | <a class="name" href="/view?{{arg#id}}">{{arg#name}}</a> | ||||
{{arg#own_profile ? '<button class="delete btn btn-default">X</button>' : ''}} | |||||
{{arg#own_profile ? template#delete-button : ''}} | |||||
</div> | </div> |
<button class="delete btn btn-default" data-id="{{arg#id}}">X</button> |
<div class="image small-width bordered"> | <div class="image small-width bordered"> | ||||
<div class="title">{{arg#title}}</div> | <div class="title">{{arg#title}}</div> | ||||
<a href="/i?{{arg#id}}.{{arg#extension}}"> | |||||
<img class="img-rounded" src="/i?{{arg#id}}.{{arg#extension}}"> | |||||
<a href="/i?{{arg#collection}}/{{arg#id}}.{{arg#extension}}"> | |||||
<img class="img-rounded" src="/i?{{arg#collection}}/{{arg#id}}.{{arg#extension}}"> | |||||
</a> | </a> | ||||
<div class="description">{{arg#description}}</div> | <div class="description">{{arg#description}}</div> | ||||
<input class="url" type="text" value="{{conf#base_url}}/i?{{arg#id}}.{{arg#extension}}" onclick="select()"> | |||||
<input class="url" type="text" value="{{conf#base_url}}/i?{{arg#collection}}/{{arg#id}}.{{arg#extension}}" onclick="select()"> | |||||
</div> | </div> |
var fs = require("fs"); | |||||
module.exports = function(ctx) { | module.exports = function(ctx) { | ||||
ctx.getPostData(function(err, data) { | ctx.getPostData(function(err, data) { | ||||
if (err) | if (err) | ||||
if (err) | if (err) | ||||
return ctx.fail(err); | return ctx.fail(err); | ||||
ctx.session.collectionId = res.rows[0].id; | |||||
var id = res.rows[0].id; | |||||
fs.mkdir(ctx.conf.dir.imgs+"/"+id, function(err) { | |||||
if (err) | |||||
return ctx.fail(err); | |||||
ctx.session.lastCollectionId = id; | |||||
ctx.succeed({ | |||||
id: res.rows[0].id | |||||
ctx.succeed({ | |||||
id: id | |||||
}); | |||||
}); | }); | ||||
} | } | ||||
} | } |
var wrench = require("wrench"); | |||||
module.exports = function(ctx) { | |||||
ctx.getPostData(function(err, data) { | |||||
var id = parseInt(data.id); | |||||
if (isNaN(id)) | |||||
return ctx.fail("Invalid ID."); | |||||
if (id === ctx.session.lastCollectionId) | |||||
return deleteQuery(); | |||||
if (!ctx.session.loggedIn) | |||||
return ctx.fail("You're not logged in."); | |||||
ctx.db.query( | |||||
"SELECT FROM collections "+ | |||||
"WHERE user_id = $1", | |||||
[ctx.session.userId], | |||||
function(err, res) { | |||||
if (err) | |||||
return ctx.fail(err); | |||||
if (res.rows[0] === undefined) | |||||
return ctx.fail("You don't own that collection."); | |||||
deleteQuery(); | |||||
} | |||||
); | |||||
}); | |||||
function deleteQuery() { | |||||
ctx.db.query( | |||||
"DELETE FROM collections "+ | |||||
"WHERE id = $1", | |||||
[ctx.postData.data.id], | |||||
queryCallback | |||||
); | |||||
} | |||||
function queryCallback(err, res) { | |||||
if (err) | |||||
return ctx.fail(err); | |||||
try { | |||||
wrench.rmdirSyncRecursive( | |||||
ctx.conf.dir.imgs+"/"+ | |||||
ctx.postData.data.id | |||||
); | |||||
} catch (err) { | |||||
return ctx.fail(err); | |||||
} | |||||
ctx.succeed(); | |||||
} | |||||
} |
data.collectionId = parseInt(data.collectionId); | data.collectionId = parseInt(data.collectionId); | ||||
if (data.collectionId !== ctx.session.collectionId) | |||||
if (data.lastCollectionId !== ctx.session.collectionId) | |||||
return ctx.fail("You don't own that collection."); | return ctx.fail("You don't own that collection."); | ||||
//We want all extensions to be lower case. | //We want all extensions to be lower case. | ||||
ctx.db.query( | ctx.db.query( | ||||
"INSERT INTO images (name, description, extension, collection_id) "+ | "INSERT INTO images (name, description, extension, collection_id) "+ | ||||
"VALUES ($1, $2, $3, $4) "+ | "VALUES ($1, $2, $3, $4) "+ | ||||
"RETURNING id", | |||||
"RETURNING id, collection_id", | |||||
[data.name, data.description, data.extension, data.collectionId], | [data.name, data.description, data.extension, data.collectionId], | ||||
queryCallback | queryCallback | ||||
); | ); | ||||
return ctx.fail(err); | return ctx.fail(err); | ||||
var id = res.rows[0].id; | var id = res.rows[0].id; | ||||
var collectionId = res.rows[0].collection_id; | |||||
var file = ctx.postData.files.file; | var file = ctx.postData.files.file; | ||||
var readStream = fs.createReadStream(file.path); | var readStream = fs.createReadStream(file.path); | ||||
var writeStream = fs.createWriteStream(ctx.conf.dir.imgs+"/"+id); | |||||
var writeStream = fs.createWriteStream( | |||||
ctx.conf.dir.imgs+"/"+ | |||||
collectionId+"/"+ | |||||
id | |||||
); | |||||
readStream.pipe(writeStream); | readStream.pipe(writeStream); | ||||
readStream.on("end", function() { | readStream.on("end", function() { |
border: 1px solid #CCC; | border: 1px solid #CCC; | ||||
border-radius: 4px; | border-radius: 4px; | ||||
padding: 10px !important; | padding: 10px !important; | ||||
margin-bottom: 10px; | |||||
} | } | ||||
.title { | .title { |
var fs = require("fs"); | var fs = require("fs"); | ||||
module.exports = function(ctx) { | module.exports = function(ctx) { | ||||
var id = ctx.query.replace(/\..*/, ""); | |||||
if (!id) | |||||
var q = ctx.query.replace(/\..*/, ""); | |||||
var collection = parseInt(q.split("/")[0]); | |||||
var id = parseInt(q.split("/")[1]); | |||||
if (!id || !collection) | |||||
return ctx.err404(); | return ctx.err404(); | ||||
ctx.res.setHeader( | ctx.res.setHeader( | ||||
"public, max-age="+ctx.conf.cache_max_age_images | "public, max-age="+ctx.conf.cache_max_age_images | ||||
); | ); | ||||
var readStream = fs.createReadStream(ctx.conf.dir.imgs+"/"+id); | |||||
var readStream = fs.createReadStream( | |||||
ctx.conf.dir.imgs+"/"+ | |||||
collection+"/"+ | |||||
id | |||||
); | |||||
readStream.pipe(ctx.res); | readStream.pipe(ctx.res); | ||||
readStream.on("error", function(err){ | readStream.on("error", function(err){ |
var collectionId = res.id; | var collectionId = res.id; | ||||
//Go to collection once files are uploaded | |||||
//Go to collection once files are uploaded, or | |||||
//delete the collection if it failed | |||||
var a = util.async(files.length, function(res) { | var a = util.async(files.length, function(res) { | ||||
if (res.error) | |||||
util.redirect("/", 5000); | |||||
else | |||||
if (res.error) { | |||||
util.api("collection_delete", { | |||||
id: collectionId | |||||
}, function(err, res) { | |||||
if (err) | |||||
return util.error(err); | |||||
util.redirect("/", 5000); | |||||
}); | |||||
} else { | |||||
util.redirect("/view?"+collectionId); | util.redirect("/view?"+collectionId); | ||||
} | |||||
}); | }); | ||||
//Loop through files, uploading them | //Loop through files, uploading them |
$("#collections .date-created").each(function() { | $("#collections .date-created").each(function() { | ||||
this.innerHTML = util.dateToString(new Date(this.innerHTML)); | this.innerHTML = util.dateToString(new Date(this.innerHTML)); | ||||
}); | }); | ||||
var collections = []; | |||||
$("#collections .collection").each(function() { | |||||
collections[this.getAttribute("data-id")] = $(this); | |||||
}); | |||||
$("#collections .delete").on("click", function(evt) { | |||||
var id = evt.target.getAttribute("data-id"); | |||||
var collection = collections[id]; | |||||
var name = collection.children(".name").html(); | |||||
if (!confirm("Are you sure you want to delete collection "+name+"?")) | |||||
return; | |||||
util.api("collection_delete", { | |||||
id: id | |||||
}, function(err, res) { | |||||
if (err) | |||||
return util.error(err); | |||||
collection.remove(); | |||||
}); | |||||
}); | |||||
}); | }); |
} | } | ||||
.collection .date-created { | .collection .date-created { | ||||
width: 45%; | |||||
width: 190px; | |||||
} | } | ||||
.collection .name { | .collection .name { | ||||
width: calc(55% - 43px); | |||||
width: calc(100% - 43px - 190px); | |||||
} | } |
res.rows.forEach(function(row) { | res.rows.forEach(function(row) { | ||||
images += ctx.template("image", { | images += ctx.template("image", { | ||||
title: row.name, | title: row.name, | ||||
collection: id, | |||||
id: row.id, | id: row.id, | ||||
extension: row.extension, | extension: row.extension, | ||||
description: row.description | description: row.description |