most pages under pseudo-nav behavior
[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     _id: this._id,
138     name: this.name,
139     description: this.description,
140     url: this.url
141   }
142
143   return school;
144 })
145
146 SchoolSchema.method( 'authorize', function( user, cb ) {
147         return cb(user.admin || ( this.users.indexOf( user._id ) !== -1 ));
148 });
149
150 var School = mongoose.model( 'School', SchoolSchema );
151
152 // courses
153
154 var CourseSchema = new Schema( {
155         name                            : { type : String, required : true },
156         number                  : String,
157         description     : String,
158   instructor  : ObjectId,
159   subject     : String,
160   department  : String,
161         // courses are tied to one school
162         school                  : ObjectId,
163
164         // XXX: room for additional resources
165   created     : { type : Date, default : Date.now },
166   creator     : ObjectId,
167   deleted     : Boolean,
168
169         // many users may subscribe to a course
170         users                           : Array
171 });
172
173 CourseSchema.virtual( 'sanitized' ).get(function() {
174   var course = {
175     _id: this._id,
176     name: this.name,
177     number: this.number,
178     description: this.description,
179     subject: this.subject,
180     department: this.department
181   }
182
183   return course;
184 });
185
186 CourseSchema.virtual( 'displayName' )
187         .get( function() {
188                 if( this.number ) {
189                         return this.number + ': ' + this.name;
190                 } else {
191                         return this.name;
192                 }
193         });
194
195 CourseSchema.method( 'authorize', function( user, cb ) {
196         School.findById( this.school, function( err, school ) {
197                 if ( school ) {
198                         school.authorize( user, function( result ) {
199                                                         return cb( result );
200                         })
201                 }
202         });
203 });
204
205 CourseSchema.method( 'subscribed', function( user ) {
206         return ( this.users.indexOf( user ) > -1 ) ;
207 });
208
209 CourseSchema.method( 'subscribe', function( user, callback ) {
210         var id = this._id;
211
212         // mongoose issue #404
213         Course.collection.update( { '_id' : id }, { '$addToSet' : { 'users' : user } }, function( err ) {
214                 callback( err );
215         });
216 });
217
218 CourseSchema.method( 'unsubscribe', function( user, callback ) {
219         var id = this._id;
220
221         // mongoose issue #404
222         Course.collection.update( { '_id' : id }, { '$pull' : { 'users' : user } }, function( err ) {
223                 callback( err );
224         });
225 });
226
227 CourseSchema.method( 'delete', function( callback ) {
228   var id = this._id;
229
230   Course.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
231     if (callback) callback( err );
232     Lecture.find( { course: id }, function( err, lectures) {
233       if (lectures.length > 0) {
234         lectures.forEach(function(lecture) {
235           lecture.delete();
236         })
237       }
238     })
239   })
240 });
241
242 var Course = mongoose.model( 'Course', CourseSchema );
243
244 // lectures
245
246 var LectureSchema       = new Schema( {
247         name                                    : { type : String, required : true },
248         date                                    : { type : Date, default: Date.now },
249         live                                    : Boolean,
250   creator       : ObjectId,
251   deleted       : Boolean,
252
253         course                          : ObjectId
254 });
255
256 LectureSchema.method( 'authorize', function( user, cb ) {
257         Course.findById( this.course, function( err, course ) {
258                 if (course) {
259                         course.authorize( user, function( res ) {
260                                 return cb( res );
261                         })
262                 } else {
263                  return cb( false );
264                 }
265         });
266 });
267
268 LectureSchema.method( 'delete', function( callback ) {
269   var id = this._id;
270
271   Lecture.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
272     if (callback) callback( err );
273     Note.find( { lecture : id }, function(err, notes) {
274       notes.forEach(function(note) {
275         note.delete();
276       })
277     })
278     Post.find( { lecture : id }, function(err, posts) {
279       posts.forEach(function(post) {
280         post.delete();
281       })
282     })
283   })
284 });
285
286 var Lecture = mongoose.model( 'Lecture', LectureSchema );
287
288 // notes
289
290 var NoteSchema = new Schema( {
291         name                                    : { type : String, required : true },
292         path                                    : String,
293   public        : Boolean,
294   roID          : String,
295         visits                          : Number,
296   created         : { type : Date, default : Date.now },
297   creator       : ObjectId,
298   deleted       : Boolean,
299
300         lecture                         : ObjectId,
301
302         collaborators : [String]
303 });
304
305 NoteSchema.method( 'authorize', function( user, cb ) {
306         Lecture.findById( this.lecture, function( err, lecture ) {
307                 if (lecture) {
308                         lecture.authorize( user, function( res ) {
309                                 return cb( res );
310                         })
311                 } else {
312                         return cb( false );
313                 }
314         });
315 });
316
317 NoteSchema.method( 'addVisit', function() {
318         var id = this._id;
319
320         Note.collection.update( { '_id' : id }, { '$inc' : { 'visits' : 1 } } );
321 });
322
323 NoteSchema.method( 'delete', function( callback ) {
324   var id = this._id;
325
326   Note.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
327     if (callback) callback( err );
328   })
329 });
330
331 var Note = mongoose.model( 'Note', NoteSchema );
332
333 // comments
334
335 var PostSchema = new Schema({
336   date      : { type : Date, default : Date.now },
337   body      : String,
338   votes     : [String],
339   reports   : [String],
340   public    : Boolean,
341
342   userid    : String, // ObjectId,
343   userName  : String,
344   userAffil : String,
345
346   comments   : Array,
347
348   lecture   : String, // ObjectId
349   deleted   : Boolean
350 })
351
352 PostSchema.method( 'delete', function( callback ) {
353   var id = this._id;
354
355   Post.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
356     if (callback) callback( err );
357   })
358 });
359
360 mongoose.model( 'Post', PostSchema );
361
362 var ArchivedCourse = new Schema({
363   id: Number,
364   instructor: String,
365   section: String,
366   name: String,
367   description: String,
368   subject_id: Number
369 })
370
371 mongoose.model( 'ArchivedCourse', ArchivedCourse )
372
373 var ArchivedNote = new Schema({
374   course_id: Number,
375   topic: String,
376   text: String
377 })
378
379 mongoose.model( 'ArchivedNote', ArchivedNote )
380
381 var ArchivedSubject = new Schema({
382   id: Number,
383   name: String
384 })
385
386 mongoose.model( 'ArchivedSubject', ArchivedSubject )
387
388 module.exports.mongoose = mongoose;