models.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. /* vim: set ts=2: */
  2. // DEPENDENCIES
  3. var crypto = require( 'crypto' );
  4. var mongoose = require( 'mongoose' );
  5. var Schema = mongoose.Schema;
  6. var ObjectId = mongoose.SchemaTypes.ObjectId;
  7. // SUPPORT FUNCTIONS
  8. function salt() {
  9. return Math.round( ( new Date().valueOf() * Math.random() ) ).toString();
  10. }
  11. // MODELS
  12. // user
  13. var UserSchema = new Schema( {
  14. email : { type : String, require: true, index : { unique : true } },
  15. school : String,
  16. name : String,
  17. affil : String,
  18. created : { type : Date, default : Date.now },
  19. hashed : String,
  20. activated : Boolean,
  21. activateCode : String,
  22. resetPassCode : String,
  23. resetPassDate : Date,
  24. salt : String,
  25. session : String,
  26. showName : { 'type' : Boolean, 'default' : true },
  27. admin : { 'type' : Boolean, 'default' : false }
  28. });
  29. UserSchema.virtual( 'sanitized' ).get(function() {
  30. var user = {
  31. email: this.email,
  32. name: this.name,
  33. affil: this.affil,
  34. showName: this.showName,
  35. admin: this.admin
  36. }
  37. return user;
  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. UserSchema.virtual( 'password' )
  48. .set( function( password ) {
  49. this.salt = salt();
  50. this.hashed = this.encrypt( password );
  51. });
  52. UserSchema.virtual( 'isComplete' )
  53. .get( function() {
  54. // build on this as the schema develops
  55. return ( this.name && this.affil && this.hashed );
  56. });
  57. UserSchema.method( 'encrypt', function( password ) {
  58. var hmac = crypto.createHmac( 'sha1', this.salt );
  59. return hmac.update( password ).digest( 'hex' );
  60. });
  61. UserSchema.method( 'authenticate', function( plaintext ) {
  62. return ( this.encrypt( plaintext ) === this.hashed );
  63. });
  64. UserSchema.method('genRandomPassword', function () {
  65. // this function generates the random password, it does not keep or save it.
  66. var plaintext = '';
  67. var len = 8;
  68. var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  69. for (var i = 0; i < len; i++) {
  70. var randomPoz = Math.floor(Math.random() * charSet.length);
  71. plaintext += charSet.substring(randomPoz, randomPoz + 1);
  72. }
  73. return plaintext;
  74. });
  75. UserSchema.method( 'setResetPassCode', function ( code ) {
  76. this.resetPassCode = code;
  77. this.resetPassDate = new Date();
  78. return this.resetPassCode;
  79. });
  80. UserSchema.method( 'canResetPassword', function ( code ) {
  81. // ensure the passCode is valid, matches and the date has not yet expired, lets say 2 weeks for good measure.
  82. var value = false;
  83. var expDate = new Date();
  84. expDate.setDate(expDate.getDate() - 14);
  85. // we have a valid code and date
  86. if (this.resetPassCode != null && this.resetPassDate != null && this.resetPassDate >= expDate && this.resetPassCode == code) {
  87. value = true;
  88. }
  89. return value;
  90. });
  91. UserSchema.method( 'resetPassword', function ( code, newPass1, newPass2) {
  92. // ensure the date has not expired, lets say 2 weeks for good measure.
  93. var success = false;
  94. if (this.canResetPassword(code) && newPass1 != null && newPass1.length > 0 && newPass1 == newPass2) {
  95. this.password = newPass1;
  96. this.resetPassCode = null;
  97. this.resetPassDate = null;
  98. success = true;
  99. }
  100. return success;
  101. });
  102. var User = mongoose.model( 'User', UserSchema );
  103. // schools
  104. var SchoolSchema = new Schema( {
  105. name : { type : String, required : true },
  106. description : String,
  107. url : String,
  108. created : { type : Date, default : Date.now },
  109. hostnames : Array,
  110. users : Array,
  111. courseNum : Number
  112. });
  113. SchoolSchema.virtual( 'sanitized' ).get(function() {
  114. var school = {
  115. _id: this._id,
  116. name: this.name,
  117. description: this.description,
  118. url: this.url,
  119. coursesNum: this.courseNum
  120. }
  121. return school;
  122. })
  123. SchoolSchema.method( 'authorize', function( user, cb ) {
  124. return cb(user.admin || ( this.users.indexOf( user._id ) !== -1 ));
  125. });
  126. var School = mongoose.model( 'School', SchoolSchema );
  127. // harvardcourses
  128. var HarvardCourses = new Schema( {
  129. cat_num : Number, // Catalog numb, unique_togther w/ term
  130. term : String, // FALL or SPRING
  131. bracketed : Boolean,
  132. field : String, // TODO: Add docs
  133. number : Number, // string, int, float, intStringFloat
  134. title : String,
  135. faculty : String, // hash fk to faculty table
  136. description : String,
  137. prerequisites : String,
  138. notes : String,
  139. meetings : String, // TODO: try to auto parse this
  140. building : String, // FIXME: Most == '', this is why we have to update
  141. room : String
  142. });
  143. HarvardCourses.virtual( 'sanitized' ).get(function() {
  144. var hcourse = {
  145. _id : this._id,
  146. title : this.name,
  147. field : this.field,
  148. number : this.number,
  149. desc : this.description,
  150. meetings: this.meetings,
  151. building: this.building,
  152. room : this.room,
  153. faculty : this.faculty
  154. }
  155. return hcourse
  156. })
  157. // courses
  158. var CourseSchema = new Schema( {
  159. name : { type : String, required : true },
  160. number : String,
  161. description : String,
  162. instructor : ObjectId,
  163. subject : String,
  164. department : String,
  165. // courses are tied to one school
  166. school : ObjectId,
  167. // XXX: room for additional resources
  168. created : { type : Date, default : Date.now },
  169. creator : ObjectId,
  170. deleted : Boolean,
  171. // many users may subscribe to a course
  172. users : Array,
  173. lectureNum : Number
  174. });
  175. CourseSchema.virtual( 'sanitized' ).get(function() {
  176. var course = {
  177. _id: this._id,
  178. name: this.name,
  179. number: this.number || 'None',
  180. description: this.description || 'None',
  181. subject: this.subject || 'None',
  182. department: this.department || 'None',
  183. lectureNum: this.lecturenum
  184. }
  185. return course;
  186. });
  187. CourseSchema.virtual( 'displayName' )
  188. .get( function() {
  189. if( this.number ) {
  190. return this.number + ': ' + this.name;
  191. } else {
  192. return this.name;
  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. CourseSchema.method( 'subscribed', function( user ) {
  205. return ( this.users.indexOf( user ) > -1 ) ;
  206. });
  207. CourseSchema.method( 'subscribe', function( user, callback ) {
  208. var id = this._id;
  209. // mongoose issue #404
  210. Course.collection.update( { '_id' : id }, { '$addToSet' : { 'users' : user } }, function( err ) {
  211. callback( err );
  212. });
  213. });
  214. CourseSchema.method( 'unsubscribe', function( user, callback ) {
  215. var id = this._id;
  216. // mongoose issue #404
  217. Course.collection.update( { '_id' : id }, { '$pull' : { 'users' : user } }, function( err ) {
  218. callback( err );
  219. });
  220. });
  221. CourseSchema.method( 'delete', function( callback ) {
  222. var id = this._id;
  223. Course.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
  224. if (callback) callback( err );
  225. Lecture.find( { course: id }, function( err, lectures) {
  226. if (lectures.length > 0) {
  227. lectures.forEach(function(lecture) {
  228. lecture.delete();
  229. })
  230. }
  231. })
  232. })
  233. });
  234. var Course = mongoose.model( 'Course', CourseSchema );
  235. // lectures
  236. var LectureSchema = new Schema( {
  237. name : { type : String, required : true },
  238. date : { type : Date, default: Date.now },
  239. live : Boolean,
  240. creator : ObjectId,
  241. deleted : Boolean,
  242. course : ObjectId
  243. });
  244. LectureSchema.virtual( 'sanitized' ).get(function() {
  245. var lecture = {
  246. _id: this._id,
  247. name: this.name,
  248. date: this.date,
  249. live: this.live
  250. }
  251. return lecture;
  252. })
  253. LectureSchema.method( 'authorize', function( user, cb ) {
  254. Course.findById( this.course, function( err, course ) {
  255. if (course) {
  256. course.authorize( user, function( res ) {
  257. return cb( res );
  258. })
  259. } else {
  260. return cb( false );
  261. }
  262. });
  263. });
  264. LectureSchema.method( 'delete', function( callback ) {
  265. var id = this._id;
  266. Lecture.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
  267. if (callback) callback( err );
  268. Note.find( { lecture : id }, function(err, notes) {
  269. notes.forEach(function(note) {
  270. note.delete();
  271. })
  272. })
  273. Post.find( { lecture : id }, function(err, posts) {
  274. posts.forEach(function(post) {
  275. post.delete();
  276. })
  277. })
  278. })
  279. });
  280. var Lecture = mongoose.model( 'Lecture', LectureSchema );
  281. // notes
  282. var NoteSchema = new Schema( {
  283. name : { type : String, required : true },
  284. path : String,
  285. public : Boolean,
  286. roID : String,
  287. visits : Number,
  288. created : { type : Date, default : Date.now },
  289. creator : ObjectId,
  290. deleted : Boolean,
  291. lecture : ObjectId,
  292. collaborators : [String]
  293. });
  294. NoteSchema.virtual( 'sanitized').get(function() {
  295. var note = {
  296. _id: this._id,
  297. name: this.name,
  298. path: this.path,
  299. public: this.public,
  300. roID: this.roID,
  301. visits: this.visits
  302. }
  303. return note;
  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. NoteSchema.method( 'addVisit', function() {
  317. var id = this._id;
  318. Note.collection.update( { '_id' : id }, { '$inc' : { 'visits' : 1 } } );
  319. });
  320. NoteSchema.method( 'delete', function( callback ) {
  321. var id = this._id;
  322. Note.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
  323. if (callback) callback( err );
  324. })
  325. });
  326. var Note = mongoose.model( 'Note', NoteSchema );
  327. // comments
  328. var PostSchema = new Schema({
  329. date : { type : Date, default : Date.now },
  330. body : String,
  331. votes : [String],
  332. reports : [String],
  333. public : Boolean,
  334. userid : String, // ObjectId,
  335. userName : String,
  336. userAffil : String,
  337. comments : Array,
  338. lecture : String, // ObjectId
  339. deleted : Boolean
  340. })
  341. PostSchema.method( 'delete', function( callback ) {
  342. var id = this._id;
  343. Post.collection.update( { '_id' : id }, { '$set' : { 'deleted' : true } }, function( err ) {
  344. if (callback) callback( err );
  345. })
  346. });
  347. mongoose.model( 'Post', PostSchema );
  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. mongoose.model( 'ArchivedCourse', ArchivedCourse )
  357. var ArchivedNote = new Schema({
  358. course_id: Number,
  359. topic: String,
  360. text: String
  361. })
  362. ArchivedNote.virtual( 'sanitized' ).get(function() {
  363. var note = {
  364. _id: this._id,
  365. topic: this.topic === '' ? (this.text.replace(/(<(.|\n)*?>)|[\r\n\t]*/g, '')).substr(0, 15) + '...' : this.topic
  366. }
  367. return note;
  368. })
  369. mongoose.model( 'ArchivedNote', ArchivedNote )
  370. var ArchivedSubject = new Schema({
  371. id: Number,
  372. name: String
  373. })
  374. mongoose.model( 'ArchivedSubject', ArchivedSubject )
  375. module.exports.mongoose = mongoose;