5 var sys = require( 'sys' );
6 var os = require( 'os' );
7 var url = require( 'url' );
9 var express = require( 'express' );
10 var mongoStore = require( 'connect-mongo' );
11 var async = require( 'async' );
13 var db = require( './db.js' );
14 var mongoose = require( './models.js' ).mongoose;
15 //var mysql = require( 'mysql' );
17 var Mailer = require( './mailer.js' );
18 var hat = require('hat');
20 var connect = require( 'connect' );
21 var Session = connect.middleware.session.Session;
22 var parseCookie = connect.utils.parseCookie;
24 var log3 = function() {}
26 var app = module.exports = express.createServer();
30 var User = mongoose.model( 'User' );
31 var School = mongoose.model( 'School' );
32 var Course = mongoose.model( 'Course' );
33 var Lecture = mongoose.model( 'Lecture' );
34 var Note = mongoose.model( 'Note' );
36 var ArchivedCourse = mongoose.model( 'ArchivedCourse' );
37 var ArchivedNote = mongoose.model( 'ArchivedNote' );
38 var ArchivedSubject = mongoose.model( 'ArchivedSubject' );
40 var ObjectId = mongoose.SchemaTypes.ObjectId;
44 var sqlClient = mysql.createClient({
45 host : process.env.MYSQL_DB_HOSTNAME || 'localhost',
46 user : process.env.MYSQL_DB_USER || 'root',
47 password : process.env.MYSQL_DB_PASS || 'root',
48 port : process.env.MYSQL_DB_PORT || 3306,
49 database : 'fcstatic',
55 var ADMIN_EMAIL = process.env.DEV_EMAIL || 'info@finalsclub.org';
57 var serverHost = process.env.SERVER_HOST;
58 var serverPort = process.env.SERVER_PORT;
61 console.log( 'Using server hostname defined in environment: %s', serverHost );
63 serverHost = os.hostname();
65 console.log( 'No hostname defined, defaulting to os.hostname(): %s', serverHost );
68 app.configure( 'development', function() {
69 app.set( 'errorHandler', express.errorHandler( { dumpExceptions: true, showStack: true } ) );
71 app.set( 'dbHost', process.env.MONGO_HOST || 'localhost' );
72 app.set( 'dbUri', 'mongodb://' + app.set( 'dbHost' ) + '/fc' );
74 app.set( 'awsAccessKey', process.env.AWS_ACCESS_KEY_ID );
75 app.set( 'awsSecretKey', process.env.AWS_SECRET_ACCESS_KEY );
82 app.configure( 'production', function() {
83 app.set( 'errorHandler', express.errorHandler( { dumpExceptions: true, showStack: true } ) );
85 // XXX Disable view caching temp
86 app.disable( 'view cache' )
88 app.set( 'dbHost', process.env.MONGO_HOST || 'localhost' );
89 app.set( 'dbUri', 'mongodb://' + app.set( 'dbHost' ) + '/fc' );
91 app.set( 'awsAccessKey', process.env.AWS_ACCESS_KEY_ID );
92 app.set( 'awsSecretKey', process.env.AWS_SECRET_ACCESS_KEY );
99 app.configure(function(){
100 app.set( 'views', __dirname + '/views' );
101 app.set( 'view engine', 'jade' );
102 app.use( express.bodyParser() );
104 app.use( express.cookieParser() );
107 app.set( 'sessionStore', new mongoStore( {
108 'url' : app.set( 'dbUri' )
111 app.use( express.session( {
112 'secret' : 'finalsclub',
113 'maxAge' : new Date(Date.now() + (60 * 60 * 24 * 30 * 1000)),
114 'store' : app.set( 'sessionStore' )
117 app.use( express.methodOverride() );
118 app.use( app.router );
119 app.use( express.static( __dirname + '/public' ) );
121 // use the error handler defined earlier
122 var errorHandler = app.set( 'errorHandler' );
124 app.use( errorHandler );
129 function sendUserActivation( user ) {
133 'subject' : 'Activate your FinalsClub.org Account',
135 'template' : 'userActivation',
138 'serverHost' : serverHost
142 mailer.send( message, function( err, result ) {
144 // XXX: Add route to resend this email
146 console.log( 'Error sending user activation email\nError Message: '+err.Message );
148 console.log( 'Successfully sent user activation email.' );
153 function sendUserWelcome( user, school ) {
154 var template = school ? 'userWelcome' : 'userWelcomeNoSchool';
158 'subject' : 'Welcome to FinalsClub',
160 'template' : template,
163 'serverHost' : serverHost
167 mailer.send( message, function( err, result ) {
169 // XXX: Add route to resend this email
171 console.log( 'Error sending user welcome email\nError Message: '+err.Message );
173 console.log( 'Successfully sent user welcome email.' );
179 function loggedIn( req, res, next ) {
183 req.flash( 'error', 'You must be logged in to access that feature!' );
189 function loadUser( req, res, next ) {
190 var sid = req.sessionID;
192 console.log( 'got request from session ID: %s', sid );
194 User.findOne( { session : sid }, function( err, user ) {
202 req.user.loggedIn = true;
204 log3( 'authenticated user: '+req.user._id+' / '+req.user.email+'');
206 if( req.user.activated ) {
207 // is the user's profile complete? if not, redirect to their profile
208 if( ! req.user.isComplete ) {
209 if( url.parse( req.url ).pathname != '/profile' ) {
210 req.flash( 'info', 'Your profile is incomplete. Please complete your profile to fully activate your account.' );
212 res.redirect( '/profile' );
220 req.flash( 'info', 'This account has not been activated. Check your email for the activation URL.' );
225 // stash the original request so we can redirect
226 var path = url.parse( req.url ).pathname;
227 req.session.redirect = path;
236 function loadSchool( req, res, next ) {
238 var schoolId = req.params.id;
240 School.findById( schoolId, function( err, school ) {
244 school.authorize( user, function( authorized ){
245 req.school.authorized = authorized;
249 req.flash( 'error', 'Invalid school specified!' );
256 function loadCourse( req, res, next ) {
258 var courseId = req.params.id;
260 Course.findById( courseId, function( err, course ) {
264 course.authorize( user, function( authorized ) {
265 req.course.authorized = authorized;
270 req.flash( 'error', 'Invalid course specified!' );
277 function loadLecture( req, res, next ) {
279 var lectureId = req.params.id;
281 Lecture.findById( lectureId, function( err, lecture ) {
283 req.lecture = lecture;
285 lecture.authorize( user, function( authorized ) {
286 req.lecture.authorized = authorized;
291 req.flash( 'error', 'Invalid lecture specified!' );
298 function loadNote( req, res, next ) {
299 var user = req.user ? req.user : false;
300 var noteId = req.params.id;
302 Note.findById( noteId, function( err, note ) {
304 note.authorize( user, function( auth ) {
309 } else if ( note.public ) {
315 req.flash( 'error', 'You do not have permission to access that note.' );
320 } else if ( note && note.public ) {
325 } else if ( note && !note.public ) {
326 req.session.redirect = '/note/' + note._id;
327 req.flash( 'error', 'You must be logged in to view that note.' );
328 res.redirect( '/login' );
330 req.flash( 'error', 'Invalid note specified!' );
332 res.redirect( '/login' );
337 app.dynamicHelpers( {
338 // flash messages from express-messages
339 'messages' : require( 'express-messages' ),
341 'user' : function( req, res ) {
345 'session' : function( req, res ) {
352 app.get( '/', loadUser, function( req, res ) {
355 res.render( 'index' );
358 app.get( '/schools', loadUser, function( req, res ) {
361 // mongoose's documentation on sort is extremely poor, tread carefully
362 School.find( {} ).sort( 'name', '1' ).run( function( err, schools ) {
366 function( school, callback ) {
367 school.authorize( user, function( authorized ) {
368 school.authorized = authorized;
370 Course.find( { 'school' : school._id } ).sort( 'name', '1' ).run( function( err, courses ) {
371 if( courses.length > 0 ) {
372 school.courses = courses;
381 res.render( 'schools', { 'schools' : schools } );
385 res.render( 'schools', { 'schools' : [] } );
390 app.get( '/:id/course/new', loadUser, loadSchool, function( req, res ) {
391 var school = req.school;
393 if( ( ! school ) || ( ! school.authorized ) ) {
394 return res.redirect( '/schools' );
397 res.render( 'course/new', { 'school': school } );
400 app.post( '/:id/course/new', loadUser, loadSchool, function( req, res ) {
401 var school = req.school;
402 var course = new Course;
403 var instructorEmail = req.body.email.toLowerCase();
404 var instructorName = req.body.instructorName;
406 if( ( ! school ) || ( ! school.authorized ) ) {
407 res.redirect( '/schools' );
410 if ( !instructorEmail || !instructorName ) {
411 req.flash( 'error', 'Invalid parameters!' )
412 return res.render( 'course/new' );
415 course.number = req.body.number;
416 course.name = req.body.name;
417 course.description = req.body.description;
418 course.school = school._id;
419 course.creator = req.user._id;
420 course.subject = req.body.subject;
421 course.department = req.body.department;
423 // find our instructor or invite them if necessary
424 User.findOne( { 'email' : instructorEmail }, function( err, user ) {
428 user.name = instructorName
429 user.email = instructorEmail;
430 user.affil = 'Instructor';
431 user.school = school.name;
433 user.activated = false;
435 if ( ( user.email === '' ) || ( !isValidEmail( user.email ) ) ) {
436 req.flash( 'error', 'Please enter a valid email' );
437 return res.redirect( '/register' );
439 user.save(function( err ) {
441 req.flash( 'error', 'Invalid parameters!' )
442 return res.render( 'course/new' );
447 'subject' : 'A non-profit open education initiative',
449 'template' : 'instructorInvite',
454 'serverHost' : serverHost
458 mailer.send( message, function( err, result ) {
460 console.log( 'Error inviting instructor to course!' );
462 console.log( 'Successfully invited instructor to course.' );
466 course.instructor = user._id;
467 course.save( function( err ) {
469 // XXX: better validation
470 req.flash( 'error', 'Invalid parameters!' );
472 return res.render( 'course/new' );
477 'subject' : school.name+' has a new course: '+course.name,
479 'template' : 'newCourse',
484 'serverHost' : serverHost
488 mailer.send( message, function( err, result ) {
490 console.log( 'Error sending new course email to info@finalsclub.org' )
492 console.log( 'Successfully invited instructor to course')
495 res.redirect( '/schools' );
501 if (user.affil === 'Instructor') {
502 course.instructor = user._id;
503 course.save( function( err ) {
505 // XXX: better validation
506 req.flash( 'error', 'Invalid parameters!' );
508 return res.render( 'course/new' );
513 'subject' : school.name+' has a new course: '+course.name,
515 'template' : 'newCourse',
520 'serverHost' : serverHost
524 mailer.send( message, function( err, result ) {
526 console.log( 'Error sending new course email to info@finalsclub.org' )
528 console.log( 'Successfully invited instructor to course')
531 res.redirect( '/schools' );
535 req.flash( 'error', 'The existing user\'s email you entered is not an instructor' );
536 res.render( 'course/new' );
542 app.get( '/course/:id', loadUser, loadCourse, function( req, res ) {
543 var userId = req.user._id;
544 var course = req.course;
546 // are we subscribed to this course?
547 var subscribed = course.subscribed( userId );
549 // pull out our lectures
550 Lecture.find( { 'course' : course._id } ).sort( 'name', '1' ).run( function( err, lectures ) {
551 User.findById( course.instructor, function( err, instructor ) {
552 res.render( 'course/index', { 'course' : course, 'instructor': instructor, 'subscribed' : subscribed, 'lectures' : lectures } );
557 app.get( '/course/:id/delete', loadUser, loadCourse, function( req, res) {
558 var course = req.course;
562 course.delete(function( err ) {
563 if ( err ) req.flash( 'info', 'There was a problem removing course: ' + err )
564 else req.flash( 'info', 'Successfully removed course' )
565 res.redirect( '/schools' );
568 req.flash( 'error', 'You don\'t have permission to do that' )
569 res.redirect( '/schools' );
573 // subscribe and unsubscribe to course networks
574 app.get( '/course/:id/subscribe', loadUser, loadCourse, function( req, res ) {
575 var course = req.course;
576 var userId = req.user._id;
578 course.subscribe( userId, function( err ) {
580 req.flash( 'error', 'Error subscribing to course!' );
583 res.redirect( '/course/' + course._id );
587 app.get( '/course/:id/unsubscribe', loadUser, loadCourse, function( req, res ) {
588 var course = req.course;
589 var userId = req.user._id;
591 course.unsubscribe( userId, function( err ) {
593 req.flash( 'error', 'Error unsubscribing from course!' );
596 res.redirect( '/course/' + course._id );
600 app.get( '/course/:id/lecture/new', loadUser, loadCourse, function( req, res ) {
601 var courseId = req.params.id;
602 var course = req.course;
605 if( ( ! course ) || ( ! course.authorized ) ) {
606 return res.redirect( '/course/' + courseId );
609 res.render( 'lecture/new', { 'lecture' : lecture } );
612 app.post( '/course/:id/lecture/new', loadUser, loadCourse, function( req, res ) {
613 var courseId = req.params.id;
614 var course = req.course;
615 var lecture = new Lecture;
617 if( ( ! course ) || ( ! course.authorized ) ) {
618 res.redirect( '/course/' + courseId );
623 lecture.name = req.body.name;
624 lecture.date = req.body.date;
625 lecture.course = course._id;
626 lecture.creator = req.user._id;
628 lecture.save( function( err ) {
630 // XXX: better validation
631 req.flash( 'error', 'Invalid parameters!' );
633 res.render( 'lecture/new', { 'lecture' : lecture } );
635 res.redirect( '/course/' + course._id );
642 app.get( '/lecture/:id', loadUser, loadLecture, function( req, res ) {
643 var lecture = req.lecture;
645 // grab the associated course (this should be done with DBRefs eventually )
646 Course.findById( lecture.course, function( err, course ) {
648 User.findById( course.instructor, function( err, instructor ) {
649 // pull out our notes
650 Note.find( { 'lecture' : lecture._id } ).sort( 'name', '1' ).run( function( err, notes ) {
651 if ( !req.user.loggedIn || !req.lecture.authorized ) {
652 notes = notes.filter(function( note ) {
653 if ( note.public ) return note;
656 res.render( 'lecture/index', {
659 'instructor' : instructor,
663 'javascripts' : [ 'counts.js' ]
668 // with DBRefs we will be able to reassign orphaned courses/lecture/pads
670 req.flash( 'error', 'That lecture is orphaned!' );
677 app.get( '/lecture/:id/notes/new', loadUser, loadLecture, function( req, res ) {
678 var lectureId = req.params.id;
679 var lecture = req.lecture;
682 if( ( ! lecture ) || ( ! lecture.authorized ) ) {
683 res.redirect( '/lecture/' + lectureId );
688 res.render( 'notes/new', { 'note' : note } );
691 app.post( '/lecture/:id/notes/new', loadUser, loadLecture, function( req, res ) {
692 var lectureId = req.params.id;
693 var lecture = req.lecture;
695 if( ( ! lecture ) || ( ! lecture.authorized ) ) {
696 res.redirect( '/lecture/' + lectureId );
703 note.name = req.body.name;
704 note.date = req.body.date;
705 note.lecture = lecture._id;
706 note.public = req.body.private ? false : true;
707 note.creator = req.user._id;
709 note.save( function( err ) {
711 // XXX: better validation
712 req.flash( 'error', 'Invalid parameters!' );
714 res.render( 'notes/new', { 'note' : note } );
716 res.redirect( '/lecture/' + lecture._id );
723 app.get( '/note/:id', loadUser, loadNote, function( req, res ) {
725 var roID = note.roID || false;
727 var lectureId = note.lecture;
729 if ( req.session.visited ) {
730 if ( req.session.visited.indexOf( note._id.toString() ) == -1 ) {
731 req.session.visited.push( note._id );
735 req.session.visited = [];
736 req.session.visited.push( note._id );
743 db.open('mongodb://' + app.set( 'dbHost' ) + '/etherpad/etherpad', function( err, epl ) {
744 epl.findOne( { key: 'pad2readonly:' + note._id }, function(err, record) {
746 roID = record.value.replace(/"/g, '');
755 function processReq() {
756 Lecture.findById( lectureId, function( err, lecture ) {
758 req.flash( 'error', 'That notes page is orphaned!' );
762 Note.find( { 'lecture' : lecture._id }, function( err, otherNotes ) {
764 // XXX User is logged in and sees full notepad
766 res.render( 'notes/index', {
767 'layout' : 'noteLayout',
771 'otherNotes' : otherNotes,
774 'stylesheets' : [ 'dropdown.css', 'fc2.css' ],
775 'javascripts' : [ 'dropdown.js', 'counts.js', 'backchannel.js', 'jquery.tmpl.min.js' ]
778 // XXX User is not logged in and sees notepad that is public
779 res.render( 'notes/public', {
780 'layout' : 'noteLayout',
783 'otherNotes' : otherNotes,
786 'stylesheets' : [ 'dropdown.css', 'fc2.css' ],
787 'javascripts' : [ 'dropdown.js', 'counts.js', 'backchannel.js', 'jquery.tmpl.min.js' ]
797 app.get( '/about', loadUser, function( req, res ) {
798 res.redirect( 'http://blog.finalsclub.org/about.html' );
799 //res.render( 'static/about' );
802 app.get( '/press', loadUser, function( req, res ) {
803 res.render( 'static/press' );
806 app.get( '/conduct', loadUser, function( req, res ) {
807 res.render( 'static/conduct' );
810 app.get( '/legal', loadUser, function( req, res ) {
811 res.redirect( 'http://blog.finalsclub.org/legal.html' );
812 //res.render( 'static/legal' );
815 app.get( '/contact', loadUser, function( req, res ) {
816 res.redirect( 'http://blog.finalsclub.org/contact.html' );
817 //res.render( 'static/contact' );
820 app.get( '/privacy', loadUser, function( req, res ) {
821 res.render( 'static/privacy' );
826 app.get( '/login', function( req, res ) {
827 log3("get login page")
829 res.render( 'login' );
832 app.post( '/login', function( req, res ) {
833 var email = req.body.email;
834 var password = req.body.password;
835 log3("post login ...")
837 User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
842 if( ! user.activated ) {
843 // (undocumented) markdown-esque link functionality in req.flash
844 req.flash( 'error', 'This account isn\'t activated. Check your inbox or [click here](/resendActivation) to resend the activation email.' );
846 req.session.activateCode = user._id;
848 res.render( 'login' );
850 if( user.authenticate( password ) ) {
853 var sid = req.sessionID;
857 user.save( function() {
858 var redirect = req.session.redirect;
860 // login complete, remember the user's email for next time
861 req.session.email = email;
863 // alert the successful login
864 req.flash( 'info', 'Successfully logged in!' );
866 // redirect to profile if we don't have a stashed request
867 res.redirect( redirect || '/profile' );
870 req.flash( 'error', 'Invalid login!' );
872 res.render( 'login' );
877 req.flash( 'error', 'Invalid login!' );
879 res.render( 'login' );
884 app.get( '/resetpw', function( req, res ) {
885 log3("get resetpw page");
886 res.render( 'resetpw' );
888 app.get( '/resetpw/:id', function( req, res ) {
889 var resetPassCode = req.params.id
890 res.render( 'resetpw', { 'verify': true, 'resetPassCode' : resetPassCode } );
893 app.post( '/resetpw', function( req, res ) {
894 log3("post resetpw");
895 var email = req.body.email
898 User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
901 var resetPassCode = hat(64);
902 user.setResetPassCode(resetPassCode);
904 var resetPassUrl = 'http://' + serverHost + ((app.address().port != 80)? ':'+app.address().port: '') + '/resetpw/' + resetPassCode;
906 user.save( function( err ) {
907 log3('save '+user.email);
912 'subject' : 'Your FinalsClub.org Password has been Reset!',
914 'template' : 'userPasswordReset',
916 'resetPassCode' : resetPassCode,
917 'resetPassUrl' : resetPassUrl
921 mailer.send( message, function( err, result ) {
923 // XXX: Add route to resend this email
925 console.log( 'Error sending user password reset email!' );
927 console.log( 'Successfully sent user password reset email.' );
932 res.render( 'resetpw-success', { 'email' : email } );
935 res.render( 'resetpw-error', { 'email' : email } );
939 app.post( '/resetpw/:id', function( req, res ) {
940 log3("post resetpw.code");
941 var resetPassCode = req.params.id
942 var email = req.body.email
943 var pass1 = req.body.pass1
944 var pass2 = req.body.pass2
946 User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
949 var valid = user.resetPassword(resetPassCode, pass1, pass2);
951 user.save( function( err ) {
952 res.render( 'resetpw-success', { 'verify' : true, 'email' : email, 'resetPassCode' : resetPassCode } );
958 res.render( 'resetpw-error', { 'verify' : true, 'email' : email } );
963 app.get( '/register', function( req, res ) {
964 log3("get reg page");
966 School.find( {} ).sort( 'name', '1' ).run( function( err, schools ) {
967 res.render( 'register', { 'schools' : schools } );
971 app.post( '/register', function( req, res ) {
972 var sid = req.sessionId;
976 user.email = req.body.email.toLowerCase();
977 user.password = req.body.password;
979 user.school = req.body.school === 'Other' ? req.body.otherSchool : req.body.school;
980 user.name = req.body.name;
981 user.affil = req.body.affil;
982 user.activated = false;
984 if ( ( user.email === '' ) || ( !isValidEmail( user.email ) ) ) {
985 req.flash( 'error', 'Please enter a valid email' );
986 return res.redirect( '/register' );
989 if ( req.body.password.length < 6 ) {
990 req.flash( 'error', 'Please enter a password longer than eight characters' );
991 return res.redirect( '/register' );
994 var hostname = user.email.split( '@' ).pop();
996 if( /^(finalsclub.org|sleepless.com)$/.test( hostname ) ) {
1000 user.save( function( err ) {
1002 if( /dup key/.test( err.message ) ) {
1003 // attempting to register an existing address
1004 User.findOne({ 'email' : user.email }, function(err, result ) {
1005 if (result.activated) {
1006 req.flash( 'error', 'There is already someone registered with this email, if this is in error contact info@finalsclub.org for help' )
1007 return res.redirect( '/register' )
1009 req.flash( 'error', 'There is already someone registered with this email, if this is you, please check your email for the activation code' )
1010 return res.redirect( '/resendActivation' )
1014 req.flash( 'error', 'An error occurred during registration.' );
1016 return res.redirect( '/register' );
1019 // send user activation email
1020 sendUserActivation( user );
1022 School.findOne( { 'hostnames' : hostname }, function( err, school ) {
1024 sendUserWelcome( user, true );
1025 log3('school recognized '+school.name);
1026 if (!school.users) school.users = [];
1027 school.users.push( user._id );
1029 school.save( function( err ) {
1030 log3('school.save() done');
1031 req.flash( 'info', 'You have automatically been added to the ' + school.name + ' network. Please check your email for the activation link' );
1032 res.redirect( '/' );
1037 'subject' : 'FC User Registration : User added to ' + school.name,
1039 'template' : 'userSchool',
1045 sendUserWelcome( user, false );
1046 req.flash( 'info', 'Your account has been created, please check your email for the activation link' )
1047 res.redirect( '/' );
1051 'subject' : 'FC User Registration : Email did not match any schools',
1053 'template' : 'userNoSchool',
1059 mailer.send( message, function( err, result ) {
1062 console.log( 'Error sending user has no school email to admin\nError Message: '+err.Message );
1064 console.log( 'Successfully sent user has no school email to admin.' );
1074 app.get( '/resendActivation', function( req, res ) {
1075 var activateCode = req.session.activateCode;
1077 User.findById( activateCode, function( err, user ) {
1078 if( ( ! user ) || ( user.activated ) ) {
1079 res.redirect( '/' );
1081 sendUserActivation( user );
1083 req.flash( 'info', 'Your activation code has been resent.' );
1085 res.redirect( '/login' );
1090 app.get( '/activate/:code', function( req, res ) {
1091 var code = req.params.code;
1093 // could break this out into a middleware
1095 res.redirect( '/' );
1098 User.findById( code, function( err, user ) {
1099 if( err || ! user ) {
1100 req.flash( 'error', 'Invalid activation code!' );
1102 res.redirect( '/' );
1104 user.activated = true;
1106 // regenerate our session and log in as the new user
1107 req.session.regenerate( function() {
1108 user.session = req.sessionID;
1110 user.save( function( err ) {
1112 req.flash( 'error', 'Unable to activate account.' );
1114 res.redirect( '/' );
1116 req.flash( 'info', 'Account successfully activated. Please complete your profile.' );
1118 res.redirect( '/profile' );
1126 app.get( '/logout', function( req, res ) {
1127 var sid = req.sessionID;
1129 User.findOne( { 'session' : sid }, function( err, user ) {
1133 user.save( function( err ) {
1134 res.redirect( '/' );
1137 res.redirect( '/' );
1142 app.get( '/profile', loadUser, loggedIn, function( req, res ) {
1143 var user = req.user;
1145 res.render( 'profile/index', { 'user' : user } );
1148 app.post( '/profile', loadUser, loggedIn, function( req, res ) {
1149 var user = req.user;
1150 var fields = req.body;
1153 var wasComplete = user.isComplete;
1155 if( ! fields.name ) {
1156 req.flash( 'error', 'Please enter a valid name!' );
1160 user.name = fields.name;
1163 if( [ 'Student', 'Teachers Assistant' ].indexOf( fields.affiliation ) == -1 ) {
1164 req.flash( 'error', 'Please select a valid affiliation!' );
1168 user.affil = fields.affiliation;
1171 if( fields.existingPassword || fields.newPassword || fields.newPasswordConfirm ) {
1172 // changing password
1173 if( ( ! user.hashed ) || user.authenticate( fields.existingPassword ) ) {
1174 if( fields.newPassword === fields.newPasswordConfirm ) {
1175 // test password strength?
1177 user.password = fields.newPassword;
1179 req.flash( 'error', 'Mismatch in new password!' );
1184 req.flash( 'error', 'Please supply your existing password.' );
1190 user.major = fields.major;
1191 user.bio = fields.bio;
1193 user.showName = ( fields.showName ? true : false );
1196 user.save( function( err ) {
1198 req.flash( 'error', 'Unable to save user profile!' );
1200 if( ( user.isComplete ) && ( ! wasComplete ) ) {
1201 req.flash( 'info', 'Your account is now fully activated. Thank you for joining FinalsClub!' );
1203 res.redirect( '/' );
1205 res.render( 'info', 'Your profile was successfully updated!' );
1207 res.render( 'profile/index', { 'user' : user } );
1212 res.render( 'profile/index', { 'user' : user } );
1219 function loadSubject( req, res, next ) {
1220 if( url.parse( req.url ).pathname.match(/subject/) ) {
1221 ArchivedSubject.findOne({id: req.params.id }, function(err, subject) {
1223 req.flash( 'error', 'Subject with this ID does not exist' )
1224 res.redirect( '/archive' );
1226 req.subject = subject;
1235 function loadOldCourse( req, res, next ) {
1236 if( url.parse( req.url ).pathname.match(/course/) ) {
1237 ArchivedCourse.findOne({id: req.params.id }, function(err, course) {
1239 req.flash( 'error', 'Course with this ID does not exist' )
1240 res.redirect( '/archive' );
1242 req.course = course;
1251 var featuredCourses = [
1252 {name: 'The Human Mind', 'id': 1563},
1253 {name: 'Justice', 'id': 797},
1254 {name: 'Protest Literature', 'id': 1681},
1255 {name: 'Animal Cognition', 'id': 681},
1256 {name: 'Positive Psychology', 'id': 1793},
1257 {name: 'Social Psychology', 'id': 660},
1258 {name: 'The Book from Gutenberg to the Internet', 'id': 1439},
1259 {name: 'Cyberspace in Court', 'id': 1446},
1260 {name: 'Nazi Cinema', 'id': 2586},
1261 {name: 'Media and the American Mind', 'id': 2583},
1262 {name: 'Social Thought in Modern America', 'id': 2585},
1263 {name: 'Major British Writers II', 'id': 869},
1264 {name: 'Civil Procedure', 'id': 2589},
1265 {name: 'Evidence', 'id': 2590},
1266 {name: 'Management of Industrial and Nonprofit Organizations', 'id': 2591},
1269 app.get( '/learn', loadUser, function( req, res ) {
1270 res.render( 'archive/learn', { 'courses' : featuredCourses } );
1273 app.get( '/learn/random', loadUser, function( req, res ) {
1274 res.redirect( '/archive/course/'+ featuredCourses[Math.floor(Math.random()*featuredCourses.length)].id);
1277 app.get( '/archive', loadUser, function( req, res ) {
1278 ArchivedSubject.find({}).sort( 'name', '1' ).run( function( err, subjects ) {
1280 req.flash( 'error', 'There was a problem gathering the archived courses, please try again later.' );
1281 res.redirect( '/' );
1283 res.render( 'archive/index', { 'subjects' : subjects } );
1288 app.get( '/archive/subject/:id', loadUser, loadSubject, function( req, res ) {
1289 ArchivedCourse.find({subject_id: req.params.id}).sort('name', '1').run(function(err, courses) {
1291 req.flash( 'error', 'There are no archived courses' );
1292 res.redirect( '/' );
1294 res.render( 'archive/courses', { 'courses' : courses, 'subject': req.subject } );
1299 app.get( '/archive/course/:id', loadUser, loadOldCourse, function( req, res ) {
1300 ArchivedNote.find({course_id: req.params.id}).sort('name', '1').run(function(err, notes) {
1302 req.flash( 'error', 'There are no notes in this course' );
1303 res.redirect( '/archive' );
1305 res.render( 'archive/notes', { 'notes' : notes, 'course' : req.course } );
1310 app.get( '/archive/note/:id', loadUser, function( req, res ) {
1311 ArchivedNote.findById(req.params.id, function(err, note) {
1313 req.flash( 'error', 'This is not a valid id for a note' );
1314 res.redirect( '/archive' );
1316 ArchivedCourse.findOne({id: note.course_id}, function(err, course) {
1318 req.flash( 'error', 'There is no course for this note' )
1319 res.redirect( '/archive' )
1321 res.render( 'archive/note', { 'layout' : 'notesLayout', 'note' : note, 'course': course } );
1330 var io = require( 'socket.io' ).listen( app );
1332 var Post = mongoose.model( 'Post' );
1334 io.set('authorization', function ( handshake, next ) {
1335 var rawCookie = handshake.headers.cookie;
1337 handshake.cookie = parseCookie(rawCookie);
1338 handshake.sid = handshake.cookie['connect.sid'];
1340 if ( handshake.sid ) {
1341 app.set( 'sessionStore' ).get( handshake.sid, function( err, session ) {
1343 handshake.user = false;
1344 return next(null, true);
1346 // bake a new session object for full r/w
1347 handshake.session = new Session( handshake, session );
1349 User.findOne( { session : handshake.sid }, function( err, user ) {
1351 handshake.user = user;
1352 return next(null, true);
1354 handshake.user = false;
1355 return next(null, true);
1363 return next(null, true);
1368 var backchannel = io
1369 .of( '/backchannel' )
1370 .on( 'connection', function( socket ) {
1372 socket.on('subscribe', function(lecture, cb) {
1373 socket.join(lecture);
1374 Post.find({'lecture': lecture}, function(err, posts) {
1375 if (socket.handshake.user) {
1378 var posts = posts.filter(
1389 socket.on('post', function(res) {
1390 var post = new Post;
1391 var _post = res.post;
1392 var lecture = res.lecture;
1393 post.lecture = lecture;
1394 if ( _post.anonymous ) {
1396 post.userName = 'Anonymous';
1397 post.userAffil = 'N/A';
1399 post.userName = _post.userName;
1400 post.userAffil = _post.userAffil;
1403 post.public = _post.public;
1404 post.date = new Date();
1405 post.body = _post.body;
1408 post.save(function(err) {
1410 // XXX some error handling
1414 backchannel.in(lecture).emit('post', post);
1416 privateEmit(lecture, 'post', post);
1422 socket.on('vote', function(res) {
1423 var vote = res.vote;
1424 var lecture = res.lecture;
1425 Post.findById(vote.parentid, function( err, post ) {
1427 if (post.votes.indexOf(vote.userid) == -1) {
1428 post.votes.push(vote.userid);
1429 post.save(function(err) {
1431 // XXX error handling
1434 backchannel.in(lecture).emit('vote', vote);
1436 privteEmit(lecture, 'vote', vote);
1445 socket.on('report', function(res) {
1446 var report = res.report;
1447 var lecture = res.lecture;
1448 Post.findById(report.parentid, function( err, post ){
1450 if (post.reports.indexOf(report.userid) == -1) {
1451 post.reports.push(report.userid);
1452 post.save(function(err) {
1454 // XXX error handling
1457 backchannel.in(lecture).emit('report', report);
1459 privateEmit(lecture, 'report', report);
1468 socket.on('comment', function(res) {
1469 var comment = res.comment;
1470 var lecture = res.lecture;
1471 console.log('anon', comment.anonymous);
1472 if ( comment.anonymous ) {
1474 comment.userName = 'Anonymous';
1475 comment.userAffil = 'N/A';
1477 Post.findById(comment.parentid, function( err, post ) {
1479 post.comments.push(comment);
1480 post.date = new Date();
1481 post.save(function(err) {
1486 backchannel.in(lecture).emit('comment', comment);
1488 privateEmit(lecture, 'comment', comment);
1496 function privateEmit(lecture, event, data) {
1497 backchannel.clients(lecture).forEach(function(socket) {
1498 if (socket.handshake.user)
1499 socket.emit(event, data);
1503 socket.on('disconnect', function() {
1504 //delete clients[socket.id];
1513 .on( 'connection', function( socket ) {
1514 // pull out user/session information etc.
1515 var handshake = socket.handshake;
1516 var userID = handshake.user._id;
1523 socket.on( 'join', function( note ) {
1524 if (handshake.user === false) {
1526 // XXX: replace by addToSet (once it's implemented in mongoose)
1527 Note.findById( noteID, function( err, note ) {
1529 if( note.collaborators.indexOf( userID ) == -1 ) {
1530 note.collaborators.push( userID );
1538 socket.on( 'watch', function( l ) {
1539 var sendCounts = function() {
1542 Note.find( { '_id' : { '$in' : watched } }, function( err, notes ) {
1545 function( note, callback ) {
1547 var count = note.collaborators.length;
1553 socket.emit( 'counts', send );
1555 timer = setTimeout( sendCounts, 5000 );
1561 Note.find( { 'lecture' : l }, [ '_id' ], function( err, notes ) {
1562 notes.forEach( function( note ) {
1563 watched.push( note._id );
1570 socket.on( 'disconnect', function() {
1571 clearTimeout( timer );
1573 if (handshake.user === false) {
1574 // XXX: replace with $pull once it's available
1576 Note.findById( noteID, function( err, note ) {
1578 var index = note.collaborators.indexOf( userID );
1581 note.collaborators.splice( index, 1 );
1592 // Exception Catch-All
1594 process.on('uncaughtException', function (e) {
1595 console.log("!!!!!! UNCAUGHT EXCEPTION\n" + e.stack);
1601 mongoose.connect( app.set( 'dbUri' ) );
1602 mongoose.connection.db.serverConfig.connection.autoReconnect = true
1604 var mailer = new Mailer( app.set('awsAccessKey'), app.set('awsSecretKey') );
1606 app.listen( serverPort, function() {
1607 console.log( "Express server listening on port %d in %s mode", app.address().port, app.settings.env );
1609 // if run as root, downgrade to the owner of this file
1610 if (process.getuid() === 0) {
1611 require('fs').stat(__filename, function(err, stats) {
1612 if (err) { return console.log(err); }
1613 process.setuid(stats.uid);
1618 function isValidEmail(email) {
1619 var re = /[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
1620 return email.match(re);