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