diff --git a/hourglass/client/main/main.js b/hourglass/client/main/main.js index 226592d..9a042a5 100644 --- a/hourglass/client/main/main.js +++ b/hourglass/client/main/main.js @@ -984,4 +984,4 @@ startDragula = function() { } Meteor.call("reorderClasses", final); }); -} +}; diff --git a/hourglass/client/menus/menus.js b/hourglass/client/menus/menus.js index 09020c2..9cf6eb8 100644 --- a/hourglass/client/menus/menus.js +++ b/hourglass/client/menus/menus.js @@ -1,16 +1,25 @@ +/* jshint esversion: 6 */ Session.set("settingMode", "manageClass"); Session.set("classInfoMode", "general"); Session.set("notsearching", true); // If user isn't searching Session.set("noclass", null); // If user doesn't have classes. Session.set("notfound", null); // If no results for autocomplete. -var filterOpen = [false, true, true, true, true]; -var sidebarMode = [null,null]; +var filterOpen = [false, true, true, true, true]; +var sidebarMode = [null, null]; -Template.sidebarMenuPlate.rendered = function(){$(".menuWrapper").slideDown(300);} -Template.sidebarOptionPlate.rendered = function(){$(".menuWrapper").slideDown(300);} -Template.sidebarRequestPlate.rendered = function(){$(".menuWrapper").slideDown(300);} -Template.sidebarCreatePlate.rendered = function(){$(".menuWrapper").slideDown(300);} +Template.sidebarMenuPlate.rendered = function() { + $(".menuWrapper").slideDown(300); +}; +Template.sidebarOptionPlate.rendered = function() { + $(".menuWrapper").slideDown(300); +}; +Template.sidebarRequestPlate.rendered = function() { + $(".menuWrapper").slideDown(300); +}; +Template.sidebarCreatePlate.rendered = function() { + $(".menuWrapper").slideDown(300); +}; Template.sidebarMenuPlate.helpers({ modeStatus(status) { // Color status of display modes. @@ -30,24 +39,24 @@ Template.sidebarMenuPlate.helpers({ }, filterOn() { return Session.get("classDisp").length !== 0 || Session.get("typeFilter").length !== 0; - }, + } }); Template.sidebarMenuPlate.events({ 'click .classes' () { // Click classes mode button. if (Session.equals("mode", "classes")) return; - toggleToMode("classes") + toggleToMode("classes"); setTimeout(startDragula, 500); toggleToSidebar(false); }, 'click .calendar' () { // Click calendar mode button. - if (Session.equals("mode", "calendar")) return; + if (Session.equals("mode", "calendar")) return; toggleToMode("calendar"); toggleToSidebar(false); }, 'click #filterHead' (event) { - if(event.target.id === "disableFilter") return; - if(!filterOpen[0]) { + if (event.target.id === "disableFilter") return; + if (!filterOpen[0]) { $("#filterWrapper").slideDown(300); } else { $("#filterWrapper").slideUp(300); @@ -55,7 +64,7 @@ Template.sidebarMenuPlate.events({ filterOpen[0] = !filterOpen[0]; }, 'click #typeFilterWrapper' () { - if(!filterOpen[1]) { + if (!filterOpen[1]) { $("#classFilterHolder").slideDown(300); } else { $("#classFilterHolder").slideUp(300); @@ -63,7 +72,7 @@ Template.sidebarMenuPlate.events({ filterOpen[1] = !filterOpen[1]; }, 'click #classFilterWrapper' () { - if(!filterOpen[2]) { + if (!filterOpen[2]) { $("#classListHolder").slideDown(300); } else { $("#classListHolder").slideUp(300); @@ -72,7 +81,7 @@ Template.sidebarMenuPlate.events({ }, // CLASS FILTERS 'click .sideClass' (event) { // Click class list in sidebar. - var classid = event.target.getAttribute("classid") + var classid = event.target.getAttribute("classid"); var array = Session.get("classDisp"); if (array.indexOf(classid) !== -1) { array.splice(array.indexOf(classid), 1); @@ -156,7 +165,7 @@ Template.sidebarOptionPlate.events({ toggleToMode("createClass"); }, 'click #settingMode' () { - if(!filterOpen[3]) { + if (!filterOpen[3]) { $("#settingModeWrapper").slideDown(300); } else { $("#settingModeWrapper").slideUp(300); @@ -164,7 +173,7 @@ Template.sidebarOptionPlate.events({ filterOpen[3] = !filterOpen[3]; }, 'click #preferencesWrapper' () { - if(!filterOpen[4]) { + if (!filterOpen[4]) { $("#prefCont").slideDown(300); } else { $("#prefCont").slideUp(300); @@ -181,34 +190,47 @@ Template.sidebarCreatePlate.events({ Session.set("currentWork", newSetting); $(".overlay").fadeIn(250); } -}) +}); Template.registerHelper("classInfo", (info) => { - var thisClass = classes.findOne({_id:Session.get("classInfo")}); + var thisClass = classes.findOne({ + _id: Session.get("classInfo") + }); var isYou = Session.equals("classInfo", Meteor.userId()); switch (info) { case "name": return (isYou) ? "Personal" : thisClass.name; case "teacher": - return (isYou) ? "None": thisClass.teacher; + return (isYou) ? "None" : thisClass.teacher; case "hour": return (isYou) ? "None" : thisClass.hour; case "category": - return (isYou) ? "Personal" : thisClass.category[0].toUpperCase() + thisClass.category.slice(1); + return (isYou) ? "Personal" : thisClass.category[0].toUpperCase() + thisClass.category.slice(1); case "privacy": return (isYou) ? true : thisClass.privacy; case "admin": - return Meteor.users.findOne({_id: (isYou) ? Meteor.userId() : thisClass.admin}); + return Meteor.users.findOne({ + _id: (isYou) ? Meteor.userId() : thisClass.admin + }); case "code": - if(isYou) return {exists: false} - return (isYou || Meteor.userId() !== this.admin) ? {exists: false} : {exists: true, code: Meteor.call('getCode', thisClass._id)}; + if (isYou) return { + exists: false + }; + return (isYou || Meteor.userId() !== this.admin) ? { + exists: false + } : { + exists: true, + code: Meteor.call('getCode', thisClass._id) + }; case "mine": return (isYou) ? true : Meteor.userId() === thisClass.admin; case "moderators": if (isYou || thisClass.moderators.length === 0) return []; var moderators = []; thisClass.moderators.forEach(function(ele) { - var array = Meteor.users.findOne({_id: ele}); + var array = Meteor.users.findOne({ + _id: ele + }); array.delete = true; moderators.push(array); }); @@ -217,7 +239,9 @@ Template.registerHelper("classInfo", (info) => { if (isYou || thisClass.banned.length === 0) return []; var banned = []; thisClass.banned.forEach(function(ele) { - var array = Meteor.users.findOne({_id: ele}); + var array = Meteor.users.findOne({ + _id: ele + }); array.delete = true; banned.push(array); }); @@ -226,7 +250,9 @@ Template.registerHelper("classInfo", (info) => { if (isYou || thisClass.subscribers.length === 0) return []; var subscribers = []; thisClass.subscribers.forEach(function(ele) { - subscribers.push(Meteor.users.findOne({_id: ele})); + subscribers.push(Meteor.users.findOne({ + _id: ele + })); }); return subscribers; case "personal": @@ -235,7 +261,7 @@ Template.registerHelper("classInfo", (info) => { }); Template.registerHelper("classInfoMode", (mode, check) => { - if(typeof check === "string") return Session.equals("classInfoMode",mode); + if (typeof check === "string") return Session.equals("classInfoMode", mode); return (Session.equals("classInfoMode", mode)) ? Session.get("user").preferences.theme.modeHighlight + ";background-color:rgba(0,0,0,0.1);" : "rgba(0,0,0,0)"; }); @@ -246,15 +272,15 @@ Template.registerHelper("classSelected", () => { Template.manageClass.events({ 'click .classBox' (event) { var classId = event.target.getAttribute("classid"); - if(Session.equals("classInfo",classId)) return; - toggleToClassInfo(classId); + if (Session.equals("classInfo", classId)) return; + toggleToClassInfo(classId); }, 'click #classInfoModeWrapper span:first-child' () { - if(Session.equals("classInfoMode","general")) return; + if (Session.equals("classInfoMode", "general")) return; toggleToClassInfoMode("general"); }, 'click #classInfoModeWrapper span:last-child' () { - if(Session.equals("classInfoMode","users")) return; + if (Session.equals("classInfoMode", "users")) return; toggleToClassInfoMode("users"); }, 'click .infoCard .fa-pencil-square-o' () { @@ -267,7 +293,7 @@ Template.manageClass.events({ var user = Meteor.users.findOne({ "services.google.email": value }); - if(!user) { + if (!user) { sAlert.error("Invalid email!", { effect: 'stackslide', position: 'top', @@ -298,21 +324,34 @@ Template.manageClass.events({ }); Template.joinClass.helpers({ - classes() { // Loads all of the possible classes ( Limit of twenty shown ) ( Sorts by class size ) ( Only your school) + classes() { // Loads all of the possible classes ( Limit of twenty shown ) ( Sorts by class size ) ( Only your school) var array = classes.find({ - status: {$eq: true}, - privacy: {$eq: false}, - _id: {$nin: Session.get("user").classes}, - school: {$eq: Session.get("user").school} - }, - {sort: {subscribers: -1}}, - {limit: 20} - ).fetch(); + status: { + $eq: true + }, + privacy: { + $eq: false + }, + _id: { + $nin: Session.get("user").classes + }, + school: { + $eq: Session.get("user").school + } + }, { + sort: { + subscribers: -1 + } + }, { + limit: 20 + }).fetch(); for (var i = 0; i < array.length; i++) { array[i].join = true; array[i].subscribers = array[i].subscribers.length; - array[i].teachershort = array[i].teacher.split(" ").slice(1).reduce(function(a,b) { return a+ " " + b;}); + array[i].teachershort = array[i].teacher.split(" ").slice(1).reduce(function(a, b) { + return a + " " + b; + }); } if (array.length === 0) { Session.set("noclass", true); @@ -356,21 +395,21 @@ Template.joinClass.helpers({ }, notfound() { // Returns if autocomplete has no results. return Session.get("notfound"); - }, + } }); Template.joinClass.events({ 'click .classBox' (event) { var classId = event.target.getAttribute("classid"); - if(Session.equals("classInfo",classId)) return; + if (Session.equals("classInfo", classId)) return; toggleToClassInfo(classId); }, 'click #classInfoModeWrapper span:first-child' () { - if(Session.equals("classInfoMode","general")) return; + if (Session.equals("classInfoMode", "general")) return; toggleToClassInfoMode("general"); }, 'click #classInfoModeWrapper span:last-child' () { - if(Session.equals("classInfoMode","users")) return; + if (Session.equals("classInfoMode", "users")) return; toggleToClassInfoMode("users"); }, 'input #classSearch' (event) { // Auto-complete updater @@ -400,7 +439,7 @@ Template.joinClass.events({ }); } Session.set("autocompleteDivs", divs.sort(function(a, b) { - return b.subscribers - a.subscribers + return b.subscribers - a.subscribers; })); } catch (err) {} }, @@ -408,15 +447,15 @@ Template.joinClass.events({ serverData = [event.target.parentNode.getAttribute("classid"), ""]; confirm = "joinClass"; Session.set("confirmText", "Join this class?"); - $("#confirmOverlay").fadeIn(250); + $("#confirmOverlay").fadeIn(250); }, 'click #private' () { - $("#privateCode").css('display','inline-block'); - var input = document.getElementById("privateCode"); - input.focus(); - if(input.value === "") return; - Meteor.call("joinPrivateClass", input.value, function(error, result) { - if(result) { + $("#privateCode").css('display', 'inline-block'); + var input = document.getElementById("privateCode"); + input.focus(); + if (input.value === "") return; + Meteor.call("joinPrivateClass", input.value, function(error, result) { + if (result) { sAlert.success("Joined!", { effect: 'genie', position: 'bottom-right', @@ -458,26 +497,29 @@ Template.createClass.helpers({ template: Template.teacherList }] }; - }, + } }); Template.createClass.events({ 'click #creSubmit' () { var inputs = document.getElementsByClassName("creInput"); - var values = new Object; - var required = ["school","name","privacy","category"]; + var values = {}; + var required = ["school", "name", "privacy", "category"]; var no = []; - for(var i = 0; i < inputs.length; i++) { + for (var i = 0; i < inputs.length; i++) { var val = inputs[i].value; var where = inputs[i].getAttribute("form"); - if(val === "" && _.contains(required, where)) { + if (val === "" && _.contains(required, where)) { no.push(where); } values[where] = val; } - - if(no.length !== 0) { // Check missing fields. - sAlert.error("Missing " + no.reduce(function(a,b) { return (b === no[no.length-1]) ? a + ", and " + b : a + ", " + b }), { + console.log(values); + console.log(no); + if (no.length > 0) { // Check missing fields. + sAlert.error("Missing " + no.reduce(function(a, b) { + return (b === no[no.length - 1]) ? a + ", and " + b : a + ", " + b; + }), { effect: 'stackslide', position: 'top', timeout: 3000 @@ -489,9 +531,10 @@ Template.createClass.events({ values.category.toLowerCase(); values.code = ""; serverData = values; - if(!teachers.findOne({name: values["teacher"]})) { - Meteor.call("createTeacher", values["teacher"], function(error,result) { - console.log(error); + if (!teachers.findOne({ + name: values.teacher + })) { + Meteor.call("createTeacher", values.teacher, values.school, function(error, result) { if (error !== undefined) { sAlert.error(error.message, { effect: 'stackslide', @@ -504,7 +547,7 @@ Template.createClass.events({ } else { sendData("createClass"); } - } + } }); Template.classInfoUsers.events({ @@ -516,7 +559,7 @@ Template.classInfoUsers.events({ var user = Meteor.users.findOne({ "services.google.email": value }); - if(!user) { + if (!user) { sAlert.error("Invalid email!", { effect: 'stackslide', position: 'top', @@ -534,10 +577,12 @@ Template.classInfoUsers.events({ input.value = ""; }, 'click .userDisp .fa' (event) { - var outerInput = event.target.parentNode.parentNode.parentNode.parentNode.childNodes[1] + var outerInput = event.target.parentNode.parentNode.parentNode.parentNode.childNodes[1]; var type = outerInput.childNodes[6].getAttribute("user"); var userid = event.target.parentNode.parentNode.getAttribute("userid"); - if(!Meteor.users.findOne({_id: userid})) { + if (!Meteor.users.findOne({ + _id: userid + })) { sAlert.error("Stop hacking, reload the page.", { effect: 'stackslide', position: 'top', @@ -570,29 +615,37 @@ Template.classInfoCode.events({ toggleToMode = function(mode) { $("#mainBody").fadeOut(250, function() { - (Session.equals("sidebarMode", "option")) ? Session.set("settingMode", mode) : Session.set("mode",mode); + (Session.equals("sidebarMode", "option")) ? Session.set("settingMode", mode): Session.set("mode", mode); Session.set("classInfo", null); $("#mainBody").fadeIn(250); }); -} +}; toggleToSidebar = function(sidebar) { try { $("#backgroundOverlay").fadeOut(250); - } catch(err) {} - if(Session.equals("sidebarMode", sidebar) || !sidebar) { - $("#menuContainer").hide("slide", {direction: "left"}, 250); - $("#divCenter").stop().animate({left: '6vh'}, 250, function() { + } catch (err) {} + if (Session.equals("sidebarMode", sidebar) || !sidebar) { + $("#menuContainer").hide("slide", { + direction: "left" + }, 250); + $("#divCenter").stop().animate({ + left: '6vh' + }, 250, function() { Session.set("sidebarMode", ""); }); } else { - $("#menuContainer").show("slide", {direction: "left"}, 250); - $("#divCenter").stop().animate({left: '36vh'}, 250); + $("#menuContainer").show("slide", { + direction: "left" + }, 250); + $("#divCenter").stop().animate({ + left: '36vh' + }, 250); $(".menuWrapper").fadeOut(200, function() { Session.set("sidebarMode", sidebar); }); } -} +}; toggleToClassInfo = function(classId) { $("#changeAdminWrapper").fadeOut(250); @@ -601,11 +654,11 @@ toggleToClassInfo = function(classId) { Session.set("classInfoMode", "general"); $(this).fadeIn(250); }); -} +}; toggleToClassInfoMode = function(mode) { $("#infoClassCont").fadeOut(250, function() { Session.set("classInfoMode", mode); $(this).fadeIn(250); - }); -} + }); +}; diff --git a/hourglass/client/profile/profile.css b/hourglass/client/profile/profile.css index ab7f9e4..83e581d 100644 --- a/hourglass/client/profile/profile.css +++ b/hourglass/client/profile/profile.css @@ -2,7 +2,7 @@ width: 100%; height: 100%; - background-color: #DBDBDB; + background-color: #282933; position: absolute; top: 0; @@ -15,16 +15,17 @@ #newUserWrapper { width: 25%; - padding: 3%; + padding: 2.5%; margin: auto; - background-color: rgba(255,255,255,0.3); + background-color: rgba(255,255,255,0.2); } #newUserWrapper .formDiv { width: 100%; margin: 0; padding: 0; + background-color: transparent; } .opTitle { @@ -53,4 +54,3 @@ #basicNext:hover { background-color: rgba(0,0,0,0.1); } - diff --git a/hourglass/client/profile/profile.js b/hourglass/client/profile/profile.js index 7c90fcc..54145d8 100644 --- a/hourglass/client/profile/profile.js +++ b/hourglass/client/profile/profile.js @@ -16,7 +16,6 @@ Template.profile.helpers({ Template.profile.events({ 'click' (event) { // Closes respective divs when clicking outside of them. Order matters. - console.log("asdf"); var e = event.target.className; if(modifyingInput !== null && event.target !== document.getElementById(modifyingInput)) { diff --git a/hourglass/collections/main.js b/hourglass/collections/main.js index 8aff872..4456ea9 100644 --- a/hourglass/collections/main.js +++ b/hourglass/collections/main.js @@ -10,9 +10,11 @@ schools.schema = new SimpleSchema({ }); teachers.schema = new SimpleSchema({ - name: {type: String}, - school: {type: String} -}) + name: {type: String}, + school: {type: String}, + creator: {type: String, optional: true} + +}); classes.schema = new SimpleSchema({ school: {type: String}, @@ -59,7 +61,13 @@ userSchema = new SimpleSchema({ 'profile.school': {type: String, label: "School"}, 'services.google.email': {type: String, label: "Email"}, 'services.google.picture': {type: String, label: "Icon URL"}, - 'profile.classes': {type: [String], label: "Classes"} + 'profile.classes': {type: [String], label: "Classes"}, + 'profile.complete': {type: Boolean} +}); + +teachers.schema = new SimpleSchema({ + name: {type: String}, + school: {type: String} }); schools.attachSchema(schools.schema); @@ -67,3 +75,4 @@ teachers.attachSchema(teachers.schema); classes.attachSchema(classes.schema); work.attachSchema(work.schema); requests.attachSchema(requests.schema); +teachers.attachSchema(teachers.schema); diff --git a/hourglass/lib/router.js b/hourglass/lib/router.js index 5456e8f..a35cb02 100644 --- a/hourglass/lib/router.js +++ b/hourglass/lib/router.js @@ -1,6 +1,19 @@ +function completeProfile() { + if (Meteor.user().profile.school && + Meteor.user().profile.grade && + Meteor.user().profile.classes.length > 1 && + Meteor.user().profile.complete) { + return true; + } else { + return false; + } +} + Router.route('/', { waitOn: function() { - if (!Meteor.userId()) { + if (!Meteor.userId() || !completeProfile()) { + console.log("hello") + console.log(completeProfile()); this.redirect('/login'); } else { return [ @@ -28,7 +41,7 @@ Router.route('/login', { action: function() { if (!Meteor.userId()) { this.render("login"); - } else if (!Meteor.user().profile.school) { + } else if (!completeProfile()) { Session.set("user", Meteor.user().profile); this.redirect('/profile'); } else { @@ -40,7 +53,7 @@ Router.route('/login', { Router.route('/profile', { waitOn: function() { - if (!Meteor.userId()) { + if (!Meteor.userId() || completeProfile()) { this.redirect('/login'); } else { return [ diff --git a/hourglass/server/main.js b/hourglass/server/main.js index 31b4e8c..433e6db 100644 --- a/hourglass/server/main.js +++ b/hourglass/server/main.js @@ -26,8 +26,8 @@ Meteor.publish('schools', function() { }); Meteor.publish('teachers', function() { - return teachers.find(); -}) + return teachers.find({}, {fields: {name: 1, school: 1}}); +}); // Returns the code for classes (for debug) @@ -129,9 +129,16 @@ Meteor.publish('users', function() { } }); + // Allows only superadmins to edit collections from client Security.permit(['insert', 'update', 'remove']).collections([schools, classes, work]).ifHasRole('superadmin'); +Accounts.validateLoginAttempt(function(info) { + var user = info.user; + if(user.banned) throw new Meteor.Error(403, 'You are banned'); + return true; +}); + var errors = [ "Success.", // 0 @@ -153,11 +160,11 @@ var errors = [ ["trivial", "This request is too long."], ["trivial", "Not a valid work type"], ["unauthorized", "This class has not been approved yet"], - - ["unauthorized", "Sorry, you are not authorized to complete this action."], - ["other", "Error could not be processed"], ["matching", "This teacher already exists!"], - ["trivial", "Please put the full name!"] + ["trivial", "Please put the full name!"], // 20 + + ["unauthorized", "Sorry, you are not authorized to complete this action."], // n - 2 + ["other", "Error could not be processed"] // n - 1 ]; function securityCheck(checklist, input) { @@ -271,13 +278,17 @@ function securityCheck(checklist, input) { case 25: if (Meteor.userId() === null) error = errors.length - 1; break; - // Teacher already exists + // New Teacher doesn't already exist case 26: - if (teachers.findOne({name: {$eq: input.teacher}, school: {$eq: input.school}})) error = 21; + if (teachers.find({name: input.teachername, school: input.school}).fetch().length > 0) error = 19; + break; + // Not banning admin + case 27: + if (Roles.userIsInRole(input.userId, ['superadmin', 'admin'])) error = errors.length - 2; break; // Incorrect teacher format - case 27: - if(input.split(" ").length < 2) error = 22; + case 28: + if (input.teachername.split(" ").length < 2) error = 20; break; } results.push(error); @@ -330,18 +341,6 @@ Meteor.methods({ throw new Meteor.Error(errors[security]); } }, - 'createTeacher': function(name) { - teachers.schema.validate({name: name}); - var security = securityCheck([26, 27, true], - name); - if(!security) { - teachers.insert({ - name: name - }); - } else { - throw new Meteor.Error(errors[security]); - } - }, // Class Functions 'createClass': function(input) { classes.schema.validate(input); @@ -609,7 +608,8 @@ Meteor.methods({ "description": change.description, "banner": change.banner, "preferences": change.preferences, - "name": current.name + "name": current.name, + "complete": current.complete }; if (current.description && current.description.length > 50) { current.description = current.description.slice(0, 50); @@ -807,5 +807,34 @@ Meteor.methods({ } else { throw new Meteor.Error(errors[security]); } + }, + 'createTeacher': function(teacherName, schoolName) { + teachers.schema.validate({name: teacherName, school: schoolName}); + var security = securityCheck([26, 28, 3, true], {teachername: teacherName, school: schoolName}); + if (!security) { + teachers.insert({ + name: teacherName, + school: schoolName, + creator: Meteor.userId() + }); + } else { + throw new Meteor.Error(errors[security]); + } + }, + 'ban': function(studentId) { + var security = securityCheck([1, 27, true], {userId: studentId}); + if (!security) { + Meteor.users.update({_id: studentId}, {$set: {banned: true}}); + } else { + throw new Meteor.Error(errors[security]); + } + }, + 'unban': function(studentId) { + var security = securityCheck([1, true], {userId: studentId}); + if (!security) { + Meteor.users.update({_id: studentId}, {$set: {banned: false}}); + } else { + throw new Meteor.Error(errors[security]); + } } });