From 941510c9312ae5adf2656d8f0e8bdb044272f5ba Mon Sep 17 00:00:00 2001 From: Yaman Qalieh Date: Mon, 7 Nov 2016 21:34:30 -0500 Subject: [PATCH 1/4] teacher collection --- hourglass/collections/main.js | 9 ++++++++- hourglass/server/main.js | 21 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/hourglass/collections/main.js b/hourglass/collections/main.js index 7cfa5be..697e2c6 100644 --- a/hourglass/collections/main.js +++ b/hourglass/collections/main.js @@ -3,9 +3,10 @@ classes = new Mongo.Collection("Classes"); work = new Mongo.Collection("Work"); requests = new Mongo.Collection("Requests"); admins = Meteor.users; +teachers = new Mongo.Collection("Teachers"); schools.schema = new SimpleSchema({ - name: {type: String}, + name: {type: String} }); classes.schema = new SimpleSchema({ @@ -56,7 +57,13 @@ userSchema = new SimpleSchema({ 'profile.classes': {type: [String], label: "Classes"} }); +teachers.schema = new SimpleSchema({ + name: {type: String}, + school: {type: String} +}); + schools.attachSchema(schools.schema); classes.attachSchema(classes.schema); work.attachSchema(work.schema); requests.attachSchema(requests.schema); +teachers.attachSchema(teachers.schema); diff --git a/hourglass/server/main.js b/hourglass/server/main.js index db1ec44..de66d24 100644 --- a/hourglass/server/main.js +++ b/hourglass/server/main.js @@ -149,9 +149,10 @@ var errors = [ ["trivial", "This request is too long."], ["trivial", "Not a valid work type"], ["unauthorized", "This class has not been approved yet"], + ["matching", "This teacher already exists"], - ["unauthorized", "Sorry, you are not authorized to complete this action."], - ["other", "Error could not be processed"] + ["unauthorized", "Sorry, you are not authorized to complete this action."], // n - 2 + ["other", "Error could not be processed"] // n - 1 ]; function securityCheck(checklist, input) { @@ -182,7 +183,6 @@ function securityCheck(checklist, input) { case 3: if (!schools.findOne({name: input.school})) error = 2; break; - // TODO: teachers with same name // Duplicate classes case 4: if (classes.findOne({school: input.school, status: true, privacy: false, teacher: input.teacher, hour: input.hour}) || (input.teacher === "" && input.hour === "")) error = 3; @@ -266,6 +266,10 @@ function securityCheck(checklist, input) { case 25: if (Meteor.userId() === null) error = errors.length - 1; break; + // New Teacher doesn't already exist + case 26: + if (teachers.find({name: input.teacherName, school: input.school}).fetch().length > 0) error = 19; + break; } results.push(error); } @@ -782,5 +786,16 @@ Meteor.methods({ } else { throw new Meteor.Error(errors[security]); } + }, + 'createTeacher': function(teacherName, schoolName) { + var security = securityCheck([26, 3, true], {teachername: teacherName, school: schoolName}); + if (!security) { + teachers.insert({ + name: teacherName, + school: schoolName + }); + } else { + throw new Meteor.Error(errors[security]); + } } }); From 7e1c11dcec4e4a406b3c856bacf24edb909ebdf2 Mon Sep 17 00:00:00 2001 From: Yaman Qalieh Date: Fri, 18 Nov 2016 19:54:39 -0500 Subject: [PATCH 2/4] add banning --- hourglass/server/main.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/hourglass/server/main.js b/hourglass/server/main.js index de66d24..efa059c 100644 --- a/hourglass/server/main.js +++ b/hourglass/server/main.js @@ -128,6 +128,13 @@ 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.isBanned) throw new Meteor.Error(403, 'You are banned'); + +}); + var errors = [ "Success.", // 0 @@ -270,6 +277,10 @@ function securityCheck(checklist, input) { case 26: 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; } results.push(error); } @@ -797,5 +808,21 @@ Meteor.methods({ } 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]); + } } }); From c3d2f8252d735e17a14d0f83bbb66a4810c537f3 Mon Sep 17 00:00:00 2001 From: Yaman Qalieh Date: Fri, 18 Nov 2016 20:57:40 -0500 Subject: [PATCH 3/4] fix banned bug --- hourglass/client/menus/menus.js | 178 +++++++++++++++++++++----------- hourglass/server/main.js | 10 +- 2 files changed, 120 insertions(+), 68 deletions(-) diff --git a/hourglass/client/menus/menus.js b/hourglass/client/menus/menus.js index fc7bfa2..9cf6eb8 100644 --- a/hourglass/client/menus/menus.js +++ b/hourglass/client/menus/menus.js @@ -5,13 +5,21 @@ 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. @@ -42,13 +50,13 @@ Template.sidebarMenuPlate.events({ 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); @@ -56,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); @@ -64,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); @@ -157,7 +165,7 @@ Template.sidebarOptionPlate.events({ toggleToMode("createClass"); }, 'click #settingMode' () { - if(!filterOpen[3]) { + if (!filterOpen[3]) { $("#settingModeWrapper").slideDown(300); } else { $("#settingModeWrapper").slideUp(300); @@ -165,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); @@ -185,31 +193,44 @@ Template.sidebarCreatePlate.events({ }); 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); }); @@ -218,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); }); @@ -227,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": @@ -236,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)"; }); @@ -247,15 +272,15 @@ Template.registerHelper("classSelected", () => { Template.manageClass.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"); }, 'click .infoCard .fa-pencil-square-o' () { @@ -268,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', @@ -299,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); @@ -363,15 +401,15 @@ Template.joinClass.helpers({ 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 @@ -409,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', @@ -466,20 +504,22 @@ Template.createClass.events({ 'click #creSubmit' () { var inputs = document.getElementsByClassName("creInput"); var values = {}; - var required = ["school","name","privacy","category"]; + 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; } 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;}), { + 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 @@ -491,8 +531,10 @@ Template.createClass.events({ values.category.toLowerCase(); values.code = ""; serverData = values; - if(!teachers.findOne({name: values.teacher})) { - Meteor.call("createTeacher", values.teacher, values.school, function(error,result) { + 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', @@ -517,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', @@ -538,7 +580,9 @@ Template.classInfoUsers.events({ 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', @@ -571,7 +615,7 @@ 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); }); @@ -580,15 +624,23 @@ toggleToMode = function(mode) { 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); }); diff --git a/hourglass/server/main.js b/hourglass/server/main.js index 9ad9433..d9979f4 100644 --- a/hourglass/server/main.js +++ b/hourglass/server/main.js @@ -133,11 +133,11 @@ 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'); - -// }); +Accounts.validateLoginAttempt(function(info) { + var user = info.user; + if(user.banned) throw new Meteor.Error(403, 'You are banned'); + return true; +}); var errors = [ From 793a7ce03f852ca00b676625832f5f55ec229bf9 Mon Sep 17 00:00:00 2001 From: Yaman Qalieh Date: Sun, 15 Jan 2017 22:00:02 -0500 Subject: [PATCH 4/4] routing + grade + schoolyear of prof page --- hourglass/client/profile/profile.css | 11 + hourglass/client/profile/profile.html | 40 +- hourglass/client/profile/profile.js | 723 +++++++++++++++++++++++++- hourglass/collections/main.js | 3 +- hourglass/lib/router.js | 19 +- hourglass/server/main.js | 3 +- 6 files changed, 782 insertions(+), 17 deletions(-) diff --git a/hourglass/client/profile/profile.css b/hourglass/client/profile/profile.css index e69de29..7b55c75 100644 --- a/hourglass/client/profile/profile.css +++ b/hourglass/client/profile/profile.css @@ -0,0 +1,11 @@ +#profInfo { + margin: auto; + margin-top: 5%; + width: 50%; + padding: 1%; +} + +#schoolnext { + text-align: right; + margin-right: 2%; +} \ No newline at end of file diff --git a/hourglass/client/profile/profile.html b/hourglass/client/profile/profile.html index 3e8c4d3..ed4c83a 100644 --- a/hourglass/client/profile/profile.html +++ b/hourglass/client/profile/profile.html @@ -1,12 +1,42 @@ diff --git a/hourglass/client/profile/profile.js b/hourglass/client/profile/profile.js index e1b73d0..7b70f2d 100644 --- a/hourglass/client/profile/profile.js +++ b/hourglass/client/profile/profile.js @@ -3,19 +3,728 @@ import { Template } from 'meteor/templating'; +var openValues = { + "owned": "-650px", + "priv": "-160px" +}; + +confirm = null; // Sets function to execute after confirmation click. + +// Sets up global variables + +Session.set("profClassTab", "manClass"); // Set default classes card mode to 'Manage Classes.' +Session.set("owned", false); // Status of createdClasses. +Session.set("privClass", false); //Status of joinPrivClass. +Session.set("modifying", null); // Stores current open input. +Session.set("notsearching", true); // If user is searching in search box. +Session.set("autocompleteDivs", null); // Stores returned autocomplete results. +Session.set("confirmText", null); // Stores text for different confirmation functions. +Session.set("selectedClass", null); // Stores selected owned class info. +Session.set("code", null); // If owned class has a code. +Session.set("noclass", null); // If user doesn't have classes. +Session.set("notfound", null); // If no results for autocomplete. + Template.profile.helpers({ - schoolgradenext() { - if(_.contains([null, undefined, ""], Meteor.user().profile.school || - _.contains([null, undefined, ""], Meteor.user().profile.grade))) { - return ""; - } else { - return "disabled"; + /* themeName() { + var vals = _.values(themeColors); + var curtheme = Session.get("user").preferences.theme; + for (var i = 0; i < vals.length; i++) { + if (_.isEqual(vals[i], curtheme)) { + var name = _.keys(themeColors)[i]; + return name.charAt(0).toUpperCase() + name.slice(1); + } + } + return "Custom"; + },*/ + classSettings() { // Returns autocomplete array for classes. + return { + position: "bottom", + limit: 10, + rules: [{ + token: '', + collection: classes, + template: Template.classDisplay, + filter: { + privacy: false, + status: true + }, + selector: (match) => { + regex = new RegExp(match, 'i'); + return { + $or: [{ + 'name': regex + }, { + 'teacher': regex + }, { + 'hour': regex + }] + }; + } + }] + }; + }, + schoolComplete() { // Returns autocomplete array for schools. + return { + position: "bottom", + limit: 6, + rules: [{ + token: '', + collection: schools, + field: 'name', + matchAll: true, + template: Template.schoolList + }] + }; + }, + teacherComplete() { // Returns autocomplete array for teachers. + return { + position: "bottom", + limit: 1, + rules: [{ + token: '', + collection: classes, + field: 'teacher', + template: Template.teacherList + }] + }; + }, + banner() { // Returns banner + return Session.get("user").banner; + }, + avatar() { // Returns avatar + return Meteor.user().services.google.picture; + }, + username() { //Returns current user's username + return Session.get("user").name; + }, + description() { // Returns the current user's description + if (Session.get("user").description !== undefined && Session.get("user").description !== null && Session.get("user").description !== "") return Session.get("user").description; + return "Say something about yourself!"; + }, + school() { // Returns the current user's school's name + if (!_.contains([null, undefined, ""], Session.get("user").school)) return Session.get("user").school; + return "Click here to edit..."; + }, + grade() { // Returns the current user's grade + if (Session.get("user").grade !== undefined && + Session.get("user").grade !== null && + Session.get("user").grade !== "") return (Session.get("user").grade === 0) ? "Faculty" : Session.get("user").grade; + return "Click here to edit..."; + }, + 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(); + + for (var i = 0; i < array.length; i++) { + array[i].subscribers = array[i].subscribers.length; } + if (array.length === 0) { + Session.set("noclass", true); + } else { + Session.set("noclass", false); + } + return array; + }, + ownedStatus() { // Status of createdClasses + if (!Session.get("owned")) return openValues.owned; + return "0px"; + }, + privStatus() { + if (!Session.get("privClass")) return openValues.priv; + return "0px"; + }, + profClassTabColor(status) { // Change this [Supposed to show the current mode that's selected via color] + if (Session.equals("profClassTab", status)) { + return Meteor.user().profile.preferences.theme.modeHighlight; + } else { + return; + } + }, + profClassTab(tab) { // Tells current class + if (Session.equals("profClassTab", tab)) { + return true; + } else { + return false; + } + }, + notsearching() { // Tells whether user is using the searchbox + return Session.get("notsearching"); + }, + autocompleteClasses() { // Returns current auto-completes for classes + return Session.get("autocompleteDivs"); + }, + notfound() { // Returns if autocomplete has no results. + return Session.get("notfound"); + }, + noclass() { // Returns if user has classes. + return Session.get("noclass"); + }, + confirmText() { // Returns respective text for different confirm functions. + return Session.get("confirmText"); + }, + selectedClass(val) { // Returns values for selectedClass + if (Session.equals("selectedClass", null)) return; + return Session.get("selectedClass")[val]; + }, + code() { // Returns if selected class has code. + return Session.get("code"); } }); Template.profile.events({ + 'click' (event) { // Whenever a click happens' + var e = event.target.className; + if (modifyingInput !== null && event.target !== document.getElementById(modifyingInput)) { + if (!(e.includes("optionHolder") || e.includes("optionText"))) { + if (document.getElementById(modifyingInput).className.includes("dropdown")) { + $(".optionHolder") + .fadeOut(250, "linear"); + + $(".selectedOption").removeClass("selectedOption"); + } else { + if (modifyingInput === "description") { + Session.set("restrictText", {}); + $("#" + modifyingInput).css('cursor', 'pointer'); + var newSetting = Session.get("user"); + newSetting[modifyingInput] = document.getElementById(modifyingInput).value; + serverData = newSetting; + sendData("editProfile"); + } + } + modifyingInput = null; + } + } + + }, + // MAIN BUTTONS + 'click #mainpage' () { + if (!Meteor.userId() || _.contains([null, undefined, ""], Meteor.user().profile.school)) { + sAlert.closeAll(); + sAlert.error('Please fill in your profile!', { + effect: 'stackslide', + position: 'top' + }); + } else { + window.location = '/'; + } + }, 'click #schoolnext' () { - // Animation to display class section + if (Meteor.user().profile.school && Meteor.user().profile.grade) { + document.getElementById('schoolnext').style.display = "none"; + document.getElementById('classsection').style.display = ""; + } else { + sAlert.error("Please fill out the fields", { + effect: 'stackslide', + position: 'top' + }); + } + }, + 'click .addClass' () { + if (Session.equals("profClassTab", "addClass")) return; + var functionHolder = document.getElementById("profClassInfoHolder"); + closeDivFade(functionHolder); + var div = document.getElementById("profClasses"); + div.style.height = "50%"; + setTimeout(function() { + Session.set("profClassTab", "addClass"); + div.style.height = "70%"; + openDivFade(functionHolder); + }, 400); + }, + 'click .manageClass' () { + if (Session.equals("profClassTab", "manClass")) return; + var functionHolder = document.getElementById("profClassInfoHolder"); + closeDivFade(functionHolder); + var div = document.getElementById("profClasses"); + div.style.height = "50%"; + setTimeout(function() { + Session.set("profClassTab", "manClass"); + div.style.height = "70%"; + openDivFade(functionHolder); + }, 400); + }, + 'click .createClass' () { + if (Session.equals("profClassTab", "creClass")) return; + var functionHolder = document.getElementById("profClassInfoHolder"); + closeDivFade(functionHolder); + var div = document.getElementById("profClasses"); + div.style.height = "50%"; + setTimeout(function() { + Session.set("profClassTab", "creClass"); + div.style.height = "70%"; + openDivFade(functionHolder); + }, 400); + }, + 'click .classBox' (event) { // When you click on a box that holds class + if (event.target.id === "label" || + Session.equals("profClassTab", "manClass") || + event.target.className.includes("fa-times")) return; + + if (event.target.className !== "classBox") { + var attribute = event.target.parentNode.getAttribute("classid"); + } else { + var attribute = event.target.getAttribute("classid"); + } + var data = [attribute, ""]; + serverData = data; + confirm = "joinClass"; + Session.set("confirmText", "Join class?"); + + openDivFade(document.getElementsByClassName("overlay")[0]); + setTimeout(function() { + document.getElementsByClassName("overlay")[0].style.opacity = "1"; + }, 200); + }, + 'click .owned' (event) { // When you click your own class + if (event.target.id === "label") return; + if (!event.target.className.includes("owned")) { + var attribute = event.target.parentNode.getAttribute("classid"); + } else { + var attribute = event.target.getAttribute("classid"); + } + if (attribute === Meteor.userId()) return; + Session.set("selectedClass", null); + var usertype = ["moderators", "banned"]; + var array = classes.findOne({ + _id: attribute + }); + + for (var i = 0; i < usertype.length; i++) { + var users = array[usertype[i]]; + array[usertype[i]] = []; + for (var j = 0; j < users.length; j++) { + var detailusers = {}; + var user = Meteor.users.findOne({ + _id: users[j] + }); + detailusers._id = user._id; + detailusers.email = user.services.google.email; + detailusers.name = user.profile.name; + array[usertype[i]].push(detailusers); + } + } + + Meteor.call('getCode', attribute, function(err, result) { + array.code = result; + if (result === "None") { + Session.set("code", false); + } else { + Session.set("code", true); + } + + Session.set("selectedClass", array); + Session.set("owned", true); + }); + }, + 'click .classBox .fa-times' (event) { // Leaves a class + var box = event.target.parentNode; + var classid = box.getAttribute("classid"); + serverData = box.getAttribute("classid"); + confirm = "leaveClass"; + Session.set("confirmText", "Leave this class?"); + openDivFade(document.getElementsByClassName("overlay")[0]); + }, + 'click #creSubmit' () { //Submits form data for class + var data = getCreateFormData(); + if (data === null) return; + serverData = data; + confirm = "createClass"; + Session.set("confirmText", "Submit request?"); + + openDivFade(document.getElementsByClassName("overlay")[0]); + }, + 'click #private' (event) { // Joins private class + if (Session.get("privClass")) return; + var input = document.getElementById("privateCode"); + input.className = ""; + input.placeholder = "Enter code here..."; + Session.set("privClass", true); + }, + 'click #privSubmit' () { // Submits private class code + var input = document.getElementById("privateCode"); + var code = input.value; + input.value = ""; + serverData = code; + Meteor.call("joinPrivateClass", code, function(error, result) { + if (result) { + Session.set("privClass", false); + } else { + input.className = "formInvalid"; + input.placeholder = "Invalid code."; + } + }); + }, + // OWNED CLASS BUTTONS + 'click #copy' () { // Copies code for private classes. + if (document.getElementById("code").value === "None") return; + document.getElementById("code").select(); + document.execCommand("copy"); + }, + 'click .userAdder .fa-plus' (event) { // Gives/Removes user privileges + var input = event.target.parentNode.childNodes[3]; + input.placeholder = "1234@abc.xyz"; + input.className.replace(" formInvalid", ""); + var value = input.value; + var classid = document.getElementById("createdClasses").getAttribute("classid"); + input.value = " "; + if (checkUser(value, classid)) { + input.className += " formInvalid"; + input.placeholder = "Not a valid user"; + return; + } + var user = Meteor.users.findOne({ + "services.google.email": value + }); + serverData = [ + user._id, + classid, + event.target.parentNode.childNodes[1].childNodes[0].nodeValue.replace(":", "").toLowerCase(), + true + ]; + sendData("trackUserInClass"); + }, + 'click .userBox .fa-times' (event) { // Removes user from permissions + var box = event.target.parentNode; + serverData = [ + box.getAttribute("userid"), + document.getElementById("createdClasses").getAttribute("classid"), + box.parentNode.parentNode.childNodes[1].childNodes[1].childNodes[0].nodeValue.replace(":", "").toLowerCase(), + false + ]; + sendData("trackUserInClass"); + }, + 'click #deleteClass' () { + serverData = document.getElementById("createdClasses").getAttribute("classid"); + confirm = "deleteClass"; + Session.set("confirmText", "Delete this class?"); + openDivFade(document.getElementsByClassName("overlay")[0]); + }, + 'click #changeAdmin span' (event) { // Click to give ownership of class. + if (Session.get("changeAdmin")) return; + Session.set("changeAdmin", true); + var input = document.createElement("input"); + input.placeholder = "1234@abc.xyz"; + var i = document.createElement("i"); + i.className = "fa fa-exchange"; + i.setAttribute("aria-hidden", "true"); + event.target.parentNode.appendChild(input); + event.target.parentNode.appendChild(i); + }, + 'click .fa-exchange' (event) { //Changes class admin upon confirmation + var input = event.target.parentNode.childNodes[3]; + input.placeholder = "1234@abc.xyz"; + input.className.replace(" formInvalid", ""); + var value = input.value; + var classid = document.getElementById("createdClasses").getAttribute("classid"); + input.value = ""; + if (checkUser(value, classid)) { + input.className += " formInvalid"; + input.placeholder = "Not a valid user"; + return; + } + var user = Meteor.users.findOne({ + "services.google.email": value + }); + serverData = [user._id, classid]; + confirm = "changeAdmin"; + Session.set("confirmText", "Are you really sure?"); + openDivFade(document.getElementsByClassName("overlay")[0]); + document.getElementById("createdClasses").style.marginRight = "-40%"; + }, + // OVERLAY BUTTONS + 'click .fa-check-circle-o' () { // Confirmation Button + sendData(confirm); + closeDivFade(document.getElementsByClassName("overlay")[0]); + if (confirm === "createClass") { + var form = document.getElementsByClassName("creInput"); + for (var i = 0; i < form.length; i++) form[i].value = ""; + } + serverData = null; + confirm = null; + }, + 'click .fa-times-circle-o' () { // Deny Button + closeDivFade(document.getElementsByClassName("overlay")[0]); + serverData = null; + confirm = null; + }, + // INPUT HANDLING + 'focus .clickModify' (event) { + $(".optionHolder") + .fadeOut(250, "linear"); + + if (modifyingInput !== null) { + if (!$("#" + modifyingInput)[0].className.includes("dropdown")) closeInput(modifyingInput); + } + modifyingInput = event.target.id; + if (!$("#" + modifyingInput)[0].className.includes("dropdown")) { + event.target.select(); + event.target.style.cursor = "text"; + } + }, + 'keydown .dropdown' (event) { + event.preventDefault(); + var first = $("#" + modifyingInput).next().children("p:first-child"); + var last = $("#" + modifyingInput).next().children("p:last-child"); + var next = $(".selectedOption").next(); + var prev = $(".selectedOption").prev(); + var lastSel = $(".selectedOption"); + + if (event.keyCode === 38) { + event.preventDefault(); + if (lastSel === undefined) { + last.addClass("selectedOption"); + } else { + if (prev.length === 0) { + last.addClass("selectedOption"); + lastSel.removeClass("selectedOption"); + } else { + prev.addClass("selectedOption"); + lastSel.removeClass("selectedOption"); + } + } + } else if (event.keyCode === 40) { + event.preventDefault(); + if (lastSel === undefined) { + first.addClass("selectedOption"); + last.removeClass("selectedOption"); + } else { + if (next.length === 0) { + first.addClass("selectedOption"); + lastSel.removeClass("selectedOption"); + } else { + next.addClass("selectedOption"); + lastSel.removeClass("selectedOption"); + } + } + } else if (event.keyCode === 13) { + lastSel[0].click(); + } + }, + 'focus .dropdown' (event) { + $(".selectedOption").removeClass("selectedOption"); + + $("#" + modifyingInput).next() + .css('opacity', 0) + .slideDown(300) + .animate({ + opacity: 1 + }, { + queue: false, + duration: 100 + }); + }, + 'click .optionText' (event) { // Click each preferences setting. + var option = event.target.childNodes[0].nodeValue; + var userSettings = ["description", "school", "grade"]; + var newSetting = Session.get("user"); + + if (modifyingInput === "privacy" || modifyingInput === "category") { + document.getElementById(modifyingInput).value = option; + $("#" + modifyingInput).next() + .fadeOut(250, "linear"); + $(".selectedOption").removeClass("selectedOption"); + return; + } + + if (_.contains(userSettings, modifyingInput)) { + newSetting[modifyingInput] = (modifyingInput === "grade") ? parseInt(option) : option; + } else { + newSetting.preferences[modifyingInput] = (function() { + var value = options[modifyingInput].filter(function(entry) { + return option === entry.alias; + })[0].val; + return (modifyingInput === 'theme') ? themeColors[value] : value; + })(); + } + Session.set("user", newSetting); + serverData = Session.get("user"); + sendData("editProfile"); + + $("#" + modifyingInput).next() + .fadeOut(250, "linear"); + + $(".selectedOption").removeClass("selectedOption"); + }, + 'input .restrict' (event) { + var restrict = event.target.maxLength; + var chars = restrict - event.target.value.length; + var newSetting = Session.get("restrictText"); + newSetting[event.target.id] = (chars === restrict) ? "" : (chars.toString() + ((chars === 1) ? " character " : " characters ") + "left"); + newSetting.selected = event.target.id; + Session.set("restrictText", newSetting); + }, + // AUTOCOMPLETE HANDLING + 'input #profClassSearch' (event) { // Auto-complete updater + if (event.target.value.length === 0) { + Session.set("notsearching", true); + } else { + Session.set("notsearching", false); + } + Session.set("autocompleteDivs", null); + var divs = []; + try { + var items = document.getElementsByClassName("-autocomplete-container")[0].childNodes[3].childNodes; + if (items.length === 0) { // If no results. + Session.set("notfound", true); + } else { + Session.set("notfound", false); + } + for (var i = 2; i < items.length; i += 3) { // Iterate through autocomplete div. + var item = items[i].childNodes[3]; + if (Meteor.user().profile.classes.indexOf(item.getAttribute("classid")) !== -1) continue; + divs.push({ + name: item.childNodes[1].childNodes[0].nodeValue, + teacher: item.childNodes[3].childNodes[0].nodeValue, + hour: item.childNodes[5].childNodes[0].nodeValue, + subscribers: Math.floor(item.childNodes[7].childNodes[0].nodeValue.replace(",", "").length / 17), + _id: item.getAttribute("classid") + }); + } + Session.set("autocompleteDivs", divs.sort(function(a, b) { + return b.subscribers - a.subscribers + })); + } catch (err) {} } }); + +function openDivFade(div) { + div.style.display = "block"; + div.style.opacity = "0"; + setTimeout(function() { + div.style.opacity = "1"; + }, 100); +} + +function closeDivFade(div) { + div.style.opacity = "0"; + setTimeout(function() { + div.style.display = "none"; + }, 100); +} + +function sendData(funcName) { + Meteor.call(funcName, serverData, function(err, result) { + if (funcName === "trackUserInClass") { + var selectedClass = Session.get("selectedClass"); + var array = classes.findOne({ + _id: selectedClass._id + }); + var usertype = ["moderators", "banned"]; + for (var i = 0; i < usertype.length; i++) { + var users = array[usertype[i]]; + array[usertype[i]] = []; + for (var j = 0; j < users.length; j++) { + var detailusers = {}; + var user = Meteor.users.findOne({ + _id: users[j] + }); + detailusers._id = user._id; + detailusers.email = user.services.google.email; + detailusers.name = user.profile.name; + array[usertype[i]].push(detailusers); + } + } + selectedClass.moderators = array.moderators; + selectedClass.banned = array.banned; + Session.set("selectedClass", selectedClass); + } + }); +} + +function getProfileData() { // Gets all data related to profile. + var profile = Session.get("user"); + + profile.description = document.getElementById("motd").childNodes[0].nodeValue; + if (profile.description.includes("Say something about yourself!")) profile.description = ""; + + profile.school = document.getElementById("school").childNodes[0].nodeValue; + if (profile.school === "Click here to edit...") profile.school = ""; + + var gradein = document.getElementById("grade").childNodes[0].nodeValue; + profile.grade = parseInt(gradein.substring(gradein.length - 2, gradein)); + if (!profile.grade) profile.grade = ""; + + profile.avatar = document.getElementById("profAvatar").src; + profile.banner = document.getElementById("profBanner").src; + + var themename = document.getElementById("prefTheme").childNodes[0].nodeValue.toLowerCase(); + var themeobj = themeColors[themename]; + profile.preferences = { + "theme": themeobj, + "mode": document.getElementById("prefMode").childNodes[0].nodeValue.toLowerCase(), + "timeHide": ref[document.getElementById("prefHide").childNodes[0].nodeValue], + "done": ref[document.getElementById("prefDone").childNodes[0].nodeValue], + "hideReport": ref[document.getElementById("prefReport").childNodes[0].nodeValue] + }; + return profile; +} + +function getCreateFormData() { // Gets create class form data, and returns null. + var stop; + var form = document.getElementsByClassName("creInput"); + for (var i = 0; i < form.length; i++) { // Checks for missing/invalid fields. + if (i === 1 || i === 2) continue; + if (form[i].value === "") { + form[i].focus(); + form[i].placeholder = "Missing field"; + form[i].className += " formInvalid"; + stop = true; + } else { + form[i].className = form[i].className.replace(" formInvalid", ""); + } + } + if (stop) return null; + + var school = form[0].value; + var hour = form[1].value; + var teacher = form[2].value; + var name = form[3].value; + if (form[4].value == "Public") { + var privacy = false; + } else { + var privacy = true; + } + var category = form[5].value.toLowerCase(); + return { + school: school, + hour: hour, + teacher: teacher, + name: name, + privacy: privacy, + category: category, + status: false, + code: "" + }; +} + +function checkUser(email, classid) { // Checks if user email exists. + var user = Meteor.users.findOne({ + "services.google.email": email + }); + if (user === undefined) { + return true; + } else { + if (classes.findOne({ + _id: classid + }).subscribers) + return false; + } +} diff --git a/hourglass/collections/main.js b/hourglass/collections/main.js index c52e70a..4456ea9 100644 --- a/hourglass/collections/main.js +++ b/hourglass/collections/main.js @@ -61,7 +61,8 @@ 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({ diff --git a/hourglass/lib/router.js b/hourglass/lib/router.js index 50de376..4c18d4c 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 d9979f4..433e6db 100644 --- a/hourglass/server/main.js +++ b/hourglass/server/main.js @@ -608,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);