app.js 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994
  1. // FinalsClub Server
  2. //
  3. // This file consists of the main webserver for FinalsClub.org
  4. // and is split between a standard CRUD style webserver and
  5. // a websocket based realtime webserver.
  6. //
  7. // A note on house keeping: Anything with XXX is marked
  8. // as such because it should be looked at and possibly
  9. // revamped or removed depending on circumstances.
  10. // Module loading
  11. var sys = require( 'sys' );
  12. var os = require( 'os' );
  13. var url = require( 'url' );
  14. var express = require( 'express' );
  15. var mongoStore = require( 'connect-mongo' );
  16. var async = require( 'async' );
  17. var db = require( './db.js' );
  18. var mongoose = require( './models.js' ).mongoose;
  19. var Mailer = require( './mailer.js' );
  20. var hat = require('hat');
  21. var connect = require( 'connect' );
  22. var Session = connect.middleware.session.Session;
  23. var parseCookie = connect.utils.parseCookie;
  24. var Backchannel = require('./bc/backchannel');
  25. // ********************************
  26. // For facebook oauth and connect
  27. // ********************************
  28. var everyauth = require('everyauth');
  29. var FacebookClient = require('facebook-client').FacebookClient;
  30. var facebook = new FacebookClient();
  31. everyauth.debug = true;
  32. everyauth.everymodule.logoutPath('/bye');
  33. // configure facebook authentication
  34. everyauth.facebook
  35. .appId('foobieblechreplacethisXXX')
  36. .appSecret('foobiederpreplacethisXXX')
  37. .myHostname('http://localhost:8000')
  38. .scope( 'email')
  39. .entryPath('/fbauth')
  40. .redirectPath('/schools')
  41. .findOrCreateUser(function(session, accessToken, accessTokExtra, fbUserMetadata, req) {
  42. console.log('req.session');
  43. console.log(req.session);
  44. var userPromise = this.Promise();
  45. User.findOne( {'email': fbUserMetadata.email }, function( err, euser ) {
  46. console.log("Found a fc user for this fb email");
  47. if (err) return userPromise.fail(err);
  48. // if a user exists with that email, call them logged in
  49. // FIXME: change this to different query on 'fbid'
  50. if(euser) {
  51. //hsession = new Session( handshake, session );
  52. // save thhat this cookie/session-id is right for this user
  53. req.session.regenerate( function() {
  54. euser.session = req.sessionID;
  55. euser.save( );
  56. console.log( req.sessionID );
  57. req.user = euser;
  58. });
  59. }
  60. if (euser) return userPromise.fulfill(euser);
  61. });
  62. return userPromise;
  63. });
  64. //.callbackPath('/fbsucc')
  65. // Depracated
  66. // Used for initial testing
  67. var log3 = function() {}
  68. // Create webserver
  69. var app = module.exports = express.createServer();
  70. // Load Mongoose Schemas
  71. // The actual schemas are located in models.j
  72. var User = mongoose.model( 'User' );
  73. var School = mongoose.model( 'School' );
  74. var Course = mongoose.model( 'Course' );
  75. var Lecture = mongoose.model( 'Lecture' );
  76. var Note = mongoose.model( 'Note' );
  77. // More schemas used for legacy data
  78. var ArchivedCourse = mongoose.model( 'ArchivedCourse' );
  79. var ArchivedNote = mongoose.model( 'ArchivedNote' );
  80. var ArchivedSubject = mongoose.model( 'ArchivedSubject' );
  81. // XXX Not sure if necessary
  82. var ObjectId = mongoose.SchemaTypes.ObjectId;
  83. // Configuration
  84. // Use the environment variable DEV_EMAIL for testing
  85. var ADMIN_EMAIL = process.env.DEV_EMAIL || 'info@finalsclub.org';
  86. // Set server hostname and port from environment variables,
  87. // then check if set.
  88. // XXX Can be cleaned up
  89. var serverHost = process.env.SERVER_HOST;
  90. var serverPort = process.env.SERVER_PORT;
  91. if( serverHost ) {
  92. console.log( 'Using server hostname defined in environment: %s', serverHost );
  93. } else {
  94. serverHost = os.hostname();
  95. console.log( 'No hostname defined, defaulting to os.hostname(): %s', serverHost );
  96. }
  97. // Express configuration depending on environment
  98. // development is intended for developing locally or
  99. // when not in production, otherwise production is used
  100. // when the site will be run live for regular usage.
  101. app.configure( 'development', function() {
  102. // In development mode, all errors and stack traces will be
  103. // dumped to the console and on page for easier troubleshooting
  104. // and debugging.
  105. app.set( 'errorHandler', express.errorHandler( { dumpExceptions: true, showStack: true } ) );
  106. // Set database connection information from environment
  107. // variables otherwise use localhost.
  108. app.set( 'dbHost', process.env.MONGO_HOST || 'localhost' );
  109. app.set( 'dbUri', 'mongodb://' + app.set( 'dbHost' ) + '/fc' );
  110. // Set Amazon access and secret keys from environment
  111. // variables. These keys are intended to be secret, so
  112. // are not included in the source code, but set on the server
  113. // manually.
  114. app.set( 'awsAccessKey', process.env.AWS_ACCESS_KEY_ID );
  115. app.set( 'awsSecretKey', process.env.AWS_SECRET_ACCESS_KEY );
  116. // If a port wasn't set earlier, set to 3000
  117. if ( !serverPort ) {
  118. serverPort = 3000;
  119. }
  120. });
  121. // Production configuration settings
  122. app.configure( 'production', function() {
  123. // At the moment we have errors outputting everything
  124. // so if there are any issues it is easier to track down.
  125. // Once the site is more stable it will be prudent to
  126. // use less error tracing.
  127. app.set( 'errorHandler', express.errorHandler( { dumpExceptions: true, showStack: true } ) );
  128. // Disable view cache due to stale views.
  129. // XXX Disable view caching temp
  130. app.disable( 'view cache' )
  131. // Against setting the database connection information
  132. // XXX Can be cleaned up or combined
  133. app.set( 'dbHost', process.env.MONGO_HOST || 'localhost' );
  134. app.set( 'dbUri', 'mongodb://' + app.set( 'dbHost' ) + '/fc' );
  135. // XXX Can be cleaned up or combined
  136. app.set( 'awsAccessKey', process.env.AWS_ACCESS_KEY_ID );
  137. app.set( 'awsSecretKey', process.env.AWS_SECRET_ACCESS_KEY );
  138. // Set to port 80 if not set through environment variables
  139. if ( !serverPort ) {
  140. serverPort = 80;
  141. }
  142. });
  143. // General Express configuration settings
  144. app.configure(function(){
  145. // Views are rendered from public/index.html and main.js except for the pad that surrounds EPL and BC
  146. // FIXME: make all views exist inside of public/index.html
  147. // Views are housed in the views folder
  148. app.set( 'views', __dirname + '/views' );
  149. // All templates use jade for rendering
  150. app.set( 'view engine', 'jade' );
  151. // Bodyparser is required to handle form submissions
  152. // without manually parsing them.
  153. app.use( express.bodyParser() );
  154. app.use( express.cookieParser() );
  155. // Sessions are stored in mongodb which allows them
  156. // to be persisted even between server restarts.
  157. app.set( 'sessionStore', new mongoStore( {
  158. 'db' : 'fc',
  159. 'url' : app.set( 'dbUri' )
  160. }));
  161. // This is where the actual Express session handler
  162. // is defined, with a mongoStore being set as the
  163. // session storage versus in memory storage that is
  164. // used by default.
  165. app.use( express.session( {
  166. // A secret 'password' for encrypting and decrypting
  167. // cookies.
  168. // XXX Should be handled differently
  169. 'secret' : 'finalsclub',
  170. // The max age of the cookies that is allowed
  171. // 60 (seconds) * 60 (minutes) * 24 (hours) * 30 (days) * 1000 (milliseconds)
  172. 'maxAge' : new Date(Date.now() + (60 * 60 * 24 * 30 * 1000)),
  173. 'store' : app.set( 'sessionStore' )
  174. }));
  175. // methodOverride is used to handle PUT and DELETE HTTP
  176. // requests that otherwise aren't handled by default.
  177. app.use( express.methodOverride() );
  178. // Static files are loaded when no dynamic views match.
  179. app.use( express.static( __dirname + '/public' ) );
  180. // EveryAuth fb connect
  181. app.use( everyauth.middleware() );
  182. // Sets the routers middleware to load after everything set
  183. // before it, but before static files.
  184. app.use( app.router );
  185. app.use(express.logger({ format: ':method :url' }));
  186. // This is the command to use the default express error logger/handler
  187. app.use(express.errorHandler({ dumpExceptions: true }));
  188. });
  189. // Mailer functions and helpers
  190. // These are helper functions that make for cleaner code.
  191. // sendUserActivation is for when a user registers and
  192. // first needs to activate their account to use it.
  193. function sendUserActivation( user ) {
  194. var message = {
  195. 'to' : user.email,
  196. 'subject' : 'Activate your FinalsClub.org Account',
  197. // Templates are in the email folder and use ejs
  198. 'template' : 'userActivation',
  199. // Locals are used inside ejs so dynamic information
  200. // can be rendered properly.
  201. 'locals' : {
  202. 'user' : user,
  203. 'serverHost' : serverHost
  204. }
  205. };
  206. // Email is sent here
  207. mailer.send( message, function( err, result ) {
  208. if( err ) {
  209. // XXX: Add route to resend this email
  210. console.log( 'Error sending user activation email\nError Message: '+err.Message );
  211. } else {
  212. console.log( 'Successfully sent user activation email.' );
  213. }
  214. });
  215. }
  216. // sendUserWelcome is for when a user registers and
  217. // a welcome email is sent.
  218. function sendUserWelcome( user, school ) {
  219. // If a user is not apart of a supported school, they are
  220. // sent a different template than if they are apart of a
  221. // supported school.
  222. var template = school ? 'userWelcome' : 'userWelcomeNoSchool';
  223. var message = {
  224. 'to' : user.email,
  225. 'subject' : 'Welcome to FinalsClub',
  226. 'template' : template,
  227. 'locals' : {
  228. 'user' : user,
  229. 'serverHost' : serverHost
  230. }
  231. };
  232. mailer.send( message, function( err, result ) {
  233. if( err ) {
  234. // XXX: Add route to resend this email
  235. console.log( 'Error sending user welcome email\nError Message: '+err.Message );
  236. } else {
  237. console.log( 'Successfully sent user welcome email.' );
  238. }
  239. });
  240. }
  241. // Helper middleware
  242. // These functions are used later in the routes to help
  243. // load information and variables, as well as handle
  244. // various instances like checking if a user is logged in
  245. // or not.
  246. function loggedIn( req, res, next ) {
  247. // If req.user is set, then pass on to the next function
  248. // or else alert the user with an error message.
  249. if( req.user ) {
  250. next();
  251. } else {
  252. req.flash( 'error', 'You must be logged in to access that feature!' );
  253. res.redirect( '/' );
  254. }
  255. }
  256. // This loads the user if logged in
  257. function loadUser( req, res, next ) {
  258. var sid = req.sessionID;
  259. console.log( 'got request from session ID: %s', sid );
  260. // Find a user based on their stored session id
  261. User.findOne( { session : sid }, function( err, user ) {
  262. log3(err);
  263. log3(user);
  264. // If a user is found then set req.user the contents of user
  265. // and make sure req.user.loggedIn is true.
  266. if( user ) {
  267. req.user = user;
  268. req.user.loggedIn = true;
  269. log3( 'authenticated user: '+req.user._id+' / '+req.user.email+'');
  270. // Check if a user is activated. If not, then redirec
  271. // to the homepage and tell them to check their email
  272. // for the activation email.
  273. if( req.user.activated ) {
  274. // Is the user's profile complete? If not, redirect to their profile
  275. if( ! req.user.isComplete ) {
  276. if( url.parse( req.url ).pathname != '/profile' ) {
  277. req.flash( 'info', 'Your profile is incomplete. Please complete your profile to fully activate your account.' );
  278. res.redirect( '/profile' );
  279. } else {
  280. next();
  281. }
  282. } else {
  283. next();
  284. }
  285. } else {
  286. req.flash( 'info', 'This account has not been activated. Check your email for the activation URL.' );
  287. res.redirect( '/' );
  288. }
  289. } else if('a'==='b'){
  290. console.log('never. in. behrlin.');
  291. } else {
  292. // If no user record was found, then we store the requested
  293. // path they intended to view and redirect them after they
  294. // login if it is requred.
  295. var path = url.parse( req.url ).pathname;
  296. req.session.redirect = path;
  297. // Set req.user to an empty object so it doesn't throw errors
  298. // later on that it isn't defined.
  299. req.user = {
  300. sanitized: {}
  301. };
  302. next();
  303. }
  304. });
  305. }
  306. // loadSchool is used to load a school by it's id
  307. function loadSchool( req, res, next ) {
  308. var user = req.user;
  309. var schoolName = req.params.name;
  310. console.log( 'loading a school by id' );
  311. School.findOne({'name': schoolName}).run( function( err, school ) {
  312. //sys.puts(school);
  313. if( school ) {
  314. req.school = school;
  315. // If a school is found, the user is checked to see if they are
  316. // authorized to see or interact with anything related to that
  317. // school.
  318. school.authorize( user, function( authorized ){
  319. req.school.authorized = authorized;
  320. });
  321. next();
  322. } else {
  323. // If no school is found, display an appropriate error.
  324. sendJson(res, {status: 'not_found', message: 'Invalid school specified!'} );
  325. }
  326. });
  327. }
  328. function loadSchoolSlug( req, res, next ) {
  329. var user = req.user;
  330. var schoolSlug = req.params.slug;
  331. console.log("loading a school by slug");
  332. //console.log(schoolSlug);
  333. School.findOne({ 'slug': schoolSlug }, function( err, school ) {
  334. console.log( school );
  335. if( school ) {
  336. req.school = school;
  337. // If a school is found, the user is checked to see if they are
  338. // authorized to see or interact with anything related to that
  339. // school.
  340. next()
  341. //school.authorize( user, function( authorized ){
  342. //req.school.authorized = authorized;
  343. //next();
  344. //});
  345. } else {
  346. // If no school is found, display an appropriate error.
  347. sendJson(res, {status: 'not_found', message: 'Invalid school specified!'} );
  348. }
  349. });
  350. }
  351. // loadSchool is used to load a course by it's id
  352. function loadCourse( req, res, next ) {
  353. var user = req.user;
  354. var courseId = req.params.id;
  355. Course.findById( courseId, function( err, course ) {
  356. if( course && !course.deleted ) {
  357. req.course = course;
  358. // If a course is found, the user is checked to see if they are
  359. // authorized to see or interact with anything related to that
  360. // school.
  361. course.authorize( user, function( authorized ) {
  362. req.course.authorized = authorized;
  363. next();
  364. });
  365. } else {
  366. // If no course is found, display an appropriate error.
  367. sendJson(res, {status: 'not_found', message: 'Invalid course specified!'} );
  368. }
  369. });
  370. }
  371. // loadLecture is used to load a lecture by it's id
  372. function loadLecture( req, res, next ) {
  373. var user = req.user;
  374. var lectureId = req.params.id;
  375. Lecture.findById( lectureId, function( err, lecture ) {
  376. if( lecture && !lecture.deleted ) {
  377. req.lecture = lecture;
  378. // If a lecture is found, the user is checked to see if they are
  379. // authorized to see or interact with anything related to that
  380. // school.
  381. lecture.authorize( user, function( authorized ) {
  382. req.lecture.authorized = authorized;
  383. next();
  384. });
  385. } else {
  386. // If no lecture is found, display an appropriate error.
  387. sendJson(res, {status: 'not_found', message: 'Invalid lecture specified!'} );
  388. }
  389. });
  390. }
  391. // loadNote is used to load a note by it's id
  392. // This is a lot more complicated than the above
  393. // due to public/private handling of notes.
  394. function loadNote( req, res, next ) {
  395. var user = req.user ? req.user : false;
  396. var noteId = req.params.id;
  397. Note.findById( noteId, function( err, note ) {
  398. // If a note is found, and user is set, check if
  399. // user is authorized to interact with that note.
  400. if( note && user && !note.deleted ) {
  401. note.authorize( user, function( auth ) {
  402. if( auth ) {
  403. // If authorzied, then set req.note to be used later
  404. req.note = note;
  405. next();
  406. } else if ( note.public ) {
  407. // If not authorized, but the note is public, then
  408. // designate the note read only (RO) and store req.note
  409. // FIXME: this should be req.RO = true, disabled due to auth issues
  410. // TODO: ^^^ Important
  411. //req.RO = true;
  412. req.RO = false;
  413. req.note = note;
  414. next();
  415. } else {
  416. // If the user is not authorized and the note is private
  417. // then display and error.
  418. sendJson(res, {status: 'error', message: 'You do not have permission to access that note.'} );
  419. }
  420. })
  421. } else if ( note && note.public && !note.deleted ) {
  422. // If note is found, but user is not set because they are not
  423. // logged in, and the note is public, set the note to read only
  424. // and store the note for later.
  425. req.note = note;
  426. req.RO = true;
  427. next();
  428. } else if ( note && !note.public && !note.deleted ) {
  429. // If the note is found, but user is not logged in and the note is
  430. // not public, then ask them to login to view the note. Once logged
  431. // in they will be redirected to the note, at which time authorization
  432. // handling will be put in effect above.
  433. //req.session.redirect = '/note/' + note._id;
  434. sendJson(res, {status: 'error', message: 'You must be logged in to view that note.'} );
  435. } else {
  436. // No note was found
  437. sendJson(res, {status: 'error', message: 'Invalid note specified!'} );
  438. }
  439. });
  440. }
  441. function checkAjax( req, res, next ) {
  442. if ( req.xhr ) {
  443. next();
  444. } else {
  445. res.sendfile( 'public/index.html' );
  446. }
  447. }
  448. // Dynamic Helpers are loaded automatically into views
  449. app.dynamicHelpers( {
  450. // express-messages is for flash messages for easy
  451. // errors and information display
  452. 'messages' : require( 'express-messages' ),
  453. // By default the req object isn't sen't to views
  454. // during rendering, this allows you to use the
  455. // user object if available in views.
  456. 'user' : function( req, res ) {
  457. return req.user;
  458. },
  459. // Same, this allows session to be available in views.
  460. 'session' : function( req, res ) {
  461. return req.session;
  462. }
  463. });
  464. function sendJson( res, obj ) {
  465. res.header('Cache-Control', 'no-cache, no-store');
  466. res.json(obj);
  467. }
  468. // Routes
  469. // The following are the main CRUD routes that are used
  470. // to make up this web app.
  471. // Homepage
  472. // Public
  473. /*
  474. app.get( '/', loadUser, function( req, res ) {
  475. log3("get / page");
  476. res.render( 'index' );
  477. });
  478. */
  479. // Schools list
  480. // Used to display all available schools and any courses
  481. // in those schools.
  482. // Public with some private information
  483. app.get( '/schools', checkAjax, loadUser, function( req, res ) {
  484. sys.puts('loading schools');
  485. console.log(req.user);
  486. var user = req.user;
  487. var schoolList = [];
  488. // Find all schools and sort by name
  489. // XXX mongoose's documentation on sort is extremely poor, tread carefully
  490. School.find( {} ).sort( 'name', '1' ).run( function( err, schools ) {
  491. if( schools ) {
  492. // If schools are found, loop through them gathering any courses that are
  493. // associated with them and then render the page with that information.
  494. var schools_todo = schools.length;
  495. schools.map(function (school) {
  496. Course.find( { 'school': school.id } ).run(function (err, courses) {
  497. school.courses_length = courses.length
  498. schools_todo -= 1;
  499. if (schools_todo <= 0) {
  500. sendJson(res, { 'user': user.sanitized, 'schools': schools.map( function(s) {
  501. var school = {
  502. _id: s._id,
  503. name: s.name,
  504. description: s.description,
  505. url: s.url,
  506. slug: s.slug,
  507. courses: s.courses_length
  508. };
  509. return school;
  510. })
  511. });
  512. }
  513. });
  514. });
  515. } else {
  516. // If no schools have been found, display none
  517. //res.render( 'schools', { 'schools' : [] } );
  518. sendJson(res, { 'schools' : [] , 'user': user.sanitized });
  519. }
  520. });
  521. });
  522. app.get( '/school/:name', checkAjax, loadUser, loadSchool, function( req, res ) {
  523. var school = req.school;
  524. var user = req.user;
  525. var courses;
  526. console.log( 'loading a school by school/:id now name' );
  527. //school.authorize( user, function( authorized ) {
  528. // This is used to display interface elements for those users
  529. // that are are allowed to see th)m, for instance a 'New Course' button.
  530. //var sanitizedSchool = school.sanitized;
  531. var sanitizedSchool = {
  532. _id: school.id,
  533. name: school.name,
  534. description: school.description,
  535. url: school.url
  536. };
  537. //sanitizedSchool.authorized = authorized;
  538. // Find all courses for school by it's id and sort by name
  539. Course.find( { 'school' : school._id } ).sort( 'name', '1' ).run( function( err, courses ) {
  540. // If any courses are found, set them to the appropriate school, otherwise
  541. // leave empty.
  542. sys.puts(courses);
  543. if( courses.length > 0 ) {
  544. courses = courses.filter(function(course) {
  545. if (!course.deleted) return course;
  546. }).map(function(course) {
  547. return course.sanitized;
  548. });
  549. } else {
  550. school.courses = [];
  551. }
  552. sanitizedSchool.courses = courses;
  553. sys.puts(courses);
  554. // This tells async (the module) that each iteration of forEach is
  555. // done and will continue to call the rest until they have all been
  556. // completed, at which time the last function below will be called.
  557. sendJson(res, { 'school': sanitizedSchool, 'user': user.sanitized })
  558. });
  559. //});
  560. });
  561. // FIXME: version of the same using school slugs instead of ids
  562. // TODO: merge this with the :id funciton or depricate it
  563. app.get( '/schoolslug/:slug', checkAjax, loadUser, loadSchoolSlug, function( req, res ) {
  564. var school = req.school;
  565. var user = req.user;
  566. console.log( 'loading a schoolslug/:slug' );
  567. school.authorize( user, function( authorized ) {
  568. // This is used to display interface elements for those users
  569. // that are are allowed to see th)m, for instance a 'New Course' button.
  570. var sanitizedSchool = school.sanitized;
  571. sanitizedSchool.authorized = authorized;
  572. // Find all courses for school by it's id and sort by name
  573. Course.find( { 'school' : school._id } ).sort( 'name', '1' ).run( function( err, courses ) {
  574. // If any courses are found, set them to the appropriate school, otherwise
  575. // leave empty.
  576. if( courses.length > 0 ) {
  577. sanitizedSchool.courses = courses.filter(function(course) {
  578. if (!course.deleted) return course;
  579. }).map(function(course) {
  580. return course.sanitized;
  581. });
  582. } else {
  583. sanitizedSchool.courses = [];
  584. }
  585. // This tells async (the module) that each iteration of forEach is
  586. // done and will continue to call the rest until they have all been
  587. // completed, at which time the last function below will be called.
  588. sendJson(res, { 'school': sanitizedSchool, 'user': user.sanitized })
  589. });
  590. });
  591. });
  592. // Recieves new course form
  593. app.post( '/school/:id', checkAjax, loadUser, loadSchool, function( req, res ) {
  594. var school = req.school;
  595. // Creates new course from Course Schema
  596. var course = new Course;
  597. // Gathers instructor information from form
  598. var instructorEmail = req.body.email.toLowerCase();
  599. var instructorName = req.body.instructorName;
  600. if( ( ! school ) || ( ! school.authorized ) ) {
  601. return sendJson(res, {status: 'error', message: 'There was a problem trying to create a course'})
  602. }
  603. if ( !instructorName ) {
  604. return sendJson(res, {status: 'error', message: 'Invalid parameters!'} )
  605. }
  606. if ( ( instructorEmail === '' ) || ( !isValidEmail( instructorEmail ) ) ) {
  607. return sendJson(res, {status: 'error', message:'Please enter a valid email'} );
  608. }
  609. // Fill out the course with information from the form
  610. course.number = req.body.number;
  611. course.name = req.body.name;
  612. course.description = req.body.description;
  613. course.school = school._id;
  614. course.creator = req.user._id;
  615. course.subject = req.body.subject;
  616. course.department = req.body.department;
  617. // Check if a user exists with the instructorEmail, if not then create
  618. // a new user and send them an instructor welcome email.
  619. User.findOne( { 'email' : instructorEmail }, function( err, user ) {
  620. if ( !user ) {
  621. var user = new User;
  622. user.name = instructorName
  623. user.email = instructorEmail;
  624. user.affil = 'Instructor';
  625. user.school = school.name;
  626. user.activated = false;
  627. // Once the new user information has been completed, save the user
  628. // to the database then email them the instructor welcome email.
  629. user.save(function( err ) {
  630. // If there was an error saving the instructor, prompt the user to fill out
  631. // the information again.
  632. if ( err ) {
  633. return sendJson(res, {status: 'error', message: 'Invalid parameters!'} )
  634. } else {
  635. var message = {
  636. to : user.email,
  637. 'subject' : 'A non-profit open education initiative',
  638. 'template' : 'instructorInvite',
  639. 'locals' : {
  640. 'course' : course,
  641. 'school' : school,
  642. 'user' : user,
  643. 'serverHost' : serverHost
  644. }
  645. };
  646. mailer.send( message, function( err, result ) {
  647. if( err ) {
  648. console.log( 'Error inviting instructor to course!' );
  649. } else {
  650. console.log( 'Successfully invited instructor to course.' );
  651. }
  652. });
  653. // After emails are sent, set the courses instructor to the
  654. // new users id and then save the course to the database.
  655. course.instructor = user._id;
  656. course.save( function( err ) {
  657. if( err ) {
  658. return sendJson(res, {status: 'error', message: 'Invalid parameters!'} )
  659. } else {
  660. // Once the course has been completed email the admin with information
  661. // on the course and new instructor
  662. var message = {
  663. to : ADMIN_EMAIL,
  664. 'subject' : school.name+' has a new course: '+course.name,
  665. 'template' : 'newCourse',
  666. 'locals' : {
  667. 'course' : course,
  668. 'instructor' : user,
  669. 'user' : req.user,
  670. 'serverHost' : serverHost
  671. }
  672. };
  673. mailer.send( message, function( err, result ) {
  674. if ( err ) {
  675. console.log( 'Error sending new course email to info@finalsclub.org' )
  676. } else {
  677. console.log( 'Successfully invited instructor to course')
  678. }
  679. })
  680. // Redirect the user to the schools page where they can see
  681. // their new course.
  682. // XXX Redirect to the new course instead
  683. return sendJson(res, {status: 'ok', message: 'Course created'} )
  684. }
  685. });
  686. }
  687. })
  688. } else {
  689. // If the user exists, then check if they are already and instructor
  690. if (user.affil === 'Instructor') {
  691. // If they are an instructor, then save the course with the appropriate
  692. // information and email the admin.
  693. course.instructor = user._id;
  694. course.save( function( err ) {
  695. if( err ) {
  696. // XXX better validation
  697. return sendJson(res, {status: 'error', message: 'Invalid parameters!'} )
  698. } else {
  699. var message = {
  700. to : ADMIN_EMAIL,
  701. 'subject' : school.name+' has a new course: '+course.name,
  702. 'template' : 'newCourse',
  703. 'locals' : {
  704. 'course' : course,
  705. 'instructor' : user,
  706. 'user' : req.user,
  707. 'serverHost' : serverHost
  708. }
  709. };
  710. mailer.send( message, function( err, result ) {
  711. if ( err ) {
  712. console.log( 'Error sending new course email to info@finalsclub.org' )
  713. } else {
  714. console.log( 'Successfully invited instructor to course')
  715. }
  716. })
  717. // XXX Redirect to the new course instead
  718. return sendJson(res, {status: 'ok', message: 'Course created'} )
  719. }
  720. });
  721. } else {
  722. // The existing user isn't an instructor, so the user is notified of the error
  723. // and the course isn't created.
  724. sendJson(res, {status: 'error', message: 'The existing user\'s email you entered is not an instructor'} )
  725. }
  726. }
  727. })
  728. });
  729. // Individual Course Listing
  730. // Public with private information
  731. app.get( '/course/:id', checkAjax, loadUser, loadCourse, function( req, res ) {
  732. var userId = req.user._id;
  733. var course = req.course;
  734. // Check if the user is subscribed to the course
  735. // XXX Not currently used for anything
  736. //var subscribed = course.subscribed( userId );
  737. // Find lectures associated with this course and sort by name
  738. Lecture.find( { 'course' : course._id } ).sort( 'name', '1' ).run( function( err, lectures ) {
  739. // Get course instructor information using their id
  740. User.findById( course.instructor, function( err, instructor ) {
  741. // Render course and lectures
  742. var sanitizedInstructor = instructor.sanitized;
  743. var sanitizedCourse = course.sanitized;
  744. if (!course.authorized) {
  745. delete sanitizedInstructor.email;
  746. } else {
  747. sanitizedCourse.authorized = course.authorized;
  748. }
  749. sendJson(res, { 'course' : sanitizedCourse, 'instructor': sanitizedInstructor, 'lectures' : lectures.map(function(lecture) { return lecture.sanitized })} );
  750. })
  751. });
  752. });
  753. // Recieve New Lecture Form
  754. app.post( '/course/:id', checkAjax, loadUser, loadCourse, function( req, res ) {
  755. var course = req.course;
  756. // Create new lecture from Lecture schema
  757. var lecture = new Lecture;
  758. if( ( ! course ) || ( ! course.authorized ) ) {
  759. return sendJson(res, {status: 'error', message: 'There was a problem trying to create a lecture'})
  760. }
  761. // Populate lecture with form data
  762. lecture.name = req.body.name;
  763. lecture.date = req.body.date;
  764. lecture.course = course._id;
  765. lecture.creator = req.user._id;
  766. // Save lecture to database
  767. lecture.save( function( err ) {
  768. if( err ) {
  769. // XXX better validation
  770. sendJson(res, {status: 'error', message: 'Invalid parameters!'} );
  771. } else {
  772. sendJson(res, {status: 'ok', message: 'Created new lecture'} );
  773. }
  774. });
  775. });
  776. // Edit Course
  777. app.get( '/course/:id/edit', loadUser, loadCourse, function( req, res) {
  778. var course = req.course;
  779. var user = req.user;
  780. if ( user.admin ) {
  781. res.render( 'course/new', {course: course} )
  782. } else {
  783. req.flash( 'error', 'You don\'t have permission to do that' )
  784. res.redirect( '/schools' );
  785. }
  786. })
  787. // Recieve Course Edit Form
  788. app.post( '/course/:id/edit', loadUser, loadCourse, function( req, res ) {
  789. var course = req.course;
  790. var user = req.user;
  791. if (user.admin) {
  792. var courseChanges = req.body;
  793. course.number = courseChanges.number;
  794. course.name = courseChanges.name;
  795. course.description = courseChanges.description;
  796. course.department = courseChanges.department;
  797. course.save(function(err) {
  798. if (err) {
  799. req.flash( 'error', 'There was an error saving the course' );
  800. }
  801. res.redirect( '/course/'+ course._id.toString());
  802. })
  803. } else {
  804. req.flash( 'error', 'You don\'t have permission to do that' )
  805. res.redirect( '/schools' );
  806. }
  807. });
  808. // Delete Course
  809. app.get( '/course/:id/delete', loadUser, loadCourse, function( req, res) {
  810. var course = req.course;
  811. var user = req.user;
  812. if ( user.admin ) {
  813. course.delete(function( err ) {
  814. if ( err ) req.flash( 'info', 'There was a problem removing course: ' + err )
  815. else req.flash( 'info', 'Successfully removed course' )
  816. res.redirect( '/schools' );
  817. });
  818. } else {
  819. req.flash( 'error', 'You don\'t have permission to do that' )
  820. res.redirect( '/schools' );
  821. }
  822. })
  823. // Subscribe to course
  824. // XXX Not currently used for anything
  825. app.get( '/course/:id/subscribe', loadUser, loadCourse, function( req, res ) {
  826. var course = req.course;
  827. var userId = req.user._id;
  828. course.subscribe( userId, function( err ) {
  829. if( err ) {
  830. req.flash( 'error', 'Error subscribing to course!' );
  831. }
  832. res.redirect( '/course/' + course._id );
  833. });
  834. });
  835. // Unsubscribe from course
  836. // XXX Not currently used for anything
  837. app.get( '/course/:id/unsubscribe', loadUser, loadCourse, function( req, res ) {
  838. var course = req.course;
  839. var userId = req.user._id;
  840. course.unsubscribe( userId, function( err ) {
  841. if( err ) {
  842. req.flash( 'error', 'Error unsubscribing from course!' );
  843. }
  844. res.redirect( '/course/' + course._id );
  845. });
  846. });
  847. // Display individual lecture and related notes
  848. app.get( '/lecture/:id', checkAjax, loadUser, loadLecture, function( req, res ) {
  849. var lecture = req.lecture;
  850. // Grab the associated course
  851. // XXX this should be done with DBRefs eventually
  852. Course.findById( lecture.course, function( err, course ) {
  853. if( course ) {
  854. // If course is found, find instructor information to be displayed on page
  855. User.findById( course.instructor, function( err, instructor ) {
  856. // Pull out our notes
  857. Note.find( { 'lecture' : lecture._id } ).sort( 'name', '1' ).run( function( err, notes ) {
  858. if ( !req.user.loggedIn || !req.lecture.authorized ) {
  859. // Loop through notes and only return those that are public if the
  860. // user is not logged in or not authorized for that lecture
  861. notes = notes.filter(function( note ) {
  862. if ( note.public ) return note;
  863. })
  864. }
  865. var sanitizedInstructor = instructor.sanitized;
  866. var sanitizedLecture = lecture.sanitized;
  867. if (!lecture.authorized) {
  868. delete sanitizedInstructor.email;
  869. } else {
  870. sanitizedLecture.authorized = lecture.authorized;
  871. }
  872. sendJson(res, {
  873. 'lecture' : sanitizedLecture,
  874. 'course' : course.sanitized,
  875. 'instructor' : sanitizedInstructor,
  876. 'notes' : notes.map(function(note) {
  877. return note.sanitized;
  878. })
  879. });
  880. });
  881. })
  882. } else {
  883. sendJson(res, { status: 'not_found', msg: 'This course is orphaned' })
  884. }
  885. });
  886. });
  887. // Recieve new note form
  888. app.post( '/lecture/:id', checkAjax, loadUser, loadLecture, function( req, res ) {
  889. var lecture = req.lecture;
  890. if( ( ! lecture ) || ( ! lecture.authorized ) ) {
  891. return sendJson(res, {status: 'error', message: 'There was a problem trying to create a note pad'})
  892. }
  893. // Create note from Note schema
  894. var note = new Note;
  895. // Populate note from form data
  896. note.name = req.body.name;
  897. note.date = req.body.date;
  898. note.lecture = lecture._id;
  899. note.public = req.body.private ? false : true;
  900. note.creator = req.user._id;
  901. // Save note to database
  902. note.save( function( err ) {
  903. if( err ) {
  904. // XXX better validation
  905. sendJson(res, {status: 'error', message: 'There was a problem trying to create a note pad'})
  906. } else {
  907. sendJson(res, {status: 'ok', message: 'Successfully created a new note pad'})
  908. }
  909. });
  910. });
  911. // Display individual note page
  912. app.get( '/note/:id', /*checkAjax,*/ loadUser, loadNote, function( req, res ) {
  913. var note = req.note;
  914. // Set read only id for etherpad-lite or false for later check
  915. var roID = note.roID || false;
  916. var lectureId = note.lecture;
  917. // Count the amount of visits, but only once per session
  918. if ( req.session.visited ) {
  919. if ( req.session.visited.indexOf( note._id.toString() ) == -1 ) {
  920. req.session.visited.push( note._id );
  921. note.addVisit();
  922. }
  923. } else {
  924. req.session.visited = [];
  925. req.session.visited.push( note._id );
  926. note.addVisit();
  927. }
  928. // If a read only id exists process note
  929. if (roID) {
  930. processReq();
  931. } else {
  932. // If read only id doesn't, then fetch the read only id from the database and then
  933. // process note.
  934. // XXX Soon to be depracated due to a new API in etherpad that makes for a
  935. // much cleaner solution.
  936. db.open('mongodb://' + app.set( 'dbHost' ) + '/etherpad/etherpad', function( err, epl ) {
  937. epl.findOne( { key: 'pad2readonly:' + note._id }, function(err, record) {
  938. if ( record ) {
  939. roID = record.value.replace(/"/g, '');
  940. } else {
  941. roID = false;
  942. }
  943. processReq();
  944. })
  945. })
  946. }
  947. function processReq() {
  948. // Find lecture
  949. Lecture.findById( lectureId, function( err, lecture ) {
  950. if( ! lecture ) {
  951. req.flash( 'error', 'That notes page is orphaned!' );
  952. res.redirect( '/' );
  953. }
  954. // Find notes based on lecture id, which will be displayed in a dropdown
  955. // on the page
  956. Note.find( { 'lecture' : lecture._id }, function( err, otherNotes ) {
  957. /*
  958. sendJson(res, {
  959. 'host' : serverHost,
  960. 'note' : note.sanitized,
  961. 'lecture' : lecture.sanitized,
  962. 'otherNotes' : otherNotes.map(function(note) {
  963. return note.sanitized;
  964. }),
  965. 'RO' : req.RO,
  966. 'roID' : roID,
  967. });
  968. */
  969. if( !req.RO ) {
  970. // User is logged in and sees full notepad
  971. res.render( 'notes/index', {
  972. 'layout' : 'noteLayout',
  973. 'host' : serverHost,
  974. 'note' : note,
  975. 'lecture' : lecture,
  976. 'otherNotes' : otherNotes,
  977. 'RO' : false,
  978. 'roID' : roID,
  979. 'stylesheets' : [ 'dropdown.css', 'fc2.css' ],
  980. 'javascripts' : [ 'dropdown.js', 'counts.js', 'backchannel.js', 'jquery.tmpl.min.js' ]
  981. });
  982. } else {
  983. // User is not logged in and sees notepad that is public
  984. res.render( 'notes/public', {
  985. 'layout' : 'noteLayout',
  986. 'host' : serverHost,
  987. 'note' : note,
  988. 'otherNotes' : otherNotes,
  989. 'roID' : roID,
  990. 'lecture' : lecture,
  991. 'stylesheets' : [ 'dropdown.css', 'fc2.css' ],
  992. 'javascripts' : [ 'dropdown.js', 'counts.js', 'backchannel.js', 'jquery.tmpl.min.js' ]
  993. });
  994. }
  995. });
  996. });
  997. }
  998. });
  999. // Static pages and redirects
  1000. /*
  1001. app.get( '/about', loadUser, function( req, res ) {
  1002. res.redirect( 'http://blog.finalsclub.org/about.html' );
  1003. });
  1004. app.get( '/press', loadUser, function( req, res ) {
  1005. res.render( 'static/press' );
  1006. });
  1007. app.get( '/conduct', loadUser, function( req, res ) {
  1008. res.render( 'static/conduct' );
  1009. });
  1010. app.get( '/legal', loadUser, function( req, res ) {
  1011. res.redirect( 'http://blog.finalsclub.org/legal.html' );
  1012. });
  1013. app.get( '/contact', loadUser, function( req, res ) {
  1014. res.redirect( 'http://blog.finalsclub.org/contact.html' );
  1015. });
  1016. app.get( '/privacy', loadUser, function( req, res ) {
  1017. res.render( 'static/privacy' );
  1018. });
  1019. */
  1020. // Authentication routes
  1021. // These are used for logging in, logging out, registering
  1022. // and other user authentication purposes
  1023. // Render login page
  1024. /*
  1025. app.get( '/login', function( req, res ) {
  1026. log3("get login page")
  1027. res.render( 'login' );
  1028. });
  1029. */
  1030. app.get( '/checkuser', checkAjax, loadUser, function( req, res ) {
  1031. sendJson(res, {user: req.user.sanitized});
  1032. });
  1033. // Recieve login form
  1034. app.post( '/login', checkAjax, function( req, res ) {
  1035. var email = req.body.email;
  1036. var password = req.body.password;
  1037. log3("post login ...")
  1038. // Find user from email
  1039. User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
  1040. log3(err)
  1041. log3(user)
  1042. // If user exists, check if activated, if not notify them and send them to
  1043. // the login form
  1044. if( user ) {
  1045. if( ! user.activated ) {
  1046. // (undocumented) markdown-esque link functionality in req.flash
  1047. req.session.activateCode = user._id;
  1048. sendJson(res, {status: 'error', message: 'This account isn\'t activated.'} );
  1049. } else {
  1050. // If user is activated, check if their password is correct
  1051. if( user.authenticate( password ) ) {
  1052. log3("pass ok")
  1053. var sid = req.sessionID;
  1054. user.session = sid;
  1055. // Set the session then save the user to the database
  1056. user.save( function() {
  1057. var redirect = req.session.redirect;
  1058. // login complete, remember the user's email for next time
  1059. req.session.email = email;
  1060. // alert the successful login
  1061. sendJson(res, {status: 'ok', message:'Successfully logged in!'} );
  1062. // redirect to profile if we don't have a stashed request
  1063. //res.redirect( redirect || '/profile' );
  1064. });
  1065. } else {
  1066. // Notify user of bad login
  1067. sendJson(res, {status: 'error', message: 'Invalid login!'} );
  1068. //res.render( 'login' );
  1069. }
  1070. }
  1071. } else {
  1072. // Notify user of bad login
  1073. log3("bad login")
  1074. sendJson(res, {status: 'error', message: 'Invalid login!'} );
  1075. //res.render( 'login' );
  1076. }
  1077. });
  1078. });
  1079. // Recieve reset password request form
  1080. app.post( '/resetpass', checkAjax, function( req, res ) {
  1081. log3("post resetpw");
  1082. var email = req.body.email
  1083. // Search for user
  1084. User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
  1085. if( user ) {
  1086. // If user exists, create reset code
  1087. var resetPassCode = hat(64);
  1088. user.setResetPassCode(resetPassCode);
  1089. // Construct url that the user can then click to reset password
  1090. var resetPassUrl = 'http://' + serverHost + ((app.address().port != 80)? ':'+app.address().port: '') + '/resetpw/' + resetPassCode;
  1091. // Save user to database
  1092. user.save( function( err ) {
  1093. log3('save '+user.email);
  1094. // Construct email and send it to the user
  1095. var message = {
  1096. 'to' : user.email,
  1097. 'subject' : 'Your FinalsClub.org Password has been Reset!',
  1098. 'template' : 'userPasswordReset',
  1099. 'locals' : {
  1100. 'resetPassCode' : resetPassCode,
  1101. 'resetPassUrl' : resetPassUrl
  1102. }
  1103. };
  1104. mailer.send( message, function( err, result ) {
  1105. if( err ) {
  1106. // XXX: Add route to resend this email
  1107. console.log( 'Error sending user password reset email!' );
  1108. } else {
  1109. console.log( 'Successfully sent user password reset email.' );
  1110. }
  1111. });
  1112. // Render request success page
  1113. sendJson(res, {status: 'ok', message: 'Your password has been reset. An email has been sent to ' + email })
  1114. });
  1115. } else {
  1116. // Notify of error
  1117. sendJson(res, {status: 'error', message: 'We were unable to reset the password using that email address. Please try again.' })
  1118. }
  1119. });
  1120. });
  1121. // Recieve reset password form
  1122. app.post( '/resetpw/:id', checkAjax, function( req, res ) {
  1123. log3("post resetpw.code");
  1124. var resetPassCode = req.params.id
  1125. var email = req.body.email
  1126. var pass1 = req.body.pass1
  1127. var pass2 = req.body.pass2
  1128. // Find user by email
  1129. User.findOne( { 'email' : email.toLowerCase() }, function( err, user ) {
  1130. var valid = false;
  1131. // If user exists, and the resetPassCode is valid, pass1 and pass2 match, then
  1132. // save user with new password and display success message.
  1133. if( user ) {
  1134. var valid = user.resetPassword(resetPassCode, pass1, pass2);
  1135. if (valid) {
  1136. user.save( function( err ) {
  1137. sendJson(res, {status: 'ok', message: 'Your password has been reset. You can now login with your the new password you just created.'})
  1138. });
  1139. }
  1140. }
  1141. // If there was a problem, notify user
  1142. if (!valid) {
  1143. sendJson(res, {status: 'error', message: 'We were unable to reset the password. Please try again.' })
  1144. }
  1145. });
  1146. });
  1147. // Display registration page
  1148. /*
  1149. app.get( '/register', function( req, res ) {
  1150. log3("get reg page");
  1151. // Populate school dropdown list
  1152. School.find( {} ).sort( 'name', '1' ).run( function( err, schools ) {
  1153. res.render( 'register', { 'schools' : schools } );
  1154. })
  1155. });
  1156. */
  1157. // Recieve registration form
  1158. app.post( '/register', checkAjax, function( req, res ) {
  1159. var sid = req.sessionId;
  1160. // Create new user from User schema
  1161. var user = new User;
  1162. // Populate user from form
  1163. user.email = req.body.email.toLowerCase();
  1164. user.password = req.body.password;
  1165. user.session = sid;
  1166. // If school is set to other, then fill in school as what the user entered
  1167. user.school = req.body.school === 'Other' ? req.body.otherSchool : req.body.school;
  1168. user.name = req.body.name;
  1169. user.affil = req.body.affil;
  1170. user.activated = false;
  1171. // Validate email
  1172. if ( ( user.email === '' ) || ( !isValidEmail( user.email ) ) ) {
  1173. return sendJson(res, {status: 'error', message: 'Please enter a valid email'} );
  1174. }
  1175. // Check if password is greater than 6 characters, otherwise notify user
  1176. if ( req.body.password.length < 6 ) {
  1177. return sendJson(res, {status: 'error', message: 'Please enter a password longer than eight characters'} );
  1178. }
  1179. // Pull out hostname from email
  1180. var hostname = user.email.split( '@' ).pop();
  1181. // Check if email is from one of the special domains
  1182. if( /^(finalsclub.org)$/.test( hostname ) ) {
  1183. user.admin = true;
  1184. }
  1185. // Save user to database
  1186. user.save( function( err ) {
  1187. // If error, check if it is because the user already exists, if so
  1188. // get the user information and let them know
  1189. if ( err ) {
  1190. if( /dup key/.test( err.message ) ) {
  1191. // attempting to register an existing address
  1192. User.findOne({ 'email' : user.email }, function(err, result ) {
  1193. if (result.activated) {
  1194. // If activated, make sure they know how to contact the admin
  1195. return sendJson(res, {status: 'error', message: 'There is already someone registered with this email, if this is in error contact info@finalsclub.org for help'} );
  1196. } else {
  1197. // If not activated, direct them to the resendActivation page
  1198. return sendJson(res, {status: 'error', message: 'There is already someone registered with this email, if this is you, please check your email for the activation code'} );
  1199. }
  1200. });
  1201. } else {
  1202. // If any other type of error, prompt them to enter the registration again
  1203. return sendJson(res, {status: 'error', message: 'An error occurred during registration.'} );
  1204. }
  1205. } else {
  1206. // send user activation email
  1207. sendUserActivation( user );
  1208. // Check if the hostname matches any in the approved schools
  1209. School.findOne( { 'hostnames' : hostname }, function( err, school ) {
  1210. if( school ) {
  1211. // If there is a match, send associated welcome message
  1212. sendUserWelcome( user, true );
  1213. log3('school recognized '+school.name);
  1214. // If no users exist for the school, create empty array
  1215. if (!school.users) school.users = [];
  1216. // Add user to the school
  1217. school.users.push( user._id );
  1218. // Save school to the database
  1219. school.save( function( err ) {
  1220. log3('school.save() done');
  1221. // Notify user that they have been added to the school
  1222. sendJson(res, {status: 'ok', message: 'You have automatically been added to the ' + school.name + ' network. Please check your email for the activation link'} );
  1223. });
  1224. // Construct admin email about user registration
  1225. var message = {
  1226. 'to' : ADMIN_EMAIL,
  1227. 'subject' : 'FC User Registration : User added to ' + school.name,
  1228. 'template' : 'userSchool',
  1229. 'locals' : {
  1230. 'user' : user
  1231. }
  1232. }
  1233. } else {
  1234. // If there isn't a match, send associated welcome message
  1235. sendUserWelcome( user, false );
  1236. // Tell user to check for activation link
  1237. sendJson(res, {status: 'ok', message: 'Your account has been created, please check your email for the activation link'} );
  1238. // Construct admin email about user registration
  1239. var message = {
  1240. 'to' : ADMIN_EMAIL,
  1241. 'subject' : 'FC User Registration : Email did not match any schools',
  1242. 'template' : 'userNoSchool',
  1243. 'locals' : {
  1244. 'user' : user
  1245. }
  1246. }
  1247. }
  1248. // Send email to admin
  1249. mailer.send( message, function( err, result ) {
  1250. if ( err ) {
  1251. console.log( 'Error sending user has no school email to admin\nError Message: '+err.Message );
  1252. } else {
  1253. console.log( 'Successfully sent user has no school email to admin.' );
  1254. }
  1255. })
  1256. });
  1257. }
  1258. });
  1259. });
  1260. // Display resendActivation request page
  1261. app.get( '/resendActivation', function( req, res ) {
  1262. var activateCode = req.session.activateCode;
  1263. // Check if user exists by activateCode set in their session
  1264. User.findById( activateCode, function( err, user ) {
  1265. if( ( ! user ) || ( user.activated ) ) {
  1266. res.redirect( '/' );
  1267. } else {
  1268. // Send activation and redirect to login
  1269. sendUserActivation( user );
  1270. req.flash( 'info', 'Your activation code has been resent.' );
  1271. res.redirect( '/login' );
  1272. }
  1273. });
  1274. });
  1275. // Display activation page
  1276. app.get( '/activate/:code', checkAjax, function( req, res ) {
  1277. var code = req.params.code;
  1278. // XXX could break this out into a middleware
  1279. if( ! code ) {
  1280. return sendJson(res, {status:'error', message: 'Invalid activation code!'} );
  1281. }
  1282. // Find user by activation code
  1283. User.findById( code, function( err, user ) {
  1284. if( err || ! user ) {
  1285. // If not found, notify user of invalid code
  1286. sendJson(res, {status:'error', message:'Invalid activation code!'} );
  1287. } else {
  1288. // If valid, then activate user
  1289. user.activated = true;
  1290. // Regenerate our session and log in as the new user
  1291. req.session.regenerate( function() {
  1292. user.session = req.sessionID;
  1293. // Save user to database
  1294. user.save( function( err ) {
  1295. if( err ) {
  1296. sendJson(res, {status: 'error', message: 'Unable to activate account.'} );
  1297. } else {
  1298. sendJson(res, {status: 'info', message: 'Account successfully activated. Please complete your profile.'} );
  1299. }
  1300. });
  1301. });
  1302. }
  1303. });
  1304. });
  1305. // Logut user
  1306. app.get( '/logout', checkAjax, function( req, res ) {
  1307. sys.puts("logging out");
  1308. var sid = req.sessionID;
  1309. // Find user by session id
  1310. User.findOne( { 'session' : sid }, function( err, user ) {
  1311. if( user ) {
  1312. // Empty out session id
  1313. user.session = '';
  1314. // Save user to database
  1315. user.save( function( err ) {
  1316. sendJson(res, {status: 'ok', message: 'Successfully logged out'});
  1317. });
  1318. } else {
  1319. sendJson(res, {status: 'ok', message: ''});
  1320. }
  1321. });
  1322. });
  1323. // Recieve profile edit page form
  1324. app.post( '/profile', checkAjax, loadUser, loggedIn, function( req, res ) {
  1325. var user = req.user;
  1326. var fields = req.body;
  1327. var error = false;
  1328. var wasComplete = user.isComplete;
  1329. if( ! fields.name ) {
  1330. return sendJson(res, {status: 'error', message: 'Please enter a valid name!'} );
  1331. } else {
  1332. user.name = fields.name;
  1333. }
  1334. if( [ 'Student', 'Teachers Assistant' ].indexOf( fields.affiliation ) == -1 ) {
  1335. return sendJson(res, {status: 'error', message: 'Please select a valid affiliation!'} );
  1336. } else {
  1337. user.affil = fields.affiliation;
  1338. }
  1339. if( fields.existingPassword || fields.newPassword || fields.newPasswordConfirm ) {
  1340. // changing password
  1341. if( ( ! user.hashed ) || user.authenticate( fields.existingPassword ) ) {
  1342. if( fields.newPassword === fields.newPasswordConfirm ) {
  1343. // test password strength?
  1344. user.password = fields.newPassword;
  1345. } else {
  1346. return sendJson(res, {status: 'error', message: 'Mismatch in new password!'} );
  1347. }
  1348. } else {
  1349. return sendJson(res, {status: 'error', message: 'Please supply your existing password.'} );
  1350. }
  1351. }
  1352. user.major = fields.major;
  1353. user.bio = fields.bio;
  1354. user.showName = ( fields.showName ? true : false );
  1355. user.save( function( err ) {
  1356. if( err ) {
  1357. sendJson(res, {status: 'error', message: 'Unable to save user profile!'} );
  1358. } else {
  1359. if( ( user.isComplete ) && ( ! wasComplete ) ) {
  1360. sendJson(res, {status: 'ok', message: 'Your account is now fully activated. Thank you for joining FinalsClub!'} );
  1361. } else {
  1362. sendJson(res, {status:'ok', message:'Your profile was successfully updated!'} );
  1363. }
  1364. }
  1365. });
  1366. });
  1367. // Old Notes
  1368. function loadSubject( req, res, next ) {
  1369. if( url.parse( req.url ).pathname.match(/subject/) ) {
  1370. ArchivedSubject.findOne({id: req.params.id }, function(err, subject) {
  1371. if ( err || !subject) {
  1372. sendJson(res, {status: 'not_found', message: 'Subject with this ID does not exist'} )
  1373. } else {
  1374. req.subject = subject;
  1375. next()
  1376. }
  1377. })
  1378. } else {
  1379. next()
  1380. }
  1381. }
  1382. function loadOldCourse( req, res, next ) {
  1383. if( url.parse( req.url ).pathname.match(/course/) ) {
  1384. ArchivedCourse.findOne({id: req.params.id }, function(err, course) {
  1385. if ( err || !course ) {
  1386. sendJson(res, {status: 'not_found', message: 'Course with this ID does not exist'} )
  1387. } else {
  1388. req.course = course;
  1389. next()
  1390. }
  1391. })
  1392. } else {
  1393. next()
  1394. }
  1395. }
  1396. var featuredCourses = [
  1397. {name: 'The Human Mind', 'id': 1563},
  1398. {name: 'Justice', 'id': 797},
  1399. {name: 'Protest Literature', 'id': 1681},
  1400. {name: 'Animal Cognition', 'id': 681},
  1401. {name: 'Positive Psychology', 'id': 1793},
  1402. {name: 'Social Psychology', 'id': 660},
  1403. {name: 'The Book from Gutenberg to the Internet', 'id': 1439},
  1404. {name: 'Cyberspace in Court', 'id': 1446},
  1405. {name: 'Nazi Cinema', 'id': 2586},
  1406. {name: 'Media and the American Mind', 'id': 2583},
  1407. {name: 'Social Thought in Modern America', 'id': 2585},
  1408. {name: 'Major British Writers II', 'id': 869},
  1409. {name: 'Civil Procedure', 'id': 2589},
  1410. {name: 'Evidence', 'id': 2590},
  1411. {name: 'Management of Industrial and Nonprofit Organizations', 'id': 2591},
  1412. ];
  1413. app.get( '/learn', loadUser, function( req, res ) {
  1414. res.render( 'archive/learn', { 'courses' : featuredCourses } );
  1415. })
  1416. app.get( '/learn/random', checkAjax, function( req, res ) {
  1417. sendJson(res, {status: 'ok', data: '/archive/course/'+ featuredCourses[Math.floor(Math.random()*featuredCourses.length)].id });
  1418. })
  1419. app.get( '/archive', checkAjax, loadUser, function( req, res ) {
  1420. ArchivedSubject.find({}).sort( 'name', '1' ).run( function( err, subjects ) {
  1421. if ( err || subjects.length === 0) {
  1422. sendJson(res, {status: 'error', message: 'There was a problem gathering the archived courses, please try again later.'} );
  1423. } else {
  1424. sendJson(res, { 'subjects' : subjects, 'user': req.user.sanitized } );
  1425. }
  1426. })
  1427. })
  1428. app.get( '/archive/subject/:id', checkAjax, loadUser, loadSubject, function( req, res ) {
  1429. ArchivedCourse.find({subject_id: req.params.id}).sort('name', '1').run(function(err, courses) {
  1430. if ( err || courses.length === 0 ) {
  1431. sendJson(res, {status: 'not_found', message: 'There are no archived courses'} );
  1432. } else {
  1433. sendJson(res, { 'courses' : courses, 'subject': req.subject, 'user': req.user.sanitized } );
  1434. }
  1435. })
  1436. })
  1437. app.get( '/archive/course/:id', checkAjax, loadUser, loadOldCourse, function( req, res ) {
  1438. ArchivedNote.find({course_id: req.params.id}).sort('name', '1').run(function(err, notes) {
  1439. if ( err || notes.length === 0) {
  1440. sendJson(res, {status: 'not_found', message: 'There are no notes in this course'} );
  1441. } else {
  1442. notes = notes.map(function(note) { return note.sanitized });
  1443. sendJson(res, { 'notes': notes, 'course' : req.course, 'user': req.user.sanitized } );
  1444. }
  1445. })
  1446. })
  1447. app.get( '/archive/note/:id', checkAjax, loadUser, function( req, res ) {
  1448. console.log( "id="+req.params.id)
  1449. ArchivedNote.findById(req.params.id, function(err, note) {
  1450. if ( err || !note ) {
  1451. sendJson(res, {status: 'not_found', message: 'This is not a valid id for a note'} );
  1452. } else {
  1453. ArchivedCourse.findOne({id: note.course_id}, function(err, course) {
  1454. if ( err || !course ) {
  1455. sendJson(res, {status: 'not_found', message: 'There is no course for this note'} )
  1456. } else {
  1457. sendJson(res, { 'layout' : 'notesLayout', 'note' : note, 'course': course, 'user': req.user.sanitized } );
  1458. }
  1459. })
  1460. }
  1461. })
  1462. })
  1463. app.get( '*', function(req, res) {
  1464. res.sendfile('public/index.html');
  1465. });
  1466. // socket.io server
  1467. // The finalsclub backchannel server uses socket.io to handle communication between the server and
  1468. // the browser which facilitates near realtime interaction. This allows the user to post questions
  1469. // and comments and other users to get those almost immediately after they are posted, without
  1470. // reloading the page or pressing a button to refresh.
  1471. //
  1472. // The server code itself is fairly simple, mainly taking incomming messages from client browsers,
  1473. // saving the data to the database, and then sending it out to everyone else connected.
  1474. //
  1475. // Data types:
  1476. // Posts - Posts are the main items in backchannel, useful for questions or discussion points
  1477. // [[ example object needed with explanation E.G:
  1478. /*
  1479. Post: { postID: '999-1',
  1480. userID: '1234',
  1481. userName: 'Bob Jones',
  1482. userAffil: 'Instructor',
  1483. body: 'This is the text content of the post.',
  1484. comments: { {<commentObj>, <commentObj>, ...},
  1485. public: true,
  1486. votes: [ <userID>, <userID>, ...],
  1487. reports: [ <userID>, <userID>, ...]
  1488. }
  1489. Comment: { body: 'foo bar', userName: 'Bob Jones', userAffil: 'Instructor' }
  1490. if anonymous: userName => 'Anonymous', userAffil => 'N/A'
  1491. */
  1492. //
  1493. //
  1494. //
  1495. // Comments - Comments are replies to posts, for clarification or answering questions
  1496. // [[ example object needed]]
  1497. // Votes - Votes signifyg a users approval of a post
  1498. // [[ example object needed]]
  1499. // Flags - Flagging a post signifies that it is against the rules, 2 flags moves it to the bottomw
  1500. // [[ example object needed]]
  1501. //
  1502. //
  1503. // Post Schema
  1504. // body - Main content of the post
  1505. // userId - Not currently used, but would contain the users id that made the post
  1506. // userName - Users name that made post
  1507. // userAffil - Users affiliation to their school
  1508. // public - Boolean which denotes if the post is public to everyone, or private to school users only
  1509. // date - Date post was made, updates when any comments are made for the post
  1510. // comments - An array of comments which contain a body, userName, and userAffil
  1511. // votes - An array of user ids which are the users that voted
  1512. // [[ example needed ]]
  1513. // reports - An array of user ids which are the users that reported the post
  1514. // [[ reports would be "this post is flagged as inappropriate"? ]]
  1515. // [[ bruml: consistent terminology needed ]]
  1516. //
  1517. // Posts and comments can be made anonymously. When a post is anonymous, the users info is stripped
  1518. // from the post and the userName is set to Anonymous and the userAffil to N/A. This is to allow
  1519. // users the ability to make posts or comments that they might not otherwise due to not wanting
  1520. // the content of the post/comment to be attributed to them.
  1521. //
  1522. // Each time a user connects to the server, it passes through authorization which checks for a cookie
  1523. // that is set by Express. If a session exists and it is for a valid logged in user, then handshake.user
  1524. // is set to the users data, otherwise it is set to false. handshake.user is used later on to check if a
  1525. // user is logged in, and if so display information that otherwise might not be visible to them if they
  1526. // aren't apart of a particular school.
  1527. //
  1528. // After the authorization step, the client browser sends the lecture id which is rendered into the html
  1529. // page on page load from Express. This is then used to assign a 'room' for the user which is grouped
  1530. // by lecture. All posts are grouped by lecture, and only exist for that lecture. After the user is
  1531. // grouped into a 'room', they are sent a payload of all existing posts for that lecture, which are then
  1532. // rendered in the browser.
  1533. //
  1534. // Everything else from this point on is handled in an event form and requires a user initiating it. The
  1535. // events are as follows.
  1536. //
  1537. // Post event
  1538. // A user makes a new post. A payload of data containing the post and lecture id is sent to the server.
  1539. // The server recieves the data, assembles a new post object for the database and then fills it with
  1540. // the appropriate data. If a user selected for the post to be anonymous, the userName and userAffil are
  1541. // replaced. If the user chose for the post to be private, then public will be set to false and it
  1542. // will be filtered from being sent to users not logged into and not having access to the school. Once
  1543. // the post has been created and saved into the database, it is sent to all connected users to that
  1544. // particular lecture, unless it is private, than only logged in users will get it.
  1545. //
  1546. // Vote event
  1547. // A user votes for a post. A payload of data containing the post id and lecture id are sent along with
  1548. // the user id. A new vote is created by first fetching the parent post, then adding the user id to the
  1549. // votes array, and then the post is subsequently saved back to the database and sent to all connected
  1550. // users unless the post is private, which then it will be only sent to logged in users.
  1551. //
  1552. // Report event
  1553. // Similar to the vote event, reports are sent as a payload of a post id, lecture id, and user id, which
  1554. // are then used to fetch the parent post, add the user id to the reports array, and then saved to the db.
  1555. // Then the report is sent out to all connected users unless it is a private post, which will be only sent
  1556. // to logged in users. On the client, once a post has more two (2) or more reports, it will be moved to the
  1557. // bottom of the interface.
  1558. //
  1559. // Comment event
  1560. // A user posts a comment to a post. A payload of data containing the post id, lecture id, comment body,
  1561. // user name, and user affiliation are sent to the server, which are then used to find the parent post
  1562. // and then a new comment object is assembled. When new comments are made, it updates the posts date
  1563. // which allows the post to be sorted by date and the posts with the freshest comments would be pushed
  1564. // to the top of the interface. The comment can be anonymous, which then will have the user
  1565. // name and affiliation stripped before saving to the database. The comment then will be sent out to all
  1566. // connected users unless the post is private, then only logged in users will recieve the comment.
  1567. var io = require( 'socket.io' ).listen( app );
  1568. var Post = mongoose.model( 'Post' );
  1569. io.set('authorization', function ( handshake, next ) {
  1570. var rawCookie = handshake.headers.cookie;
  1571. if (rawCookie) {
  1572. handshake.cookie = parseCookie(rawCookie);
  1573. handshake.sid = handshake.cookie['connect.sid'];
  1574. if ( handshake.sid ) {
  1575. app.set( 'sessionStore' ).get( handshake.sid, function( err, session ) {
  1576. if( err ) {
  1577. handshake.user = false;
  1578. return next(null, true);
  1579. } else {
  1580. // bake a new session object for full r/w
  1581. handshake.session = new Session( handshake, session );
  1582. User.findOne( { session : handshake.sid }, function( err, user ) {
  1583. if( user ) {
  1584. handshake.user = user;
  1585. return next(null, true);
  1586. } else {
  1587. handshake.user = false;
  1588. return next(null, true);
  1589. }
  1590. });
  1591. }
  1592. })
  1593. }
  1594. } else {
  1595. data.user = false;
  1596. return next(null, true);
  1597. }
  1598. });
  1599. var backchannel = new Backchannel(app, io.of('/backchannel'), {
  1600. // TODO: if lecture belongs to course (find pinker's courseId) pass a 'no-answers' true/false
  1601. subscribe: function(lecture, send) {
  1602. Post.find({'lecture': lecture}, function(err, posts) {
  1603. send(posts);
  1604. });
  1605. },
  1606. post: function(fillPost) {
  1607. var post = new Post;
  1608. fillPost(post, function(send) {
  1609. post.save(function(err) {
  1610. send();
  1611. });
  1612. });
  1613. },
  1614. items: function(postId, addItem) {
  1615. Post.findById(postId, function( err, post ) {
  1616. addItem(post, function(send) {
  1617. post.save(function(err) {
  1618. send();
  1619. });
  1620. })
  1621. })
  1622. }
  1623. });
  1624. var counters = {};
  1625. var counts = io
  1626. .of( '/counts' )
  1627. .on( 'connection', function( socket ) {
  1628. // pull out user/session information etc.
  1629. var handshake = socket.handshake;
  1630. var userID = handshake.user._id;
  1631. var watched = [];
  1632. var noteID = null;
  1633. var timer = null;
  1634. socket.on( 'join', function( note ) {
  1635. if (handshake.user === false) {
  1636. noteID = note;
  1637. // XXX: replace by addToSet (once it's implemented in mongoose)
  1638. Note.findById( noteID, function( err, note ) {
  1639. if( note ) {
  1640. if( note.collaborators.indexOf( userID ) == -1 ) {
  1641. note.collaborators.push( userID );
  1642. note.save();
  1643. }
  1644. }
  1645. });
  1646. }
  1647. });
  1648. socket.on( 'watch', function( l ) {
  1649. var sendCounts = function() {
  1650. var send = {};
  1651. Note.find( { '_id' : { '$in' : watched } }, function( err, notes ) {
  1652. async.forEach(
  1653. notes,
  1654. function( note, callback ) {
  1655. var id = note._id;
  1656. var count = note.collaborators.length;
  1657. send[ id ] = count;
  1658. callback();
  1659. }, function() {
  1660. socket.emit( 'counts', send );
  1661. timer = setTimeout( sendCounts, 5000 );
  1662. }
  1663. );
  1664. });
  1665. }
  1666. Note.find( { 'lecture' : l }, [ '_id' ], function( err, notes ) {
  1667. notes.forEach( function( note ) {
  1668. watched.push( note._id );
  1669. });
  1670. });
  1671. sendCounts();
  1672. });
  1673. socket.on( 'disconnect', function() {
  1674. clearTimeout( timer );
  1675. if (handshake.user === false) {
  1676. // XXX: replace with $pull once it's available
  1677. if( noteID ) {
  1678. Note.findById( noteID, function( err, note ) {
  1679. if( note ) {
  1680. var index = note.collaborators.indexOf( userID );
  1681. if( index != -1 ) {
  1682. note.collaborators.splice( index, 1 );
  1683. }
  1684. note.save();
  1685. }
  1686. });
  1687. }
  1688. }
  1689. });
  1690. });
  1691. // Exception Catch-All
  1692. process.on('uncaughtException', function (e) {
  1693. console.log("!!!!!! UNCAUGHT EXCEPTION\n" + e.stack);
  1694. });
  1695. // Launch
  1696. // mongoose now exepects a mongo url
  1697. mongoose.connect( 'mongodb://localhost/fc' ); // FIXME: make relative to hostname
  1698. var mailer = new Mailer( app.set('awsAccessKey'), app.set('awsSecretKey') );
  1699. everyauth.helpExpress(app);
  1700. app.listen( serverPort, function() {
  1701. console.log( "Express server listening on port %d in %s mode", app.address().port, app.settings.env );
  1702. // if run as root, downgrade to the owner of this file
  1703. if (process.getuid() === 0) {
  1704. require('fs').stat(__filename, function(err, stats) {
  1705. if (err) { return console.log(err); }
  1706. process.setuid(stats.uid);
  1707. });
  1708. }
  1709. });
  1710. function isValidEmail(email) {
  1711. 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])?/;
  1712. return email.match(re);
  1713. }
  1714. // Facebook connect