Checking in changes made in-place on the server to be merged
[oweals/finalsclub.git] / models.js
1 /* vim: set ts=2: */
2
3 // DEPENDENCIES
4
5 var crypto = require( 'crypto' );
6
7 var mongoose    = require( 'mongoose' );
8
9 var Schema              = mongoose.Schema;
10 var ObjectId    = mongoose.SchemaTypes.ObjectId;
11
12 // SUPPORT FUNCTIONS
13
14 function salt() {
15         return Math.round( ( new Date().valueOf() * Math.random() ) ).toString();
16 }
17
18 // MODELS
19
20 // user
21
22 var UserSchema = new Schema( {
23         email                                           : { type : String, require: true, index : { unique : true } },
24         school                                  : String,
25         name                                            : String,
26         affil                                           : String,
27   created         : { type : Date, default : Date.now },
28         hashed                                  : String,
29         activated                               : Boolean,
30         activateCode            : String,
31         resetPassCode           : String,
32         resetPassDate           : Date,
33         salt                                            : String,
34         session                                 : String,
35         showName                                : { 'type' : Boolean, 'default' : true },
36         admin                                           : { 'type' : Boolean, 'default' : false }
37 });
38
39 UserSchema.virtual( 'sanitized' ).get(function() {
40   var user = {
41     email: this.email,
42     name: this.name,
43     affil: this.affil,
44     showName: this.showName,
45     admin: this.admin
46   }
47
48   return user;
49 });
50
51 UserSchema.virtual( 'displayName' )
52         .get( function() {
53                 if( this.showName ) {
54                         return this.name;
55                 } else {
56                         return this.email;
57                 }
58         });
59
60 UserSchema.virtual( 'password' )
61         .set( function( password ) {
62                 this.salt                               = salt();
63                 this.hashed                     = this.encrypt( password );
64         });
65
66 UserSchema.virtual( 'isComplete' )
67         .get( function() {
68                 // build on this as the schema develops
69
70                 return ( this.name && this.affil && this.hashed );
71         });
72
73 UserSchema.method( 'encrypt', function( password ) {
74         var hmac = crypto.createHmac( 'sha1', this.salt );
75
76         return hmac.update( password ).digest( 'hex' );
77 });
78
79 UserSchema.method( 'authenticate', function( plaintext ) {
80         return ( this.encrypt( plaintext ) === this.hashed );
81 });
82
83 UserSchema.method('genRandomPassword', function () {
84         // this function generates the random password, it does not keep or save it.
85         var plaintext = '';
86
87         var len = 8;
88         var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
89         for (var i = 0; i < len; i++) {
90                 var randomPoz = Math.floor(Math.random() * charSet.length);
91                 plaintext += charSet.substring(randomPoz, randomPoz + 1);
92         }
93
94         return plaintext;
95 });
96
97 UserSchema.method( 'setResetPassCode', function ( code ) {
98         this.resetPassCode = code;
99         this.resetPassDate = new Date();
100         return this.resetPassCode;
101 });
102
103 UserSchema.method( 'canResetPassword', function ( code ) {
104         // ensure the passCode is valid, matches and the date has not yet expired, lets say 2 weeks for good measure.
105         var value = false;
106
107         var expDate = new Date();
108         expDate.setDate(expDate.getDate() - 14);
109
110         // we have a valid code and date
111         if (this.resetPassCode != null && this.resetPassDate != null && this.resetPassDate >= expDate && this.resetPassCode == code) {
112                 value = true;
113         }
114
115         return value;
116 });
117
118 UserSchema.method( 'resetPassword', function ( code, newPass1,  newPass2) {
119         // ensure the date has not expired, lets say 2 weeks for good measure.
120
121         var success = false;
122         if (this.canResetPassword(code) && newPass1 != null && newPass1.length > 0 && newPass1 == newPass2) {
123                 this.password = newPass1;
124                 this.resetPassCode = null;
125                 this.resetPassDate = null;
126                 success = true;
127         }
128
129         return success;
130 });
131
132 var User = mongoose.model( 'User', UserSchema );
133
134 // schools
135
136 var SchoolSchema = new Schema( {
137         name                            : { type : String, required : true },
138         description     : String,
139         url                                     : String,
140
141   created       : { type : Date, default : Date.now },
142   hostnames     : Array,
143
144   users         : Array,
145
146   slug          : String
147 });
148 // slug is the url version of a school
149
150 SchoolSchema.virtual( 'sanitized' ).get(function() {
151   var school = {
152     _id: this._id,
153     name: this.name,
154     description: this.description,
155     url: this.url,
156     slug: this.slug
157   }
158
159   return school;
160 })
161
162 SchoolSchema.method( 'authorize', function( user, cb ) {
163         return cb(user.admin || ( this.users.indexOf( user._id ) !== -1 ));
164 });
165
166 var School = mongoose.model( 'School', SchoolSchema );
167
168 // harvardcourses
169
170 var HarvardCourses = new Schema( {
171     cat_num         : Number,   // Catalog numb, unique_togther w/ term
172     term            : String,   // FALL or SPRING
173     bracketed       : Boolean,
174     field           : String,   // TODO: Add docs
175     number          : Number,   // string, int, float, intStringFloat
176     title           : String,
177     faculty         : String,   // hash fk to faculty table
178     description     : String,
179     prerequisites   : String,
180     notes           : String,
181     meetings        : String,   // TODO: try to auto parse this
182     building        : String,   // FIXME: Most == '', this is why we have to update
183     room            : String
184 });
185
186 HarvardCourses.virtual( 'sanitized' ).get(function() {
187     var hcourse = {
188         _id     : this._id,
189         title   : this.name,
190         field   : this.field,
191         number  : this.number,
192         desc    : this.description,
193         meetings: this.meetings,
194         building: this.building,
195         room    : this.room,
196         faculty : this.faculty
197         }
198     return hcourse
199 })
200
201 // courses
202
203 var CourseSchema = new Schema( {
204         name                            : { type : String, required : true },
205         number                  : String,
206         description     : String,
207   instructor  : ObjectId,
208   subject     : String,
209   department  : String,
210         // courses are tied to one school
211         school                  : ObjectId,
212
213         // XXX: room for additional resources
214   created     : { type : Date, default : Date.now },
215   creator     : ObjectId,
216   deleted     : Boolean,
217
218         // many users may subscribe to a course
219         users                           : Array
220 });
221
222 CourseSchema.virtual( 'sanitized' ).get(function() {
223   var course = {
224     _id: this._id,
225     name: this.name,
226     number: this.number || 'None',
227     description: this.description || 'None',
228     subject: this.subject || 'None',
229     department: this.department || 'None'
230   }
231
232   return course;
233 });
234
235 CourseSchema.virtual( 'displayName' )
236         .get( function() {
237                 if( this.number ) {
238                         return this.number + ': ' + this.name;
239                 } else {
240                         return this.name;
241                 }
242         });
243
244 CourseSchema.method( 'authorize', function( user, cb ) {
245         School.findById( this.school, function( err, school ) {
246                 if ( school ) {
247                         school.authorize( user, function( result ) {
248                                                         return cb( result );
249                         })
250                 }
251         });
252 });
253
254 CourseSchema.method( 'subscribed', function( user ) {
255         return ( this.users.indexOf( user ) > -1 ) ;
256 });
257
258 CourseSchema.method( 'subscribe', function( user, callback ) {
259         var id = this._id;
260
261         // mongoose issue #404
262         Course.collection.update( { '_id' : id }, { '$addToSet' : { 'users' : user } }, function( err ) {
263                 callback( err );
264         });
265 });
266
267 CourseSchema.method( 'unsubscribe', function( user, callback ) {
268         var id = this._id;
269
270         // mongoose issue #404
271         Course.collection.update( { '_id' : id }, { '$pull' : { 'users' : user } }, function( err ) {
272                 callback( err );
273         });
274 });
275
276 CourseSchema.method( 'delete', function( callback ) {
277   var id = this._id;
278
279   Course.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
280     if (callback) callback( err );
281     Lecture.find( { course: id }, function( err, lectures) {
282       if (lectures.length > 0) {
283         lectures.forEach(function(lecture) {
284           lecture.delete();
285         })
286       }
287     })
288   })
289 });
290
291 var Course = mongoose.model( 'Course', CourseSchema );
292
293 // lectures
294
295 var LectureSchema       = new Schema( {
296         name                                    : { type : String, required : true },
297         date                                    : { type : Date, default: Date.now },
298         live                                    : Boolean,
299   creator       : ObjectId,
300   deleted       : Boolean,
301
302         course                          : ObjectId
303 });
304
305 LectureSchema.virtual( 'sanitized' ).get(function() {
306   var lecture = {
307     _id: this._id,
308     name: this.name,
309     date: this.date,
310     live: this.live
311   }
312
313   return lecture;
314 })
315
316 LectureSchema.method( 'authorize', function( user, cb ) {
317         Course.findById( this.course, function( err, course ) {
318                 if (course) {
319                         course.authorize( user, function( res ) {
320                                 return cb( res );
321                         })
322                 } else {
323                  return cb( false );
324                 }
325         });
326 });
327
328 LectureSchema.method( 'delete', function( callback ) {
329   var id = this._id;
330
331   Lecture.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
332     if (callback) callback( err );
333     Note.find( { lecture : id }, function(err, notes) {
334       notes.forEach(function(note) {
335         note.delete();
336       })
337     })
338     Post.find( { lecture : id }, function(err, posts) {
339       posts.forEach(function(post) {
340         post.delete();
341       })
342     })
343   })
344 });
345
346 var Lecture = mongoose.model( 'Lecture', LectureSchema );
347
348 // notes
349
350 var NoteSchema = new Schema( {
351         name                                    : { type : String, required : true },
352         path                                    : String,
353   public        : Boolean,
354   roID          : String,
355         visits                          : Number,
356   created         : { type : Date, default : Date.now },
357   creator       : ObjectId,
358   deleted       : Boolean,
359
360         lecture                         : ObjectId,
361
362         collaborators : [String]
363 });
364
365 NoteSchema.virtual( 'sanitized').get(function() {
366   var note = {
367     _id: this._id,
368     name: this.name,
369     path: this.path,
370     public: this.public,
371     roID: this.roID,
372     visits: this.visits
373   }
374
375   return note;
376 });
377
378 NoteSchema.method( 'authorize', function( user, cb ) {
379         Lecture.findById( this.lecture, function( err, lecture ) {
380                 if (lecture) {
381                         lecture.authorize( user, function( res ) {
382                                 return cb( res );
383                         })
384                 } else {
385                         return cb( false );
386                 }
387         });
388 });
389
390 NoteSchema.method( 'addVisit', function() {
391         var id = this._id;
392
393         Note.collection.update( { '_id' : id }, { '$inc' : { 'visits' : 1 } } );
394 });
395
396 NoteSchema.method( 'delete', function( callback ) {
397   var id = this._id;
398
399   Note.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
400     if (callback) callback( err );
401   })
402 });
403
404 var Note = mongoose.model( 'Note', NoteSchema );
405
406 // comments
407
408 var PostSchema = new Schema({
409   date      : { type : Date, default : Date.now },
410   body      : String,
411   votes     : [String],
412   reports   : [String],
413   public    : Boolean,
414
415   userid    : String, // ObjectId,
416   userName  : String,
417   userAffil : String,
418
419   comments   : Array,
420
421   lecture   : String, // ObjectId
422   deleted   : Boolean
423 })
424
425 PostSchema.method( 'delete', function( callback ) {
426   var id = this._id;
427
428   Post.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
429     if (callback) callback( err );
430   })
431 });
432
433 mongoose.model( 'Post', PostSchema );
434
435 var ArchivedCourse = new Schema({
436   id: Number,
437   instructor: String,
438   section: String,
439   name: String,
440   description: String,
441   subject_id: Number
442 })
443
444 mongoose.model( 'ArchivedCourse', ArchivedCourse )
445
446 var ArchivedNote = new Schema({
447   course_id: Number,
448   topic: String,
449   text: String
450 })
451
452 ArchivedNote.virtual( 'sanitized' ).get(function() {
453   var note = {
454     _id: this._id,
455     topic: this.topic === '' ? (this.text.replace(/(<(.|\n)*?>)|[\r\n\t]*/g, '')).substr(0, 15) + '...' : this.topic
456   }
457   return note;
458 })
459
460 mongoose.model( 'ArchivedNote', ArchivedNote )
461
462 var ArchivedSubject = new Schema({
463   id: Number,
464   name: String
465 })
466
467 mongoose.model( 'ArchivedSubject', ArchivedSubject )
468
469 module.exports.mongoose = mongoose;