updated start/stop scripts to run form /etc/rc.local (without sudo)
[oweals/finalsclub.git] / app.js
diff --git a/app.js b/app.js
index 5d8860d788ff9b0cbeeba95e691f39247fa16949..b7e50e08294dc68d8e9277e2dbcbb97fc144d070 100644 (file)
--- a/app.js
+++ b/app.js
@@ -22,6 +22,7 @@ var hat                                       = require('hat');
 var connect                    = require( 'connect' );
 var Session                    = connect.middleware.session.Session;
 var parseCookie = connect.utils.parseCookie;
+var Backchannel = require('../bc/backchannel');
 
 // Depracated
 // Used for initial testing
@@ -1638,6 +1639,108 @@ app.get( '/archive/note/:id', loadUser, function( req, res ) {
 
 // socket.io server
 
+// The finalsclub backchannel server uses socket.io to handle communication between the server and
+// the browser which facilitates near realtime interaction. This allows the user to post questions
+// and comments and other users to get those almost immediately after they are posted, without
+// reloading the page or pressing a button to refresh.
+//
+// The server code itself is fairly simple, mainly taking incomming messages from client browsers,
+// saving the data to the database, and then sending it out to everyone else connected. 
+//
+// Data types:
+// Posts -  Posts are the main items in backchannel, useful for questions or discussion points
+//             [[ example object needed with explanation E.G: 
+/*
+               Post: { postID: '999-1',
+                                 userID: '1234',
+                                 userName: 'Bob Jones',
+                                 userAffil: 'Instructor',
+                                 body: 'This is the text content of the post.',
+                                 comments: { {<commentObj>, <commentObj>, ...},
+                                 public: true,
+                                 votes:   [ <userID>, <userID>, ...],
+                                 reports: [ <userID>, <userID>, ...]
+                               }
+                 Comment: { body: 'foo bar', userName: 'Bob Jones', userAffil: 'Instructor' }
+               
+                 if anonymous: userName => 'Anonymous', userAffil => 'N/A'
+*/
+//
+//
+//
+// Comments - Comments are replies to posts, for clarification or answering questions
+//             [[ example object needed]]
+// Votes - Votes signifyg a users approval of a post
+//             [[ example object needed]]
+// Flags - Flagging a post signifies that it is against the rules, 2 flags moves it to the bottomw
+//             [[ example object needed]]
+//
+//
+// Post Schema
+// body - Main content of the post
+// userId - Not currently used, but would contain the users id that made the post
+// userName - Users name that made post
+// userAffil - Users affiliation to their school
+// public - Boolean which denotes if the post is public to everyone, or private to school users only
+// date - Date post was made, updates when any comments are made for the post
+// comments - An array of comments which contain a body, userName, and userAffil
+// votes - An array of user ids which are the users that voted
+//             [[ example needed ]]
+// reports - An array of user ids which are the users that reported the post
+//             [[ reports would be "this post is flagged as inappropriate"? ]]
+//             [[ bruml: consistent terminology needed ]]
+//
+// Posts and comments can be made anonymously. When a post is anonymous, the users info is stripped
+// from the post and the userName is set to Anonymous and the userAffil to N/A. This is to allow
+// users the ability to make posts or comments that they might not otherwise due to not wanting
+// the content of the post/comment to be attributed to them.
+//
+// Each time a user connects to the server, it passes through authorization which checks for a cookie
+// that is set by Express. If a session exists and it is for a valid logged in user, then handshake.user
+// is set to the users data, otherwise it is set to false. handshake.user is used later on to check if a
+// user is logged in, and if so display information that otherwise might not be visible to them if they
+// aren't apart of a particular school.
+//
+// After the authorization step, the client browser sends the lecture id which is rendered into the html
+// page on page load from Express. This is then used to assign a 'room' for the user which is grouped
+// by lecture. All posts are grouped by lecture, and only exist for that lecture. After the user is
+// grouped into a 'room', they are sent a payload of all existing posts for that lecture, which are then
+// rendered in the browser.
+//
+// Everything else from this point on is handled in an event form and requires a user initiating it. The
+// events are as follows.
+//
+// Post event
+// A user makes a new post. A payload of data containing the post and lecture id is sent to the server.
+// The server recieves the data, assembles a new post object for the database and then fills it with
+// the appropriate data. If a user selected for the post to be anonymous, the userName and userAffil are
+// replaced. If the user chose for the post to be private, then public will be set to false and it
+// will be filtered from being sent to users not logged into and not having access to the school. Once
+// the post has been created and saved into the database, it is sent to all connected users to that
+// particular lecture, unless it is private, than only logged in users will get it.
+//
+// Vote event
+// A user votes for a post. A payload of data containing the post id and lecture id are sent along with
+// the user id. A new vote is created by first fetching the parent post, then adding the user id to the
+// votes array, and then the post is subsequently saved back to the database and sent to all connected
+// users unless the post is private, which then it will be only sent to logged in users.
+//
+// Report event
+// Similar to the vote event, reports are sent as a payload of a post id, lecture id, and user id, which
+// are then used to fetch the parent post, add the user id to the reports array, and then saved to the db.
+// Then the report is sent out to all connected users unless it is a private post, which will be only sent
+// to logged in users. On the client, once a post has more two (2) or more reports, it will be moved to the
+// bottom of the interface.
+//
+// Comment event
+// A user posts a comment to a post. A payload of data containing the post id, lecture id, comment body,
+// user name, and user affiliation are sent to the server, which are then used to find the parent post
+// and then a new comment object is assembled. When new comments are made, it updates the posts date
+// which allows the post to be sorted by date and the posts with the freshest comments would be pushed
+// to the top of the interface. The comment can be anonymous, which then will have the user
+// name and affiliation stripped before saving to the database. The comment then will be sent out to all
+// connected users unless the post is private, then only logged in users will recieve the comment.
+
 var io = require( 'socket.io' ).listen( app );
 
 var Post = mongoose.model( 'Post' );
@@ -1675,148 +1778,34 @@ io.set('authorization', function ( handshake, next ) {
   }
 });
 
-
-var backchannel = io
-.of( '/backchannel' )
-.on( 'connection', function( socket ) {
-
-  socket.on('subscribe', function(lecture, cb) {
-    socket.join(lecture);
+var backchannel = new Backchannel(app, io.of('/backchannel'), {
+  subscribe: function(lecture, send) {
     Post.find({'lecture': lecture}, function(err, posts) {
-      if (socket.handshake.user) {
-        cb(posts);
-      } else {
-        var posts = posts.filter(
-          function(post) {
-          if (post.public)
-            return post;
-        }
-        )
-        cb(posts)
-      }
+      send(posts);
     });
-  });
-
-  socket.on('post', function(res) {
+  },
+  post: function(fillPost) {
     var post = new Post;
-    var _post = res.post;
-    var lecture = res.lecture;
-    post.lecture = lecture;
-    if ( _post.anonymous ) {
-      post.userid              = 0;
-      post.userName    = 'Anonymous';
-      post.userAffil = 'N/A';
-    } else {
-      post.userName = _post.userName;
-      post.userAffil = _post.userAffil;
-    }
-
-    post.public = _post.public;
-    post.date = new Date();
-    post.body = _post.body;
-    post.votes = [];
-    post.reports = [];
-    post.save(function(err) {
-      if (err) {
-        // XXX some error handling
-        console.log(err);
-      } else {
-        if (post.public) {
-          backchannel.in(lecture).emit('post', post);
-        } else {
-          privateEmit(lecture, 'post', post);
-        }
-      }
+    fillPost(post, function(send) {
+      post.save(function(err) {
+        send();
+      });
     });
-  });
-
-  socket.on('vote', function(res) {
-    var vote = res.vote;
-    var lecture = res.lecture;
-    Post.findById(vote.parentid, function( err, post ) {
-      if (!err) {
-        if (post.votes.indexOf(vote.userid) == -1) {
-          post.votes.push(vote.userid);
-          post.save(function(err) {
-            if (err) {
-              // XXX error handling
-            } else {
-              if (post.public) {
-                backchannel.in(lecture).emit('vote', vote);
-              } else {
-                privteEmit(lecture, 'vote', vote);
-              }
-            }
-          });
-        }
-      }
-    })
-  });
-
-  socket.on('report', function(res) {
-    var report = res.report;
-    var lecture = res.lecture;
-    Post.findById(report.parentid, function( err, post ){
-      if (!err) {
-        if (post.reports.indexOf(report.userid) == -1) {
-          post.reports.push(report.userid);
-          post.save(function(err) {
-            if (err) {
-              // XXX error handling
-            } else {
-              if (post.public) {
-                backchannel.in(lecture).emit('report', report);
-              } else {
-                privateEmit(lecture, 'report', report);
-              }
-            }
-          });
-        }
-      }
-    })
-  });
-
-  socket.on('comment', function(res) {
-    var comment = res.comment;
-    var lecture = res.lecture;
-    console.log('anon', comment.anonymous);
-    if ( comment.anonymous ) {
-      comment.userid           = 0;
-      comment.userName = 'Anonymous';
-      comment.userAffil = 'N/A';
-    }
-    Post.findById(comment.parentid, function( err, post ) {
-      if (!err) {
-        post.comments.push(comment);
-        post.date = new Date();
+  },
+  items: function(postId, addItem) {
+    Post.findById(postId, function( err, post ) {
+      addItem(post, function(send) {
         post.save(function(err) {
-          if (err) {
-            console.log(err);
-          } else {
-            if (post.public) {
-              backchannel.in(lecture).emit('comment', comment);
-            } else {
-              privateEmit(lecture, 'comment', comment);
-            }
-          }
-        })
-      }
-    })
-  });
-
-  function privateEmit(lecture, event, data) {
-    backchannel.clients(lecture).forEach(function(socket) {
-      if (socket.handshake.user)
-        socket.emit(event, data);
+          send();
+        });
+      })
     })
   }
-
-  socket.on('disconnect', function() {
-    //delete clients[socket.id];
-  });
 });
 
 
+
+
 var counters = {};
 
 var counts = io