summaryrefslogtreecommitdiff
path: root/bucky
diff options
context:
space:
mode:
Diffstat (limited to 'bucky')
-rw-r--r--bucky/app/bucky.js189
-rw-r--r--bucky/app/index.js60
-rw-r--r--bucky/app/router.js150
-rw-r--r--bucky/db/bookshelf.js24
-rw-r--r--bucky/db/fortune.js28
-rw-r--r--bucky/db/index.js156
-rw-r--r--bucky/util/auth.js78
-rw-r--r--bucky/util/middleware.js23
-rw-r--r--bucky/util/util.js4
9 files changed, 712 insertions, 0 deletions
diff --git a/bucky/app/bucky.js b/bucky/app/bucky.js
new file mode 100644
index 0000000..43799fe
--- /dev/null
+++ b/bucky/app/bucky.js
@@ -0,0 +1,189 @@
+var db = require('../db')
+var util = require('../lib/util')
+var _ = require('lodash')
+
+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){
+ thread.set("comment_count", lookup[thread.id].count)
+ })
+ next()
+ })
+ },
+ ensureFileCountsForThreads: function (req, res, next){
+ db.getFileCounts(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)
+ })
+ 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, 9, 0, "desc").then(function(hootbox){
+ res.hootbox = hootbox
+ next()
+ })
+ },
+ ensureLastlog: function (req, res, next){
+ db.getLastlog(6).then(function(lastlog){
+ res.lastlog = lastlog
+ 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)
+ }
+ })
+ },
+ 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()
+ })
+ },
+
+ /* 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()
+ })
+ },
+ 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()
+ })
+ },
+
+ /* 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){
+ db.getMessages(req.user.get('username'), req.params.box, 50, 0).then(function(messages){
+ res.messages = messages
+ next()
+ })
+ },
+ ensureMessage: function(req, res, next){
+ db.getMessage(req.params.id).then(function(message){
+ var username = req.user.get('username')
+ if (username !== message.get('recipient') && username !== message.get('sender')) {
+ res.sendStatus(404)
+ return
+ }
+ res.message = message
+ next()
+ })
+ }
+} \ No newline at end of file
diff --git a/bucky/app/index.js b/bucky/app/index.js
new file mode 100644
index 0000000..ad97526
--- /dev/null
+++ b/bucky/app/index.js
@@ -0,0 +1,60 @@
+require('dotenv').load();
+var fs = require('fs')
+var app, express = require('express');
+var http = require('http');
+var https = require('https');
+var bodyParser = require('body-parser')
+var cookieParser = require('cookie-parser')
+var csurf = require('csurf')
+var path = require('path')
+var multiparty = require('multiparty')
+var ejs = require('ejs')
+var passport = require('passport')
+var sessionstore = require('sessionstore')
+var session = require('express-session')
+var multer = require('multer')
+
+var app, server
+
+var mongodb = require('mongodb')
+
+var site = module.exports = {}
+site.init = function(){
+ app = express()
+ app.set('port', 5000)
+ app.set('view engine', 'ejs')
+ app.set('views', path.join(__dirname, '../views'))
+ app.use(express.static(path.join(__dirname, '../public')))
+ app.use(bodyParser.json())
+ app.use(bodyParser.urlencoded({ extended: false }))
+ app.use( multer({ dest:'./uploads/' }).single("file") )
+
+ app.use(session({
+ key: 'bucky.sid',
+ secret: 'argonauts',
+ cookie: { domain: '.' + process.env.HOST_NAME, maxAge: 43200000000 },
+ store: sessionstore.createSessionStore({
+ type: 'mongodb',
+ host: 'localhost',
+ port: 27017,
+ dbName: 'sessionDb',
+ collectionName: 'sessions',
+ timeout: 10000
+ }),
+ resave: true,
+ saveUninitialized: false,
+ }))
+ app.use(csurf({ cookie: false }))
+
+ app.use(express.query())
+ app.use(passport.initialize())
+ app.use(passport.session())
+
+ server = http.createServer(app).listen(5000, function () {
+ console.log('Bucky listening at http://5.k:%s', server.address().port)
+ })
+
+ site.route(app)
+}
+
+site.route = require('./router')
diff --git a/bucky/app/router.js b/bucky/app/router.js
new file mode 100644
index 0000000..7ac6599
--- /dev/null
+++ b/bucky/app/router.js
@@ -0,0 +1,150 @@
+var auth = require('../util/auth')
+var middleware = require('../util/middleware')
+var fortune = require('../db/fortune')
+var bucky = require('../app/bucky')
+var util = require('../util/util')
+
+module.exports = function(app){
+ app.all('*', middleware.ensureLocals)
+
+ auth.init()
+
+ app.get("/", middleware.ensureAuthenticated, function(req, res){
+ res.redirect('/index')
+ })
+ app.get("/login", function(req, res){
+ res.render("pages/login", {
+ title: "login"
+ })
+ })
+ app.get("/index", middleware.ensureAuthenticated, function(req, res){
+ res.render("pages/index", {
+ title: fortune("titles"),
+ hoot_text: fortune("hoots"),
+ })
+ })
+ app.get("/details/:id", middleware.ensureAuthenticated, function(req, res){
+ res.render("pages/details", {})
+ })
+
+ app.post("/api/login", auth.loggedInLocal)
+ app.get("/api/index",
+ middleware.ensureAuthenticated,
+ bucky.ensureLatestThreads,
+ bucky.ensureCommentCountsForThreads,
+ bucky.ensureFileCountsForThreads,
+ bucky.ensureKeywordsForThreads,
+ bucky.ensureHootbox,
+ bucky.ensureLastlog,
+ function(req, res){
+ res.json({
+ threads: res.threads,
+ hootbox: res.hootbox,
+ lastlog: res.lastlog,
+ })
+ }
+ )
+ app.get("/api/thread/:id",
+ middleware.ensureAuthenticated,
+ bucky.ensureThread,
+ bucky.ensureKeywordForThread,
+ bucky.ensureCommentsForThread,
+ bucky.ensureFilesForThread,
+ function(req, res){
+ res.json({
+ thread: res.thread,
+ comments: res.comments,
+ files: res.files,
+ keyword: res.keyword,
+ })
+ }
+ )
+ app.post("/api/thread",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ // make a new thread
+ })
+ app.post("/api/thread/:id/comment",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ // add comments and files
+ })
+ app.delete("/api/thread/:id",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ // delete a thread
+ })
+ app.put("/api/comment/:id",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ // edit a comment
+ })
+ app.delete("/api/comment/:id",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ // delete a comment
+ })
+
+
+ app.get("/api/keyword/:keyword",
+ middleware.ensureAuthenticated,
+ bucky.ensureKeyword,
+ bucky.ensureThreadsForKeyword,
+ bucky.ensureCommentCountsForThreads,
+ bucky.ensureFileCountsForThreads,
+ bucky.ensureKeywordsForThreads,
+ function(req, res){
+ res.json({
+ keyword: res.keyword,
+ threads: res.threads,
+ })
+ }
+ )
+
+ app.get("/mail/",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ res.render("pages/mailbox", {title: "inbox" })
+ }
+ )
+ app.get("/mail/:box",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ res.render("pages/mailbox", { title: util.sanitize(req.params.box) })
+ }
+ )
+ app.get("/message/:id",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ res.render("pages/message", { title: util.sanitize(req.params.box) })
+ }
+ )
+ app.get("/api/mailbox/:box",
+ middleware.ensureAuthenticated,
+ bucky.ensureMailboxes,
+ bucky.ensureMailboxCounts,
+ bucky.ensureMessages,
+ function(req, res){
+ res.json({
+ user: { id: req.user.get("id"), username: req.user.get("username") },
+ messages: res.messages,
+ boxes: res.boxes,
+ })
+ }
+ )
+ app.get("/api/message/:id",
+ middleware.ensureAuthenticated,
+ bucky.ensureMessage,
+ function(req, res){
+ res.json({
+ message: res.message,
+ })
+ })
+ app.post("/mail/",
+ middleware.ensureAuthenticated,
+ function(req, res){
+ // send new mail
+ }
+ )
+
+}
diff --git a/bucky/db/bookshelf.js b/bucky/db/bookshelf.js
new file mode 100644
index 0000000..69157cc
--- /dev/null
+++ b/bucky/db/bookshelf.js
@@ -0,0 +1,24 @@
+var knex = require('knex')({
+ client: 'mysql2',
+ connection: {
+ host : process.env.DB_HOST,
+ user : process.env.DB_USER,
+ password : process.env.DB_PASS,
+ database : process.env.DB_NAME,
+ charset : 'utf8',
+ typecast : function (field, next) {
+ console.log(field.type)
+ if (field.type == 'BLOB') {
+ return field.string()
+ }
+ return next()
+ }
+ }
+})
+
+var bookshelf = require('bookshelf')(knex)
+
+module.exports = {
+ bookshelf: bookshelf,
+ knex: knex,
+}
diff --git a/bucky/db/fortune.js b/bucky/db/fortune.js
new file mode 100644
index 0000000..7adba5a
--- /dev/null
+++ b/bucky/db/fortune.js
@@ -0,0 +1,28 @@
+function choice (a){ return a[ Math.floor(Math.random()*a.length) ] }
+
+var fs = require("fs"), path = require("path")
+var fortunes = {}
+var dir = "fortune"
+
+fs.readdirSync(path.resolve(dir)).forEach(function(fn){
+
+ var file = dir + '/' + fn
+ var stat = fs.statSync(file)
+
+ if (stat && ! stat.isDirectory()) {
+ fortunes[fn] = fs.readFileSync(file)
+ .toString()
+ .split("\n")
+ .filter(function(s){ return !! s })
+ }
+
+})
+
+module.exports = function(tag){
+ if (tag in fortunes) {
+ return choice(fortunes[tag])
+ }
+ else {
+ return "bucky"
+ }
+} \ No newline at end of file
diff --git a/bucky/db/index.js b/bucky/db/index.js
new file mode 100644
index 0000000..b6fa235
--- /dev/null
+++ b/bucky/db/index.js
@@ -0,0 +1,156 @@
+var db = module.exports
+
+var connection = require("./bookshelf")
+var bookshelf = connection.bookshelf
+var knex = connection.knex
+
+
+/* MODELS */
+
+var User = db.User = bookshelf.Model.extend({
+ tableName: 'users',
+ hasTimestamps: false,
+})
+var Thread = db.Thread = bookshelf.Model.extend({
+ tableName: 'threads',
+ hasTimestamps: false,
+})
+var Comment = db.Comment = bookshelf.Model.extend({
+ tableName: 'comments',
+ hasTimestamps: false,
+})
+var File = db.File = bookshelf.Model.extend({
+ tableName: 'files',
+ hasTimestamps: false,
+})
+var Keyword = db.Keyword = bookshelf.Model.extend({
+ tableName: 'keywords',
+ hasTimestamps: false,
+})
+var Mailbox = db.Mailbox = bookshelf.Model.extend({
+ tableName: 'boxes',
+ hasTimestamps: false,
+})
+var Message = db.Message = bookshelf.Model.extend({
+ tableName: 'messages',
+ hasTimestamps: false,
+})
+
+/* USERS */
+
+db.createUser = function(data){
+ return new db.User(data).save()
+}
+db.getUsers = function () {
+ return User.query(function(qb){
+ qb.orderBy("id", "desc")
+ }).fetchAll()
+}
+db.getUser = function(id) {
+ var model = new User({'id': id})
+ return model.fetch()
+}
+db.getUserByUsername = function(username) {
+ var model = new User({'username': username})
+ return model.fetch()
+}
+db.getLastlog = function(limit){
+ return knex.column('username').column('lastseen').select().from('users').orderBy('lastseen', 'desc').limit(limit || 10)
+}
+
+
+/* THREADS */
+
+db.getLatestThreads = function () {
+ return Thread.query(function(qb){
+ qb.orderBy("id", "desc").limit(50)
+ }).fetchAll()
+}
+db.getThreadsForKeyword = function (keyword) {
+ return Thread.query(function(qb){
+ qb.where("keyword", "=", keyword).orderBy("id", "desc")
+ }).fetchAll()
+}
+db.getThread = function (id) {
+ return Thread.query("where", "id", "=", id).fetch()
+}
+
+
+/* FILES */
+
+db.getFilesForThread = function (id){
+ return File.query("where", "thread", "=", id).fetchAll()
+}
+db.getFileCounts = function(ids){
+ return knex.column('thread').count('* as count').select().from('files').where('thread', 'in', ids).groupBy('thread')
+}
+db.getFileSizes = function(ids){
+ return knex.column('thread').sum('size as size').select().from('files').where('thread', 'in', ids).groupBy('thread')
+}
+
+
+/* COMMENTS */
+
+db.getCommentsForThread = function (id, limit, offset, order){
+ order = order || "asc"
+ return Comment.query(function(qb){
+ qb.where("thread", "=", id).orderBy("id", order)
+ if (limit) {
+ qb.limit(limit)
+ }
+ if (offset) {
+ qb.offset(offset)
+ }
+ }).fetchAll().then(function(comments){
+ comments.forEach(function(comment){
+ comment.set("comment", comment.get("comment").toString() )
+ })
+ return comments
+ })
+}
+db.getCommentCounts = function(ids){
+ return knex.column('thread').count('* as count').select().from('comments').where('thread', 'in', ids).groupBy('thread')
+}
+
+
+/* KEYWORDS */
+
+db.getKeywords = function (keywords){
+ return Keyword.query("where", "keyword", "in", keywords).fetchAll()
+}
+db.getKeyword = function (keyword) {
+ return Keyword.query("where", "keyword", "=", keyword).fetch()
+}
+
+
+/* MAILBOXES */
+
+db.getMailboxes = function(username){
+ return Mailbox.query("where", "owner", "=", username).fetchAll()
+}
+db.getMailboxCounts = function(boxes){
+ return knex.column('mbox').count('* as count').select().from('messages').where('mbox', 'in', boxes).groupBy('mbox')
+}
+
+
+/* MESSAGES */
+
+db.getMessages = function(username, box, limit, offset){
+ var mbox = username + "." + box
+ return Message.query(function(qb){
+ qb.column('id', 'mbox', 'unread', 'sender', 'recipient', 'date', 'subject', knex.raw("CHAR_LENGTH(body) as size")).where("mbox", "=", mbox).orderBy("id", "desc")
+ if (limit) {
+ qb.limit(limit)
+ }
+ if (offset) {
+ qb.offset(offset)
+ }
+ }).fetchAll()
+}
+db.getMessage = function (id){
+ var model = new Message({'id': id})
+ return model.fetch().then(function(message){
+ message.set("body", message.get("body").toString() )
+ return message
+ })
+}
diff --git a/bucky/util/auth.js b/bucky/util/auth.js
new file mode 100644
index 0000000..436d5e6
--- /dev/null
+++ b/bucky/util/auth.js
@@ -0,0 +1,78 @@
+
+var passport = require('passport'),
+ LocalStrategy = require('passport-local').Strategy,
+ crypto = require('crypto'),
+ db = require('../db');
+
+
+var auth = module.exports = {
+
+ init: function(){
+ passport.serializeUser(auth.serializeUser)
+ passport.deserializeUser(auth.deserializeUser)
+
+ passport.use(new LocalStrategy(auth.verifyLocalUser))
+ },
+
+ serializeUser: function (user, done) {
+ done(null, user.id);
+ },
+
+ deserializeUser: function (id, done) {
+ db.getUser(id).then(function(user){
+ done(! user, user)
+ })
+ },
+
+ validPassword: function(user, pw){
+ var shasum = crypto.createHash('sha1')
+ shasum.update(pw)
+ return user.get('password') === shasum.digest('hex');
+ },
+
+ verifyLocalUser: function (username, password, done) {
+ // handle passwords!!
+ db.getUserByUsername(username).then(function(user){
+
+ // if (err) { return done(err); }
+ if (! user) { return done("no user") }
+
+ return done(null, user)
+
+ if (! user) {
+ return done(null, false, { error: { errors: { username: { message: 'No such username.' } }}})
+ }
+ if (! auth.validPassword(user, password)) {
+ return done(null, false, { error: { errors: { password: { message: 'Incorrect password.' } }}})
+ }
+ return done(null, user);
+ })
+ },
+
+ loggedInLocal: function (req, res, next) {
+ passport.authenticate("local", function(err, user, info){
+ if (err) {
+ return res.json({ error: err });
+ }
+ if (! user) {
+ return info ? res.json(info) : res.redirect("/login");
+ }
+
+ // user.last_seen = new Date ()
+ // user.save(function(err, data){ if (err) console.err('error setting ip for user') })
+
+ req.logIn(user, function(err) {
+ if (err) { return next(err); }
+ var returnTo = req.session.returnTo
+ delete req.session.returnTo
+ return res.json({ status: "OK", returnTo: returnTo || "/index" })
+ });
+ })(req, res, next)
+ },
+
+ logout: function (req, res) {
+ req.logout();
+ res.redirect('/');
+ },
+
+} \ No newline at end of file
diff --git a/bucky/util/middleware.js b/bucky/util/middleware.js
new file mode 100644
index 0000000..a744c89
--- /dev/null
+++ b/bucky/util/middleware.js
@@ -0,0 +1,23 @@
+var middleware = module.exports = {
+
+ ensureAuthenticated: function (req, res, next) {
+ if (! req.isAuthenticated()) {
+ req.session.returnTo = req.path
+ return res.redirect('/login')
+ }
+ next()
+ },
+
+ ensureLocals: function (req, res, next) {
+ res.locals.csrfToken = req.csrfToken()
+ res.locals.title = "bucky"
+ if (req.isAuthenticated()) {
+ res.locals.show_header = true
+ }
+ else {
+ res.locals.show_header = false
+ }
+ next()
+ },
+
+} \ No newline at end of file
diff --git a/bucky/util/util.js b/bucky/util/util.js
new file mode 100644
index 0000000..e67488b
--- /dev/null
+++ b/bucky/util/util.js
@@ -0,0 +1,4 @@
+var util = module.exports = {}
+
+util.sanitizeName = function (s){ return (s || "").replace(new RegExp("[^-_a-zA-Z0-9]", 'g'), "") }
+util.sanitize = function (s){ return (s || "").replace(/<>&/g, "") }