var _ = require("lodash"); var db = require("../db"); var util = require("../util/util"); var upload = require("../util/upload"); var bucky = (module.exports = { /* INDEX */ ensureLatestThreads: function (req, res, next) { db.getLatestThreads().then(function (threads) { res.threads = threads; res.threads_ids = res.threads.pluck("id").sort(); res.keywords = _.uniq(res.threads.pluck("keyword")); next(); }); }, ensureCommentCountsForThreads: function (req, res, next) { db.getCommentCounts(res.threads_ids).then(function (counts) { var lookup = {}; counts.forEach(function (c) { lookup[c.thread] = c; }); res.threads.forEach(function (thread) { if (lookup[thread.id]) { thread.set("comment_count", lookup[thread.id].count); } }); next(); }); }, ensureFileCountsForThreads: function (req, res, next) { db.getFileSizes(res.threads_ids).then(function (counts) { var lookup = {}; counts.forEach(function (c) { lookup[c.thread] = c; }); res.threads.forEach(function (t) { var c = lookup[t.id]; t.set("file_count", c ? c.count : 0); t.set("size", c ? c.size : 0); }); next(); }); }, ensureKeywordsForThreads: function (req, res, next) { db.getKeywords(res.keywords).then(function (keywords) { var lookup = {}; keywords.forEach(function (k) { lookup[k.get("keyword")] = k; }); res.threads.forEach(function (t) { var kw = t.get("keyword"); if (!kw) return; var k = lookup[kw]; if (!k) return; if (!t.get("color")) { t.set("color", k.get("color")); } }); next(); }); }, ensureHootbox: function (req, res, next) { db.getCommentsForThread(1, 20, 0, "desc").then(function (hootbox) { res.hootbox = hootbox; next(); }); }, ensureHootstream: function (req, res, next) { const query = { thread: parseInt(req.query?.thread) || null, keyword: req.query?.keyword || null, username: req.query?.username || null, limit: req.query?.limit || 20, offset: req.query?.offset || 0, }; query.has_query = query.thread || query.keyword || query.username; Promise.all([ db.getHootstreamFiles(query), db.getHootstreamComments(query), ]).then(([files, comments]) => { db.getHootstreamThreads({ files, comments }).then((threads) => { res.query = query; res.files = files; res.comments = comments.map((comment) => { comment.comment = comment.comment.toString(); return comment; }); res.threads = threads; res.threads_ids = res.threads.pluck("id").sort(); res.keywords = _.uniq(res.threads.pluck("keyword")); next(); }); }); }, ensureLastlog: function (req, res, next) { db.getLastlog(5).then(function (lastlog) { res.lastlog = lastlog; next(); }); }, createThread: function (req, res, next) { if (!req.body.title || !req.body.title.length) { res.json({ error: "no title" }); return; } var data = { title: req.body.title, keyword: req.body.keyword, username: req.user.get("username"), createdate: util.now(), lastmodified: util.now(), size: 0, privacy: false, color: req.body.color, viewed: 0, revision: 0, }; db.createThread(data).then(function (thread) { res.thread = thread; next(); }); }, /* DETAILS */ ensureThread: function (req, res, next) { var id = req.params.id.replace(/\D/g, ""); if (!id) { return res.sendStatus(404); } db.getThread(id).then(function (thread) { if (thread) { res.thread = thread; next(); } else { res.sendStatus(404); } }); }, ensureThreadById: function (req, res, next) { var id = req.params.thread_id.replace(/\D/g, ""); if (!id) { return res.sendStatus(404); } db.getThread(id).then(function (thread) { if (thread) { res.thread = thread; next(); } else { res.sendStatus(404); } }); }, prepareThread: function (req, res, next) { var thread = res.thread; if (thread) { var settings; try { settings = JSON.parse(thread.get("settings") || "{}"); } catch (e) { settings = {}; } res.thread.set("settings", settings); } next(); }, ensureCommentThread: function (req, res, next) { if (!res.comment) { return res.sendStatus(404); } var id = res.comment.get("thread"); if (!id) { return res.sendStatus(404); } db.getThread(id).then(function (thread) { if (thread) { res.thread = thread; next(); } else { res.sendStatus(404); } }); }, ensureKeywordForThread: function (req, res, next) { var keyword = res.thread.get("keyword"); if (!keyword) return next(); db.getKeyword(keyword).then(function (keyword) { res.keyword = keyword; next(); }); }, ensureCommentsForThread: function (req, res, next) { db.getCommentsForThread(res.thread.get("id")).then(function (comments) { res.comments = comments || []; next(); }); }, ensureFilesForThread: function (req, res, next) { db.getFilesForThread(res.thread.get("id")).then(function (files) { res.files = files || []; next(); }); }, bumpViewCount: function (req, res, next) { res.thread.set("viewed", res.thread.get("viewed") + 1); res.thread.save().then(() => next()); }, bumpThreadRevisions: function (req, res, next) { // don't bump the hootbox! if (res.thread.get("id") == 1) { return next(); } res.thread.set("revision", res.thread.get("revision") + 1); res.thread.set("lastmodified", util.now()); res.thread.save().then(() => next()); }, updateThreadSettings: function (req, res, next) { var title = util.sanitize(req.body.title || ""); if (!title || !title.length) { return res.sendStatus(500); } var keyword = util.sanitize(req.body.keyword || ""); var privacy = parseInt(req.body.privacy) || 0; var allowed = util.sanitize(req.body.allowed || ""); var settings; if (typeof req.body.settings === "object") { try { settings = JSON.stringify(req.body.settings); } catch (e) { console.error("JSON error in thread settings!!!!"); return res.sendStatus(500); } } if (!settings) { return res.sendStatus(500); } res.thread.set("title", title); res.thread.set("keyword", keyword); res.thread.set("color", util.sanitize(req.body.color || "blue")); res.thread.set("revision", res.thread.get("revision") + 1); res.thread.set("settings", settings); res.thread.set("privacy", privacy); res.thread.set("allowed", allowed); res.thread .save() .then(() => next()) .catch((err) => { console.error(err); next(); }); }, buryThread: function (req, res, next) { res.thread.set("lastmodified", util.now() - 14 * 86400); res.thread.save().then(() => next()); }, // ensureInterestedUsers: function(req, res, next){ // // given a thread, find people who might be interested in it // // - other people who have been in threads with you // // - other people who have posted on the keyword // // for now though, just show the last 20 people who have logged in.. // db.getLastlog(21).then( (users) => { // res.interestedUsers = users // next() // }).catch( () => { // res.interestedUsers = [] // next() // }) // }, // ensureThreadUsers: function(req, res, next) { // db.getThreadUsers(res.thread.get('id')).then(thread_users => { // res.thread_users = thread_users // next() // }) // }, checkUsernames: function (req, res, next) { if (!req.body.usernames) return res.sendStatus(500); db.checkUsernames(req.body.usernames) .then((users) => { res.usernames = users.map((user) => user.username); next(); }) .catch((err) => { console.log(err); res.usernames = []; next(); }); }, destroyThread: function (req, res, next) { console.log(">>> destroying thread", res.thread.get("id")); var commentPromises = res.comments.map((comment) => { return comment.destroy(); }); var filePromises = db.destroyFiles(res.files); var threadPromise = res.thread.destroy(); var promises = [threadPromise].concat(commentPromises).concat(filePromises); Promise.all(promises) .then(() => { next(); }) .catch((err) => { res.sendStatus(500); }); }, /* KEYWORDS */ ensureKeyword: function (req, res, next) { var keyword = req.params.keyword; if (!keyword) { return res.sendStatus(404); } db.getKeyword(keyword).then(function (k) { if (!k) { return res.sendStatus(404); } res.keyword = k; next(); }); }, ensureKeywords: function (req, res, next) { db.getKeywords().then(function (k) { if (!k) { return res.sendStatus(404); } res.keywords = k; next(); }); }, ensureLatestKeywordThreads: function (req, res, next) { db.getLatestKeywordThreads().then(function (threads) { res.threads = threads; next(); }); }, ensureThreadGroups: function (req, res, next) { db.getThreadGroups().then(function (threadGroups) { res.threadGroups = threadGroups; next(); }); }, ensureThreadsForKeyword: function (req, res, next) { var keyword = req.params.keyword; if (!keyword) { res.sendStatus(404); } db.getThreadsForKeyword(keyword).then(function (threads) { res.threads = threads; res.threads_ids = res.threads.pluck("id").sort(); res.keywords = _.uniq(res.threads.pluck("keyword")); next(); }); }, ensureThreadsForUser: function (req, res, next) { var username = res.user.username; var limit = parseInt(req.params.limit) || 10; var offset = parseInt(req.params.offset) || 0; if (!username) { res.sendStatus(404); } db.getThreadsForUser(username, limit, offset).then(function (threads) { res.threads = threads; res.threads_ids = res.threads.pluck("id").sort(); res.keywords = _.uniq(res.threads.pluck("keyword")); next(); }); }, ensureTopThreadsForUser: function (req, res, next) { var username = res.user.username; var limit = parseInt(req.params.limit) || 10; var offset = parseInt(req.params.offset) || 0; if (!username) { res.sendStatus(404); } db.getTopThreadsForUser(username, limit, offset).then(function ( top_threads ) { res.topThreads = top_threads; res.topThreads_ids = res.topThreads.pluck("id").sort(); res.topKeywords = _.uniq(res.topThreads.pluck("keyword")); next(); }); }, ensureCommentsForUser: function (req, res, next) { db.getCommentsForUser(res.user.username).then(function (comments) { res.comments = comments || []; next(); }); }, ensureFilesForUser: function (req, res, next) { db.getFilesForUser(res.user.username).then(function (files) { res.files = files || []; next(); }); }, createKeyword: function (req, res, next) { if (!req.body.keyword || !req.body.keyword.length) { res.json({ error: "no keyword" }); return; } var data = { keyword: req.body.keyword, owner: req.user.get("username"), createdate: util.now(), public: 1, color: req.body.color || "blue", }; db.createKeyword(data).then(function (keyword) { res.keyword = keyword; next(); }); }, /* POSTING */ verifyFilesOrComment: function (req, res, next) { var hasComment = req.body.comment && req.body.comment.length; var hasFile = req.files && req.files.length; if (!hasComment && !hasFile) { console.log(">>> no files or comment"); return res.sendStatus(400); } next(); }, /* COMMENTS */ ensureComment: function (req, res, next) { var id = req.params.id.replace(/\D/g, ""); if (!id) { return res.sendStatus(404); } db.getCommentById(id).then(function (comment) { if (comment) { comment.set("comment", comment.get("comment").toString()); res.comment = comment; next(); } else { res.sendStatus(404); } }); }, createOptionalComment: function (req, res, next) { if (!req.body.comment || !req.body.comment.length) { return next(); } bucky.createComment(req, res, next); }, createComment: function (req, res, next) { if (!req.body.comment || !req.body.comment.length) { res.json({ error: "no comment" }); return; } var data = { thread: res.thread.get("id"), parent_id: req.body.parent_id || -1, username: req.user.get("username"), date: util.now(), comment: req.body.comment, }; db.createComment(data).then(function (comment) { res.comment = comment; next(); }); }, updateComment: function (req, res, next) { if (!req.body.comment || !req.body.comment.length) { return res.sendStatus(500); } res.comment.set("comment", req.body.comment); res.comment.set("date", util.now()); res.comment .save() .then(() => { next(); }) .catch((err) => { res.sendStatus(500); }); }, moveComment: function (req, res, next) { res.comment.set("thread", res.thread.get("id")); res.comment .save() .then(() => { next(); }) .catch((err) => { res.sendStatus(500); }); }, destroyComment: function (req, res, next) { res.comment .destroy() .then(() => { next(); }) .catch((err) => { res.sendStatus(500); }); }, /* FILES */ ensureFile: function (req, res, next) { var id = req.params.id.replace(/\D/g, ""); if (!id) { return res.sendStatus(404); } db.getFileById(id).then(function (file) { if (file) { res.file = file; next(); } else { res.sendStatus(404); } }); }, createOptionalFiles: function (req, res, next) { if (!req.files || !req.files.length) { return next(); } bucky.createFiles(req, res, next); }, createFiles: function (req, res, next) { if (!req.files || !req.files.length) { res.json({ error: "no files" }); return; } var thread_id = res.thread.get("id"); var dirname = process.env.S3_PATH + "/data/" + thread_id + "/"; var promises = req.files.map((file) => { return new Promise((resolve, reject) => { upload.put({ file: file, preserveFilename: true, dirname: dirname, unacceptable: function (err) { reject(err); }, success: function (url) { console.log("file >", url); var data = { thread: res.thread.get("id"), username: req.user.get("username"), filename: file.originalname, date: util.now(), size: file.size, privacy: false, storage: process.env.S3_BUCKET, }; db.createFile(data) .then(function (file) { resolve(file); }) .catch((err) => reject(err)); }, }); }); }); Promise.all(promises) .then((values) => { res.files = values; next(); }) .catch((err) => { console.log(err); }); }, moveFile: function (req, res, next) { db.moveFile(res.file, res.thread.get("id")) .then(() => { next(); }) .catch((err) => { res.sendStatus(500); }); }, destroyFile: function (req, res, next) { var filePromises = db.destroyFiles([res.file]); Promise.all(filePromises) .then(() => next()) .catch((err) => { console.error(err); next(); }); }, /* PROFILE / USER */ ensureUser: function (req, res, next) { var username = util.sanitizeName(req.params.username); if (!username) { return res.sendStatus(404); } db.getUserByUsername(username).then(function (user) { if (user) { res.user = user; next(); } else { res.sendStatus(404); } }); }, ensureUserFromBody: function (req, res, next) { var username = util.sanitizeName(req.body.username); if (!username) { return res.sendStatus(404); } db.getUserByUsername(username).then(function (user) { if (user) { res.user = user; next(); } else { console.log("no such user!!"); res.sendStatus(404); } }); }, ensureUserlist: function (req, res, next) { db.getUsers().then(function (users) { if (!users) { return res.sendStatus(404); } res.users = users; next(); }); }, ensureUserThreadCounts: function (req, res, next) { db.getUserThreadCounts().then(function (counts) { if (!counts) { return res.sendStatus(404); } res.threadCounts = counts; next(); }); }, ensureUserCommentCounts: function (req, res, next) { db.getUserCommentCounts().then(function (counts) { if (!counts) { return res.sendStatus(404); } res.commentCounts = counts; next(); }); }, ensureUserFileCounts: function (req, res, next) { db.getUserFileCounts().then(function (counts) { if (!counts) { return res.sendStatus(404); } res.fileCounts = counts; next(); }); }, ensureUserStatistics: function (req, res, next) { var stats = {}; res.threadCounts.forEach(function (user) { stats[user.username] = stats[user.username] || {}; stats[user.username].threads = user.count; }); res.commentCounts.forEach(function (user) { stats[user.username] = stats[user.username] || {}; stats[user.username].comments = user.count; }); res.fileCounts.forEach(function (user) { stats[user.username] = stats[user.username] || {}; stats[user.username].files = user.count; stats[user.username].fileSize = user.size; }); res.userStats = stats; next(); }, sanitizeUser: function (req, res, next) { res.user = util.sanitizeUser(res.user); next(); }, bumpLastSeen: function (req, res, next) { req.user.set("lastseen", util.now()); req.user.save().then(() => next()); }, updateProfile: function (req, res, next) { var user = res.user; "realname location email phone website twitter" .split(" ") .forEach((field) => { res.user.set(field, req.body[field]); }); next(); }, uploadAvatar: function (req, res, next) { if (!req.file) return next(); var dirname = "/bucky/profile/"; upload.put({ file: req.file, filename: req.user.get("username") + ".jpg", dirname: dirname, unacceptable: function (err) { res.sendStatus({ error: "Problem uploading avatar." }); }, success: function (url) { console.log("avatar >", url); res.user.set("avatar", url); next(); }, }); }, saveUser: function (req, res, next) { res.user.save().then(() => next()); }, /* MAIL */ ensureMailboxes: function (req, res, next) { var username = req.user.get("username"); var box = req.params.box; var mbox = username + "." + box; if (!box) { res.sendStatus(404); } db.getMailboxes(username).then(function (boxes) { if (!boxes) { return res.sendStatus(404); } if ( !boxes.models.some(function (box) { return box.get("mbox") == mbox; }) ) { return res.sendStatus(404); } res.boxes = boxes; next(); }); }, ensureMailboxCounts: function (req, res, next) { db.getMailboxCounts(res.boxes.pluck("mbox")).then(function (counts) { var lookup = {}; counts.forEach(function (c) { lookup[c.mbox] = c; }); res.boxes.forEach(function (box) { var count = lookup[box.get("mbox")] ? lookup[box.get("mbox")].count : 0; box.set("count", count); }); next(); }); }, ensureMessages: function (req, res, next) { const limit = parseInt(req.query.limit) || 50; const offset = parseInt(req.query.offset) || 0; db.getMessages( req.user.get("username"), req.params.box, limit, offset ).then(function (messages) { res.messages = messages; res.query = { limit, offset }; next(); }); }, ensureMessage: function (req, res, next) { db.getMessage(req.params.id).then(function (message) { if (!message) { return res.sendStatus(404); } var username = req.user.get("username"); if ( username !== message.get("recipient") && username !== message.get("sender") ) { res.sendStatus(404); return; } res.message = message; next(); }); }, checkMail: function (req, res, next) { db.checkMail(req.user.get("username")).then(function (mail) { res.mail = mail ? mail[0] : { count: 0 }; next(); }); }, markMessageUnread: function (req, res, next) { if (res.message.get("unread")) { res.message.set("unread", 0); res.message.save().then(() => next()); } else { next(); } }, ensureRecipient: function (req, res, next) { db.getUserByUsername(util.sanitizeName(req.body.username)).then((user) => { if (!user) { res.send({ error: "No such recipient" }); return; } next(); }); }, sendMessage: function (req, res, next) { var recipient = util.sanitizeName(req.body.username); var sender = req.user.get("username"); var subject = util.sanitize(req.body.subject); var body = util.sanitize(req.body.body); res.mail = { sender: sender, recipient: recipient }; var recipientMessage = { mbox: recipient + ".inbox", unread: true, sender: sender, recipient: recipient, date: util.now(), subject: subject, body: body, }; var senderMessage = { mbox: sender + ".outbox", unread: false, sender: sender, recipient: recipient, date: util.now(), subject: subject, body: body, }; Promise.all([ db.createMessage(recipientMessage), db.createMessage(senderMessage), ]).then(() => next()); }, deleteDraft: function (req, res, next) { if (!req.body.draft_id) return next(); db.getMessage(req.body.draft_id).then((message) => { if (message.get("sender") === req.user.get("username")) { return message.destroy().then(() => next()); } // erroneous draft message?? next(); }); }, destroyMessage: function (req, res, next) { res.message.destroy().then(() => next()); }, });