Inital Commit
[oweals/finalsclub.git] / app.js
1 /* vim: set ts=2: */
2
3 // Prerequisites
4
5 var sys                                 = require( 'sys' );
6 var os                                  = require( 'os' );
7 var url                                 = require( 'url' );
8
9 var express                     = require( 'express' );
10 var mongoStore  = require( 'connect-mongo' );
11 var async                               = require( 'async' );
12
13 var db                                  = require( './db.js' );
14 var mongoose            = require( './models.js' ).mongoose;
15 //var mysql                             = require( 'mysql' );
16
17 var Mailer                      = require( './mailer.js' );
18 var hat                                 = require('hat');
19
20 var connect                     = require( 'connect' );
21 var Session                     = connect.middleware.session.Session;
22 var parseCookie = connect.utils.parseCookie;
23
24 var log3 = function() {}
25
26 var app = module.exports = express.createServer();
27
28 // Database
29
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' );
35
36 var ArchivedCourse = mongoose.model( 'ArchivedCourse' );
37 var ArchivedNote = mongoose.model( 'ArchivedNote' );
38 var ArchivedSubject = mongoose.model( 'ArchivedSubject' );
39
40 var ObjectId    = mongoose.SchemaTypes.ObjectId;
41
42 // Mysql Init
43 /*
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',
50 })
51 */
52
53 // Configuration
54
55 var ADMIN_EMAIL = process.env.DEV_EMAIL || 'info@finalsclub.org';
56
57 var serverHost = process.env.SERVER_HOST;
58 var serverPort = process.env.SERVER_PORT;
59
60 if( serverHost ) {
61         console.log( 'Using server hostname defined in environment: %s', serverHost );
62 } else {
63         serverHost = os.hostname();
64
65         console.log( 'No hostname defined, defaulting to os.hostname(): %s', serverHost );
66 }
67
68 app.configure( 'development', function() { 
69         app.set( 'errorHandler', express.errorHandler( { dumpExceptions: true, showStack: true } ) );
70
71         app.set( 'dbHost', process.env.MONGO_HOST || 'localhost' );
72         app.set( 'dbUri', 'mongodb://' + app.set( 'dbHost' ) + '/fc' );
73
74         app.set( 'awsAccessKey', process.env.AWS_ACCESS_KEY_ID );
75         app.set( 'awsSecretKey', process.env.AWS_SECRET_ACCESS_KEY );
76
77         if ( !serverPort ) {
78                 serverPort = 3000;
79         }        
80 });
81
82 app.configure( 'production', function() {
83         app.set( 'errorHandler', express.errorHandler( { dumpExceptions: true, showStack: true } ) );
84
85         // XXX Disable view caching temp
86         app.disable( 'view cache' )
87
88         app.set( 'dbHost', process.env.MONGO_HOST || 'localhost' );
89         app.set( 'dbUri', 'mongodb://' + app.set( 'dbHost' ) + '/fc' );
90
91         app.set( 'awsAccessKey', process.env.AWS_ACCESS_KEY_ID );
92         app.set( 'awsSecretKey', process.env.AWS_SECRET_ACCESS_KEY );
93
94         if ( !serverPort ) {
95                 serverPort = 80;
96         }       
97 });
98
99 app.configure(function(){
100         app.set( 'views', __dirname + '/views' );
101         app.set( 'view engine', 'jade' );
102         app.use( express.bodyParser() );
103
104         app.use( express.cookieParser() );
105
106         // sessions
107         app.set( 'sessionStore', new mongoStore( {
108                 'url' : app.set( 'dbUri' )
109         }));
110
111         app.use( express.session( {
112                 'secret'        : 'finalsclub',
113                 'maxAge'        : new Date(Date.now() + (60 * 60 * 24 * 30 * 1000)),
114                 'store'         : app.set( 'sessionStore' )
115         }));
116
117         app.use( express.methodOverride() );
118         app.use( app.router );
119         app.use( express.static( __dirname + '/public' ) );
120
121         // use the error handler defined earlier
122         var errorHandler = app.set( 'errorHandler' );
123
124         app.use( errorHandler );
125 });
126
127 // Mailers
128
129 function sendUserActivation( user ) {
130         var message = {
131                 'to'                            : user.email,
132
133                 'subject'               : 'Activate your FinalsClub.org Account',
134
135                 'template'      : 'userActivation',
136                 'locals'                : {
137                         'user'                          : user,
138                         'serverHost'    : serverHost
139                 }
140         };
141
142         mailer.send( message, function( err, result ) {
143                 if( err ) {
144                         // XXX: Add route to resend this email
145
146                         console.log( 'Error sending user activation email\nError Message: '+err.Message );
147                 } else {
148                         console.log( 'Successfully sent user activation email.' );
149                 }
150         });
151 }
152
153 function sendUserWelcome( user, school ) {
154   var template = school ? 'userWelcome' : 'userWelcomeNoSchool';
155         var message = {
156                 'to'                            : user.email,
157
158                 'subject'               : 'Welcome to FinalsClub',
159
160                 'template'      : template,
161                 'locals'                : {
162                         'user'                          : user,
163                         'serverHost'    : serverHost
164                 }
165         };
166
167         mailer.send( message, function( err, result ) {
168                 if( err ) {
169                         // XXX: Add route to resend this email
170
171                         console.log( 'Error sending user welcome email\nError Message: '+err.Message );
172                 } else {
173                         console.log( 'Successfully sent user welcome email.' );
174                 }
175         });
176 }
177 // Middleware
178
179 function loggedIn( req, res, next ) {
180         if( req.user ) {
181                 next();
182         } else {
183                 req.flash( 'error', 'You must be logged in to access that feature!' );
184
185                 res.redirect( '/' );
186         }
187 }
188
189 function loadUser( req, res, next ) {
190         var sid = req.sessionID;
191
192         console.log( 'got request from session ID: %s', sid );
193
194         User.findOne( { session : sid }, function( err, user ) {
195
196                 log3(err);
197                 log3(user);
198
199                 if( user ) {
200                         req.user = user;
201
202                         req.user.loggedIn = true;
203
204                         log3( 'authenticated user: '+req.user._id+' / '+req.user.email+'');
205
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.' );
211
212                                                 res.redirect( '/profile' );
213                                         } else {
214                                                 next();
215                                         }
216                                 } else {
217                 next();
218                                 }
219       } else {
220                                 req.flash( 'info', 'This account has not been activated. Check your email for the activation URL.' );
221
222                                 res.redirect( '/' );
223       }
224                 } else {
225                         // stash the original request so we can redirect
226                         var path = url.parse( req.url ).pathname;
227                         req.session.redirect = path;
228
229                         req.user = {};
230
231                         next();
232                 }
233         });
234 }
235
236 function loadSchool( req, res, next ) {
237         var user                        = req.user;
238         var schoolId    = req.params.id;
239
240         School.findById( schoolId, function( err, school ) {
241                 if( school ) {
242                         req.school = school;
243
244                         school.authorize( user, function( authorized ){
245                                 req.school.authorized = authorized;
246                                 next();
247                         });
248                 } else {
249                         req.flash( 'error', 'Invalid school specified!' );
250
251                         res.redirect( '/' );
252                 }
253         });
254 }
255
256 function loadCourse( req, res, next ) {
257         var user                        = req.user;
258         var courseId    = req.params.id;
259
260         Course.findById( courseId, function( err, course ) {
261                 if( course ) {
262                         req.course = course;
263
264                         course.authorize( user, function( authorized )  {
265                                 req.course.authorized = authorized;
266
267                                 next();
268                         });
269                 } else {
270                         req.flash( 'error', 'Invalid course specified!' );
271
272                         res.redirect( '/' );
273                 }
274         });
275 }
276
277 function loadLecture( req, res, next ) {
278         var user                        = req.user;
279         var lectureId   = req.params.id;
280
281         Lecture.findById( lectureId, function( err, lecture ) {
282                 if( lecture ) {
283                         req.lecture = lecture;
284
285                         lecture.authorize( user, function( authorized ) {
286                                 req.lecture.authorized = authorized;
287
288                                 next();
289                         });
290                 } else {
291                         req.flash( 'error', 'Invalid lecture specified!' );
292
293                         res.redirect( '/' );
294                 }
295         });
296 }
297
298 function loadNote( req, res, next ) {
299         var user         = req.user ? req.user : false;
300         var noteId = req.params.id;
301
302         Note.findById( noteId, function( err, note ) {
303                 if( note && user ) {
304                         note.authorize( user, function( auth ) {
305                                 if( auth ) {
306                                         req.note = note;
307
308                                         next();
309                                 } else if ( note.public ) {
310                                         req.RO = true;
311                                         req.note = note;
312
313                                         next();
314                                 } else {
315                                         req.flash( 'error', 'You do not have permission to access that note.' );
316
317                                         res.redirect( '/' );
318                                 }
319                         })
320                 } else if ( note && note.public ) {
321                         req.note = note;
322                         req.RO = true;
323
324                         next();
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' );
329                 } else {
330                         req.flash( 'error', 'Invalid note specified!' );
331
332                         res.redirect( '/login' );
333                 }
334         });
335 }
336
337 app.dynamicHelpers( {
338         // flash messages from express-messages
339         'messages' : require( 'express-messages' ),
340
341         'user' : function( req, res ) {
342                 return req.user;
343         },
344
345         'session' : function( req, res ) {
346                 return req.session;
347         }
348 });
349
350 // Routes
351
352 app.get( '/', loadUser, function( req, res ) {
353         log3("get / page");
354
355         res.render( 'index' );
356 });
357
358 app.get( '/schools', loadUser, function( req, res ) {
359         var user = req.user;
360
361         // mongoose's documentation on sort is extremely poor, tread carefully
362         School.find( {} ).sort( 'name', '1' ).run( function( err, schools ) {
363                 if( schools ) {
364                         async.forEach(
365                                 schools,
366                                 function( school, callback ) {
367                                         school.authorize( user, function( authorized ) {
368                                                 school.authorized = authorized;
369
370                                                 Course.find( { 'school' : school._id } ).sort( 'name', '1' ).run( function( err, courses ) {
371                                                                                 if( courses.length > 0 ) {
372                                                                                                                 school.courses = courses;
373                                                                                 } else {
374                                                                                                                 school.courses = [];
375                                                                                 }
376                                                                                 callback();
377                                                 });
378                                         });
379                                 },
380                                 function( err ) {
381                                         res.render( 'schools', { 'schools' : schools } );
382                                 }
383                         );
384                 } else {
385                         res.render( 'schools', { 'schools' : [] } );
386                 }
387         });
388 });
389
390 app.get( '/:id/course/new', loadUser, loadSchool, function( req, res ) {
391         var school = req.school;
392
393         if( ( ! school ) || ( ! school.authorized ) ) {
394                 return res.redirect( '/schools' );
395         }
396
397         res.render( 'course/new', { 'school': school } );
398 });
399
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;
405
406         if( ( ! school ) || ( ! school.authorized ) ) {
407                 res.redirect( '/schools' );
408         }
409
410   if ( !instructorEmail || !instructorName ) {
411     req.flash( 'error', 'Invalid parameters!' )
412     return res.render( 'course/new' );
413   }
414
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;
422
423         // find our instructor or invite them if necessary
424   User.findOne( { 'email' : instructorEmail }, function( err, user ) {
425     if ( !user ) {
426       var user          = new User;
427
428                         user.name                                       = instructorName
429       user.email        = instructorEmail;
430                         user.affil        = 'Instructor';
431       user.school       = school.name;
432
433       user.activated    = false;
434       
435                         if ( ( user.email === '' ) || ( !isValidEmail( user.email ) ) ) {
436                                 req.flash( 'error', 'Please enter a valid email' );
437                                 return res.redirect( '/register' );
438                         }
439       user.save(function( err ) {
440         if ( err ) {
441           req.flash( 'error', 'Invalid parameters!' )
442           return res.render( 'course/new' );
443         } else {
444                                         var message = {
445                                                 to                                      : user.email,
446
447                                                 'subject'               : 'A non-profit open education initiative',
448         
449                                                 'template'      : 'instructorInvite',
450                                                 'locals'                : {
451                                                         'course'                        : course,
452                                                         'school'                        : school,
453                                                         'user'                          : user,
454                                                         'serverHost'    : serverHost
455                                                 }
456                                         };
457
458                                         mailer.send( message, function( err, result ) {
459                                                 if( err ) {
460                                                         console.log( 'Error inviting instructor to course!' );
461                                                 } else {
462                                                         console.log( 'Successfully invited instructor to course.' );
463                                                 }
464                                         });
465
466           course.instructor = user._id;
467                                         course.save( function( err ) {
468                                                 if( err ) {
469                                                         // XXX: better validation
470                                                         req.flash( 'error', 'Invalid parameters!' );
471
472                                                         return res.render( 'course/new' );
473                                                 } else {
474                                                         var message = {
475                                                                 to                                      : ADMIN_EMAIL,
476
477                                                                 'subject'               : school.name+' has a new course: '+course.name,
478                                         
479                                                                 'template'      : 'newCourse',
480                                                                 'locals'                : {
481                                                                         'course'                        : course,
482                   'instructor'  : user,
483                                                                         'user'                          : req.user,
484                                                                         'serverHost'    : serverHost
485                                                                 }
486                                                         };
487
488                                                         mailer.send( message, function( err, result ) {
489                                                                 if ( err ) {
490                                                                         console.log( 'Error sending new course email to info@finalsclub.org' )
491                                                                 } else {
492                                                                         console.log( 'Successfully invited instructor to course')
493                                                                 }
494                                                         })
495                                                         res.redirect( '/schools' );
496                                                 }
497                                         });
498                                 }
499                         })
500                 } else {
501                         if (user.affil === 'Instructor') {
502         course.instructor = user._id;
503                                 course.save( function( err ) {
504                                         if( err ) {
505                                                 // XXX: better validation
506                                                 req.flash( 'error', 'Invalid parameters!' );
507
508                                                 return res.render( 'course/new' );
509                                         } else {
510                                                 var message = {
511                                                         to                                      : ADMIN_EMAIL,
512
513                                                         'subject'               : school.name+' has a new course: '+course.name,
514                                         
515                                                         'template'      : 'newCourse',
516                                                         'locals'                : {
517                                                                 'course'                        : course,
518                 'instructor'  : user,
519                                                                 'user'                          : req.user,
520                                                                 'serverHost'    : serverHost
521                                                         }
522                                                 };
523
524                                                 mailer.send( message, function( err, result ) {
525                                                         if ( err ) {
526                                                                 console.log( 'Error sending new course email to info@finalsclub.org' )
527                                                         } else {
528                                                                 console.log( 'Successfully invited instructor to course')
529                                                         }
530                                                 })
531                                                 res.redirect( '/schools' );
532                                         }
533                                 });
534                         } else {
535                                 req.flash( 'error', 'The existing user\'s email you entered is not an instructor' );
536                                 res.render( 'course/new' );
537                         }
538                 }
539         })
540 });
541
542 app.get( '/course/:id', loadUser, loadCourse, function( req, res ) {
543         var userId = req.user._id;
544         var course = req.course;
545
546         // are we subscribed to this course?
547         var subscribed = course.subscribed( userId );
548
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 } );
553     })
554         });
555 });
556
557 app.get( '/course/:id/delete', loadUser, loadCourse, function( req, res) {
558         var course = req.course;
559         var user = req.user;
560
561         if ( user.admin ) {
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' );
566     });
567         } else {
568                 req.flash( 'error', 'You don\'t have permission to do that' )
569                 res.redirect( '/schools' );
570         }
571 })
572
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;
577
578         course.subscribe( userId, function( err ) {
579                 if( err ) {
580                         req.flash( 'error', 'Error subscribing to course!' );
581                 }
582
583                 res.redirect( '/course/' + course._id );
584         });
585 });
586
587 app.get( '/course/:id/unsubscribe', loadUser, loadCourse, function( req, res ) {
588         var course = req.course;
589         var userId = req.user._id;
590
591         course.unsubscribe( userId, function( err ) {
592                 if( err ) {
593                         req.flash( 'error', 'Error unsubscribing from course!' );
594                 }
595
596                 res.redirect( '/course/' + course._id );
597         });
598 });
599
600 app.get( '/course/:id/lecture/new', loadUser, loadCourse, function( req, res ) {
601         var courseId    = req.params.id;
602         var course              = req.course;
603         var lecture             = {};
604
605         if( ( ! course ) || ( ! course.authorized ) ) {
606                 return res.redirect( '/course/' + courseId );
607         }
608
609         res.render( 'lecture/new', { 'lecture' : lecture } );
610 });
611
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;
616
617         if( ( ! course ) || ( ! course.authorized ) ) {
618                 res.redirect( '/course/' + courseId );
619
620                 return;
621         }
622
623         lecture.name            = req.body.name;
624         lecture.date            = req.body.date;
625         lecture.course  = course._id;
626   lecture.creator = req.user._id;
627
628         lecture.save( function( err ) {
629                 if( err ) {
630                         // XXX: better validation
631                         req.flash( 'error', 'Invalid parameters!' );
632
633                         res.render( 'lecture/new', { 'lecture' : lecture } );
634                 } else {
635                         res.redirect( '/course/' + course._id );
636                 }
637         });
638 });
639
640 // lecture
641
642 app.get( '/lecture/:id', loadUser, loadLecture, function( req, res ) {
643         var lecture     = req.lecture;
644
645         // grab the associated course (this should be done with DBRefs eventually )
646         Course.findById( lecture.course, function( err, course ) {
647                 if( 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;
654             })
655           }
656           res.render( 'lecture/index', {
657             'lecture'                   : lecture,
658             'course'                    : course,
659             'instructor'  : instructor,
660             'notes'                             : notes,
661             'counts'                    : counts,
662
663             'javascripts'       : [ 'counts.js' ]
664           });
665         });
666       })
667                 } else {
668                         // with DBRefs we will be able to reassign orphaned courses/lecture/pads
669
670                         req.flash( 'error', 'That lecture is orphaned!' );
671
672                         res.redirect( '/' );
673                 }
674         });
675 });
676
677 app.get( '/lecture/:id/notes/new', loadUser, loadLecture, function( req, res ) {
678         var lectureId   = req.params.id;
679         var lecture             = req.lecture;
680         var note                        = {};
681
682         if( ( ! lecture ) || ( ! lecture.authorized ) ) {
683                 res.redirect( '/lecture/' + lectureId );
684
685                 return;
686         }
687
688         res.render( 'notes/new', { 'note' : note } );
689 });
690
691 app.post( '/lecture/:id/notes/new', loadUser, loadLecture, function( req, res ) {
692         var lectureId   = req.params.id;
693         var lecture             = req.lecture;
694
695         if( ( ! lecture ) || ( ! lecture.authorized ) ) {
696                 res.redirect( '/lecture/' + lectureId );
697
698                 return;
699         }
700
701         var note                = new Note;
702
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;
708
709         note.save( function( err ) {
710                 if( err ) {
711                         // XXX: better validation
712                         req.flash( 'error', 'Invalid parameters!' );
713
714                         res.render( 'notes/new', { 'note' : note } );
715                 } else {
716                         res.redirect( '/lecture/' + lecture._id );
717                 }
718         });
719 });
720
721 // notes
722
723 app.get( '/note/:id', loadUser, loadNote, function( req, res ) {
724         var note = req.note;
725         var roID = note.roID || false;
726
727         var lectureId = note.lecture;
728
729         if ( req.session.visited ) {
730                         if ( req.session.visited.indexOf( note._id.toString() ) == -1 ) {
731                                         req.session.visited.push( note._id );
732                                         note.addVisit();
733                         }
734         } else {
735                 req.session.visited = [];
736                 req.session.visited.push( note._id );
737                 note.addVisit();
738         }
739
740         if (roID) {
741                 processReq();
742         } else {
743                 db.open('mongodb://' + app.set( 'dbHost' ) + '/etherpad/etherpad', function( err, epl ) {
744                         epl.findOne( { key: 'pad2readonly:' + note._id }, function(err, record) {
745                                 if ( record ) {
746                                         roID = record.value.replace(/"/g, '');
747                                 } else {
748                                         roID = false;
749                                 }
750                                 processReq();
751                         })
752                 })
753         }
754
755         function processReq() {
756                 Lecture.findById( lectureId, function( err, lecture ) {
757                         if( ! lecture ) {
758                                 req.flash( 'error', 'That notes page is orphaned!' );
759
760                                 res.redirect( '/' );
761                         }
762                         Note.find( { 'lecture' : lecture._id }, function( err, otherNotes ) {
763                                 if( !req.RO ) {
764                                         // XXX User is logged in and sees full notepad
765
766                                         res.render( 'notes/index', {
767                                                 'layout'                        : 'noteLayout',
768                                                 'host'                          : serverHost,
769                                                 'note'                          : note,
770                                                 'lecture'                       : lecture,
771                                                 'otherNotes'    : otherNotes,
772                                                 'RO'                                    : false,
773                                                 'roID'                          : roID,
774                                                 'stylesheets' : [ 'dropdown.css', 'fc2.css' ],
775                                                 'javascripts'   : [ 'dropdown.js', 'counts.js', 'backchannel.js', 'jquery.tmpl.min.js' ]
776                                         });
777                                 } else {
778                                         // XXX User is not logged in and sees notepad that is public
779                                         res.render( 'notes/public', {
780                                                 'layout'                        : 'noteLayout',
781                                                 'host'                          : serverHost,
782                                                 'note'                          : note,
783                                                 'otherNotes'    : otherNotes,
784                                                 'roID'                          : roID,
785                                                 'lecture'                       : lecture,
786                                                 'stylesheets' : [ 'dropdown.css', 'fc2.css' ],
787                                                 'javascripts'   : [ 'dropdown.js', 'counts.js', 'backchannel.js', 'jquery.tmpl.min.js' ]
788                                         });
789                                 }
790                         });
791                 });
792         }
793 });
794
795 // static pages
796
797 app.get( '/about', loadUser, function( req, res ) {
798   res.redirect( 'http://blog.finalsclub.org/about.html' );
799   //res.render( 'static/about' );
800 });
801
802 app.get( '/press', loadUser, function( req, res ) {
803   res.render( 'static/press' );
804 });
805
806 app.get( '/conduct', loadUser, function( req, res ) {
807   res.render( 'static/conduct' );
808 });
809
810 app.get( '/legal', loadUser, function( req, res ) {
811   res.redirect( 'http://blog.finalsclub.org/legal.html' );
812   //res.render( 'static/legal' );
813 });
814
815 app.get( '/contact', loadUser, function( req, res ) {
816   res.redirect( 'http://blog.finalsclub.org/contact.html' );
817         //res.render( 'static/contact' );
818 });
819
820 app.get( '/privacy', loadUser, function( req, res ) {
821         res.render( 'static/privacy' );
822 });
823
824 // authentication
825
826 app.get( '/login', function( req, res ) {
827         log3("get login page")
828
829         res.render( 'login' );  
830 });
831
832 app.post( '/login', function( req, res ) {
833         var email                = req.body.email;
834         var password = req.body.password;
835         log3("post login ...")
836
837         User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
838                 log3(err) 
839                 log3(user) 
840
841                 if( 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.' );
845
846                                 req.session.activateCode = user._id;
847
848                                 res.render( 'login' );
849                         } else {
850                                 if( user.authenticate( password ) ) {
851                                         log3("pass ok") 
852
853                                         var sid = req.sessionID;
854
855                                         user.session = sid;
856
857                                         user.save( function() {
858                                                 var redirect = req.session.redirect;
859         
860                                                 // login complete, remember the user's email for next time
861                                                 req.session.email = email;
862
863                                                 // alert the successful login
864                                                 req.flash( 'info', 'Successfully logged in!' );
865
866                                                 // redirect to profile if we don't have a stashed request
867                                                 res.redirect( redirect || '/profile' );
868                                         });
869                                 } else {
870                                         req.flash( 'error', 'Invalid login!' );
871
872                                         res.render( 'login' );
873                                 }
874                         }
875                 } else {
876                         log3("bad login")
877                         req.flash( 'error', 'Invalid login!' );
878
879                         res.render( 'login' );
880                 }
881         });
882 });
883
884 app.get( '/resetpw', function( req, res ) {
885         log3("get resetpw page");
886         res.render( 'resetpw' );
887 });
888 app.get( '/resetpw/:id', function( req, res ) {
889         var resetPassCode = req.params.id
890         res.render( 'resetpw', { 'verify': true, 'resetPassCode' : resetPassCode } );
891 });
892
893 app.post( '/resetpw', function( req, res ) {
894         log3("post resetpw");
895         var email = req.body.email
896
897
898         User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
899                 if( user ) {
900
901                         var resetPassCode = hat(64);
902                         user.setResetPassCode(resetPassCode);
903
904                         var resetPassUrl = 'http://' + serverHost + ((app.address().port != 80)? ':'+app.address().port: '') + '/resetpw/' + resetPassCode;
905
906                         user.save( function( err ) {
907                                 log3('save '+user.email);
908
909                                 var message = {
910                                         'to'                            : user.email,
911
912                                         'subject'               : 'Your FinalsClub.org Password has been Reset!',
913
914                                         'template'      : 'userPasswordReset',
915                                                 'locals'                : {
916                                                         'resetPassCode'         : resetPassCode,
917                                                         'resetPassUrl'          : resetPassUrl
918                                         }
919                                 };
920
921                                 mailer.send( message, function( err, result ) {
922                                         if( err ) {
923                                                 // XXX: Add route to resend this email
924
925                                                 console.log( 'Error sending user password reset email!' );
926                                         } else {
927                                                 console.log( 'Successfully sent user password reset email.' );
928                                         }
929
930                                 }); 
931
932                                 res.render( 'resetpw-success', { 'email' : email } );
933                         });                     
934                 } else {
935                         res.render( 'resetpw-error', { 'email' : email } );
936                 }
937         });
938 });
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
945
946         User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
947                 var valid = false;
948                 if( user ) {
949                         var valid = user.resetPassword(resetPassCode, pass1, pass2);
950                         if (valid) {
951                                 user.save( function( err ) {
952                                         res.render( 'resetpw-success', { 'verify' : true, 'email' : email, 'resetPassCode' : resetPassCode } );         
953                                 });                     
954                         }
955                 } 
956
957                 if (!valid) {
958                         res.render( 'resetpw-error', { 'verify' : true, 'email' : email } );
959                 }
960         });
961 });
962
963 app.get( '/register', function( req, res ) {
964         log3("get reg page");
965
966         School.find( {} ).sort( 'name', '1' ).run( function( err, schools ) {
967                 res.render( 'register', { 'schools' : schools } );
968         })
969 });
970
971 app.post( '/register', function( req, res ) {
972         var sid = req.sessionId;
973
974         var user = new User;
975   
976         user.email        = req.body.email.toLowerCase();
977         user.password     = req.body.password;
978         user.session      = sid;
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;
983
984         if ( ( user.email === '' ) || ( !isValidEmail( user.email ) ) ) {
985                 req.flash( 'error', 'Please enter a valid email' );
986                 return res.redirect( '/register' );
987         }
988
989         if ( req.body.password.length < 6 ) {
990                 req.flash( 'error', 'Please enter a password longer than eight characters' );
991                 return res.redirect( '/register' );
992         }
993
994         var hostname = user.email.split( '@' ).pop();
995
996         if( /^(finalsclub.org|sleepless.com)$/.test( hostname ) ) {
997                 user.admin = true;
998         }
999
1000         user.save( function( err ) {
1001                 if ( 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' )
1008                                                 } else {
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' )
1011                                                 }
1012                                         });
1013                         } else {
1014                                 req.flash( 'error', 'An error occurred during registration.' );
1015
1016                                 return res.redirect( '/register' );
1017                         }
1018                 } else {
1019                         // send user activation email
1020                         sendUserActivation( user );
1021
1022                         School.findOne( { 'hostnames' : hostname }, function( err, school ) {
1023                                 if( school ) {
1024           sendUserWelcome( user, true );
1025                                         log3('school recognized '+school.name);
1026                                         if (!school.users) school.users = [];
1027                                         school.users.push( user._id );
1028
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( '/' );
1033                                         });
1034                                         var message = {
1035                                                 'to'       : ADMIN_EMAIL,
1036
1037                                                 'subject'  : 'FC User Registration : User added to ' + school.name,
1038
1039                                                 'template' : 'userSchool',
1040                                                 'locals'   : {
1041                                                         'user'   : user
1042                                                 }
1043                                         }
1044                                 } else {
1045           sendUserWelcome( user, false );
1046                                         req.flash( 'info', 'Your account has been created, please check your email for the activation link' )
1047                                         res.redirect( '/' );
1048                                         var message = {
1049                                                 'to'       : ADMIN_EMAIL,
1050
1051                                                 'subject'  : 'FC User Registration : Email did not match any schools',
1052
1053                                                 'template' : 'userNoSchool',
1054                                                 'locals'   : {
1055                                                         'user'   : user
1056                                                 }
1057                                         }
1058                                 }
1059                                         mailer.send( message, function( err, result ) {
1060                                                 if ( err ) {
1061                                         
1062                                                         console.log( 'Error sending user has no school email to admin\nError Message: '+err.Message );
1063                                                 } else {
1064                                                         console.log( 'Successfully sent user has no school email to admin.' );
1065                                                 }
1066                                         })
1067
1068                         });
1069                 }
1070
1071         });
1072 });
1073
1074 app.get( '/resendActivation', function( req, res ) {
1075         var activateCode = req.session.activateCode;
1076
1077         User.findById( activateCode, function( err, user ) {
1078                 if( ( ! user ) || ( user.activated ) ) {
1079                         res.redirect( '/' );
1080                 } else {
1081                         sendUserActivation( user );
1082
1083                         req.flash( 'info', 'Your activation code has been resent.' );
1084
1085                         res.redirect( '/login' );
1086                 }
1087         });
1088 });
1089
1090 app.get( '/activate/:code', function( req, res ) {
1091         var code = req.params.code;
1092
1093         // could break this out into a middleware
1094         if( ! code ) {
1095                 res.redirect( '/' );
1096         }
1097
1098         User.findById( code, function( err, user ) {
1099                 if( err || ! user ) {
1100                         req.flash( 'error', 'Invalid activation code!' );
1101
1102                         res.redirect( '/' );
1103                 } else {
1104                         user.activated = true;
1105
1106                         // regenerate our session and log in as the new user
1107                         req.session.regenerate( function() {
1108                                 user.session = req.sessionID;
1109
1110                                 user.save( function( err ) {
1111                                         if( err ) {
1112                                                 req.flash( 'error', 'Unable to activate account.' );
1113
1114                                                 res.redirect( '/' );
1115                                         } else {
1116                                                 req.flash( 'info', 'Account successfully activated. Please complete your profile.' );
1117
1118                                                 res.redirect( '/profile' );
1119                                         }
1120                                 });
1121                         });
1122                 }
1123         });
1124 });
1125
1126 app.get( '/logout', function( req, res ) {
1127         var sid = req.sessionID;
1128
1129         User.findOne( { 'session' : sid }, function( err, user ) {
1130                 if( user ) {
1131                         user.session = '';
1132
1133                         user.save( function( err ) {
1134                                 res.redirect( '/' );
1135                         });
1136                 } else {
1137                         res.redirect( '/' );
1138                 }
1139         });
1140 });
1141
1142 app.get( '/profile', loadUser, loggedIn, function( req, res ) {
1143         var user = req.user;
1144         
1145         res.render( 'profile/index', { 'user' : user } );
1146 });
1147
1148 app.post( '/profile', loadUser, loggedIn, function( req, res ) {
1149         var user                = req.user;
1150         var fields      = req.body;
1151
1152         var error                               = false;
1153         var wasComplete = user.isComplete;
1154
1155         if( ! fields.name ) {
1156                 req.flash( 'error', 'Please enter a valid name!' );
1157
1158                 error = true;
1159         } else {
1160                 user.name = fields.name;
1161         }
1162
1163         if( [ 'Student', 'Teachers Assistant' ].indexOf( fields.affiliation ) == -1 ) {
1164                 req.flash( 'error', 'Please select a valid affiliation!' );
1165
1166                 error = true;
1167         } else {
1168                 user.affil = fields.affiliation;
1169         }
1170
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?
1176
1177                                 user.password = fields.newPassword;
1178                         } else {
1179                                 req.flash( 'error', 'Mismatch in new password!' );
1180
1181                                 error = true;
1182                         }
1183                 } else {
1184                         req.flash( 'error', 'Please supply your existing password.' );
1185
1186                         error = true;
1187                 }
1188         }
1189
1190         user.major              = fields.major;
1191         user.bio                        = fields.bio;
1192
1193         user.showName   = ( fields.showName ? true : false );
1194
1195         if( ! error ) {
1196                 user.save( function( err ) {
1197                         if( err ) {
1198                                 req.flash( 'error', 'Unable to save user profile!' );
1199                         } else {
1200                                 if( ( user.isComplete ) && ( ! wasComplete ) ) {
1201                                         req.flash( 'info', 'Your account is now fully activated. Thank you for joining FinalsClub!' );
1202
1203                                         res.redirect( '/' );
1204                                 } else {
1205                                         res.render( 'info', 'Your profile was successfully updated!' );
1206
1207                                         res.render( 'profile/index', { 'user' : user } );
1208                                 }
1209                         }
1210                 });
1211         } else {
1212                 res.render( 'profile/index', { 'user' : user } );
1213         }
1214 });
1215
1216
1217 // Old Notes
1218
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) {
1222       if ( err ) {
1223         req.flash( 'error', 'Subject with this ID does not exist' )
1224         res.redirect( '/archive' );
1225       } else {
1226         req.subject = subject;
1227         next()
1228       }
1229     })
1230   } else {
1231     next()
1232   } 
1233 }
1234
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) {
1238       if ( err ) {
1239         req.flash( 'error', 'Course with this ID does not exist' )
1240         res.redirect( '/archive' );
1241       } else {
1242         req.course = course;
1243         next()
1244       }
1245     })
1246   } else {
1247     next()
1248   } 
1249 }
1250
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},
1267 ];
1268
1269 app.get( '/learn', loadUser, function( req, res ) {
1270         res.render( 'archive/learn', { 'courses' : featuredCourses } );
1271 })
1272
1273 app.get( '/learn/random', loadUser, function( req, res ) {
1274         res.redirect( '/archive/course/'+ featuredCourses[Math.floor(Math.random()*featuredCourses.length)].id);
1275 })
1276
1277 app.get( '/archive', loadUser, function( req, res ) {
1278   ArchivedSubject.find({}).sort( 'name', '1' ).run( function( err, subjects ) {
1279     if ( err ) {
1280       req.flash( 'error', 'There was a problem gathering the archived courses, please try again later.' );
1281       res.redirect( '/' );
1282     } else {
1283       res.render( 'archive/index', { 'subjects' : subjects } );
1284     }
1285   })
1286 })
1287
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) {
1290     if ( err ) {
1291       req.flash( 'error', 'There are no archived courses' );
1292       res.redirect( '/' );
1293     } else {
1294       res.render( 'archive/courses', { 'courses' : courses, 'subject': req.subject } );
1295     }
1296   })
1297 })
1298
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) {
1301     if ( err ) {
1302       req.flash( 'error', 'There are no notes in this course' );
1303       res.redirect( '/archive' );
1304     } else {
1305       res.render( 'archive/notes', { 'notes' : notes, 'course' : req.course } );
1306     }
1307   })
1308 })
1309
1310 app.get( '/archive/note/:id', loadUser, function( req, res ) {
1311   ArchivedNote.findById(req.params.id, function(err, note) {
1312     if ( err ) {
1313       req.flash( 'error', 'This is not a valid id for a note' );
1314       res.redirect( '/archive' );
1315     } else {
1316       ArchivedCourse.findOne({id: note.course_id}, function(err, course) {
1317         if ( err ) {
1318           req.flash( 'error', 'There is no course for this note' )
1319           res.redirect( '/archive' )
1320         } else {
1321           res.render( 'archive/note', { 'layout' : 'notesLayout', 'note' : note, 'course': course } );
1322         }
1323       })
1324     }
1325   })
1326 })
1327
1328 // socket.io server
1329
1330 var io = require( 'socket.io' ).listen( app );
1331
1332 var Post = mongoose.model( 'Post' );
1333
1334 io.set('authorization', function ( handshake, next ) {
1335         var rawCookie = handshake.headers.cookie;
1336         if (rawCookie) {
1337                 handshake.cookie = parseCookie(rawCookie);
1338                 handshake.sid = handshake.cookie['connect.sid'];
1339
1340                 if ( handshake.sid ) {
1341                         app.set( 'sessionStore' ).get( handshake.sid, function( err, session ) {
1342                                 if( err ) {
1343                                         handshake.user = false;
1344                                         return next(null, true);
1345                                 } else {
1346                                         // bake a new session object for full r/w
1347                                         handshake.session = new Session( handshake, session );
1348
1349                                         User.findOne( { session : handshake.sid }, function( err, user ) {
1350                                                 if( user ) {
1351                                                         handshake.user = user;
1352                                                         return next(null, true);
1353                                                 } else {
1354                                                         handshake.user = false;
1355                                                         return next(null, true);
1356                                                 }
1357                                         });
1358                                 }
1359                         })
1360                 }
1361         } else {
1362                 data.user = false;
1363                 return next(null, true);
1364         }
1365 });
1366
1367
1368 var backchannel = io
1369         .of( '/backchannel' )
1370         .on( 'connection', function( socket ) {
1371
1372                 socket.on('subscribe', function(lecture, cb) {
1373                         socket.join(lecture);
1374                         Post.find({'lecture': lecture}, function(err, posts) {
1375                                 if (socket.handshake.user) {
1376                                         cb(posts);
1377                                 } else {
1378                                         var posts = posts.filter(
1379                                                 function(post) {
1380                                                 if (post.public)
1381                                                         return post;
1382                                         }
1383                                         )
1384                                         cb(posts)
1385                                 }
1386                         });
1387                 });
1388
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 ) {
1395                                 post.userid             = 0;
1396                                 post.userName   = 'Anonymous';
1397                                 post.userAffil = 'N/A';
1398                         } else {
1399                                 post.userName = _post.userName;
1400                                 post.userAffil = _post.userAffil;
1401                         }
1402
1403                         post.public = _post.public;
1404                         post.date = new Date();
1405                         post.body = _post.body;
1406                         post.votes = [];
1407                         post.reports = [];
1408                         post.save(function(err) {
1409                                 if (err) {
1410                                         // XXX some error handling
1411                                         console.log(err);
1412                                 } else {
1413                                         if (post.public) {
1414                                                 backchannel.in(lecture).emit('post', post);
1415                                         } else {
1416                                                 privateEmit(lecture, 'post', post);
1417                                         }
1418                                 }
1419                         });
1420                 });
1421
1422                 socket.on('vote', function(res) {
1423                         var vote = res.vote;
1424                         var lecture = res.lecture;
1425                         Post.findById(vote.parentid, function( err, post ) {
1426                                 if (!err) {
1427                                         if (post.votes.indexOf(vote.userid) == -1) {
1428                                                 post.votes.push(vote.userid);
1429                                                 post.save(function(err) {
1430                                                         if (err) {
1431                                                                 // XXX error handling
1432                                                         } else {
1433                                                                 if (post.public) {
1434                                                                         backchannel.in(lecture).emit('vote', vote);
1435                                                                 } else {
1436                                                                         privteEmit(lecture, 'vote', vote);
1437                                                                 }
1438                                                         }
1439                                                 });
1440                                         }
1441                                 }
1442                         })
1443                 });
1444
1445                 socket.on('report', function(res) {
1446                         var report = res.report;
1447                         var lecture = res.lecture;
1448                         Post.findById(report.parentid, function( err, post ){
1449                                 if (!err) {
1450                                         if (post.reports.indexOf(report.userid) == -1) {
1451                                                 post.reports.push(report.userid);
1452                                                 post.save(function(err) {
1453                                                         if (err) {
1454                                                                 // XXX error handling
1455                                                         } else {
1456                                                                 if (post.public) {
1457                                                                         backchannel.in(lecture).emit('report', report);
1458                                                                 } else {
1459                                                                         privateEmit(lecture, 'report', report);
1460                                                                 }
1461                                                         }
1462                                                 });
1463                                         }
1464                                 }
1465                         })
1466                 });
1467
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 ) {
1473                                 comment.userid          = 0;
1474                                 comment.userName        = 'Anonymous';
1475                                 comment.userAffil = 'N/A';
1476                         }
1477                         Post.findById(comment.parentid, function( err, post ) {
1478                                 if (!err) {
1479                                         post.comments.push(comment);
1480                                         post.date = new Date();
1481                                         post.save(function(err) {
1482                                                 if (err) {
1483                                                         console.log(err);
1484                                                 } else {
1485                                                         if (post.public) {
1486                                                                 backchannel.in(lecture).emit('comment', comment);
1487                                                         } else {
1488                                                                 privateEmit(lecture, 'comment', comment);
1489                                                         }
1490                                                 }
1491                                         })
1492                                 }
1493                         })
1494                 });
1495
1496                 function privateEmit(lecture, event, data) {
1497                         backchannel.clients(lecture).forEach(function(socket) {
1498                                 if (socket.handshake.user)
1499                                         socket.emit(event, data);
1500                         })
1501                 }
1502
1503                 socket.on('disconnect', function() {
1504                         //delete clients[socket.id];
1505                 });
1506         });
1507
1508
1509 var counters = {};
1510
1511 var counts = io
1512 .of( '/counts' )
1513 .on( 'connection', function( socket ) {
1514         // pull out user/session information etc.
1515         var handshake = socket.handshake;
1516         var userID              = handshake.user._id;
1517
1518         var watched             = [];
1519         var noteID              = null;
1520
1521         var timer                       = null;
1522
1523         socket.on( 'join', function( note ) {
1524                 if (handshake.user === false) {
1525                         noteID                  = note;
1526                         // XXX: replace by addToSet (once it's implemented in mongoose)
1527                         Note.findById( noteID, function( err, note ) {
1528                                 if( note ) {
1529                                         if( note.collaborators.indexOf( userID ) == -1 ) {
1530                                                 note.collaborators.push( userID );
1531                                                 note.save();
1532                                         }
1533                                 }
1534                         });
1535                 }
1536         });
1537
1538         socket.on( 'watch', function( l ) {
1539                 var sendCounts = function() {
1540                         var send = {};
1541
1542                         Note.find( { '_id' : { '$in' : watched } }, function( err, notes ) {
1543                                 async.forEach(
1544                                         notes,
1545                                         function( note, callback ) {
1546                                                 var id          = note._id;
1547                                                 var count       = note.collaborators.length;
1548
1549                                                 send[ id ] = count;
1550
1551                                                 callback();
1552                                         }, function() {
1553                                                 socket.emit( 'counts', send );
1554
1555                                                 timer = setTimeout( sendCounts, 5000 );
1556                                         }
1557                                 );
1558                         });
1559                 }
1560
1561                 Note.find( { 'lecture' : l }, [ '_id' ], function( err, notes ) {
1562                         notes.forEach( function( note ) {
1563                                 watched.push( note._id );
1564                         });
1565                 });
1566
1567                 sendCounts();
1568         });
1569
1570         socket.on( 'disconnect', function() {
1571                 clearTimeout( timer );
1572
1573                 if (handshake.user === false) {
1574                         // XXX: replace with $pull once it's available
1575                         if( noteID ) {
1576                                 Note.findById( noteID, function( err, note ) {
1577                                         if( note ) {
1578                                                 var index = note.collaborators.indexOf( userID );
1579
1580                                                 if( index != -1 ) {
1581                                                         note.collaborators.splice( index, 1 );
1582                                                 }
1583
1584                                                 note.save();
1585                                         }
1586                                 });
1587                         }
1588                 }
1589         });
1590 });
1591
1592 // Exception Catch-All
1593
1594 process.on('uncaughtException', function (e) {
1595         console.log("!!!!!! UNCAUGHT EXCEPTION\n" + e.stack);
1596 });
1597
1598
1599 // Launch
1600
1601 mongoose.connect( app.set( 'dbUri' ) );
1602 mongoose.connection.db.serverConfig.connection.autoReconnect = true
1603
1604 var mailer = new Mailer( app.set('awsAccessKey'), app.set('awsSecretKey') );
1605
1606 app.listen( serverPort, function() {
1607         console.log( "Express server listening on port %d in %s mode", app.address().port, app.settings.env );
1608
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);
1614                 });
1615         }
1616 });
1617
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);
1621 }