diff --git a/tutor/.gitignore b/tutor/.gitignore index 62eaaf0e..5830e151 100644 --- a/tutor/.gitignore +++ b/tutor/.gitignore @@ -40,4 +40,4 @@ models.svg # Ignoring .java and .class files /students_solutions/Java/*.java -/students_solutions/Class/*.class +/students_solutions/Class/*.class \ No newline at end of file diff --git a/tutor/Gemfile b/tutor/Gemfile index 7382b0e9..412fa977 100644 --- a/tutor/Gemfile +++ b/tutor/Gemfile @@ -5,9 +5,16 @@ ruby '2.1.0' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.0.4' +# User authentication +gem 'devise' + +# File uploading +# gem 'carrierwave' +# gem 'rmagick' + # Use sqlite3 as the database for Active Record gem 'sqlite3' -gem 'devise' + # Use SCSS for stylesheets gem 'sass-rails', '~> 4.0.2' @@ -47,7 +54,12 @@ gem 'foreman' # Use debugger # gem 'debugger', group: [:development, :test] -# User authentication - # Use composite primary keys in models gem 'composite_primary_keys', '~> 6.0.1' + +#search +gem 'tire' +gem 'json_builder', '3.1.0' + +# whenever gem for scheduling tasks +gem 'whenever', :require => false \ No newline at end of file diff --git a/tutor/Gemfile.lock b/tutor/Gemfile.lock index fa0d2aac..74021c43 100644 --- a/tutor/Gemfile.lock +++ b/tutor/Gemfile.lock @@ -25,10 +25,11 @@ GEM multi_json (~> 1.3) thread_safe (~> 0.1) tzinfo (~> 0.3.37) + ansi (1.4.3) arel (4.0.2) - atomic (1.1.16) bcrypt (3.1.7) builder (3.1.4) + chronic (0.10.2) coffee-rails (4.0.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -44,12 +45,13 @@ GEM railties (>= 3.2.6, < 5) thread_safe (~> 0.1) warden (~> 1.2.3) - dotenv (0.10.0) + dotenv (0.7.0) erubis (2.7.0) execjs (2.0.2) foreman (0.63.0) dotenv (>= 0.7) thor (>= 0.13.6) + hashr (0.0.22) hike (1.2.3) i18n (0.6.9) jbuilder (1.5.3) @@ -59,6 +61,8 @@ GEM railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.1) + json_builder (3.1.0) + activesupport (>= 2.0.0) kgio (2.9.2) mail (2.5.4) mime-types (~> 1.16) @@ -85,15 +89,17 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) raindrops (0.13.0) - rake (10.1.1) + rake (10.3.1) rdoc (4.1.1) json (~> 1.4) - sass (3.2.17) - sass-rails (4.0.2) + rest-client (1.6.7) + mime-types (>= 1.16) + sass (3.2.19) + sass-rails (4.0.3) railties (>= 4.0.0, < 5.0) sass (~> 3.2.0) sprockets (~> 2.8, <= 2.11.0) - sprockets-rails (~> 2.0.0) + sprockets-rails (~> 2.0) sdoc (0.4.0) json (~> 1.8) rdoc (~> 4.0, < 5.0) @@ -107,14 +113,21 @@ GEM activesupport (>= 3.0) sprockets (~> 2.8) sqlite3 (1.3.9) - thor (0.19.0) - thread_safe (0.3.1) - atomic (>= 1.1.7, < 2) + thor (0.19.1) + thread_safe (0.3.3) tilt (1.4.1) + tire (0.6.2) + activemodel (>= 3.0) + activesupport + ansi + hashr (~> 0.0.19) + multi_json (~> 1.3) + rake + rest-client (~> 1.6) treetop (1.4.15) polyglot polyglot (>= 0.3.1) - turbolinks (2.2.1) + turbolinks (2.2.2) coffee-rails tzinfo (0.3.39) uglifier (2.5.0) @@ -126,6 +139,9 @@ GEM raindrops (~> 0.7) warden (1.2.3) rack (>= 1.0) + whenever (0.9.2) + activesupport (>= 2.3.4) + chronic (>= 0.6.3) PLATFORMS ruby @@ -137,10 +153,13 @@ DEPENDENCIES foreman jbuilder (~> 1.2) jquery-rails (~> 3.1.0) + json_builder (= 3.1.0) rails (= 4.0.4) sass-rails (~> 4.0.2) sdoc sqlite3 + tire turbolinks uglifier (>= 1.3.0) unicorn + whenever diff --git a/tutor/app/assets/images/add_buttom.png b/tutor/app/assets/images/add_buttom.png new file mode 100644 index 00000000..699f98f2 Binary files /dev/null and b/tutor/app/assets/images/add_buttom.png differ diff --git a/tutor/app/assets/images/add_button.png b/tutor/app/assets/images/add_button.png new file mode 100644 index 00000000..68367d14 Binary files /dev/null and b/tutor/app/assets/images/add_button.png differ diff --git a/tutor/app/assets/images/delete_button.png b/tutor/app/assets/images/delete_button.png new file mode 100644 index 00000000..67ca6160 Binary files /dev/null and b/tutor/app/assets/images/delete_button.png differ diff --git a/tutor/app/assets/javascripts/acknowledgements.js.coffee b/tutor/app/assets/javascripts/acknowledgements.js.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/tutor/app/assets/javascripts/acknowledgements.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/tutor/app/assets/javascripts/application.js b/tutor/app/assets/javascripts/application.js index d6925fa4..237e12d9 100644 --- a/tutor/app/assets/javascripts/application.js +++ b/tutor/app/assets/javascripts/application.js @@ -14,3 +14,5 @@ //= require jquery_ujs //= require turbolinks //= require_tree . +//= require jquery.tokeninput +//= require utilities \ No newline at end of file diff --git a/tutor/app/assets/javascripts/jquery.bpopup.min.js b/tutor/app/assets/javascripts/jquery.bpopup.min.js new file mode 100644 index 00000000..fcb1c820 --- /dev/null +++ b/tutor/app/assets/javascripts/jquery.bpopup.min.js @@ -0,0 +1,7 @@ +/*================================================================================ + * @name: bPopup - if you can't get it up, use bPopup + * @author: (c)Bjoern Klinggaard (twitter@bklinggaard) + * @demo: http://dinbror.dk/bpopup + * @version: 0.9.4.min + ================================================================================*/ + (function(b){b.fn.bPopup=function(z,F){function K(){a.contentContainer=b(a.contentContainer||c);switch(a.content){case "iframe":var h=b('");h.appendTo(a.contentContainer);r=c.outerHeight(!0);s=c.outerWidth(!0);A();h.attr("src",a.loadUrl);k(a.loadCallback);break;case "image":A();b("").load(function(){k(a.loadCallback);G(b(this))}).attr("src",a.loadUrl).hide().appendTo(a.contentContainer);break;default:A(),b('
').load(a.loadUrl,a.loadData,function(){k(a.loadCallback);G(b(this))}).hide().appendTo(a.contentContainer)}}function A(){a.modal&&b('
').css({backgroundColor:a.modalColor,position:"fixed",top:0,right:0,bottom:0,left:0,opacity:0,zIndex:a.zIndex+t}).appendTo(a.appendTo).fadeTo(a.speed,a.opacity);D();c.data("bPopup",a).data("id",e).css({left:"slideIn"==a.transition||"slideBack"==a.transition?"slideBack"==a.transition?g.scrollLeft()+u:-1*(v+s):l(!(!a.follow[0]&&m||f)),position:a.positionStyle||"absolute",top:"slideDown"==a.transition||"slideUp"==a.transition?"slideUp"==a.transition?g.scrollTop()+w:x+-1*r:n(!(!a.follow[1]&&p||f)),"z-index":a.zIndex+t+1}).each(function(){a.appending&&b(this).appendTo(a.appendTo)});H(!0)}function q(){a.modal&&b(".b-modal."+c.data("id")).fadeTo(a.speed,0,function(){b(this).remove()});a.scrollBar||b("html").css("overflow","auto");b(".b-modal."+e).unbind("click");g.unbind("keydown."+e);d.unbind("."+e).data("bPopup",0=c.height()&&(d.height=c.height());b>=c.width()&&(d.width=c.width());r=c.outerHeight(!0);s=c.outerWidth(!0);D();a.contentContainer.css({height:"auto",width:"auto"});d.left=l(!(!a.follow[0]&&m||f));d.top=n(!(!a.follow[1]&&p||f));c.animate(d,250,function(){h.show();B=E()})}function L(){d.data("bPopup",t);c.delegate(".bClose, ."+a.closeClass,"click."+e,q);a.modalClose&&b(".b-modal."+e).css("cursor","pointer").bind("click",q);M||!a.follow[0]&&!a.follow[1]||d.bind("scroll."+e,function(){B&&c.dequeue().animate({left:a.follow[0]?l(!f):"auto",top:a.follow[1]?n(!f):"auto"},a.followSpeed,a.followEasing)}).bind("resize."+e,function(){w=y.innerHeight||d.height();u=y.innerWidth||d.width();if(B=E())clearTimeout(I),I=setTimeout(function(){D();c.dequeue().each(function(){f?b(this).css({left:v,top:x}):b(this).animate({left:a.follow[0]?l(!0):"auto",top:a.follow[1]?n(!0):"auto"},a.followSpeed,a.followEasing)})},50)});a.escClose&&g.bind("keydown."+e,function(a){27==a.which&&q()})}function H(b){function d(e){c.css({display:"block",opacity:1}).animate(e,a.speed,a.easing,function(){J(b)})}switch(b?a.transition:a.transitionClose||a.transition){case "slideIn":d({left:b?l(!(!a.follow[0]&&m||f)):g.scrollLeft()-(s||c.outerWidth(!0))-C});break;case "slideBack":d({left:b?l(!(!a.follow[0]&&m||f)):g.scrollLeft()+u+C});break;case "slideDown":d({top:b?n(!(!a.follow[1]&&p||f)):g.scrollTop()-(r||c.outerHeight(!0))-C});break;case "slideUp":d({top:b?n(!(!a.follow[1]&&p||f)):g.scrollTop()+w+C});break;default:c.stop().fadeTo(a.speed,b?1:0,function(){J(b)})}}function J(b){b?(L(),k(F),a.autoClose&&setTimeout(q,a.autoClose)):(c.hide(),k(a.onClose),a.loadUrl&&(a.contentContainer.empty(),c.css({height:"auto",width:"auto"})))}function l(a){return a?v+g.scrollLeft():v}function n(a){return a?x+g.scrollTop():x}function k(a){b.isFunction(a)&&a.call(c)}function D(){x=p?a.position[1]:Math.max(0,(w-c.outerHeight(!0))/2-a.amsl);v=m?a.position[0]:(u-c.outerWidth(!0))/2;B=E()}function E(){return w>c.outerHeight(!0)&&u>c.outerWidth(!0)}b.isFunction(z)&&(F=z,z=null);var a=b.extend({},b.fn.bPopup.defaults,z);a.scrollBar||b("html").css("overflow","hidden");var c=this,g=b(document),y=window,d=b(y),w=y.innerHeight||d.height(),u=y.innerWidth||d.width(),M=/OS 6(_\d)+/i.test(navigator.userAgent),C=200,t=0,e,B,p,m,f,x,v,r,s,I;c.close=function(){a=this.data("bPopup");e="__b-popup"+d.data("bPopup")+"__";q()};return c.each(function(){b(this).data("bPopup")||(k(a.onOpen),t=(d.data("bPopup")||0)+1,e="__b-popup"+t+"__",p="auto"!==a.position[1],m="auto"!==a.position[0],f="fixed"===a.positionStyle,r=c.outerHeight(!0),s=c.outerWidth(!0),a.loadUrl?K():A())})};b.fn.bPopup.defaults={amsl:50,appending:!0,appendTo:"body",autoClose:!1,closeClass:"b-close",content:"ajax",contentContainer:!1,easing:"swing",escClose:!0,follow:[!0,!0],followEasing:"swing",followSpeed:500,iframeAttr:'scrolling="no" frameborder="0"',loadCallback:!1,loadData:!1,loadUrl:!1,modal:!0,modalClose:!0,modalColor:"#000",onClose:!1,onOpen:!1,opacity:0.7,position:["auto","auto"],positionStyle:"absolute",scrollBar:!0,speed:250,transition:"fadeIn",transitionClose:!1,zIndex:9997}})(jQuery); \ No newline at end of file diff --git a/tutor/app/assets/javascripts/lecturers.js b/tutor/app/assets/javascripts/lecturers.js new file mode 100644 index 00000000..5857b2e3 --- /dev/null +++ b/tutor/app/assets/javascripts/lecturers.js @@ -0,0 +1,13 @@ +$(document).ready(function() { + $('.tabs .tab-links a').on('click', function(element) { + var currentAttrValue = $(this).attr('href'); + // Show/Hide Tabs + $('.tabs ' + currentAttrValue).siblings().slideUp(400); + $('.tabs ' + currentAttrValue).delay(400).slideDown(400); + //or use fade + //$('.tabs ' + currentAttrValue).fadeIn(400).siblings().hide(); + // Change/remove current tab to active + $(this).parent('li').addClass('active').siblings().removeClass('active'); + element.preventDefault(); + }); +}); \ No newline at end of file diff --git a/tutor/app/assets/javascripts/posts.js.coffee b/tutor/app/assets/javascripts/posts.js.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/tutor/app/assets/javascripts/posts.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/tutor/app/assets/javascripts/solutions.js.coffee b/tutor/app/assets/javascripts/solutions.js.coffee index 0ee6d912..71e2c0dd 100644 --- a/tutor/app/assets/javascripts/solutions.js.coffee +++ b/tutor/app/assets/javascripts/solutions.js.coffee @@ -11,7 +11,10 @@ index_number = 0 # Author: Mussab ElDash @start_debug = (problem_id) -> input = $('#solution_code').val() - test = $('#testcases').val() + if input.length is 0 + alert "You didn't write any code" + return + test = $('#solution_input').val() start_spin() $.ajax type: "POST" @@ -19,32 +22,84 @@ index_number = 0 data: {code : input , case : test} datatype: 'json' success: (data) -> - toggleDebug() - variables = data stop_spin() + if !data["success"] + compilation_error data["data"]["errors"] + return + variables = data["data"] + toggleDebug() + debug_console() jump_state 0 error: -> stop_spin() return return +# [Debugger: Debug - Story 3.6] +# Fills the console with the compilation errors +# Parameters: none +# Returns: none +# Author: Mussab ElDash +compilation_error = (data) -> + $('.compilation_failed').html("Compilation Failed!") + $('.compilation_feedback').html(data) + return + +# [Debugger: Debug - Story 3.6] +# Clears the console +# Parameters: none +# Returns: none +# Author: Mussab ElDash +clear_console = -> + $('.compilation_failed').html("") + $('.compilation_feedback').html("") + return + +# [Debugger: Debug - Story 3.6] +# Write successful debug in the console +# Parameters: none +# Returns: none +# Author: Mussab ElDash +debug_console = -> + $('.compilation_succeeded').html("Debugging Succeeded!") + return + +# [Debugger: Debug - Story 3.6] +# Starts the Spinner +# Parameters: none +# Returns: none +# Author: Mussab ElDash @start_spin = -> - $('#spinner').attr "class" , "spinner" + $('#spinner').addClass "spinner" return +# [Debugger: Debug - Story 3.6] +# Stops the Spinner +# Parameters: none +# Returns: none +# Author: Mussab ElDash @stop_spin = -> - $('#spinner').attr "class" , "" + $('#spinner').removeClass "spinner" return +# [Debugger: Debug - Story 3.6] +# Toggles the Spinner +# Parameters: none +# Returns: none +# Author: Mussab ElDash +@toggle_spin = -> + $('#spinner').toggleClass "spinner" + # [Execute Line By Line - Story 3.8] # Toggles debugging mode by changing the available buttons. # Parameters: none # Returns: none # Author: Rami Khalil (Temporary) -@toggleDebug = () -> +@toggleDebug = -> $('#debugButton').prop 'hidden', !$('#debugButton').prop 'hidden' $('#compileButton').prop 'hidden', !$('#compileButton').prop 'hidden' $('#testButton').prop 'hidden', !$('#testButton').prop 'hidden' + $('#solution_code').prop 'disabled', !$('#solution_code').prop 'disabled' $('#nextButton').prop 'hidden', !$('#nextButton').prop 'hidden' $('#previousButton').prop 'hidden', !$('#previousButton').prop 'hidden' @@ -130,9 +185,31 @@ index_number = 0 # Parameters: # stateNumber: The target state number. # Returns: none -# Author: Rami Khalil +# Author: Rami Khalil + Khaled Helmy @jump_state = (stateNumber) -> - highlight_line variables[stateNumber]['line'] - 1 + highlight_line variables[stateNumber]['line'] - 2 + update_memory_contents stateNumber + +# [View Variables - Story 3.7] +# Updates the variables values according to a certain state +# Parameters: +# stateNumber: The target state number. +# Returns: none +# Author: Khaled Helmy +@update_memory_contents = (stateNumber) -> + div = document.getElementById("memory") + list_of_variables = variables[stateNumber]["locals"] + content = '' + content += "" + i = 0 + while i < list_of_variables.length + values = list_of_variables[i].split " = " + content += "" + content += "" + i++ + content += "
VariableValue
" + values[0] + "" + values[1] + "
" + div.innerHTML = content + return # [Debug - Story 3.6] # Stops the debugging session. @@ -142,4 +219,44 @@ index_number = 0 @stop = () -> toggleDebug() index_number = 0; - variables = null; \ No newline at end of file + variables = null; + +# To be Used when changing to ajax in order not to refresh page +# [Compiler: Validate - Story 3.5] +# submits a solution in the form without refreshing +# using ajax showing an alert box for success and failure scenarios +# Parameters: +# problem_id: the id of the problem being solved +# Returns: a json object containing two arrays one for the errors +# of the current code and the other containing success messages +# Author: MOHAMEDSAEED +@validate_code = (problem_id) -> + code = $('#solution_code').val() + mins = parseInt($('#mins').text()) + secs = parseInt($('#secs').text()) + time = mins*60 + secs + start_spin() + $.ajax + type: "POST" + url: '/solutions' + data: {problem_id: problem_id, code: code, time: time} + datatype: 'json' + success: (data) -> + stop_spin() + success = $('#validate_success') + errors = $('#validate_error') + success.html("") + for i in data["success"] + success.append("#{i}
") + errors.html("") + for i in data["failure"] + errors.append("#{i}
") + if code.length isnt 0 + alert 'Solution has been submitted successfully' + else + alert 'Blank submissions are not allowed' + return + error: (data) -> + stop_spin() + return + return diff --git a/tutor/app/assets/javascripts/solutions_constraints.js b/tutor/app/assets/javascripts/solutions_constraints.js new file mode 100644 index 00000000..e8a09da8 --- /dev/null +++ b/tutor/app/assets/javascripts/solutions_constraints.js @@ -0,0 +1,256 @@ +//# Place all the behaviors and hooks related to the matching controller here. +//# All this logic will automatically be available in application.js. +//# You can use CoffeeScript in this file: http://coffeescript.org/ + +var type = new Array(); +var param_name = new Array(); +var var_type = new Array(); +var var_name = new Array(); + +//# [Add Solutions' constraints - Story 4.14] +//# Description: Adds the parameters from the text_fields to array +//# Parameters: none +//# Returns: none +//# Author: Ahmed Mohamed Magdi +function add_params(field) { + var tmp_type = document.getElementById("params_type").value; + var tmp_name = document.getElementById("params_name").value; + if (tmp_name != "" && tmp_type != "") { + document.getElementById("params_type").style.border= ""; + document.getElementById("params_name").style.border= ""; + type[type.length] = tmp_type; + document.getElementById("params_type").value = ""; + param_name[param_name.length] = tmp_name; + document.getElementById("params_name").value = ""; + document.getElementById("parameter").innerHTML = "

Paramters Constraints

"; + for (var i = 0; i < type.length; i++) { + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + } + $('#parameter').append("
\"delete
"); + } + else { + if (tmp_name == "" && tmp_type == "") { + document.getElementById("params_type").style.border= "red 1px solid"; + document.getElementById("params_name").style.border= "red 1px solid"; + } + else if(tmp_name == "") { + document.getElementById("params_type").style.border= ""; + document.getElementById("params_name").style.border= "red 1px solid"; + } + else{ + document.getElementById("params_type").style.border= "red 1px solid"; + document.getElementById("params_name").style.border= ""; + } + } +} + +//# [Add Solutions' constraints - Story 4.14] +//# Description: removes selected variable from method Contraints +//# Parameters: none +//# Returns: none +//# Author: Ahmed Mohamed Magdi +function remove_params(field) { + var index = field.id.split("_")[1]; + type.splice(index,1); + param_name.splice(index,1); + document.getElementById("parameter").innerHTML = "

Paramters Constraints

"; + if(type.length == 0) { + $('#parameter').append("No paramter"); + } + for (var i = 0; i < type.length; i++) { + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + $('#parameter').append(""); + } + $('#parameter').append("
\"delete
"); +} + +//# [Add Solutions' constraints - Story 4.14] +//# Description: Adds the parameters from the text_fields to array +//# Parameters: none +//# Returns: none +//# Author: Ahmed Mohamed Magdi +function add_variable(field) { + var tmp_type = document.getElementById("variable_type").value; + var tmp_name = document.getElementById("variable_name").value; + if (tmp_name != "" && tmp_type != "") { + document.getElementById("variable_type").style.border= ""; + document.getElementById("variable_name").style.border= ""; + var_type[var_type.length] = tmp_type; + document.getElementById("variable_type").value = ""; + var_name[var_name.length] = tmp_name; + document.getElementById("variable_name").value = ""; + document.getElementById("variable").innerHTML = "

Variables Constraints

"; + for (var i = 0; i < var_type.length; i++) { + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + } + $('#variable').append("
\"delete
"); + } + else { + if (tmp_name == "" && tmp_type == "") { + document.getElementById("variable_type").style.border= "red 1px solid"; + document.getElementById("variable_name").style.border= "red 1px solid"; + } + else if(tmp_name == "") { + document.getElementById("variable_type").style.border= ""; + document.getElementById("variable_name").style.border= "red 1px solid"; + } + else { + document.getElementById("variable_type").style.border= "red 1px solid"; + document.getElementById("variable_name").style.border= ""; + } + } +} + +//# [Add Solutions' constraints - Story 4.14] +//# Description: removes selected variable from Variable Contraints +//# Parameters: none +//# Returns: none +//# Author: Ahmed Mohamed Magdi +function remove_variable(field) { + var index = field.id.split("_")[1]; + var_type.splice(index,1); + var_name.splice(index,1); + document.getElementById("variable").innerHTML = "

Variables Constraints

"; + if(var_type.length == 0) { + $('#variable').append("No Variables"); + } + for (var i = 0; i < var_type.length; i++) { + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + $('#variable').append(""); + } + $('#variable').append("
\"delete
"); +} + +//# [Add Solutions' constraints - Story 4.14] +//# Description: for showing the error massages for the user +//# Parameters: +//# errorArray: array of Errors,for multi error massage show +//# Returns: none +//# Author: Ahmed Mohamed Magdi +function showErrorMessage(arrayOfErrors) { + for (var i = 0; i < arrayOfErrors.length; i++) { + $("#errors").append("
"+arrayOfErrors[i]+"
"); + }; +} + +//# [Add Solutions' constraints - Story 4.14] +//# Description: Validates Data before sending it to the Server side +//# Parameters: +//# method_cons: Hash containting the Method parameters +//# Returns: none +//# Author: Ahmed Mohamed Magdi +function testingValidation(errorArray,method,name) { + if (type.length == 0 && + var_type.length == 0 && + method == 0 && + name == 0) { + errorArray.push("Can not submit an empty Data,\ + Try filling either Method Constraint for Variable Constraint"); + return false; + }; + if (type.length > 0) { + if (method == "" && name == "") { + document.getElementById("_constrain_method_return").style.border= "red 1px solid"; + document.getElementById("_constrain_method_name").style.border= "red 1px solid"; + errorArray.push("Enter method name and return type .."); + } + }; + if(method == "" && name != "") { + document.getElementById("_constrain_method_name").style.border= ""; + document.getElementById("_constrain_method_return").style.border= "red 1px solid"; + errorArray.push("Enter method return type .."); + } + else if(name == "" && method != "") { + document.getElementById("_constrain_method_name").style.border= "red 1px solid"; + document.getElementById("_constrain_method_return").style.border= ""; + errorArray.push("Enter method name .."); + } + return errorArray.length == 0; +} + +//# [Add Solutions' constraints - Story 4.14] +//# Description: submits via ajax to the controller +//# Parameters: none +//# Returns: none +//# Author: Ahmed Mohamed Magdi +function submitParams() { + $("#errors").html(""); + errorArray = new Array(); + var hash_p = new Array(); + var hash_v = new Array(); + method = $('#_constrain_method_return').val() + name = $('#_constrain_method_name').val() + if (!testingValidation(errorArray,method,name)) { + showErrorMessage(errorArray); + return; + }; + for (var i = 0; i < type.length; i++) { + hash_p.push({ + type: type[i] , + name: param_name[i] + }); + }; + for (var i = 0; i < type.length; i++) { + hash_v.push({ + type: var_type[i] , + name: var_name[i] + }); + }; + $.ajax({ + type: "POST", + url: "/solutions_constraints", + data:{ + parameter_constraint: hash_p, + variable_constraint: hash_v, + method_return: method, + method_name: name + }, + success: function(data) { + if (data) { + alert("Data have been Added successfully"); + window.location = window.location + } + else { + alert("Data messigin/incorrect !"); + } + }, + datatype: "JSON", + error: function() { + alert("Failed to Add Constraints, Check again"); + } + }); +} \ No newline at end of file diff --git a/tutor/app/assets/javascripts/students.js b/tutor/app/assets/javascripts/students.js new file mode 100644 index 00000000..5857b2e3 --- /dev/null +++ b/tutor/app/assets/javascripts/students.js @@ -0,0 +1,13 @@ +$(document).ready(function() { + $('.tabs .tab-links a').on('click', function(element) { + var currentAttrValue = $(this).attr('href'); + // Show/Hide Tabs + $('.tabs ' + currentAttrValue).siblings().slideUp(400); + $('.tabs ' + currentAttrValue).delay(400).slideDown(400); + //or use fade + //$('.tabs ' + currentAttrValue).fadeIn(400).siblings().hide(); + // Change/remove current tab to active + $(this).parent('li').addClass('active').siblings().removeClass('active'); + element.preventDefault(); + }); +}); \ No newline at end of file diff --git a/tutor/app/assets/javascripts/teaching_assistants.js b/tutor/app/assets/javascripts/teaching_assistants.js new file mode 100644 index 00000000..5857b2e3 --- /dev/null +++ b/tutor/app/assets/javascripts/teaching_assistants.js @@ -0,0 +1,13 @@ +$(document).ready(function() { + $('.tabs .tab-links a').on('click', function(element) { + var currentAttrValue = $(this).attr('href'); + // Show/Hide Tabs + $('.tabs ' + currentAttrValue).siblings().slideUp(400); + $('.tabs ' + currentAttrValue).delay(400).slideDown(400); + //or use fade + //$('.tabs ' + currentAttrValue).fadeIn(400).siblings().hide(); + // Change/remove current tab to active + $(this).parent('li').addClass('active').siblings().removeClass('active'); + element.preventDefault(); + }); +}); \ No newline at end of file diff --git a/tutor/as b/tutor/app/assets/javascripts/teaching_assistants.js.coffee similarity index 100% rename from tutor/as rename to tutor/app/assets/javascripts/teaching_assistants.js.coffee diff --git a/tutor/app/assets/javascripts/tracks.js b/tutor/app/assets/javascripts/tracks.js new file mode 100644 index 00000000..73572a12 --- /dev/null +++ b/tutor/app/assets/javascripts/tracks.js @@ -0,0 +1,77 @@ +// [Recommendation to students] +// Shows the dialog containing names of students +// who can recieve recommendation and a button to send recommendation +// Parameters: +// problem_id: id of problem +// recommender_id: id of student recommending the problem +// Returns: calls the function which fills the dialog with student names +// Author: Mohab Ghanim +function showdialog(problem_id, recommender_id){ + $.ajax({ + type: "GET", + url: '/tracks/show_classmates/' + problem_id, + datatype: 'json', + success: function(json){ + fill(json, problem_id, recommender_id)} + }); +} + +// [Recommendation to students] +// Fills the dialog with student names and recommend buttons +// Parameters: +// problem_id: id of problem +// recommender_id: id of student recommending the problem +// data: data returned from the above method +// Returns: appends html in /tracks/id +// Author: Mohab Ghanim +function fill(data, problem_id, recommender_id){ + elem = $('#container'); + elem.html(""); + if(Object.keys(data).length == 0){ + elem.html("
No classmates to recommend this problem to
"); + $('.classmates_list').bPopup(); + return; + } + elem.html("

Recommend this problem to

") + elem.append(""); + $('.classmates_list').bPopup(); +} + +// [Recommendation to students] +// Sends problem id , recommender id, student getting recommendation id +// to the controller to be inserted in the database +// Parameters: +// problem_id: id of problem +// recommender_id: id of student recommending the problem +// student_id: id of student recieving the problem +// Returns: none +// Author: Mohab Ghanim +function recommend(problem_id, recommender_id, student_id){ + + $.ajax({ + type: "POST", + url: '/tracks/insert_recommendation/', + data: { p_id : problem_id, + r_id : recommender_id, + s_id : student_id + } + }); + showdialog(problem_id, recommender_id); + +} diff --git a/tutor/app/assets/javascripts/utilities.js.coffee b/tutor/app/assets/javascripts/utilities.js.coffee new file mode 100644 index 00000000..a5e909df --- /dev/null +++ b/tutor/app/assets/javascripts/utilities.js.coffee @@ -0,0 +1,12 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ +$ -> + $("#auto_complete").tokenInput "/utilities/auto_complete.json", + crossDomain: false + prePopulate: [$("#auto_complete").data("load")] + hintText: "" + tokenLimit: 1 + minChars: 1 + resultsLimit: 1 + theme: "" \ No newline at end of file diff --git a/tutor/app/assets/stylesheets/acknowledgements.css.scss b/tutor/app/assets/stylesheets/acknowledgements.css.scss new file mode 100644 index 00000000..c90441b1 --- /dev/null +++ b/tutor/app/assets/stylesheets/acknowledgements.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Acknowledgements controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/tutor/app/assets/stylesheets/application.css b/tutor/app/assets/stylesheets/application.css index f2a5e126..2288e797 100644 --- a/tutor/app/assets/stylesheets/application.css +++ b/tutor/app/assets/stylesheets/application.css @@ -18,28 +18,39 @@ } .side-right { - margin-top:70px; + margin-top:80px; height:auto; - margin-right:1.5%; + margin-right:0.5%; } .main-content { - margin-top:70px; + margin-top:80px; margin-bottom:70px; - height:auto; - width:71%; - margin-left:2%; - margin-bottom: 100px; + width:78%; + margin-left:1%; + min-width:500px; + overflow:scroll; + position:static; } .footer { - height:20px; - width:96%; + width:100%; border-radius: 2px; - margin-left:2%; - margin-right:2%; } .auto-height { height: auto; +} + +.form-devise > div { + margin: 10px; + width: 300px; +} + +.button-devise { + margin-top: 10px; +} + +.home { + overflow:scroll; } \ No newline at end of file diff --git a/tutor/app/assets/stylesheets/courses.css.scss b/tutor/app/assets/stylesheets/courses.css.scss index 911dd8de..d8b93842 100644 --- a/tutor/app/assets/stylesheets/courses.css.scss +++ b/tutor/app/assets/stylesheets/courses.css.scss @@ -5,4 +5,8 @@ font-weight: bold; font-family: Courier; float: left; +} + +#error_explanation { + color: red; } \ No newline at end of file diff --git a/tutor/app/assets/stylesheets/lecturers.css b/tutor/app/assets/stylesheets/lecturers.css new file mode 100644 index 00000000..2f23dc87 --- /dev/null +++ b/tutor/app/assets/stylesheets/lecturers.css @@ -0,0 +1,44 @@ +.tabs { + width:100%; + display:inline-block; +} +.tab-links:after { + display:block; + clear:both; + content:''; +} +.tab-links li { + margin:0px 5px; + float:left; + list-style:none; +} +.tab-links a { + padding:9px 15px; + display:inline-block; + border-radius:3px 3px 0px 0px; + background:#7FB5DA; + font-size:16px; + font-weight:600; + color:#4c4c4c; + transition:all linear 0.15s; +} +.tab-links a:hover { + background:#a7cce5; + text-decoration:none; +} +li.active a, li.active a:hover { + background:#fff; + color:#4c4c4c; +} +.tab-content { + padding:15px; + border-radius:3px; + box-shadow:-1px 1px 1px rgba(0,0,0,0.15); + background:#fff; +} +.tab { + display:none; +} +.tab.active { + display:block; +} \ No newline at end of file diff --git a/tutor/app/assets/stylesheets/model_answers.css b/tutor/app/assets/stylesheets/model_answers.css new file mode 100644 index 00000000..435a38e9 --- /dev/null +++ b/tutor/app/assets/stylesheets/model_answers.css @@ -0,0 +1,12 @@ +.red { + color: #FF0000; +} +.bold { + font-weight: bold; +} +a.Lst { + font-size: 30px; + line-height: 0px; + padding-left: 0px; + font-weight: normal; +} \ No newline at end of file diff --git a/tutor/app/assets/stylesheets/posts.css.scss b/tutor/app/assets/stylesheets/posts.css.scss new file mode 100644 index 00000000..1a7e1539 --- /dev/null +++ b/tutor/app/assets/stylesheets/posts.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the posts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/tutor/app/assets/stylesheets/solutions.css b/tutor/app/assets/stylesheets/solutions.css index 0d43be75..622b7fa9 100644 --- a/tutor/app/assets/stylesheets/solutions.css +++ b/tutor/app/assets/stylesheets/solutions.css @@ -37,6 +37,12 @@ textarea { overflow-x:hidden; } +.fixed-horizontal { + max-width: 175px; + overflow-x:scroll; + overflow-y:hidden; +} + .editor { background-color: black; color: white; diff --git a/tutor/app/assets/stylesheets/solutions_constraints.css.scss b/tutor/app/assets/stylesheets/solutions_constraints.css.scss new file mode 100644 index 00000000..95649de1 --- /dev/null +++ b/tutor/app/assets/stylesheets/solutions_constraints.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the solutionsConstraints controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/tutor/app/assets/stylesheets/students.css b/tutor/app/assets/stylesheets/students.css new file mode 100644 index 00000000..2f23dc87 --- /dev/null +++ b/tutor/app/assets/stylesheets/students.css @@ -0,0 +1,44 @@ +.tabs { + width:100%; + display:inline-block; +} +.tab-links:after { + display:block; + clear:both; + content:''; +} +.tab-links li { + margin:0px 5px; + float:left; + list-style:none; +} +.tab-links a { + padding:9px 15px; + display:inline-block; + border-radius:3px 3px 0px 0px; + background:#7FB5DA; + font-size:16px; + font-weight:600; + color:#4c4c4c; + transition:all linear 0.15s; +} +.tab-links a:hover { + background:#a7cce5; + text-decoration:none; +} +li.active a, li.active a:hover { + background:#fff; + color:#4c4c4c; +} +.tab-content { + padding:15px; + border-radius:3px; + box-shadow:-1px 1px 1px rgba(0,0,0,0.15); + background:#fff; +} +.tab { + display:none; +} +.tab.active { + display:block; +} \ No newline at end of file diff --git a/tutor/app/assets/stylesheets/teaching_assistants.css b/tutor/app/assets/stylesheets/teaching_assistants.css new file mode 100644 index 00000000..2f23dc87 --- /dev/null +++ b/tutor/app/assets/stylesheets/teaching_assistants.css @@ -0,0 +1,44 @@ +.tabs { + width:100%; + display:inline-block; +} +.tab-links:after { + display:block; + clear:both; + content:''; +} +.tab-links li { + margin:0px 5px; + float:left; + list-style:none; +} +.tab-links a { + padding:9px 15px; + display:inline-block; + border-radius:3px 3px 0px 0px; + background:#7FB5DA; + font-size:16px; + font-weight:600; + color:#4c4c4c; + transition:all linear 0.15s; +} +.tab-links a:hover { + background:#a7cce5; + text-decoration:none; +} +li.active a, li.active a:hover { + background:#fff; + color:#4c4c4c; +} +.tab-content { + padding:15px; + border-radius:3px; + box-shadow:-1px 1px 1px rgba(0,0,0,0.15); + background:#fff; +} +.tab { + display:none; +} +.tab.active { + display:block; +} \ No newline at end of file diff --git a/tutor/app/assets/stylesheets/teaching_assistants.css.scss b/tutor/app/assets/stylesheets/teaching_assistants.css.scss new file mode 100644 index 00000000..cb40ae16 --- /dev/null +++ b/tutor/app/assets/stylesheets/teaching_assistants.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the teaching_assistants controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/tutor/app/assets/stylesheets/tracks.css b/tutor/app/assets/stylesheets/tracks.css new file mode 100644 index 00000000..2f196a81 --- /dev/null +++ b/tutor/app/assets/stylesheets/tracks.css @@ -0,0 +1,16 @@ +.classmates_list { + background-color:#fff; + border-radius:10px; + color:#000; + display:none; + padding:10px; + min-width:400px; + min-height: 600px; +} +#container li{ + padding: 10px; + list-style: none; +} +.recommend { + float: right; +} \ No newline at end of file diff --git a/tutor/app/controllers/acknowledgements_controller.rb b/tutor/app/controllers/acknowledgements_controller.rb new file mode 100644 index 00000000..420309d4 --- /dev/null +++ b/tutor/app/controllers/acknowledgements_controller.rb @@ -0,0 +1,55 @@ +class AcknowledgementsController < ApplicationController + # [Student Acknowledgement - Story 1.7] + # Description: This action takes the passed course id and assings + # the respective course to an instance variable. + # Parameters: + # params[:course_id]: The current course id + # Returns: none + # Author: Muhammad Mamdouh + def new + @course = Course.find(params[:course_id]) + if !@course.can_edit(current_lecturer) + redirect_to :root + end + end + + # [Student Acknowledgement - Story 1.7] + # Description: This action takes the passed parameters from + # the creation form, creates a new acknowledgement record + # and assigns it to the respective course. If the + # creation fails the user is redirected to the form + # with an appropriate message. + # Parameters: + # array "students" which has the students selected + # the ID of the course + # the description of the acknowledgement. + # Returns: A message indicating the success or failure of the creation + # Author: Muhammad Mamdouh + def create + if params[:students] == nil + flash[:failure_notice] = "Acknowledgement failed. Please select a student" + else + params[:students].each do |student| + @student = Student.find_by_id(student) + @course = Course.find_by_id(params[:course_id]) + @acknowledgement = Acknowledgement.new + @acknowledgement.message = params[:acknowledgement][:description] + bool = @acknowledgement.save + if bool == true + if @student == nil + flash[:failure_notice]= "Please choose a student to acknowledge." + else + flash[:success_notice] = "Acknowledgement successfully created" + @student.acknowledgements << @acknowledgement + current_lecturer.acknowledgements << @acknowledgement + @course.acknowledgements << @acknowledgement + end + else + flash[:failure_notice] = "Acknowledgement failed." + end + end + end + redirect_to :action => 'new' + end + +end \ No newline at end of file diff --git a/tutor/app/controllers/application_controller.rb b/tutor/app/controllers/application_controller.rb index c169d06c..ad7a561a 100644 --- a/tutor/app/controllers/application_controller.rb +++ b/tutor/app/controllers/application_controller.rb @@ -1,36 +1,49 @@ class ApplicationController < ActionController::Base - # Prevent CSRF attacks by raising an exception. - # For APIs, you may want to use :null_session instead. - before_action :authenticate! - protect_from_forgery with: :exception + # Prevent CSRF attacks by raising an exception. + # For APIs, you may want to use :null_session instead. + before_action :authenticate! + protect_from_forgery with: :exception before_action :update_sanitized_params, if: :devise_controller? - #UPDATE ALL FIELDS + # [User Authentication Advanced - Story 5.9, 5.10, 5.11, 5.14, 5.15] + # Permits some fields to be passed through sign up forms to update the lecturer, + # student, and teaching_assistant models + # Parameters: None + # Returns: None + # Author: Khaled Helmy private - def update_sanitized_params - if "#{resource_name}" == "lecturer" - devise_parameter_sanitizer.for(:sign_up) { - |u| u.permit(:name, :email, :password, :password_confirmation, :degree) - } - elsif "#{resource_name}" == "student" - devise_parameter_sanitizer.for(:sign_up) { - |u| u.permit(:name, :email, :password, :password_confirmation) - } - elsif "#{resource_name}" == "teaching_assistant" - devise_parameter_sanitizer.for(:sign_up) { - |u| u.permit(:name, :email, :password, :password_confirmation) - } - end + def update_sanitized_params + if "#{resource_name}" == "lecturer" + devise_parameter_sanitizer.for(:sign_up) { + |u| u.permit(:name, :email, :password, :password_confirmation, :gender, :dob, :degree, + :university, :department, :profile_image, :profile_image_cache) + } + elsif "#{resource_name}" == "student" + devise_parameter_sanitizer.for(:sign_up) { + |u| u.permit(:name, :email, :password, :password_confirmation, :gender, :dob, :university, + :faculty, :major, :semester, :advising, :probation, :profile_image, :profile_image_cache) + } + elsif "#{resource_name}" == "teaching_assistant" + devise_parameter_sanitizer.for(:sign_up) { + |u| u.permit(:name, :email, :password, :password_confirmation, :gender, :dob, + :graduated_from, :graduated_year, :degree, :university, :department, + :profile_image, :profile_image_cache) + } end + end - def authenticate! - if signed_in? or params[:controller].include?"devise" - return true - else - flash[:notice] = "You're not logged in!" - redirect_to :root - end + # [User Authentication Advanced - Story 5.9, 5.10, 5.11, 5.14, 5.15] + # Checks if a user is signed-in in order to be used in authentication over different pages + # where they are redirected to homepage if they aren't authenticated + # Parameters: None + # Returns: None + # Author: Khaled Helmy + def authenticate! + unless signed_in? or params[:controller].include?"devise" + flash[:notice] = "You're not logged in!" + redirect_to :root end + end end \ No newline at end of file diff --git a/tutor/app/controllers/courses_controller.rb b/tutor/app/controllers/courses_controller.rb index 1577f2f7..b456e77e 100644 --- a/tutor/app/controllers/courses_controller.rb +++ b/tutor/app/controllers/courses_controller.rb @@ -61,6 +61,7 @@ def index @courses = current_teaching_assistant.courses.order("created_at desc") else @courses = current_student.courses.order("created_at desc") + @share = find_state @courses end end @@ -134,18 +135,125 @@ def create # Author: Mohamed Mamdouh def edit @course = Course.find_by_id(params[:id]) - @discussionBoard = @course.discussion_board + if !@course.can_edit(current_lecturer) + redirect_to :root + end + @discussion_board = @course.discussion_board end - + + # [View a course - story 1.21] + #Description: This action is resposible for the view of a specific course. + #Parameters: + # id: Course id + # Returns: The view of the requested course + # Author: Mohamed Metawaa def show + @course = Course.find_by_id(params[:id]) + if @course + @topics = @course.topics + tracks = [] + @topics.each do |t| + tracks = tracks + t.tracks + end + else + render ('public/404') + end end def manage end + # [Edit a course - story 1.17] + #Description: This action is resposible for editing a specific course. + #Parameters: + # id: Course id + # Returns: + # null + # Author: Mohamed Metawaa + def update + @course = Course.find_by_id(params[:id]) + @discussion_board = @course.discussion_board + if @course.update(course_params) + @topics = @course.topics + render 'show' + else + render 'edit' + end + end + + # [Share Performance - Story 5.2, 5.13] + # Updates the database with the value of whether to share + # performance or not and it redirects to an error page if an error + # occurs + # Parameters: + # params[:id]: The course id + # params[:value]: The decision of the student whether to share his + # performance or not + # Returns: none + # Author: Khaled Helmy + def share + if student_signed_in? + student_id = current_student.id + course_id = params[:id] + value = to_boolean params[:value] + result = CourseStudent.where("student_id = ? AND course_id = ?", + student_id, course_id)[0] + if result.share == value + render ('public/404') + else + result.update(share: value) + redirect_to "/courses" + end + else + render ('public/404') + end + end + private def course_params params.require(:course).permit(:name,:code,:year,:semester,:description) end + # [Share Performance - Story 5.2, 5.13] + # Fetches the sharing status for each course that the current + # signed in student is subscribing to in the database, appends them in + # a list and returns that list + # Parameters: none + # Returns: + # An array that contains the sharing status of each course for the + # current signed in student + # Author: Khaled Helmy + def find_state courses + states = Hash.new + student_id = current_student.id + puts student_id + courses.each do |c| + course_id = c.id + result = CourseStudent.where("student_id = ? AND course_id = ?", + student_id, course_id)[0] + if result.share == false + states[result.course_id] = "Show" + else + states[result.course_id] = "Hide" + end + end + return states + end + + # [Share Performance - Story 5.2, 5.13] + # Converts a string consisting of either "true" or "false" + # into the corresponding boolean value + # Parameters: + # value: String consisting of "true" or "false" to be converted to boolean + # Returns: + # A boolean converted from a string + # Author: Khaled Helmy + def to_boolean value + if value == "true" + return true + else + return false + end + end + end \ No newline at end of file diff --git a/tutor/app/controllers/debuggers_controller.rb b/tutor/app/controllers/debuggers_controller.rb index 35fd7f70..a0c2af34 100644 --- a/tutor/app/controllers/debuggers_controller.rb +++ b/tutor/app/controllers/debuggers_controller.rb @@ -16,9 +16,8 @@ def start id = current_student.id pid = params[:id] input = params[:code] - cases = params[:case] + cases = if params[:case] then params[:case] else "" end result = Debugger.debug(id, pid, input, cases) - p result render json: result end diff --git a/tutor/app/controllers/lecturers_controller.rb b/tutor/app/controllers/lecturers_controller.rb new file mode 100644 index 00000000..b1031281 --- /dev/null +++ b/tutor/app/controllers/lecturers_controller.rb @@ -0,0 +1,12 @@ +class LecturersController < ApplicationController + # [Profile - Story 5.8] + # Displays the profile of the lecturer chosen + # Parameters: + # id: the Lecturer's id + # Returns: none + # Author: Serag + def show + @lecturer = Lecturer.find(params[:id]) + @courses = @lecturer.courses.order("created_at desc") + end +end \ No newline at end of file diff --git a/tutor/app/controllers/model_answers_controller.rb b/tutor/app/controllers/model_answers_controller.rb index f1f15621..7f94c0a4 100644 --- a/tutor/app/controllers/model_answers_controller.rb +++ b/tutor/app/controllers/model_answers_controller.rb @@ -1,46 +1,94 @@ class ModelAnswersController < ApplicationController + # [Add answer story 4.6] # It creates the new answer. # Parameters: - # @answer: the new answer the user enters. - # @answers: All the previous answers that had been entered before. - # Return : none + # @problem: To fetch the problem to which the answer is added. + # @answer: The new answer the user enters. + # @answers: All the previous answers that had been entered before. + # Returns: none # Author: Nadine Adel def new - @answers = ModelAnswer.all - @answer = ModelAnswer.new + @problem = Problem.find(params[:problem_id]) + session[:problem_id] = params[:problem_id] + if(@new_answer == nil) + @new_answer = ModelAnswer.new + end end # [Add answer story 4.6] - # The new answer is saved. + # The new answer is saved and check that the user is a lecturer or TA. # Parameters: - # @answer:answer provided by the user. - # Returns: Returns a message if the answer is added and another message if answer was not added. + # @new_answer: Answer provided by the user. + # @problems: The problem to which the answer is linked. + # Returns: + # A message if the answer is added and another message if answer was not added. # Author: Nadine Adel def create + @new_answer = ModelAnswer.new + @new_answer.title = post_params[:title] + @new_answer.answer = post_params[:answer] + @new_answer.problem_id = session[:problem_id] + @problems = Problem.find_by_id(session[:problem_id]) if lecturer_signed_in? - @answer = ModelAnswer.new(post_params) - @answer.owner_id = current_lecturer.id - @answer.owner_type = "lecturer" + @new_answer.owner_id = current_lecturer.id + @new_answer.owner_type = "lecturer" elsif teaching_assistant_signed_in? - @answer = ModelAnswer.new(post_params) - @answer.owner_id = current_teaching_assistant.id - @answer.owner_type = "teaching assistant" + @new_answer.owner_id = current_teaching_assistant.id + @new_answer.owner_type = "teaching assistant" end - if @answer.save - flash[:notice] = "Your Answer is now added" - redirect_to :controller => 'problems', :action => 'edit', :id => @answer.problem_id + if @new_answer.save + flash[:success_creation]= "Answer added." + @problems.model_answers << @new_answer + redirect_to :controller => 'model_answers', :action => 'edit', :id => @new_answer.id else - flash[:notice] = "Your Answer can not be added" - redirect_to :controller => 'problems', :action => 'edit', :id => @answer.problem_id + render :action=>'new', :problem_id => post_params[:problem_id] end end + # [Edit answer story 4.7] + # Answer that has been created before is edited + # Parameters: + # @answer: Answer that is being edited. + # @problem: The problem to which the answer is linked. + # @tips: Tips that are linked to the answer being edited. + # @tips_check: Used to check the type of Hint. + # @hints: Hints that are linked to the answer being edited. + # @hints_check: Used to check the type of Hint. + # Returns: + # A message if the answer is edited and another message if answer was not edited. + # Author: Nadine Adel + def edit + @answer = ModelAnswer.find(params[:id]) + @problem = Problem.find(@answer.problem_id) + @tips = @answer.hints + @tips_check = @answer.hints + @hints = @answer.hints + @hints_check = @answer.hints + end + + # [Edit answer story 4.7] + # Answer is updated in the database. + # Parameters: + # @answer: Answer that is being updated. + # Returns: none + # Author: Nadine Adel + def update + @answer = ModelAnswer.find(params[:id]) + if @answer.update_attributes(post_params) + flash[:notice] = "Your Answer is now updated" + redirect_to :controller => 'problems', :action => 'edit', + :id => session[:problem_id] + else + render :action=>'edit', :problem_id => @answer.problem_id + end + end + # [Add answer story 4.6] # It shows answer that was entered before. # Parameters: - # @answer:previous answer. - # Return : none + # @answer: Previous answer. + # Returns: none # Author: Nadine Adel def show @answer = ModelAnswer.find(params[:problem_id]) @@ -49,21 +97,22 @@ def show # [Add answer story 4.6] # It shows all the answers that are saved in the database. # Parameters: - # @answers:previous answer that are saved in the database. - # Return : none + # @answers: Previous answers that are saved in the database. + # @problem: Problem to which the current answer is added. + # Returns: none # Author: Nadine Adel def index + @problem = Problem.find_by_id(params[:id]) @answers = ModelAnswer.all end # [Add answer story 4.6] # It requires the attributes from the form that we are interested in. - # Parameters: - # @answer:the answer that the user wants to add. - # Return : none + # Parameters: none + # Returns: none # Author: Nadine Adel private def post_params - params.require(:model_answer).permit(:answer, :problem_id) - end + params.require(:model_answer).permit(:title, :answer, :problem_id) + end end \ No newline at end of file diff --git a/tutor/app/controllers/posts_controller.rb b/tutor/app/controllers/posts_controller.rb new file mode 100644 index 00000000..afb13e76 --- /dev/null +++ b/tutor/app/controllers/posts_controller.rb @@ -0,0 +1,132 @@ + class PostsController < ApplicationController + + # [Add Post - Story 1.13] + # Description: This action takes the passed discussion board id and assigns + # the discussion board with that id to an instance variable. + # Parameters: + # params[:discussion_board_id]: The discussion board id + # Returns: + # none + # Author: Ahmed Atef + def new + @discussion_board = DiscussionBoard.find_by_id(params[:discussion_board_id]) + @new_post = Post.new + end + + # [Edit Post - Story 1.18] + # Description: This action takes the passed post id + # to be passed to the Edit form. + # Parameters: + # params[:id]: The post id + # Returns: + # none + # Author: Ahmed Atef + def edit + @post = Post.find(params[:id]) + end + + # [Delete Post- Story 1.15] + # This action takes the post id, remove it from the database + # and then redirects the user to the show page of the discussion board + # with a "Post successfully Deleted" message. + # Parameters: + # params[:id]: The current post's id + # Returns: + # flash[:notice]: A message indicating the success of the deletion + # Author: Ahmed Atef + def destroy + @disscusion_board = DiscussionBoard.find_by_id + (Post.find_by_id(params[:id]).discussion_board_id) + if Post.find_by_id(params[:id]).destroy + flash[:notice] = "Post successfully Deleted" + redirect_to(:controller => 'discussion_boards', + :action => 'show', :id => @disscusion_board.course_id) + end + end + + # [Add Post - Story 1.13] + # Description: Displays the post that the user chose + # Parameters: + # @post: The current post the user is in. + # @posts = The list of replies of @post + # Returns: The view of the post + # Author: Ahmed Atef + def show + @post = Post.find(params[:id]) + @replies = @post.replies.order("created_at desc") + end + + # [Add Post - Story 1.13] + # Description: This action takes the passed parameters from + # the add post form, creates a new Post record + # and assigns it to the respective discussion board.If the + # creation fails the user is redirected to the form + # Parameters: + # topic_params[]: A list that has all fields entered by the user to in the + # add_post_form + # Returns: + # flash[:notice]: A message indicating the success or failure of the creation + # Author: Ahmed Atef + def create + @new_post = Post.new(post_params) + @new_post.views_count = 0 + if lecturer_signed_in? + current_lecturer.posts << @new_post + elsif teaching_assistant_signed_in? + current_teaching_assistant.posts << @new_post + elsif student_signed_in? + current_student.posts << @new_post + end + @discussion_board = DiscussionBoard.find(discussion_board_params[:discussion_board_id]) + if @new_post.save + @discussion_board.posts << @new_post + redirect_to :controller => 'posts', :action => 'show', :id => @new_post.id + else + if @new_post.errors.any? + flash[:notice] = @new_post.errors.full_messages.first + end + render :action => 'new' + end + end + + # [Edit Post - Story 1.18] + # Description: This action takes the passed parameters from + # the edit post form, updates the passed post parameters.If the + # update fails the user is redirected to the form + # Parameters: + # topic_params[]: A list that has all fields entered by the user to in the + # Edit_post_form + # Returns: + # flash[:notice]: A message indicating the success or failure of the creation + # Author: Ahmed Atef + def update + @post = Post.find(params[:id]) + if @post.update_attributes(post_params) + flash[:notice] = "Post successfully updated" + redirect_to(:action => 'show' ,:id => @post.id) + else + if @post.errors.any? + flash[:notice] = @post.errors.full_messages.first + end + render :action => 'edit' + end + end + + # [Add Post - story 1.13] + # private method. Controls the post form parameters that can be accessed. + # Parameters: + # content: The content of the Post + # title: The title of the Post + # discussion_board_id: hidden field for the discussion board id + # Returns: None + # Author: Ahmed Atef + private + def post_params + params.require(:post).permit(:content,:title) + end + + def discussion_board_params + params.require(:post).permit(:discussion_board_id) + end + +end \ No newline at end of file diff --git a/tutor/app/controllers/problems_controller.rb b/tutor/app/controllers/problems_controller.rb index fbe1a307..d5ba96aa 100644 --- a/tutor/app/controllers/problems_controller.rb +++ b/tutor/app/controllers/problems_controller.rb @@ -5,7 +5,7 @@ class ProblemsController < ApplicationController # id: The problem statement id # Returns: none # Author: MIMI - def show + def show @problem = Problem.find_by_id(params[:id]) if @problem.nil? render "problem_not_found" @@ -19,43 +19,40 @@ def show # [Add Problem - 4.4] # Creates a new record to Problem Table # Parameters: - # title: problem's title through permitCreate action - # description: problem's description through permitCreate action - # Returns: Redirects to edit page on success, refreshes on failure + # title: problem's title through permitCreate action + # description: problem's description through permitCreate action + # Returns: + # Redirects to edit page on success, refreshes on failure # Author: Abdullrahman Elhusseny def create - p = Problem.new(permitCreate) + problem = Problem.new(problem_params) if lecturer_signed_in? - p.owner_id = current_lecturer.id - p.owner_type = "lecturer" + problem.owner_id = current_lecturer.id + problem.owner_type = "lecturer" elsif teaching_assistant_signed_in? - p.owner_id = current_teaching_assistant.id - p.owner_type = "teaching assistant" + problem.owner_id = current_teaching_assistant.id + problem.owner_type = "teaching assistant" end - if p.save - redirect_to :action => "edit", :id => p.id - else - flash.keep[:notice] = "Problem is missing paramaters" + problem.incomplete = true + begin + if problem.save + redirect_to :action => "edit", :id => problem.id + else + flash.keep[:notice] = "Problem is missing paramaters" + redirect_to :back + end + rescue + flash.keep[:notice] = "The track has a problem with the same title" redirect_to :back end end - # [Add Problem - 4.4] - # Passes the input of the form as paramaters for create action to use it - # Parameters: - # title: problem's title - # description: problem's description - # Returns: params to create action - # Author: Abdullrahman Elhusseny - def permitCreate - params.require(:Problem).permit(:title , :description) - end - # [Edit Problem - 4.5] # Shows the problem's title and description (Further development is in Sprint 1) # Parameters: - # id: The id of the problem to be edited or newly created - # Returns: Redirects to edit page on success, refreshes on failure + # id: The id of the problem to be edited or newly created + # Returns: + # Redirects to edit page on success, refreshes on failure # Author: Abdullrahman Elhusseny def edit if lecturer_signed_in? || teaching_assistant_signed_in? @@ -71,14 +68,113 @@ def edit # Checks if a lecturer or TA is signed in and shows the problem's add page(title & description) # on success and renders 404 on failure # Parameters: - # none - # Returns: Redirects to add page on success or 404 on failure + # track_id: The track id of the track that the problem will be added to + # Returns: + # Redirects to add page on success or 404 on failure # Author: Abdullrahman Elhusseny def new if lecturer_signed_in? || teaching_assistant_signed_in? - render ('new') + if params[:id].blank? + render ('public/404') + else + render ('new') + end else render ('public/404') end end + + # [Remove Problem - Story 4.18] + # This action takes the problem id, remove it from the database + # and then redirects the user to the show page of the track that had the problem + # with a "Problem successfully Deleted" message. + # Parameters: + # params[:id]: The current problem's id + # Returns: + # flash[:notice]: A message indicating the success of the deletion + # Author: Ahmed Atef + def destroy + @track = Problem.find_by_id(params[:id]).track_id + if Problem.find_by_id(params[:id]).destroy + flash[:notice] = "Problem successfully Deleted" + redirect_to(:controller => 'tracks', + :action => 'show' ,:id => @track) + end + end + + # [Edit Problem - 4.5] + # Update the problem's title or description + # Parameters: + # problem_params: a problem's title & description + # Returns: + # Refreshes divisions in the page using AJAX without refreshing the whole page + # Author: Abdullrahman Elhusseny + def update + @problem = Problem.find_by_id(params[:id]) + if (problem_params[:title] == @problem.title) + if (problem_params[:description] != @problem.description) + @message = "Description updated" + end + elsif (problem_params[:title] != @problem.title) + @message = "Title updated" + end + begin + if @problem.update_attributes(problem_params) + flash.keep[:notice] = @message + respond_to do |format| + format.html {redirect_to :action => "edit", :id => @problem.id} + format.js + end + else + flash.keep[:notice] = "Update paramater is empty" + @problem = Problem.find_by_id(params[:id]) + respond_to do |format| + format.html {redirect_to :action => "edit", :id => @problem.id} + format.js + end + end + rescue + flash.keep[:notice] = "The track has a problem with the same title" + @problem = Problem.find_by_id(params[:id]) + respond_to do |format| + format.html {redirect_to :action => "edit", :id => @problem.id} + format.js + end + end + end + + # [Edit Problem - 4.5] + # Checks if problem is complete or not by checking the number of test cases and answers + # Parameters: + # problem_id: ID of the problem being edited + # Returns: + # On success redirects to the track page, on failure redirects to the edit page + # Author: Abdullrahman Elhusseny + def done + @problem = Problem.find_by_id(params[:problem_id]) + if @problem.model_answers.empty? || @problem.test_cases.empty? + @failure = true + flash.keep[:notice] = "Problem is incomplete, + please add necessary paramaters or save as incomplete" + redirect_to :action => "edit", :id => @problem.id + else + @problem.incomplete = false + @problem.save + redirect_to :controller => "tracks", :action => "show", :id => @problem.track_id + end + end + + # [Add Problem - 4.4] + # Passes the input of the form as paramaters for create action to use it + # Parameters: + # title: problem's title + # description: problem's description + # Returns: + # Params to create action + # Author: Abdullrahman Elhusseny + private + def problem_params + params.require(:Problem).permit(:title, :description, :track_id) + end + end \ No newline at end of file diff --git a/tutor/app/controllers/solutions_constraints_controller.rb b/tutor/app/controllers/solutions_constraints_controller.rb new file mode 100644 index 00000000..9955a709 --- /dev/null +++ b/tutor/app/controllers/solutions_constraints_controller.rb @@ -0,0 +1,52 @@ +class SolutionsConstraintsController < ApplicationController + + # [Add Solutions' constraints - Story 4.14] + # Description: Creates new record for either method or variable constraints + # Parameters: + # method_cons: Hash containting the Method parameters + # var_cons: Hash containting the Variables Constraints + # method: value of the method name. + # method_returned: value of the method return type + # Returns: none + # Author: Ahmed Mohamed Magdi + def create + method_cons = params[:parameter_constraint] + var_cons = params[:variable_constraint] + method = params[:method_name] + method_returned = params[:method_return] + unless method == "" + constarint = MethodConstraint.new + constarint.method_name = method + constarint.method_return = method_returned + if method_cons.present? + method_cons.each do |index,value| + parameters = MethodParameter.new + parameters.parameter = value[:name] + parameters.params_type = value[:type] + parameters.save + constarint.parameters << parameters + end + end + constarint.save + end + if var_cons.present? + var_cons.each do |index,value| + variable = VariableConstraint.new + variable.variable_name = var_cons[index][:name] + variable.variable_type = var_cons[index][:type] + variable.save + end + end + render json: true + end + + # [Add Solutions' constraints - Story 4.14] + # Description: init new record + # Parameters: none + # Returns: none + # Author: Ahmed Mohamed Magdi + def new + constrain = MethodConstraint.new + end + +end diff --git a/tutor/app/controllers/solutions_controller.rb b/tutor/app/controllers/solutions_controller.rb index e94720f9..1cc642e6 100644 --- a/tutor/app/controllers/solutions_controller.rb +++ b/tutor/app/controllers/solutions_controller.rb @@ -9,18 +9,14 @@ class SolutionsController < ApplicationController # Author: MOHAMEDSAEED def create if params[:commit] == 'Submit' - @solution = Solution.new(solution_params) - @solution.student_id = current_student.id - @solution.status = 0 - @solution.length = @solution.code.length - if @solution.save - flash[:success] = "Your solution has been submitted successfully" - else - flash[:alert] = "Blank submissions are not allowed!!!" + compile_solution + if flash[:compiler_fail] || flash[:alert] + redirect_to :back and return end - redirect_to :back + submit_no_ajax elsif params[:commit] == 'Compile' compile_solution + redirect_to :back and return elsif params[:commit] == 'Run Test Case' compile_solution if flash[:compiler_fail] || flash[:alert] @@ -61,6 +57,7 @@ def compile_solution @solution = Solution.new(solution_params) @solution.student_id = current_student.id @solution.length = @solution.code.length + @solution.status = 0 if @solution.save compiler_feedback = Compiler.compiler_feedback(@solution) if compiler_feedback[:success] @@ -79,28 +76,40 @@ def compile_solution end end + def submit_no_ajax + file = @solution.file_name + response_message = Solution.validate(file, solution_params[:problem_id]) + flash[:compiler_success_2] = response_message[:success] + flash[:msg_2] = response_message[:runtime_error] + flash[:exp_2] = response_message[:runtime_error_exp] + flash[:compiler_fail_2] = response_message[:logic_error] + @solution.status = response_message[:status] + @solution.save + redirect_to :back + end + private - # [Code Editor: Write Code - Story 3.3] - # Permits the id of the problem, code from the form_for - # Parameters: - # code: The written code for the problem - # problem_id: Hidden field for problem id - # Returns: - # none - # Author: MOHAMEDSAEED - def solution_params - params.require(:solution).permit(:code, :problem_id) - end + # [Code Editor: Write Code - Story 3.3] + # Permits the id of the problem, code from the form_for + # Parameters: + # code: The written code for the problem + # problem_id: Hidden field for problem id + # Returns: + # none + # Author: MOHAMEDSAEED + def solution_params + params.require(:solution).permit(:code, :problem_id) + end - # [Compiler: Test - Story 3.15] - # Permits the input - # Parameters: - # none - # Returns: - # params[:input]: The test case entered by the solver - # Author: Ahmed Akram - def input - params.require(:solution).permit(:input) - end + # [Compiler: Test - Story 3.15] + # Permits the input + # Parameters: + # none + # Returns: + # params[:input]: The test case entered by the solver + # Author: Ahmed Akram + def input + params.require(:solution).permit(:input) + end -end \ No newline at end of file +end diff --git a/tutor/app/controllers/students_controller.rb b/tutor/app/controllers/students_controller.rb new file mode 100644 index 00000000..ed4766f4 --- /dev/null +++ b/tutor/app/controllers/students_controller.rb @@ -0,0 +1,12 @@ +class StudentsController < ApplicationController + # [Profile - Story 5.8] + # Displays the profile of the student chosen + # Parameters: + # id: the Student's id + # Returns: none + # Author: Serag + def show + @student = Student.find(params[:id]) + @courses = @student.courses.order("created_at desc") + end +end \ No newline at end of file diff --git a/tutor/app/controllers/teaching_assistants_controller.rb b/tutor/app/controllers/teaching_assistants_controller.rb new file mode 100644 index 00000000..bb893dd7 --- /dev/null +++ b/tutor/app/controllers/teaching_assistants_controller.rb @@ -0,0 +1,69 @@ +class TeachingAssistantsController < ApplicationController + # [Profile - Story 5.8] + # Displays the profile of the teaching assistant chosen + # Parameters: + # id: the Teaching Assistant's id + # Returns: none + # Author: Serag + def show + @teaching_assistant = TeachingAssistant.find(params[:id]) + @courses = @teaching_assistant.courses.order("created_at desc") + end + + # [Add TA - Story 1.4] + # this action renders the form and sets the value for checkbox + # Parameters: + # check : contains the value from the previous instance of the form + # Returns: None + # Author: Muhammad Mamdouh + def new + @course = Course.find_by_id(params[:course_id]) + if !@course.can_edit(current_lecturer) + redirect_to :root + end + if @checkbox == nil + @checkbox = true + end + @checkbox = !params[:check] + end + + # [Add TA - Story 1.4,1.5,1.6] + # Adds the TA selected from the dropdown list in the view to the course and to the lecturer's history + # Parameters: teaching_assistant_id , course_id + # Returns: flash[:notice] that shows the result of trying to add the TA + # Author: Muhammad Mamdouh + def create + begin + @teaching_assistant = TeachingAssistant.find_by_id(params[:teaching_assistant][:id]) + if params[:teaching_assistant][:id] == '' + flash[:failure_notice] = 'Error! you need to select a TA' + redirect_to :action => 'new' + else + @course = Course.find_by_id(params[:course_id]) + @course.TAs << @teaching_assistant + flash[:success_notice] = 'TA added!' + @notification = NotificationMail.new + @notification.subject = 'Invitation to join tutor' + @notification.email = @teaching_assistant.email + @notification.content = 'You have been added to a course on Tutor!' + @notification.save + redirect_to :action => 'index' + end + rescue + flash[:failure_notice] = 'Error! TA is already added to the course.' + redirect_to :action => 'new' + end + end + + # [Add TA - Story 1.4, 1.5, 1.6] + # lists the TAs added to the course. + # Parameters: course_id + # Returns: + # @course + # @course_teaching_assistants + # Author: Muhammad Mamdouh + def index + @course = Course.find(params[:course_id]) + @course_teaching_assistants = @course.TAs.order('name') + end +end \ No newline at end of file diff --git a/tutor/app/controllers/topics_controller.rb b/tutor/app/controllers/topics_controller.rb index e42b836a..51504a49 100644 --- a/tutor/app/controllers/topics_controller.rb +++ b/tutor/app/controllers/topics_controller.rb @@ -43,6 +43,9 @@ def show # Author: Ahmed Akram def new @course = Course.find(params[:course_id]) + if !@course.can_edit(current_lecturer) + redirect_to :root + end @new_topic = Topic.new end diff --git a/tutor/app/controllers/tracks_controller.rb b/tutor/app/controllers/tracks_controller.rb index 51287bdc..31dc004e 100644 --- a/tutor/app/controllers/tracks_controller.rb +++ b/tutor/app/controllers/tracks_controller.rb @@ -8,11 +8,11 @@ class TracksController < ApplicationController # Author: Mussab ElDash def show id = params[:id] - track = Track.find_by_id(id) - if track - @topic = track.topic + @track = Track.find_by_id(id) + if @track + @topic = @track.topic @course = @topic.course - @problems = track.problems + @problems = @track.problems @can_edit = @course.can_edit(current_lecturer) @can_edit||= @course.can_edit(current_teaching_assistant) if student_signed_in? @@ -68,6 +68,57 @@ def create end end + # [Recommendatio to students - Story 5.7] + # Gets students who can be recommended a problem of id :id + # Parameters: none + # Returns: json containing a Hash of classmates + # Author: Mohab Ghanim + def show_classmates + problem = Problem.find_by_id(params[:id]) + track = problem.track + topic = problem.track.topic + course = problem.track.topic.course + students_enrolled = course.students + students_receiving_recommendation = Hash.new + + students_enrolled.each do |student| + student_level = TrackProgression.get_progress(student.id,topic.id) + if (student_level == track.difficulty) + students_receiving_recommendation[student.id] = Hash.new + students_receiving_recommendation[student.id]['student_name'] = student.name + students_receiving_recommendation[student.id]['recommended_before'] = 'false' + if (Recommendation.where( + :problem_id => problem.id, + :recommender_id => current_student.id, + :student_id => student.id).present?) + students_receiving_recommendation[student.id]['recommended_before'] = 'true' + end + end + end + render json: students_receiving_recommendation + end + + # [Recommendation to students - Story 5.7] + # Inserts a record in the recommendation table containing the id of the problem, + # the id of the students recommending the problem, the id of the student recieving + # the recommendation + # Parameters: none + # Returns: none + # Author: Mohab Ghanim + def insert_recommendation + problem_id = params[:p_id] + student_id = params[:s_id] + recommender_id = params[:r_id] + + recommendation = Recommendation.new + recommendation.problem_id = problem_id + recommendation.student_id = student_id + recommendation.recommender_id = recommender_id + recommendation.save + + render :nothing => true + end + # [Create Track - Story 4.1] # permits the passed parameters # Parameters: diff --git a/tutor/app/controllers/utilities_controller.rb b/tutor/app/controllers/utilities_controller.rb new file mode 100644 index 00000000..2f31e5a1 --- /dev/null +++ b/tutor/app/controllers/utilities_controller.rb @@ -0,0 +1,62 @@ +class UtilitiesController < ApplicationController + + # [Simple Search - Story 1.22] + # search for users and courses + # Parameters: search + # Returns: A hashes with search results according to the keyword + # Author: Ahmed Elassuty + def simple_search + @lecturers = Lecturer.simple_search(params[:search]) + @students = Student.simple_search(params[:search]) + @teaching_assisstants = TeachingAssistant.simple_search(params[:search]) + @courses = Course.simple_search(params[:search]) + respond_to do |format| + format.js + format.html + end + end + + # [Advanced Search - Story 1.23] + # search for users and courses and topics + # Parameters: search options + # Returns: A hashes with search results according to the keyword + # Author: Ahmed Elassuty + def advanced_search + if params[:lecturers].present? + @lecturers = Lecturer.search(params) + end + if params[:students].present? + @students = Student.search(params) + end + if params[:teaching_assistants].present? + @teaching_assistants = TeachingAssistant.search(params) + end + if params[:topics].present? + @topics = Topic.search(params) + end + if params[:courses].present? + @courses = Course.search(params) + end + if params[:posts].present? + @posts = Post.search(params) + end + respond_to do |format| + format.html + format.js + end + end + + # [Simple Search - Story 1.22] + # auto complete search keyword for users and courses + # Parameters: search + # Returns: A hashes with search results according to the keyword + # Author: Ahmed Elassuty + def auto_complete + @objects = Lecturer.simple_search(params[:q]).take(1) + respond_to do |format| + format.json {render :template => 'utilities/auto_complete', + :formats => [], :handlers => [:json_builder], :layout=>false} + end + end + +end diff --git a/tutor/app/helpers/acknowledgements_helper.rb b/tutor/app/helpers/acknowledgements_helper.rb new file mode 100644 index 00000000..cb65d0dd --- /dev/null +++ b/tutor/app/helpers/acknowledgements_helper.rb @@ -0,0 +1,2 @@ +module AcknowledgementsHelper +end diff --git a/tutor/app/helpers/application_helper.rb b/tutor/app/helpers/application_helper.rb index de6be794..f208731f 100644 --- a/tutor/app/helpers/application_helper.rb +++ b/tutor/app/helpers/application_helper.rb @@ -1,2 +1,11 @@ module ApplicationHelper + + # [Simple Search - Story 1.22] + # helper to handle ajax search + # Parameters: string controller, string action + # Returns: boolean if the requested controller equals the passed one and also the action + # Author: Ahmed Elassuty + def controller?(controller,action) + params[:controller].include?controller and params[:action].include?action + end end diff --git a/tutor/app/helpers/posts_helper.rb b/tutor/app/helpers/posts_helper.rb new file mode 100644 index 00000000..a7b8cec8 --- /dev/null +++ b/tutor/app/helpers/posts_helper.rb @@ -0,0 +1,2 @@ +module PostsHelper +end diff --git a/tutor/app/helpers/solutions_constraints_helper.rb b/tutor/app/helpers/solutions_constraints_helper.rb new file mode 100644 index 00000000..e620299b --- /dev/null +++ b/tutor/app/helpers/solutions_constraints_helper.rb @@ -0,0 +1,2 @@ +module SolutionsConstraintsHelper +end diff --git a/tutor/app/helpers/teaching_assistants_helper.rb b/tutor/app/helpers/teaching_assistants_helper.rb new file mode 100644 index 00000000..ea17ca7d --- /dev/null +++ b/tutor/app/helpers/teaching_assistants_helper.rb @@ -0,0 +1,2 @@ +module TeachingAssistantsHelper +end diff --git a/tutor/app/helpers/utilities_helper.rb b/tutor/app/helpers/utilities_helper.rb new file mode 100644 index 00000000..935bf563 --- /dev/null +++ b/tutor/app/helpers/utilities_helper.rb @@ -0,0 +1,2 @@ +module UtilitiesHelper +end diff --git a/tutor/app/mailers/system_reminders.rb b/tutor/app/mailers/system_reminders.rb new file mode 100644 index 00000000..b738be1a --- /dev/null +++ b/tutor/app/mailers/system_reminders.rb @@ -0,0 +1,14 @@ +class SystemReminders < ActionMailer::Base + default from: "from@example.com" + # [System Reminders - Story 5.4] + # Sends reminder e-mail to given user + # Parameters: + # user: user instance + # Returns: none + # Author: Amir George + def reminder_email(user) + @user = user + @url = 'http://localhost:3000' + mail(to: @user.email, subject: 'We miss you at our Awesome Tutors Website') + end +end diff --git a/tutor/app/models/concerns/searchable.rb b/tutor/app/models/concerns/searchable.rb new file mode 100644 index 00000000..eab31831 --- /dev/null +++ b/tutor/app/models/concerns/searchable.rb @@ -0,0 +1,17 @@ +module Searchable + extend ActiveSupport::Concern + + #class Methods + + # [Simple Search - Story 1.22] + # search for users + # Parameters: keyword + # Returns: A hash with search results according to the keyword + # Author: Ahmed Elassuty + module ClassMethods + def simple_search(keyword) + self.where("name LIKE ? or email LIKE ?", "%#{keyword}%", "%#{keyword}%") if keyword.present? + end + end + +end \ No newline at end of file diff --git a/tutor/app/models/course.rb b/tutor/app/models/course.rb index 908d59ed..19a510b4 100644 --- a/tutor/app/models/course.rb +++ b/tutor/app/models/course.rb @@ -1,5 +1,9 @@ class Course < ActiveRecord::Base + #Elasticsearch + include Tire::Model::Search + include Tire::Model::Callbacks + #Validations validates :name, presence: true validates :description, presence: true @@ -11,12 +15,14 @@ class Course < ActiveRecord::Base #Relations has_and_belongs_to_many :TAs, class_name:"TeachingAssistant", join_table: "courses_teaching_assistants" has_and_belongs_to_many :lecturers, join_table: "courses_lecturers" - has_and_belongs_to_many :students, join_table: "courses_students" has_one :discussion_board, dependent: :destroy has_many :topics, dependent: :destroy has_many :acknowledgements, dependent: :destroy - + + has_many :course_students + has_many :students, through: :course_students + #Scoops #Methods @@ -33,4 +39,41 @@ def can_edit(user) end end + # [Simple Search - Story 1.22] + # search for courses + # Parameters: keyword + # Returns: A hash with search results according to the keyword + # Author: Ahmed Elassuty + def self.simple_search(keyword) + where("name LIKE ? or code LIKE ?", "%#{keyword}%", "%#{keyword}%") if keyword.present? + end + + # [Advanced Search - Story 1.23] + # search for courses + # Parameters: hash of search options + # Returns: A hash with search results according to the keyword and other options + # Author: Ahmed Elassuty + def self.search(params) + if params[:keyword].present? + case params[:options] + when "exactly match" + tire.search do + query { string "name:#{params[:keyword]}" } + end + when "includes" + tire.search do + query { string "name:*#{params[:keyword]}*" } + end + when "starts with" + tire.search do + query { string "name:#{params[:keyword]}*" } + end + when "ends with" + tire.search do + query { string "name:*#{params[:keyword]}" } + end + end + end + end + end diff --git a/tutor/app/models/course_student.rb b/tutor/app/models/course_student.rb new file mode 100644 index 00000000..5f33962d --- /dev/null +++ b/tutor/app/models/course_student.rb @@ -0,0 +1,12 @@ +class CourseStudent < ActiveRecord::Base + + #Validations + + #Relations + belongs_to :course + belongs_to :student + #Scoops + + #Methods + +end diff --git a/tutor/app/models/debugger.rb b/tutor/app/models/debugger.rb index bf48d059..23fa4028 100644 --- a/tutor/app/models/debugger.rb +++ b/tutor/app/models/debugger.rb @@ -1,13 +1,8 @@ require "open3" class Debugger < ActiveRecord::Base - #Validations - - #Relations - - #Scoops - #Methods + # [Debugger: Debug - Story 3.6] # Gets the output from the output stream of the debugger # until the passed regex is encountered @@ -65,29 +60,29 @@ def input(input) # input : The arguments to be passed to the main method # Returns: A List of all 100 steps ahead # Authors: Mussab ElDash + Rami Khalil - def start(file_path, input) - to_be_compiled = file_path - if file_path =~ /.*\.java/ - file_path = file_path[0..-6] - else - to_be_compiled = file_path + ".java" - end - if !system("javac -g " + to_be_compiled) - puts "Compilation Error" - exit - end + def start(class_name, input) $all = [] + Dir.chdir(Solution::CLASS_PATH){ + begin + $input, $output, $error, $wait_thread = Open3.popen3("jdb", class_name, *input) + buffer_until_ready + input "stop in #{class_name}.main" + buffer_until_ready + input "run" + num = get_line + locals = get_variables + hash = {:line => num, :locals => locals} + $all << hash + debug + rescue => e + unless e.message === 'Exited' + p e.message + return false + end + end + } begin - $input, $output, $error, $wait_thread = Open3.popen3("jdb",file_path, input.strip) - buffer_until_ready - input "stop in #{file_path}.main" - buffer_until_ready - input "run" - num = get_line - # locals = get_variables - hash = {:line => num, :locals => []} - $all << hash - debug + Process.kill("TERM", $wait_thread.pid) rescue => e p e.message end @@ -105,14 +100,13 @@ def debug begin input "step" num = get_line - # locals = get_variables - hash = {:line => num, :locals => []} + locals = get_variables + hash = {:line => num, :locals => locals} $all << hash counter += 1 rescue => e - puts e.message $input.close - puts "closed" + raise 'Exited' end end end @@ -124,13 +118,19 @@ def debug # Author: Mussab ElDash def get_line out_stream = buffer_until_complete - puts out_stream list_of_lines = out_stream.split(/\n+/) + before_last_line = list_of_lines[-2] + /, line=\d+/ =~ before_last_line + before_last_regex_capture = $& + /\d+/ =~ before_last_regex_capture + before_last_regex_capture = $& last_line = list_of_lines[-2] /^\d+/=~ last_line - regex_capture = $& - if regex_capture - return regex_capture.to_i + last_regex_capture = $& + if last_regex_capture + return last_regex_capture.to_i + elsif before_last_regex_capture + return before_last_regex_capture.to_i else raise 'Exited' end @@ -146,25 +146,140 @@ def get_line # Returns: The result of the debugging # Author: Mussab ElDash def self.debug(student_id, problem_id, code, input) - code = change_class_name(student_id, problem_id, code) - file_name = 'st' + student_id.to_s + 'pr' + problem_id.to_s - File.open("#{file_name}.java", 'w') { |file| file.write(code) } - # return {done: "Done"} + solution = Solution.create({code: code, student_id: student_id, + problem_id: problem_id}) + compile_status = Compiler.compiler_feedback(solution) + unless compile_status[:success] + return {:success => false, data: compile_status} + end debugger = Debugger.new - return debugger.start(file_name, input) + class_name = solution.class_file_name + debugging = debugger.start(class_name, input.split(" ")) + java_file = solution.java_file_name true, true + class_file = solution.class_file_name true, true + File.delete(java_file) + File.delete(class_file) + return {:success => true, data: debugging} end - # [Debugger: Debug - Story 3.6] - # Renames the class name to be compiled and debugged correctly + # [Debugger: View Variables - Story 3.7] + # Takes a line containing an object assignment and extracts + # the object name # Parameters: - # Student_id: The id of the current signed in student - # problem_id: The id of the problem being solved - # code: The code to be debugged - # Returns: The modified code - # Author: Mussab ElDash - def self.change_class_name(student_id, problem_id, code) - name = 'st' + student_id.to_s + 'pr' + problem_id.to_s - return code.sub(code[0..code.index('{')], 'public class ' + name + ' {') + # variable: A string containing an object assignment + # Returns: + # A string. It contains the object name + # Author: Khaled Helmy + def get_name variable + name = variable.split(" = ").first + return name + end + + # [Debugger: View Variables - Story 3.7] + # Takes an object and evaluates its value + # Parameters: + # variable: a string containing an instance object + # Returns: + # An array. It contains the values inside an object + # Author: Khaled Helmy + def get_value variable + result = [] + if variable.match("instance") + variable_name = get_name variable + input "dump " + variable_name + output_buffer1 = buffer_until_complete.split("main").first + input "print " + variable_name + output_buffer2 = buffer_until_complete.split("main").first + unless output_buffer1.match("instance") + result << output_buffer1 + end + if output_buffer1 != output_buffer2 + unless output_buffer2.match("instance") + result << output_buffer2 + end + end + else + result << variable + end + return result + end + + # [Debugger: View Variables - Story 3.7] + # Return the list of instance and static variables of the + # class from within static methods + # Parameters: none + # Returns: + # An array. It contains the list of instance and static + # variables and their associated values + # Author: Khaled Helmy + def get_class_variables + result = [] + flag = 0 + input "print this.getClass().getName()" + output_buffer = buffer_until_complete + output_buffer.each_line do |line| + if flag == 1 + class_name = output_buffer.split(" = ").last.split("main").first + class_name = class_name[1..(class_name.size-3)] + input "fields " << class_name + fields_list = buffer_until_complete + fields_list.each_line do |field| + if field.match("fields list") or field.match("main") + next + else + field_name = field.split(" ").last + input "print this." + field_name + field_result = buffer_until_complete + field_result = field_result.split(".").last.split("main").first + result << field_result + end + end + else + if line.match("Exception") + break + else + flag = 1 + end + end + end + return result + end + + # [Debugger: View Variables - Story 3.7] + # Fetches the variables found in the class and returns + # a list of all variables in the class with their + # associated values + # Parameters: none + # Returns: + # An array. It contains the list of variables and their values + # Author: Khaled Helmy + def get_variables + method_arguments = [] + local_variables = [] + class_variables = get_class_variables + flag = 0 + input "locals" + output_buffer = buffer_until_complete + output_buffer.each_line do |line| + if line.match("Method arguments:") + flag = 1 + next + elsif line.match("Local variables:") + flag = 2 + next + elsif line.match("main") + break + end + if flag != 0 + variable_value = get_value line + if flag == 1 + method_arguments = method_arguments + variable_value + else + local_variables = local_variables + variable_value + end + end + end + return method_arguments + local_variables + class_variables end end diff --git a/tutor/app/models/lecturer.rb b/tutor/app/models/lecturer.rb index a9ba664d..b1e3c5e7 100644 --- a/tutor/app/models/lecturer.rb +++ b/tutor/app/models/lecturer.rb @@ -1,21 +1,39 @@ class Lecturer < ActiveRecord::Base - # Include default devise modules. Others available are: - # :confirmable, :lockable, :timeoutable and :omniauthable + devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :trackable, :validatable + :recoverable, :rememberable, :trackable, :validatable, :confirmable + + #Elasticsearch + include Tire::Model::Search + include Tire::Model::Callbacks + + #concerns + include Searchable + + #Uploader + # mount_uploader :profile_image, ProfileImageUploader #Validations + validate :duplicate_email + validates :name, presence: true + validates_format_of :name, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :degree, presence: true + validates_format_of :degree, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :university, presence: true + validates_format_of :university, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :department, presence: true + validates_format_of :department, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :dob, presence: true #Relations has_and_belongs_to_many :courses, join_table: "courses_lecturers" - has_and_belongs_to_many :worked_with, class_name:"TeachingAssistant", join_table: "lecturers_teaching_assistants" + has_and_belongs_to_many :worked_with, class_name:"TeachingAssistant", join_table: "lecturers_teaching_assistants" has_many :posts, as: :owner, dependent: :destroy has_many :replies, as: :owner, dependent: :destroy - has_many :topics has_many :tracks, as: :owner - has_many :problems, as: :ownerm + has_many :problems, as: :owner has_many :model_answers, as: :owner has_many :method_constraints, as: :owner has_many :method_parameters, as: :owner @@ -23,9 +41,46 @@ class Lecturer < ActiveRecord::Base has_many :test_cases, as: :owner has_many :hints, as: :owner has_many :acknowledgements, dependent: :destroy - - #Scoops - + #Methods -end + # [Advanced Search - Story 1.23] + # search for lecturers + # Parameters: hash of search options + # Returns: A hash with search results according to the keyword and other options + # Author: Ahmed Elassuty + def self.search(params) + if params[:keyword].present? + case params[:options] + when "exactly match" + tire.search do + query { string "name:#{params[:keyword]}" } + end + when "includes" + tire.search do + query { string "name:*#{params[:keyword]}*" } + end + when "starts with" + tire.search do + query { string "name:#{params[:keyword]}*" } + end + when "ends with" + tire.search do + query { string "name:*#{params[:keyword]}" } + end + end + end + end + # [User Authentication Advanced - Story 5.9, 5.10, 5.11, 5.14, 5.15] + # Checks if the email is already registered in tables: Student and TeachingAssistant + # before registering the email for table: Lecturer + # Parameters: None + # Returns: None + # Author: Khaled Helmy + def duplicate_email + if Student.find_by email: email or TeachingAssistant.find_by email: email + errors.add(:email, "has already been taken") + end + end + +end \ No newline at end of file diff --git a/tutor/app/models/method_constraint.rb b/tutor/app/models/method_constraint.rb index f13ce249..0b0a952b 100644 --- a/tutor/app/models/method_constraint.rb +++ b/tutor/app/models/method_constraint.rb @@ -6,7 +6,7 @@ class MethodConstraint < ActiveRecord::Base belongs_to :model_answer belongs_to :owner, polymorphic: true - has_many :method_parameter, dependent: :destroy + has_many :parameters, class_name:"MethodParameter", dependent: :destroy #Scoops diff --git a/tutor/app/models/model_answer.rb b/tutor/app/models/model_answer.rb index 7297f7fb..92ade994 100644 --- a/tutor/app/models/model_answer.rb +++ b/tutor/app/models/model_answer.rb @@ -1,8 +1,8 @@ class ModelAnswer < ActiveRecord::Base #Validations - validates :answer , :presence => true - validates :answer , :length => { :minimum => 1 } + validates :title, presence: true + validates :answer, presence: true #Relations belongs_to :problem diff --git a/tutor/app/models/post.rb b/tutor/app/models/post.rb index da15e87a..844360c1 100644 --- a/tutor/app/models/post.rb +++ b/tutor/app/models/post.rb @@ -1,6 +1,11 @@ class Post < ActiveRecord::Base - + + #Elasticsearch + include Tire::Model::Search + include Tire::Model::Callbacks + #Validations + validates :title, :content, presence: true #Relations belongs_to :owner, polymorphic: true @@ -12,4 +17,31 @@ class Post < ActiveRecord::Base #Methods + # [Advanced Search - Story 1.23] + # search for posts + # Parameters: hash of search options + # Returns: A hash with search results according to the keyword and other options + # Author: Ahmed Elassuty + def self.search(params) + if params[:keyword].present? + case params[:options] + when "exactly match" + tire.search do + query { string "title:#{params[:keyword]}" } + end + when "includes" + tire.search do + query { string "title:*#{params[:keyword]}*" } + end + when "starts with" + tire.search do + query { string "title:#{params[:keyword]}*" } + end + when "ends with" + tire.search do + query { string "title:*#{params[:keyword]}" } + end + end + end + end end diff --git a/tutor/app/models/solution.rb b/tutor/app/models/solution.rb index b896f917..ff4a5a95 100644 --- a/tutor/app/models/solution.rb +++ b/tutor/app/models/solution.rb @@ -6,10 +6,66 @@ class Solution < ActiveRecord::Base #Relations belongs_to :student belongs_to :problem - - #Scoops #Methods + # [Compiler: Validate - Story 3.5] + # Checks the validity of a submitted solution + # and show the runtime and logic errors if exist + # Parameters: + # problem_id: id of the problem being answered + # file: the name of the file which is compiled successfully + # without errors + # Returns: a hash response containing status for the solution, + # solution errors or success message. + # Author: MOHAMEDSAEED + def self.validate(file, problem_id) + response = {status: 0, success: [], runtime_error: [], runtime_error_exp: [], + logic_error: []} + testcases = Problem.find_by_id(problem_id).test_cases + testcases.each do |testcase| + input = testcase.input + expected_output = testcase.output + runtime_check = Executer.execute(file, input, problem_id) + if(runtime_check) + output = Executer.get_output() + if (output != expected_output) + response[:logic_error] << "Logic error: for input: " + + input + " ,expected output: " + + expected_output + " but your output was: " + output + unless response[:status] == 4 + response[:status] = 5 + end + else + unless(response[:status] == 4 | 5) + response[:status] = 1 + end + end + else + runtime_error = Executer.get_runtime_error(file, 'CoolSoft') + runtime_error[:error] = "for input: " + input + " " + runtime_error[:error] + response[:status] = 4 + response[:runtime_error] << runtime_error[:error] + response[:runtime_error_exp] << runtime_error[:explanation] + end + end + if response[:status] == 1 + response[:success] << "Your Solution is correct, Passed" + end + return response + end + + # [Compiler: Validate - Story 3.5] + # Parameters: + # s_id : the id of the current Student + # p_id : the id of the current Problem + # Returns: the number of trials the student made for this problem + # Author: MOHAMEDSAEED + def self.get_num_of_trials(s_id , p_id) + num_of_trials = Solution.distinct.count(:all, + :conditions => ["student_id = ? AND problem_id = ? AND status != ?", + s_id, p_id, 3]) + end + # [ # Compiler: Compile - Story 3.4 # Compiler: Validate - Story 3.5 @@ -33,7 +89,7 @@ def file_name # ] # Returns the java file name associated with this solution code. # Parameters: - # prepend_path: A boolean value deciding whether the java file folder path should be prepended. + # prepend_path: A boolean value deciding whether the java file folder path should be prepended. # append_extension: A boolean value deciding whether the file extension should be appended. # Returns: The solution's java file name # Author: Rami Khalil @@ -52,7 +108,7 @@ def java_file_name(prepend_path = false, append_extension = false) # ] # Returns the class file name associated with this solution code. # Parameters: - # prepend_path: A boolean value deciding whether the java file folder path should be prepended. + # prepend_path: A boolean value deciding whether the java file folder path should be prepended. # append_extension: A boolean value deciding whether the file extension should be appended. # Returns: The solution's class file name # Author: Rami Khalil @@ -64,9 +120,12 @@ def class_file_name(prepend_path = false, append_extension = false) end #Constants - STATUS_SUBMITTED = 0 + STATUS_SUBMITTED = 0 STATUS_ACCEPTED = 1 - + STATUS_COMPILED_WITH_ERRORS = 2 + STATUS_COMPILED_WITHOUT_ERRORS = 3 + STATUS_EXECUTED_WITH_RUNTIME_ERRORS = 4 + STATUS_EXECUTED_WITH_LOGIC_ERRORS = 5 JAVA_PATH = 'students_solutions/Java/' CLASS_PATH = 'students_solutions/Class/' diff --git a/tutor/app/models/student.rb b/tutor/app/models/student.rb index 622d04b0..3072902f 100644 --- a/tutor/app/models/student.rb +++ b/tutor/app/models/student.rb @@ -1,10 +1,33 @@ class Student < ActiveRecord::Base - # Include default devise modules. Others available are: - # :confirmable, :lockable, :timeoutable and :omniauthable + + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :trackable, :validatable + + #Elasticsearch + include Tire::Model::Search + include Tire::Model::Callbacks + + #concerns + include Searchable + devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :trackable, :validatable + :recoverable, :rememberable, :trackable, + :validatable, :confirmable + + # mount_uploader :profile_image, ProfileImageUploader #Validations + validate :duplicate_email + validates :name, presence: true + validates_format_of :name, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :university, presence: true + validates_format_of :university, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :faculty, presence: true + validates_format_of :faculty, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :major, presence: true + validates_format_of :major, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :semester, presence: false, numericality: {only_integer: true, greater_than: 0, less_than: 11} + validates :dob, presence: true #Relations has_many :solutions, dependent: :destroy @@ -17,11 +40,23 @@ class Student < ActiveRecord::Base has_many :recommendations has_many :recommended_problems, class_name: 'Problem', through: :recommendations, source: :problem - has_and_belongs_to_many :courses, join_table: 'courses_students' - - #Scoops + has_many :course_students + has_many :courses, through: :course_students, dependent: :destroy #Methods + + # [User Authentication Advanced - Story 5.9, 5.10, 5.11, 5.14, 5.15] + # Checks if the email is already registered in tables: Lecturer and TeachingAssistant + # before registering the email for table: Student + # Parameters: None + # Returns: None + # Author: Khaled Helmy + def duplicate_email + if Lecturer.find_by email: email or TeachingAssistant.find_by email: email + errors.add(:email, "has already been taken") + end + end + # [Find Recommendations - Story 3.9] # Returns a suggested problem to solve for this user # Parameters: None @@ -29,26 +64,21 @@ class Student < ActiveRecord::Base # Author: Rami Khalil def get_a_system_suggested_problem suggestions = Set.new - courses.each do |course| course.topics.each do |topic| level = TrackProgression.get_progress(self.id, topic.id) - topic.tracks.each do |track| - if(track.difficulty == level) - track.problems.each do |problem| - if(!problem.is_solved_by_student(self.id)) - suggestions.add(problem) - break - end + if(track.difficulty == level) + track.problems.each do |problem| + if !problem.is_solved_by_student(self.id) + suggestions.add(problem) + break end end + end end end end - - # Convert suggestions from set to array - # Return random element from array return suggestions.to_a().sample() end @@ -76,5 +106,94 @@ def getProblemsStatus end return res end -end + # [Advanced Search - Story 1.23] + # search for students + # Parameters: hash of search options + # Returns: A hash with search results according to the keyword and other options + # Author: Ahmed Elassuty + def self.search(params) + if params[:keyword].present? + case params[:options] + when "exactly match" + tire.search do + query { string "name:#{params[:keyword]}" } + end + when "includes" + tire.search do + query { string "name:*#{params[:keyword]}*" } + end + when "starts with" + tire.search do + query { string "name:#{params[:keyword]}*" } + end + when "ends with" + tire.search do + query { string "name:*#{params[:keyword]}" } + end + end + end + end + + # [Problem Assined - Story 5.5] + # Returns a Hash containing the next problem to solve in each course - topic - track + # Parameters: None + # Returns: A Hash of key as 'Course-Topic-track' and value as a Problem model instance + # Author: Mohab Ghanim (Modified from Rami Khalil's Story 3.9) + def get_next_problems_to_solve + next_problems_to_solve = Hash.new + courses.each do |course| + course.topics.each do |topic| + level = TrackProgression.get_progress(self.id, topic.id) + topic.tracks.each do |track| + if track.difficulty == level + track.problems.each do |problem| + if !problem.is_solved_by_student(self.id) + key = course.name + key += " - " + topic.title + key += " - " + track.title + next_problems_to_solve[key] = problem + break + end + end + end + end + end + end + return next_problems_to_solve + end + + # [Get Recommended Problems - Story 5.6] + # Gets the recommended problems for this student by classmates + # Parameters: none + # Returns: A hash with 'problem_id' as a key and a value of a hash containing + # 'recommender_name' and 'problem_title' + # Author: Mohab Ghanim + def getClassMatesRecommendations + recommended_problems = Recommendation.where(:student_id => self.id) + recommended_problems_hash = Hash.new + recommended_problems.each do |problem| + recommended_problems_hash[problem.problem_id] = Hash.new + recommender_name = Student.where(:id => problem.recommender_id).take.name + problem_title = Problem.where(:id => problem.problem_id).take.title + recommended_problems_hash[problem.problem_id]['recommender_name'] = recommender_name + recommended_problems_hash[problem.problem_id]['problem_title'] = problem_title + end + return recommended_problems_hash + end + + # [System Reminders - Story 5.4] + # Sends reminder e-mails to inactive users + # Parameters: none + # Returns: none + # Author: Amir George + def self.send_reminder_mails + all.each do |student| + if student.last_sign_in_at < Time.now - 7.days + SystemReminders.reminder_email(student).deliver + end + + end + end + +end \ No newline at end of file diff --git a/tutor/app/models/teaching_assistant.rb b/tutor/app/models/teaching_assistant.rb index 541e789e..5f93df67 100644 --- a/tutor/app/models/teaching_assistant.rb +++ b/tutor/app/models/teaching_assistant.rb @@ -1,10 +1,32 @@ class TeachingAssistant < ActiveRecord::Base - # Include default devise modules. Others available are: - # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :trackable, :validatable - + :recoverable, :rememberable, :trackable, + :validatable, :confirmable + + #Elasticsearch + include Tire::Model::Search + include Tire::Model::Callbacks + + #concerns + include Searchable + + # mount_uploader :profile_image, ProfileImageUploader + #Validations + validate :duplicate_email + validates :name, presence: true + validates_format_of :name, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :graduated_from, presence: true + validates_format_of :graduated_from, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :graduated_year, presence: false, numericality: {only_integer: true, + greater_than_or_equal_to: Date.today.year-90, less_than_or_equal_to: Date.today.year} + validates :degree, presence: true + validates_format_of :degree, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :university, presence: true + validates_format_of :university, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :department, presence: true + validates_format_of :department, :with => /\A[^0-9`!@#\$%\^&*+_=]+\z|\A\z/ + validates :dob, presence: true #Relations has_many :posts, as: :owner, dependent: :destroy @@ -12,17 +34,54 @@ class TeachingAssistant < ActiveRecord::Base has_and_belongs_to_many :courses, join_table: "courses_teaching_assistants" - has_many :tracks, as: :owner - has_many :problems, as: :owner - has_many :model_answers, as: :owner - has_many :method_constraints, as: :owner - has_many :method_parameters, as: :owner - has_many :variable_constraints, as: :owner - has_many :test_cases, as: :owner - has_many :hints, as: :ownert - - #Scoops + has_many :tracks, as: :owner + has_many :problems, as: :owner + has_many :model_answers, as: :owner + has_many :method_constraints, as: :owner + has_many :method_parameters, as: :owner + has_many :variable_constraints, as: :owner + has_many :test_cases, as: :owner + has_many :hints, as: :owner #Methods -end + # [Advanced Search - Story 1.23] + # search for students + # Parameters: hash of search options + # Returns: A hash with search results according to the keyword and other options + # Author: Ahmed Elassuty + def self.search(params) + if params[:keyword].present? + case params[:options] + when "exactly match" + tire.search do + query { string "name:#{params[:keyword]}" } + end + when "includes" + tire.search do + query { string "name:*#{params[:keyword]}*" } + end + when "starts with" + tire.search do + query { string "name:#{params[:keyword]}*" } + end + when "ends with" + tire.search do + query { string "name:*#{params[:keyword]}" } + end + end + end + end + + # [User Authentication Advanced - Story 5.9, 5.10, 5.11, 5.14, 5.15] + # Checks if the email is already registered in tables: Lecturer and Student + # before registering the email for table: TeachingAssistant + # Parameters: None + # Returns: None + # Author: Khaled Helmy + def duplicate_email + if Student.find_by email: email or Lecturer.find_by email: email + errors.add(:email, "has already been taken") + end + end +end \ No newline at end of file diff --git a/tutor/app/models/topic.rb b/tutor/app/models/topic.rb index 8dca74ad..ea9ce72b 100644 --- a/tutor/app/models/topic.rb +++ b/tutor/app/models/topic.rb @@ -1,5 +1,9 @@ class Topic < ActiveRecord::Base - + + #Elasticsearch + include Tire::Model::Search + include Tire::Model::Callbacks + #Validations validates :title, :description, presence: true validates :title, :description, uniqueness: true @@ -9,4 +13,33 @@ class Topic < ActiveRecord::Base belongs_to :course belongs_to :owner, class_name: "Lecturer", foreign_key: :lecturer_id + #Methods + + # [Advanced Search - Story 1.23] + # search for topics + # Parameters: hash of search options + # Returns: A hash with search results according to the keyword and other options + # Author: Ahmed Elassuty + def self.search(params) + if params[:keyword].present? + case params[:options] + when "exactly match" + tire.search do + query { string "title:#{params[:keyword]}" } + end + when "includes" + tire.search do + query { string "title:*#{params[:keyword]}*" } + end + when "starts with" + tire.search do + query { string "title:#{params[:keyword]}*" } + end + when "ends with" + tire.search do + query { string "title:*#{params[:keyword]}" } + end + end + end + end end diff --git a/tutor/app/uploaders/profile_image_uploader.rb b/tutor/app/uploaders/profile_image_uploader.rb new file mode 100644 index 00000000..3c3b531d --- /dev/null +++ b/tutor/app/uploaders/profile_image_uploader.rb @@ -0,0 +1,29 @@ +class ProfileImageUploader < CarrierWave::Uploader::Base + + # Include RMagick support: + include CarrierWave::RMagick + + # Choose what kind of storage to use for this uploader: + storage :file + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + + # Process files as they are uploaded: + process :resize_to_limit => [50, 50] + + # Create different versions of your uploaded files: + version :profile do + process :resize_to_fit => [160, 160] + end + + # Add a white list of extensions which are allowed to be uploaded. + # For images you might use something like this: + def extension_white_list + %w(jpg jpeg gif png) + end + +end \ No newline at end of file diff --git a/tutor/app/views/acknowledgements/new.html.erb b/tutor/app/views/acknowledgements/new.html.erb new file mode 100644 index 00000000..67402c2d --- /dev/null +++ b/tutor/app/views/acknowledgements/new.html.erb @@ -0,0 +1,26 @@ +

Acknowledge A student

+ +
+<% if flash[:failure_notice] %> +
<%= flash[:failure_notice] %>
+<% end %> +<% if flash[:success_notice] %> +
<%= flash[:success_notice] %>
+<% end %> +
+ +<%= form_for :acknowledgement, url: {action: "create"} do |f| %> +

+ <%=select_tag 'students[]', options_for_select( + @course.students.map {|p| [p.name, p.id]}), :multiple => true, style: "width:300px" %> +

+ +

+ <%= f.label :Message %>
+ <%= f.text_area :description, class:"form-control", style:"width:300px", :value => 'Good Job!' %> +

+ +

+ <%= f.submit "Send Acknowledgement", class: "btn btn-success" %> +

+<% end %> \ No newline at end of file diff --git a/tutor/app/views/courses/edit.html.erb b/tutor/app/views/courses/edit.html.erb index 2a259c5f..5485ce36 100644 --- a/tutor/app/views/courses/edit.html.erb +++ b/tutor/app/views/courses/edit.html.erb @@ -1,40 +1,90 @@ +
+ +
- -
- <%= button_to "Topics", {:controller => 'topics', :action => 'index', - :course_id => @course.id}, class: "btn btn-success" , style: "float: left;",method: :get %> -
-
<%= button_to "Add Topics", {:controller => 'topics', :action => 'new', - :course_id => @course.id}, class: "btn btn-success" , style: "float: left;margin-left: 15px",method: :post %> + :course_id => @course.id}, class: "btn btn-success", + style: "float: left;margin-left: 15px",method: :post %>
-
<%= button_to "Add a TA", {:action => 'show'}, class: "btn btn-success" , style: "float: left; margin-left: 15px", :method => :get%> +
+ <%= button_to "Add a TA", {:controller => 'teaching_assistants', :action => 'new', + :course_id => @course.id}, class: "btn btn-success", + style: "float: left; margin-left: 15px", :method => :get %> +
+ + +
+ <%= button_to "Acknowledge a student", {:controller => 'acknowledgements', :action => 'new', + :course_id => @course.id}, class: "btn btn-success", + style: "float: left; margin-left: 15px", method: :get + %>
- <% if @discussionBoard.activated == true %> - <%str = "Deactivate DiscussionBoard" %> + <% if @discussion_board.activated == true %> + <% str = "Deactivate DiscussionBoard" %> <% else %> - <%str = "Activate DiscussionBoard"%> + <% str = "Activate DiscussionBoard" %> <% end %> - <%= button_to str, {:controller => :discussion_boards, :action => 'toggle', :id => @discussionBoard.id}, class: "btn btn-success", style: "float: left; margin-left: 15px" %> + <%= button_to str, {:controller => :discussion_boards, :action => 'toggle', + :id => @discussion_board.id}, class: "btn btn-success", + style: "float: left; margin-left: 15px" %>
-
+
+
+ <%= form_for :course, url: course_path(@course), method: :patch do |c| %> +

+

<%= c.label :name %>
+ <%= c.text_field :name %> +

+ +

+ <%= c.label :code %>
+ <%= c.text_area :code %> +

+ +

+ <%= c.label :year %>
+ <%= c.text_area :year %> +

+

+ <%= c.label :semester %>
+ <%= c.text_area :semester %> +

+ +

+ <%= c.label :description %>
+ <%= c.text_area :description %> +

+ +

+ <%= c.submit %> +

+ <% end %> + <%= link_to 'Back', courses_path %> +
- <%= button_to "Delete Course", {:action => "destroy"}, class: "btn btn-danger" , style: "margin-top: 100px; float: right", method: :delete %> + <%= button_to "Delete Course", {:action => "destroy"}, class: "btn btn-danger", style: "margin-top: 100px; float: right", method: :delete %>
-<%= button_to "Back", {:controller => 'courses', :action => 'index'}, class: "btn btn-success" , style: "margin-left: 600px; margin-top: 250px", :method => :get%> \ No newline at end of file +<%= button_to "Back", {:controller => 'courses', :action => 'index'}, class: "btn btn-success", + style: "margin-left:600px; margin-top: 250px", :method => :get %> +
+
+
diff --git a/tutor/app/views/courses/index.html.erb b/tutor/app/views/courses/index.html.erb index e30f1f36..0ce82820 100644 --- a/tutor/app/views/courses/index.html.erb +++ b/tutor/app/views/courses/index.html.erb @@ -1,36 +1,61 @@ -<% if !@courses.any? %> -

You have no courses

-<% end %> -<% if current_lecturer %> - <%= button_to "Create course", {:action => 'new', :controller => 'courses'}, { - class: 'btn btn-primary'} %> -<% end %> -
-<%= flash[:success_deletion] %> -<%= flash[:success_creation] %> -<% if @courses.any? %> -

Your Courses

- - - - - - - - <% @courses.each do |c| %> - - - - - - + +
+
NameCodeOptions
<%= link_to c.name, :controller => 'courses', :action => 'show', :id => c.id, - :method => :get, class: "btn btn-success" %><%= c.code %> - <% if current_lecturer %> - <%= link_to "Manage Course", {:controller => 'courses', :action => 'edit', :method => :get, :id => c.id}, class: "btn btn-success", style: "text-decoration:none" %> - <% end %> - - <%= link_to "Discussion Board", {:controller => 'discussion_boards', :action => 'show', :id => c.id }, class: "btn btn-success", method: :get, style: "text-decoration:none" %> -
-<% end %> \ No newline at end of file + +
+
+ <% if !@courses.any? %> +

You have no courses

+ <% end %> +
+ <%= flash[:success_deletion] %> + <%= flash[:success_creation] %> + <% if @courses.any? %> +

Your Courses

+ + + + + + + + + <% @courses.each do |c| %> + + + + + + + + <% end %> +
NameCodeOptions
<%= link_to c.name, { :controller => 'courses', :action => 'show', :id => c.id }, + :method => :get, class: "btn btn-success" %><%= c.code %> + <% if current_lecturer %> + <%= link_to "Manage Course", { :controller => 'courses', :action => 'edit', + :method => :get, :id => c.id }, class: "btn btn-success", style: "text-decoration:none" %> + <% end %> + + <%= link_to "Discussion Board", { :controller => 'discussion_boards', :action => 'show', :id => c.id }, + class: "btn btn-success", method: :get, style: "text-decoration:none" %> + + <% if current_student %> + <% if @share[c.id] == "Show" %> + <%= button_to "Show Performance", {:controller => 'courses', :action => 'share', + :id => c.id, :value => true}, class: "btn btn-success", style: "text-decoration:none" %> + <% else %> + <%= button_to "Hide Performance", {:controller => 'courses', :action => 'share', + :id => c.id, :value => false}, class: "btn btn-success", style: "text-decoration:none" %> + <% end %> + <% end %> +
+ <% end %> +
+
+
diff --git a/tutor/app/views/courses/new.html.erb b/tutor/app/views/courses/new.html.erb index 059723c3..c754a63d 100644 --- a/tutor/app/views/courses/new.html.erb +++ b/tutor/app/views/courses/new.html.erb @@ -1,38 +1,69 @@ -

Create New Course

+ +
+ +
+
+

Create New Course

+ <%= form_for :course, url: courses_path do |f| %> +

+ <%= f.label :Name %>
+ <% unless @new_course.errors[:name].blank? %> + + Name <%= @new_course.errors[:name].join(", ") %> + +
+ <% end %> + <%= f.text_field :name, :value => @new_course.name, class:"form-control", style: "width:300px" %> +

+

+ <%= f.label :Code %>
+ <% unless @new_course.errors[:code].blank? %> + + Code <%= @new_course.errors[:code].join(", ") %> + +
+ <% end %> + <%= f.text_field :code, :value => @new_course.code, class:"form-control", style: "width:300px" %> +

+

+ <%= f.label :Description %>
+ <% unless @new_course.errors[:description].blank? %> + + Description <%= @new_course.errors[:description].join(", ") %> + +
+ <% end %> + <%= f.text_area :description, class:"form-control", style:"width:300px", + placeholder:"Description", :value => @new_course.description %> +

+

+ <%= f.label :Semester %>
+ <% unless @new_course.errors[:semester].blank? %> + + Semester <%= @new_course.errors[:semester].join(", ") %> + +
+ <% end %> + <%= f.text_field :semester, :value => @new_course.semester, class:"form-control", style: "width:50px" %> +

+

+ <%= f.label :Year %>
+ <% unless @new_course.errors[:year].blank? %> + + Year <%= @new_course.errors[:year].join(", ") %> + +
+ <% end %> + <%= f.text_field :year, :value => @new_course.year, class:"form-control", style: "width:100px" %> +

+

+ <%= f.submit "Save", class: "btn btn-success" %> +

+ <% end %> +
-<% end %> - -<%= form_for :course, url: courses_path do |f| %> -

- <%= f.label :Name %>
- <%= f.text_field :name, :value => @new_course.name%> -

-

- <%= f.label :Code %>
- <%= f.text_field :code, :value => @new_course.code %> -

-

- <%= f.label :Description %>
- <%= f.text_area :description, class:"form-control", style:"width:300px",placeholder:"Description", :value => @new_course.description %> -

-

- <%= f.label :Semester %>
- <%= f.text_field :semester, :value => @new_course.semester %> -

-

- <%= f.label :Year %>
- <%= f.text_field :year, :value => @new_course.year %> -

-

- <%= f.submit %> -

-<% end %> \ No newline at end of file +
diff --git a/tutor/app/views/courses/show.html.erb b/tutor/app/views/courses/show.html.erb index 38c43d0d..2e9d15b3 100644 --- a/tutor/app/views/courses/show.html.erb +++ b/tutor/app/views/courses/show.html.erb @@ -1 +1,65 @@ -

Route Not Set Yet

+
+ +
+
+

<%= @course.name %>

+
+
+

+ Code: + <%= @course.code %> +

+

+ Year: + <%= @course.year %> +

+

+ Semester: + <%= @course.semester %> +

+

+ Description: + <%= @course.description %> +

+
+ +
+ <%= link_to "View Discussion Board", { :controller => 'discussion_boards', + :action => 'show', :id => @course.id }, class: "btn btn-success", method: :get, style: "text-decoration:none" %> +
+ +
+ <% @topics.each do |t| %> +
+
+ + <%= link_to t.title, t, style: 'color:#003366;'%> + <%= t.tracks.count %> +
+
+
    + <% t.tracks.each do |tt|%> + <% color = "label label-default" %> +
  • + <%= link_to tt.title, tt, class: color %> +
  • + <% end %> +
+
+
+ <% end %> +
+
+
+
+
diff --git a/tutor/app/views/courses/sign_up.html.erb b/tutor/app/views/courses/sign_up.html.erb index 7ef61281..9ed4a190 100644 --- a/tutor/app/views/courses/sign_up.html.erb +++ b/tutor/app/views/courses/sign_up.html.erb @@ -1,69 +1,80 @@ - -

Course Sign-Up

- - - <% case @status%> - <% when "0" %> -

Sorry, only students can sign up for courses!

- <% when "1" %> -

<%=@status%> / 6

-

Please choose a University

- <% @courses.each do |course| %> - - - - <% end %> - <% when "2" %> -

<%=@status%> / 6

-

Please choose a Semester

- <% @courses.each do |course| %> - - - - <% end %> - <% when "3" %> -

<%=@status%> / 6

-

Please choose a Course

- <% @courses.each do |course| %> - - - - <% end %> - <% when "4" %> -

<%=@status%> / 6

-

Pleas choose an Instructor

- <% @course.lecturers.each do |lecturer| %> - - - - <% end %> - <% when "5" %> -

<%=@status%> / 6

-

Semester: <%=@course.semester%> - Course: <%=@course.name%> - Instructor: <%=@lecturer.name%>

- - - - <% when "6" %> -

<%=@status%> / 6

-

You have signed up to:

-

Semester: <%=@course.semester%> - Course: <%=@course.name%> - Instructor: <%=@lecturer.name%>

- <% when "7" %> -

You are already signed up to this course!

- <% end %> - -
- <%= link_to(course.university, {:action => "sign_up", :university => course.university, - :status => "2"}) %> -
- <%= link_to(course.semester, {:action => "sign_up", :university => params[:university], - :semester => course.semester, :status => "3"}) %> -
- <%= link_to(course.name, {:action => "sign_up", :university => params[:university], - :semester =>course.semester, :id => course.id, :status => "4"} )%> -
- <%= link_to(lecturer.name, {:action => "sign_up", :university => params[:university], - :semester =>@course.semester, :id => @course.id, :lecturer => lecturer.id, :status => "5"} )%> -
- <%= link_to("Sign-Up!", {:action => "sign_up", :university => params[:university], - :semester => @course.semester, :id => @course.id, :lecturer => params[:lecturer], :status => "6"} )%> -
\ No newline at end of file + +
+ +
+
+ +

Course Sign-Up

+ + + <% case @status%> + <% when "0" %> +

Sorry, only students can sign up for courses!

+ <% when "1" %> +

<%=@status%> / 6

+

Please choose a University

+ <% @courses.each do |course| %> + + + + <% end %> + <% when "2" %> +

<%=@status%> / 6

+

Please choose a Semester

+ <% @courses.each do |course| %> + + + + <% end %> + <% when "3" %> +

<%=@status%> / 6

+

Please choose a Course

+ <% @courses.each do |course| %> + + + + <% end %> + <% when "4" %> +

<%=@status%> / 6

+

Pleas choose an Instructor

+ <% @course.lecturers.each do |lecturer| %> + + + + <% end %> + <% when "5" %> +

<%=@status%> / 6

+

Semester: <%=@course.semester%> - Course: <%=@course.name%> - Instructor: <%=@lecturer.name%>

+ + + + <% when "6" %> +

<%=@status%> / 6

+

You have signed up to:

+

Semester: <%=@course.semester%> - Course: <%= @course.name %> - Instructor: <%= @lecturer.name %>

+ <% when "7" %> +

You are already signed up to this course!

+ <% end %> + +
+ <%= link_to(course.university, {:action => "sign_up", :university => course.university, + :status => "2"}) %> +
+ <%= link_to(course.semester, {:action => "sign_up", :university => params[:university], + :semester => course.semester, :status => "3"}) %> +
+ <%= link_to(course.name, {:action => "sign_up", :university => params[:university], + :semester =>course.semester, :id => course.id, :status => "4"} )%> +
+ <%= link_to(lecturer.name, {:action => "sign_up", :university => params[:university], + :semester =>@course.semester, :id => @course.id, :lecturer => lecturer.id, :status => "5"} )%> +
+ <%= link_to("Sign-Up!", {:action => "sign_up", :university => params[:university], + :semester => @course.semester, :id => @course.id, :lecturer => params[:lecturer], :status => "6"} )%> +
+
+
+
diff --git a/tutor/app/views/discussion_boards/show.html.erb b/tutor/app/views/discussion_boards/show.html.erb index 537cd8ef..9688d8b1 100644 --- a/tutor/app/views/discussion_boards/show.html.erb +++ b/tutor/app/views/discussion_boards/show.html.erb @@ -1,20 +1,29 @@
+ <% if flash[:notice] %> +
<%= flash[:notice] %>
+ <% end %> <% if @discussionBoard.activated == true %>
-

<%=@discussionBoard.title%>

- <% unless @posts.blank? %> - <% @posts.each do |post| %> -
-

<%= post.owner.name %>

- <%= post.content %>
+

<%= @discussionBoard.title %>

+ <% unless @posts.blank? %> + <% @posts.each do |post| %> +
+ <%= link_to post.title, :controller => 'posts', :action=> 'show', + :id => post.id,:method => :get, class: "btn btn-success" %> + <%= post.views_count %> -
+

<% end %> <% else %> No Posts Yet .. Try Creating a Post <% end %>
+
+ <%= link_to "Add Post", new_post_path(discussion_board_id: + @discussionBoard.id), class: "btn btn-success", + style: "float: left;margin-left: 15px" %> +
<% else %> The discusstion board is deactivated <% end %> diff --git a/tutor/app/views/hints/_index.html.erb b/tutor/app/views/hints/_index.html.erb new file mode 100644 index 00000000..5269492d --- /dev/null +++ b/tutor/app/views/hints/_index.html.erb @@ -0,0 +1 @@ +

Previous hints

\ No newline at end of file diff --git a/tutor/app/views/layouts/_footer.html.erb b/tutor/app/views/layouts/_footer.html.erb index 48c4c7bb..94abcf86 100644 --- a/tutor/app/views/layouts/_footer.html.erb +++ b/tutor/app/views/layouts/_footer.html.erb @@ -3,7 +3,7 @@ # General layout for footer # Author: Ahmed Elassuty --> -
+
<%= link_to "About us"%> diff --git a/tutor/app/views/layouts/_left_side_bar.html.erb b/tutor/app/views/layouts/_left_side_bar.html.erb deleted file mode 100644 index 30aed348..00000000 --- a/tutor/app/views/layouts/_left_side_bar.html.erb +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/tutor/app/views/layouts/_log_out_form.html.erb b/tutor/app/views/layouts/_log_out_form.html.erb index a84a439c..a5d93655 100644 --- a/tutor/app/views/layouts/_log_out_form.html.erb +++ b/tutor/app/views/layouts/_log_out_form.html.erb @@ -3,18 +3,10 @@ # logout button configurations # Author: Ahmed Elassuty --> - \ No newline at end of file +<%- if lecturer_signed_in? %> + <%= link_to 'Log Out',destroy_lecturer_session_path, method: :delete %> +<%- elsif student_signed_in? %> + <%= link_to 'Log Out',destroy_student_session_path, method: :delete %> +<%- elsif teaching_assistant_signed_in? %> + <%= link_to 'Log Out', destroy_teaching_assistant_session_path, method: :delete %> +<% end%> \ No newline at end of file diff --git a/tutor/app/views/layouts/_right_side_bar.html.erb b/tutor/app/views/layouts/_right_side_bar.html.erb index eddeb4be..bd94c82c 100644 --- a/tutor/app/views/layouts/_right_side_bar.html.erb +++ b/tutor/app/views/layouts/_right_side_bar.html.erb @@ -3,5 +3,70 @@ # General layout for right side bar # Author: Ahmed Elassuty --> - \ No newline at end of file + diff --git a/tutor/app/views/layouts/_search_form.html.erb b/tutor/app/views/layouts/_search_form.html.erb new file mode 100644 index 00000000..ad51c574 --- /dev/null +++ b/tutor/app/views/layouts/_search_form.html.erb @@ -0,0 +1,16 @@ + +<%= form_tag utilities_simple_search_path, :method => 'get', + remote: controller?("utilities","simple_search"), class: "navbar-form navbar-left" do %> +
+ <%= text_field_tag :search, params[:search], class: "form-control col-lg-8", + placeholder: "Search for Users and Courses" %> + + <%= link_to "Try Advanced", utilities_advanced_search_path, + class: "btn btn-default col-lg-12" %> + +
+<% end %> \ No newline at end of file diff --git a/tutor/app/views/layouts/_signed_in_header.html.erb b/tutor/app/views/layouts/_signed_in_header.html.erb deleted file mode 100644 index c601199b..00000000 --- a/tutor/app/views/layouts/_signed_in_header.html.erb +++ /dev/null @@ -1,13 +0,0 @@ - - - \ No newline at end of file diff --git a/tutor/app/views/layouts/application.html.erb b/tutor/app/views/layouts/application.html.erb index feee850c..a69400de 100644 --- a/tutor/app/views/layouts/application.html.erb +++ b/tutor/app/views/layouts/application.html.erb @@ -7,64 +7,62 @@ CoolSoft - Java Tutor - <%= stylesheet_link_tag "application", "bootstrap", :media => "all"%> - <%= javascript_include_tag "application","bootstrap","jquery"%> - <%= csrf_meta_tags%> + <%= stylesheet_link_tag "application", "bootstrap","token-input", :media => "all" %> + <%= javascript_include_tag "application","bootstrap", "jquery" %> + <%= csrf_meta_tags %>