1page updates
[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( 'displayName' )
40         .get( function() {
41                 if( this.showName ) {
42                         return this.name;
43                 } else {
44                         return this.email;
45                 }
46         });
47
48 UserSchema.virtual( 'password' )
49         .set( function( password ) {
50                 this.salt                               = salt();
51                 this.hashed                     = this.encrypt( password );
52         });
53
54 UserSchema.virtual( 'isComplete' )
55         .get( function() {
56                 // build on this as the schema develops
57
58                 return ( this.name && this.affil && this.hashed );
59         });
60
61 UserSchema.method( 'encrypt', function( password ) {
62         var hmac = crypto.createHmac( 'sha1', this.salt );
63
64         return hmac.update( password ).digest( 'hex' );
65 });
66
67 UserSchema.method( 'authenticate', function( plaintext ) {
68         return ( this.encrypt( plaintext ) === this.hashed );
69 });
70
71 UserSchema.method('genRandomPassword', function () {
72         // this function generates the random password, it does not keep or save it.
73         var plaintext = '';
74
75         var len = 8;
76         var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
77         for (var i = 0; i < len; i++) {
78                 var randomPoz = Math.floor(Math.random() * charSet.length);
79                 plaintext += charSet.substring(randomPoz, randomPoz + 1);
80         }
81
82         return plaintext;
83 });
84
85 UserSchema.method( 'setResetPassCode', function ( code ) {
86         this.resetPassCode = code;
87         this.resetPassDate = new Date();
88         return this.resetPassCode;
89 });
90
91 UserSchema.method( 'canResetPassword', function ( code ) {
92         // ensure the passCode is valid, matches and the date has not yet expired, lets say 2 weeks for good measure.
93         var value = false;
94
95         var expDate = new Date();
96         expDate.setDate(expDate.getDate() - 14);
97
98         // we have a valid code and date
99         if (this.resetPassCode != null && this.resetPassDate != null && this.resetPassDate >= expDate && this.resetPassCode == code) {
100                 value = true;
101         }
102
103         return value;
104 });
105
106 UserSchema.method( 'resetPassword', function ( code, newPass1,  newPass2) {
107         // ensure the date has not expired, lets say 2 weeks for good measure.
108
109         var success = false;
110         if (this.canResetPassword(code) && newPass1 != null && newPass1.length > 0 && newPass1 == newPass2) {
111                 this.password = newPass1;
112                 this.resetPassCode = null;
113                 this.resetPassDate = null;
114                 success = true;
115         }
116
117         return success;
118 });
119
120 var User = mongoose.model( 'User', UserSchema );
121
122 // schools
123
124 var SchoolSchema = new Schema( {
125         name                            : { type : String, required : true },
126         description     : String,
127         url                                     : String,
128
129   created     : { type : Date, default : Date.now },
130         hostnames               : Array,
131
132         users                           : Array
133 });
134
135 SchoolSchema.virtual( 'sanitized' ).get(function() {
136   var school = {
137     name: this.name,
138     description: this.description,
139     url: this.url
140   }
141
142   return school;
143 })
144
145 SchoolSchema.method( 'authorize', function( user, cb ) {
146         return cb(user.admin || ( this.users.indexOf( user._id ) !== -1 ));
147 });
148
149 var School = mongoose.model( 'School', SchoolSchema );
150
151 // courses
152
153 var CourseSchema = new Schema( {
154         name                            : { type : String, required : true },
155         number                  : String,
156         description     : String,
157   instructor  : ObjectId,
158   subject     : String,
159   department  : String,
160         // courses are tied to one school
161         school                  : ObjectId,
162
163         // XXX: room for additional resources
164   created     : { type : Date, default : Date.now },
165   creator     : ObjectId,
166   deleted     : Boolean,
167
168         // many users may subscribe to a course
169         users                           : Array
170 });
171
172 CourseSchema.virtual( 'displayName' )
173         .get( function() {
174                 if( this.number ) {
175                         return this.number + ': ' + this.name;
176                 } else {
177                         return this.name;
178                 }
179         });
180
181 CourseSchema.method( 'authorize', function( user, cb ) {
182         School.findById( this.school, function( err, school ) {
183                 if ( school ) {
184                         school.authorize( user, function( result ) {
185                                                         return cb( result );
186                         })
187                 }
188         });
189 });
190
191 CourseSchema.method( 'subscribed', function( user ) {
192         return ( this.users.indexOf( user ) > -1 ) ;
193 });
194
195 CourseSchema.method( 'subscribe', function( user, callback ) {
196         var id = this._id;
197
198         // mongoose issue #404
199         Course.collection.update( { '_id' : id }, { '$addToSet' : { 'users' : user } }, function( err ) {
200                 callback( err );
201         });
202 });
203
204 CourseSchema.method( 'unsubscribe', function( user, callback ) {
205         var id = this._id;
206
207         // mongoose issue #404
208         Course.collection.update( { '_id' : id }, { '$pull' : { 'users' : user } }, function( err ) {
209                 callback( err );
210         });
211 });
212
213 CourseSchema.method( 'delete', function( callback ) {
214   var id = this._id;
215
216   Course.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
217     if (callback) callback( err );
218     Lecture.find( { course: id }, function( err, lectures) {
219       if (lectures.length > 0) {
220         lectures.forEach(function(lecture) {
221           lecture.delete();
222         })
223       }
224     })
225   })
226 });
227
228 var Course = mongoose.model( 'Course', CourseSchema );
229
230 // lectures
231
232 var LectureSchema       = new Schema( {
233         name                                    : { type : String, required : true },
234         date                                    : { type : Date, default: Date.now },
235         live                                    : Boolean,
236   creator       : ObjectId,
237   deleted       : Boolean,
238
239         course                          : ObjectId
240 });
241
242 LectureSchema.method( 'authorize', function( user, cb ) {
243         Course.findById( this.course, function( err, course ) {
244                 if (course) {
245                         course.authorize( user, function( res ) {
246                                 return cb( res );
247                         })
248                 } else {
249                  return cb( false );
250                 }
251         });
252 });
253
254 LectureSchema.method( 'delete', function( callback ) {
255   var id = this._id;
256
257   Lecture.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
258     if (callback) callback( err );
259     Note.find( { lecture : id }, function(err, notes) {
260       notes.forEach(function(note) {
261         note.delete();
262       })
263     })
264     Post.find( { lecture : id }, function(err, posts) {
265       posts.forEach(function(post) {
266         post.delete();
267       })
268     })
269   })
270 });
271
272 var Lecture = mongoose.model( 'Lecture', LectureSchema );
273
274 // notes
275
276 var NoteSchema = new Schema( {
277         name                                    : { type : String, required : true },
278         path                                    : String,
279   public        : Boolean,
280   roID          : String,
281         visits                          : Number,
282   created         : { type : Date, default : Date.now },
283   creator       : ObjectId,
284   deleted       : Boolean,
285
286         lecture                         : ObjectId,
287
288         collaborators : [String]
289 });
290
291 NoteSchema.method( 'authorize', function( user, cb ) {
292         Lecture.findById( this.lecture, function( err, lecture ) {
293                 if (lecture) {
294                         lecture.authorize( user, function( res ) {
295                                 return cb( res );
296                         })
297                 } else {
298                         return cb( false );
299                 }
300         });
301 });
302
303 NoteSchema.method( 'addVisit', function() {
304         var id = this._id;
305
306         Note.collection.update( { '_id' : id }, { '$inc' : { 'visits' : 1 } } );
307 });
308
309 NoteSchema.method( 'delete', function( callback ) {
310   var id = this._id;
311
312   Note.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
313     if (callback) callback( err );
314   })
315 });
316
317 var Note = mongoose.model( 'Note', NoteSchema );
318
319 // comments
320
321 var PostSchema = new Schema({
322   date      : { type : Date, default : Date.now },
323   body      : String,
324   votes     : [String],
325   reports   : [String],
326   public    : Boolean,
327
328   userid    : String, // ObjectId,
329   userName  : String,
330   userAffil : String,
331
332   comments   : Array,
333
334   lecture   : String, // ObjectId
335   deleted   : Boolean
336 })
337
338 PostSchema.method( 'delete', function( callback ) {
339   var id = this._id;
340
341   Post.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
342     if (callback) callback( err );
343   })
344 });
345
346 mongoose.model( 'Post', PostSchema );
347
348 var ArchivedCourse = new Schema({
349   id: Number,
350   instructor: String,
351   section: String,
352   name: String,
353   description: String,
354   subject_id: Number
355 })
356
357 mongoose.model( 'ArchivedCourse', ArchivedCourse )
358
359 var ArchivedNote = new Schema({
360   course_id: Number,
361   topic: String,
362   text: String
363 })
364
365 mongoose.model( 'ArchivedNote', ArchivedNote )
366
367 var ArchivedSubject = new Schema({
368   id: Number,
369   name: String
370 })
371
372 mongoose.model( 'ArchivedSubject', ArchivedSubject )
373
374 module.exports.mongoose = mongoose;