From 8954839a22eee1c831827510d673264caf7ff312 Mon Sep 17 00:00:00 2001 From: Michael Herzberg Date: Thu, 21 Feb 2019 21:54:26 +0000 Subject: [PATCH] Major overhaul. --- .gitignore | 8 +- Gemfile | 27 +- Gemfile.lock | 258 ++++++++++-------- app/assets/javascripts/grades.coffee | 16 +- app/assets/javascripts/reports.coffee | 3 + app/assets/stylesheets/reports.scss | 3 + app/controllers/application_controller.rb | 7 +- app/controllers/grades_controller.rb | 14 +- app/controllers/reports_controller.rb | 20 ++ app/controllers/user_sessions_controller.rb | 19 +- app/controllers/users_controller.rb | 42 ++- app/helpers/reports_helper.rb | 2 + app/models/grade.rb | 12 +- app/models/user.rb | 1 - app/models/user_session.rb | 2 - app/reports/grade_report.rb | 15 + app/views/grades/edit_student.html.erb | 1 + app/views/grades/index_admin.html.erb | 6 + app/views/grades/index_lecturer.html.erb | 6 + app/views/grades/index_student.html.erb | 15 + app/views/grades/new_student.html.erb | 26 ++ app/views/user_sessions/new.html.erb | 14 +- app/views/users/reset_password.html.erb | 8 + .../users/reset_password_for_user.html.erb | 33 +++ app/views/welcome/index.html.erb | 4 +- bin/bundle | 2 +- bin/setup | 6 +- bin/update | 6 +- bin/yarn | 11 + config/application.rb | 10 +- config/boot.rb | 1 + config/cable.yml | 3 +- config/environments/development.rb | 11 +- config/environments/production.rb | 10 +- config/environments/test.rb | 6 +- .../application_controller_renderer.rb | 10 +- config/initializers/assets.rb | 9 +- .../initializers/content_security_policy.rb | 25 ++ config/initializers/new_framework_defaults.rb | 3 - .../new_framework_defaults_5_2.rb | 38 +++ config/locales/en.yml | 10 + config/puma.rb | 23 +- config/routes.rb | 7 + config/spring.rb | 4 +- config/storage.yml | 34 +++ db/development.sqlite3 | Bin 53248 -> 0 bytes db/migrate/20170331102612_create_users.rb | 13 - .../20170331102730_create_user_sessions.rb | 8 - db/migrate/20170331105119_create_lectures.rb | 10 - db/migrate/20170401153520_create_grades.rb | 12 - db/schema.rb | 58 ++-- lib/tasks/sample_data.rake | 62 +++++ test/.empty => public/reports/.keep | 0 public/uploads/.keep | 0 54 files changed, 679 insertions(+), 275 deletions(-) create mode 100644 app/assets/javascripts/reports.coffee create mode 100644 app/assets/stylesheets/reports.scss create mode 100644 app/controllers/reports_controller.rb create mode 100644 app/helpers/reports_helper.rb delete mode 100644 app/models/user_session.rb create mode 100644 app/reports/grade_report.rb create mode 100644 app/views/grades/new_student.html.erb create mode 100644 app/views/users/reset_password.html.erb create mode 100644 app/views/users/reset_password_for_user.html.erb create mode 100755 bin/yarn create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/new_framework_defaults_5_2.rb create mode 100644 config/storage.yml delete mode 100644 db/development.sqlite3 delete mode 100644 db/migrate/20170331102612_create_users.rb delete mode 100644 db/migrate/20170331102730_create_user_sessions.rb delete mode 100644 db/migrate/20170331105119_create_lectures.rb delete mode 100644 db/migrate/20170401153520_create_grades.rb create mode 100644 lib/tasks/sample_data.rake rename test/.empty => public/reports/.keep (100%) create mode 100644 public/uploads/.keep diff --git a/.gitignore b/.gitignore index 5ae70a8..c882881 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ doc/rdocs # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal -!/db/development.sqlite3 # Ignore all logfiles and tempfiles. /log/* @@ -26,3 +25,10 @@ doc/rdocs /vendor *.swp + +public/reports/* +!public/reports/.keep +public/uploads/* +!public/uploads/.keep + +storage diff --git a/Gemfile b/Gemfile index 1e2add4..81678df 100644 --- a/Gemfile +++ b/Gemfile @@ -7,26 +7,26 @@ end # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 5.0.2' +gem 'rails', '~> 5.2.0' # Use sqlite3 as the database for Active Record -gem 'sqlite3' +gem 'sqlite3', '~> 1.3.0' # Use Puma as the app server -gem 'puma', '~> 3.0' +gem 'puma' # Use SCSS for stylesheets -gem 'sass-rails', '~> 5.0' +gem 'sass-rails' # Use Uglifier as compressor for JavaScript assets -gem 'uglifier', '>= 1.3.0' +gem 'uglifier' # Use CoffeeScript for .coffee assets and views -gem 'coffee-rails', '~> 4.2' +gem 'coffee-rails' # See https://github.com/rails/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use jquery as the JavaScript library gem 'jquery-rails' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks -gem 'turbolinks', '~> 5' +gem 'turbolinks' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 2.5' +gem 'jbuilder' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 3.0' # Use ActiveModel has_secure_password @@ -42,13 +42,16 @@ end group :development do # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. - gem 'web-console', '>= 3.3.0' - gem 'listen', '~> 3.0.5' + gem 'web-console' + gem 'listen' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' - gem 'spring-watcher-listen', '~> 2.0.0' + gem 'spring-watcher-listen' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] -gem 'authlogic', '3.5.0' +gem 'rdoc' +gem 'bootsnap' +gem 'prawn' +gem 'prawn-table' diff --git a/Gemfile.lock b/Gemfile.lock index 4f43970..1ee2c98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,186 +1,204 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.0.2) - actionpack (= 5.0.2) - nio4r (>= 1.2, < 3.0) - websocket-driver (~> 0.6.1) - actionmailer (5.0.2) - actionpack (= 5.0.2) - actionview (= 5.0.2) - activejob (= 5.0.2) + actioncable (5.2.2) + actionpack (= 5.2.2) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.2) + actionpack (= 5.2.2) + actionview (= 5.2.2) + activejob (= 5.2.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.2) - actionview (= 5.0.2) - activesupport (= 5.0.2) + actionpack (5.2.2) + actionview (= 5.2.2) + activesupport (= 5.2.2) rack (~> 2.0) - rack-test (~> 0.6.3) + rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.2) - activesupport (= 5.0.2) + actionview (5.2.2) + activesupport (= 5.2.2) builder (~> 3.1) - erubis (~> 2.7.0) + erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.0.2) - activesupport (= 5.0.2) + activejob (5.2.2) + activesupport (= 5.2.2) globalid (>= 0.3.6) - activemodel (5.0.2) - activesupport (= 5.0.2) - activerecord (5.0.2) - activemodel (= 5.0.2) - activesupport (= 5.0.2) - arel (~> 7.0) - activesupport (5.0.2) + activemodel (5.2.2) + activesupport (= 5.2.2) + activerecord (5.2.2) + activemodel (= 5.2.2) + activesupport (= 5.2.2) + arel (>= 9.0) + activestorage (5.2.2) + actionpack (= 5.2.2) + activerecord (= 5.2.2) + marcel (~> 0.3.1) + activesupport (5.2.2) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) + i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - arel (7.1.4) - authlogic (3.5.0) - activerecord (>= 3.2, < 5.1) - activesupport (>= 3.2, < 5.1) - request_store (~> 1.0) - scrypt (>= 1.2, < 4.0) + arel (9.0.0) bindex (0.5.0) + bootsnap (1.3.2) + msgpack (~> 1.0) builder (3.2.3) - byebug (9.0.6) - coffee-rails (4.2.1) + byebug (10.0.2) + coffee-rails (4.2.2) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.2.x) + railties (>= 4.0.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.0.5) - erubis (2.7.0) + concurrent-ruby (1.1.4) + crass (1.0.4) + erubi (1.8.0) execjs (2.7.0) - ffi (1.9.18) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - globalid (0.3.7) - activesupport (>= 4.1.0) - i18n (0.8.1) - jbuilder (2.6.3) - activesupport (>= 3.0.0, < 5.2) - multi_json (~> 1.2) - jquery-rails (4.3.1) + ffi (1.10.0) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.5.3) + concurrent-ruby (~> 1.0) + jbuilder (2.8.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) + jquery-rails (4.3.3) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - listen (3.0.8) + listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - loofah (2.0.3) + ruby_dep (~> 1.2) + loofah (2.2.3) + crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.6.4) - mime-types (>= 1.16, < 4) - method_source (0.8.2) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_portile2 (2.1.0) - minitest (5.10.1) - multi_json (1.12.1) - nio4r (2.0.0) - nokogiri (1.7.1) - mini_portile2 (~> 2.1.0) - puma (3.8.2) - rack (2.0.1) - rack-test (0.6.3) - rack (>= 1.0) - rails (5.0.2) - actioncable (= 5.0.2) - actionmailer (= 5.0.2) - actionpack (= 5.0.2) - actionview (= 5.0.2) - activejob (= 5.0.2) - activemodel (= 5.0.2) - activerecord (= 5.0.2) - activesupport (= 5.0.2) - bundler (>= 1.3.0, < 2.0) - railties (= 5.0.2) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.3) + mini_mime (1.0.1) + mini_portile2 (2.4.0) + minitest (5.11.3) + msgpack (1.2.6) + multi_json (1.13.1) + nio4r (2.3.1) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + pdf-core (0.7.0) + prawn (2.2.2) + pdf-core (~> 0.7.0) + ttfunk (~> 1.5) + prawn-table (0.2.2) + prawn (>= 1.3.0, < 3.0.0) + puma (3.12.0) + rack (2.0.6) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.2) + actioncable (= 5.2.2) + actionmailer (= 5.2.2) + actionpack (= 5.2.2) + actionview (= 5.2.2) + activejob (= 5.2.2) + activemodel (= 5.2.2) + activerecord (= 5.2.2) + activestorage (= 5.2.2) + activesupport (= 5.2.2) + bundler (>= 1.3.0) + railties (= 5.2.2) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.2) - activesupport (>= 4.2.0, < 6.0) - nokogiri (~> 1.6) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - railties (5.0.2) - actionpack (= 5.0.2) - activesupport (= 5.0.2) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.2) + actionpack (= 5.2.2) + activesupport (= 5.2.2) method_source rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (12.0.0) - rb-fsevent (0.9.8) - rb-inotify (0.9.8) - ffi (>= 0.5.0) - request_store (1.3.2) - sass (3.4.23) - sass-rails (5.0.6) + thor (>= 0.19.0, < 2.0) + rake (12.3.2) + rb-fsevent (0.10.3) + rb-inotify (0.10.0) + ffi (~> 1.0) + rdoc (6.1.1) + ruby_dep (1.5.0) + sass (3.7.3) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.0.7) railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - scrypt (3.0.5) - ffi-compiler (>= 1.0, < 2.0) - spring (2.0.1) + spring (2.0.2) activesupport (>= 4.2) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (3.7.1) + sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.0) + sprockets-rails (3.2.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.13) - thor (0.19.4) + thor (0.20.3) thread_safe (0.3.6) - tilt (2.0.7) - turbolinks (5.0.1) - turbolinks-source (~> 5) - turbolinks-source (5.0.0) - tzinfo (1.2.3) + tilt (2.0.9) + ttfunk (1.5.1) + turbolinks (5.2.0) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.5) thread_safe (~> 0.1) - uglifier (3.1.11) + uglifier (4.1.20) execjs (>= 0.3.0, < 3) - web-console (3.5.0) + web-console (3.7.0) actionview (>= 5.0) activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - websocket-driver (0.6.5) + websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.2) + websocket-extensions (0.1.3) PLATFORMS ruby DEPENDENCIES - authlogic (= 3.5.0) + bootsnap byebug - coffee-rails (~> 4.2) - jbuilder (~> 2.5) + coffee-rails + jbuilder jquery-rails - listen (~> 3.0.5) - puma (~> 3.0) - rails (~> 5.0.2) - sass-rails (~> 5.0) + listen + prawn + prawn-table + puma + rails (~> 5.2.0) + rdoc + sass-rails spring - spring-watcher-listen (~> 2.0.0) - sqlite3 - turbolinks (~> 5) + spring-watcher-listen + sqlite3 (~> 1.3.0) + turbolinks tzinfo-data - uglifier (>= 1.3.0) - web-console (>= 3.3.0) + uglifier + web-console BUNDLED WITH - 1.14.6 + 2.0.1 diff --git a/app/assets/javascripts/grades.coffee b/app/assets/javascripts/grades.coffee index 4d9727f..10ead4c 100644 --- a/app/assets/javascripts/grades.coffee +++ b/app/assets/javascripts/grades.coffee @@ -2,6 +2,7 @@ # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ +# Parse GET parameters into urlParams object match = undefined pl = /\+/g # Regex for replacing addition symbol with a space search = /([^&=]+)=?([^&]*)/g @@ -11,4 +12,17 @@ query = window.location.search.substring(1) window.urlParams = {} while (match = search.exec(query)) urlParams[decode(match[1])] = decode(match[2]) -$ -> $("p[data-search-info]").html("You searched for lecturer: " + window.urlParams["lecturer"]) + + +(exports ? this).validate_file = (file) -> + if $(file).data("max-file-size") < file.files[0].size + alert("File exceeded maximum file size!") + $(file).val('') + else if not file.files[0].name.endsWith("." + $(file).data("allowed-extension")) + alert("File has forbidden extension!") + $(file).val('') + + +$ -> + if window.urlParams.hasOwnProperty('lecturer') + $("p[data-search-info]").html("Showing grades from lecturer " + window.urlParams["lecturer"]) diff --git a/app/assets/javascripts/reports.coffee b/app/assets/javascripts/reports.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/reports.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/app/assets/stylesheets/reports.scss b/app/assets/stylesheets/reports.scss new file mode 100644 index 0000000..91c8c0a --- /dev/null +++ b/app/assets/stylesheets/reports.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Reports controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 66a4b26..4de5f07 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -19,14 +19,9 @@ private return (current_user and current_user.role == "admin") end - def current_user_session - return @current_user_session if defined?(@current_user_session) - @current_user_session = UserSession.find - end - def current_user return @current_user if defined?(@current_user) - @current_user = current_user_session && current_user_session.user + @current_user = User.where("session = '#{cookies[:session]}' and session not NULL").first end def store_location diff --git a/app/controllers/grades_controller.rb b/app/controllers/grades_controller.rb index 21b044e..5b38aa8 100644 --- a/app/controllers/grades_controller.rb +++ b/app/controllers/grades_controller.rb @@ -5,6 +5,8 @@ class GradesController < ApplicationController render :new_admin elsif logged_in_as_lecturer render :new_lecturer + elsif logged_in_as_student + render :new_student else kick_out end @@ -47,6 +49,14 @@ class GradesController < ApplicationController else render :new_lecturer end + elsif logged_in_as_student + @grade = Grade.new(params.require(:grade).permit(:student_id, :lecture_id, :submission)) + if @grade.save + flash[:success] = "Report submitted!" + redirect_to grades_path + else + render :new_student + end else kick_out end @@ -78,7 +88,7 @@ class GradesController < ApplicationController def update if logged_in_as_admin @grade = Grade.find(params[:id]) - if @grade.update(params.require(:grade).permit(:student_id, :lecture_id, :grade, :comment)) + if @grade.update(params.require(:grade).permit(:student_id, :lecture_id, :grade, :comment, :submission)) and @grade.submission.attach(params[:submission]) flash[:success] = "Update successful!" redirect_to grades_path else @@ -97,7 +107,7 @@ class GradesController < ApplicationController if @grade.student != Student.find(current_user.id) kick_out else - if @grade.update(params.require(:grade).permit(:comment)) + if @grade.update(params.require(:grade).permit(:comment, :submission)) flash[:success] = "Update successful!" redirect_to grades_path else diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb new file mode 100644 index 0000000..bbe09b1 --- /dev/null +++ b/app/controllers/reports_controller.rb @@ -0,0 +1,20 @@ +class ReportsController < ApplicationController + @@report_dir = Rails.root.join("public", "reports") + + def create + if logged_in_as_student + user = current_user + filename = user.id.to_s + ".pdf" + report = GradeReport.new(user, Grade.where(:student => user)) + report.render_file @@report_dir.join(filename) + + redirect_to action: 'show', filename: filename + else + kick_out + end + end + + def show + send_file File.read(@@report_dir.join(params[:filename])) + end +end diff --git a/app/controllers/user_sessions_controller.rb b/app/controllers/user_sessions_controller.rb index adcc30b..771dedf 100644 --- a/app/controllers/user_sessions_controller.rb +++ b/app/controllers/user_sessions_controller.rb @@ -1,25 +1,28 @@ class UserSessionsController < ApplicationController - def new - @user_session = UserSession.new - end - def create - @user_session = UserSession.new(user_session_params) - if @user_session.save + @user = User.find_by :login => user_session_params[:login], :password => Digest::MD5.hexdigest(user_session_params[:password]) + if @user + @user.session = SecureRandom.hex + @user.save + cookies[:session] = @user.session + flash[:success] = "Login successful!" redirect_back_or_default root_path else + flash[:error] = "Login failed!" render :action => :new, :location => sign_out_url end end def destroy - current_user_session.destroy + @user = User.find_by :session => cookies[:session] + @user.session = nil + @user.save redirect_to sign_in_url end private def user_session_params - params.require(:user_session).permit(:login, :password) + params.permit(:login, :password) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d8d84a3..1427735 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -23,16 +23,16 @@ class UsersController < ApplicationController def create if logged_in_as_admin - @user = User.new(users_params) - if @user.save + @user = User.new(:login => users_params[:login], :role => users_params[:role], :password => Digest::MD5.hexdigest(users_params[:password])) + if users_params[:password] == users_params[:password_confirmation] and @user.save flash[:success] = "Account registered!" redirect_to root_path else render :new end elsif logged_in_as_lecturer - @user = User.new(users_params) - if @user.save + @user = User.new(:login => users_params[:login], :role => users_params[:role], :password => Digest::MD5.hexdigest(users_params[:password])) + if users_params[:password] == users_params[:password_confirmation] and @user.save flash[:success] = "Account registered!" redirect_to root_path else @@ -40,7 +40,39 @@ class UsersController < ApplicationController end else flash[:error] = "You do not have access to this site." - redirect_to root_url + redirect_to root_url + end + end + + def reset_password + if params[:user] + @user = User.find_by params[:user].permit(:login, :secret_answer) + if @user + if params[:user][:password] == params[:user][:password_confirmation] + @user.update_attributes(:password => Digest::MD5.hexdigest(params[:user][:password])) + @user.save + flash[:success] = "Password changed successfully!" + redirect_to root_url + else + flash[:error] = "Passwords don't match!" + render :reset_password_for_user + end + else + flash[:error] = "Wrong answer!" + @user = User.find_by :login => params[:user][:login] + render :reset_password_for_user + end + elsif params[:login] + @user = User.find_by(:login => params[:login]) + if @user and @user.secret_question + render :reset_password_for_user + else + flash[:error] = "This user does not exist or have a secret answer set!" + redirect_to reset_password_url + end + else + @user = User.new + render :reset_password end end diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb new file mode 100644 index 0000000..cae2f09 --- /dev/null +++ b/app/helpers/reports_helper.rb @@ -0,0 +1,2 @@ +module ReportsHelper +end diff --git a/app/models/grade.rb b/app/models/grade.rb index c9d0c03..b7c535f 100644 --- a/app/models/grade.rb +++ b/app/models/grade.rb @@ -1,8 +1,16 @@ class Grade < ApplicationRecord + has_one_attached :submission belongs_to :lecture belongs_to :student - validates :grade, presence: true - validates_numericality_of :grade , :less_than_or_equal_to=>100, :greater_than_or_equal_to=>0 + validates_numericality_of :grade , :less_than_or_equal_to=>100, :greater_than_or_equal_to=>0, :allow_nil => true validates :lecture, presence: true validates :student, presence: true + validate :submission_size_validation + + def submission_size_validation + if submission.attached? + errors[:grade] << "attachment must be less than 500kB" if submission.blob.byte_size > 500.kilobytes + errors[:grade] << "attachment must be a pdf" if submission.filename.extension != "pdf" + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index 5f6af20..2203c80 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,3 @@ class User < ApplicationRecord - acts_as_authentic validates :role, inclusion: { in: ["admin", "lecturer", "student"], message: "%{value} is not a valid role" } end diff --git a/app/models/user_session.rb b/app/models/user_session.rb deleted file mode 100644 index 93642e5..0000000 --- a/app/models/user_session.rb +++ /dev/null @@ -1,2 +0,0 @@ -class UserSession < Authlogic::Session::Base -end diff --git a/app/reports/grade_report.rb b/app/reports/grade_report.rb new file mode 100644 index 0000000..e7c6743 --- /dev/null +++ b/app/reports/grade_report.rb @@ -0,0 +1,15 @@ +class GradeReport < Prawn::Document + def initialize(user, grades=[]) + super() + + text "Grade Report for #{user.login}", size: 14, style: :bold_italic, align: :center + + table [["Lecture", "Grade"]] + grades.map { |g| [g.lecture.name.to_s, g.grade.to_s] }, + :row_colors => ["FFFFFF","DDDDDD"], + :header => true, + :column_widths => [100, 100], + :position => :center do + row(0).font_style = :bold + end + end +end diff --git a/app/views/grades/edit_student.html.erb b/app/views/grades/edit_student.html.erb index 654ce28..88ca3b1 100644 --- a/app/views/grades/edit_student.html.erb +++ b/app/views/grades/edit_student.html.erb @@ -12,6 +12,7 @@
<%= f.label :comment %>
<%= f.text_area :comment, class: "w3-input w3-border", style: "min-height: 150px; padding-left: 0" %>
+
<%= f.submit class: "w3-button w3-light-blue w3-margin" %>
diff --git a/app/views/grades/index_admin.html.erb b/app/views/grades/index_admin.html.erb index 2ac2402..398f1ac 100644 --- a/app/views/grades/index_admin.html.erb +++ b/app/views/grades/index_admin.html.erb @@ -4,6 +4,7 @@ Lecture Student Grade + Submission Comment Action @@ -15,6 +16,11 @@ <%= grade.student.login %> <%= grade.grade %> + <% if grade.submission.attached? %> + <%= link_to grade.submission.filename.to_s, rails_blob_url(grade.submission, disposition: "attachment") %> + <% end %> + + <% if grade.comment %> <%= grade.comment.html_safe %> <% end %> diff --git a/app/views/grades/index_lecturer.html.erb b/app/views/grades/index_lecturer.html.erb index 41fb931..4567f5d 100644 --- a/app/views/grades/index_lecturer.html.erb +++ b/app/views/grades/index_lecturer.html.erb @@ -4,6 +4,7 @@ Lecture Student Grade + Submission Comment Action @@ -15,6 +16,11 @@ <%= grade.student.login %> <%= grade.grade %> + <% if grade.submission.attached? %> + <%= link_to grade.submission.filename.to_s, rails_blob_url(grade.submission, disposition: "attachment") %> + <% end %> + + <% if grade.comment %> <%= grade.comment.html_safe %> <% end %> diff --git a/app/views/grades/index_student.html.erb b/app/views/grades/index_student.html.erb index 6cee92b..41218f9 100644 --- a/app/views/grades/index_student.html.erb +++ b/app/views/grades/index_student.html.erb @@ -5,6 +5,7 @@ Lecturer Lecture Grade + Submission Comment @@ -14,6 +15,11 @@ <%= grade.lecture.name %> <%= grade.grade %> + <% if grade.submission.attached? %> + <%= link_to grade.submission.filename.to_s, rails_blob_url(grade.submission, disposition: "attachment") %> + <% end %> + + <% if grade.comment %> <%= grade.comment.html_safe %> <% end %> @@ -22,9 +28,18 @@ <% end %> + +
+ <%= link_to "Generate Report", {controller: "reports", action: "create"}, :method => :post, :class => "w3-button w3-light-blue" %> +
+ <%= form_tag(grades_url, method: "get", class: "w3-margin") do %>
<%= submit_tag("Filter", class: "w3-button w3-light-blue") %>
<%= text_field_tag(:lecturer, "", class: "w3-input w3-border w3-round", placeholder: "Filter by lecturer...", style: "width: 90%") %> <% end %> + +
+ <%= link_to 'New Submission', new_grade_path, :class => "w3-button w3-light-blue" %> +
diff --git a/app/views/grades/new_student.html.erb b/app/views/grades/new_student.html.erb new file mode 100644 index 0000000..eadce47 --- /dev/null +++ b/app/views/grades/new_student.html.erb @@ -0,0 +1,26 @@ +<%= form_for @grade, :html => {:class => "w3-container w3-card-4 w3-margin"} do |f| %> + <%= render 'shared/errors', object: @grade %> +
+
<%= f.label :lecture %>
+
<%= f.select(:lecture_id, Lecture.all.collect {|p| [ p.name, p.id ] }) %>
+
+
+
<%= f.label :student %>
+
<%= f.select(:student_id, User.where(role: "student").collect {|p| [ p.login, p.id ] }) %>
+
+
+
<%= f.label :submission %>
+
+ <% if @grade.submission.attached? %> + <%= link_to @grade.submission.filename.to_s, rails_blob_url(@grade.submission, disposition: "attachment") %> +
+ <% end %> + <%= f.file_field :submission ,:onchange => "validate_file(this);", :data => { :allowed_extension => "pdf", :max_file_size => 500.kilobytes } %> +
+ (Maximal 500 kB, only pdf files) +
+
+
+ <%= f.submit "Submit", class: "w3-button w3-light-blue w3-margin" %> +
+<% end %> diff --git a/app/views/user_sessions/new.html.erb b/app/views/user_sessions/new.html.erb index 5e9623b..2c6dac3 100644 --- a/app/views/user_sessions/new.html.erb +++ b/app/views/user_sessions/new.html.erb @@ -1,12 +1,12 @@ -<%= form_for @user_session, :html => {:class => "w3-container w3-card-4 w3-margin-top", :style => "max-width: 320px; margin: auto"}, :url => sign_in_path do |f| %> - <%= render 'shared/errors', object: @user_session %> +<%= form_tag sign_in_path, :class => "w3-container w3-card-4 w3-margin-top", :style => "max-width: 320px; margin: auto" do %>

- <%= f.text_field :login, :class => "w3-input" %> - <%= f.label :login %> + <%= text_field_tag :login, nil, :class => "w3-input" %> + <%= label_tag :login, "Username" %>

- <%= f.password_field :password, :class => "w3-input" %> - <%= f.label :password %> + <%= password_field_tag :password, nil, :class => "w3-input" %> + <%= label_tag :password, "Password" %>

-

<%= f.submit "Login", :class => "w3-button w3-amber" %>

+

<%= submit_tag "Login", :class => "w3-button w3-amber" %>

+

<%= link_to "Reset Password", reset_password_path %>

<% end %> diff --git a/app/views/users/reset_password.html.erb b/app/views/users/reset_password.html.erb new file mode 100644 index 0000000..9b4afdf --- /dev/null +++ b/app/views/users/reset_password.html.erb @@ -0,0 +1,8 @@ +<%= form_tag reset_password_path, :class => "w3-container w3-card-4 w3-margin-top", :style => "max-width: 320px; margin: auto", :method => :get do |f| %> + +

+ <%= text_field_tag :login, nil, :class => "w3-input" %> + <%= label_tag :login, "Username" %> +

+

<%= submit_tag 'Next', class: "w3-button w3-amber" %>

+<% end %> diff --git a/app/views/users/reset_password_for_user.html.erb b/app/views/users/reset_password_for_user.html.erb new file mode 100644 index 0000000..859d432 --- /dev/null +++ b/app/views/users/reset_password_for_user.html.erb @@ -0,0 +1,33 @@ +<%= form_for @user, url: {action: "reset_password"}, :html => {:class => "w3-container w3-card-4 w3-margin"} do |f| %> + <%= render 'shared/errors', object: @user %> + + <%= f.hidden_field :login, value: @user.login %> + +
+
<%= f.label :login %>
+
<%= @user.login %>
+
+ +
+
<%= f.label :secret_question %>
+
<%= @user.secret_question %>
+
+ +
+
<%= f.label :secret_answer %>
+
<%= f.password_field :secret_answer %>
+
+ +
+
<%= f.label :password %>
+
<%= f.password_field :password %>
+
+
+
<%= f.label :password_confirmation %>
+
<%= f.password_field :password_confirmation %>
+
+ +
+ <%= f.submit 'Change Password', class: "w3-button w3-light-blue w3-margin" %> +
+<% end %> diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb index dc175d0..7cc62b7 100644 --- a/app/views/welcome/index.html.erb +++ b/app/views/welcome/index.html.erb @@ -1,4 +1,4 @@
-

The Insecure Grade Management

-

Welcome to Sheffield's new, super insecure grade management!

+

Damn Vulnerable Grade Management

+

Welcome to Sheffield's new damn vulnerable grade management system!

diff --git a/bin/bundle b/bin/bundle index 66e9889..f19acf5 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/setup b/bin/setup index e620b4d..94fd4d7 100755 --- a/bin/setup +++ b/bin/setup @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -18,6 +17,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/bin/update b/bin/update index a8e4462..58bfaed 100755 --- a/bin/update +++ b/bin/update @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -18,6 +17,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 0000000..460dd56 --- /dev/null +++ b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg", *ARGV + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config/application.rb b/config/application.rb index 9dceede..92af8b4 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,10 +6,14 @@ require 'rails/all' # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) -module Grademgmt +module DVGM class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.0 + # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. end end diff --git a/config/boot.rb b/config/boot.rb index 30f5120..b9e460c 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,3 +1,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml index 0bbde6f..2e63815 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -6,4 +6,5 @@ test: production: adapter: redis - url: redis://localhost:6379/1 + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: dvgm_production diff --git a/config/environments/development.rb b/config/environments/development.rb index 6f71970..1311e3e 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -13,12 +13,13 @@ Rails.application.configure do config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. - if Rails.root.join('tmp/caching-dev.txt').exist? + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=172800' + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -26,6 +27,9 @@ Rails.application.configure do config.cache_store = :null_store end + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false @@ -37,6 +41,9 @@ Rails.application.configure do # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. diff --git a/config/environments/production.rb b/config/environments/production.rb index 38e3eb5..7707941 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -14,6 +14,10 @@ Rails.application.configure do config.consider_all_requests_local = false config.action_controller.perform_caching = true + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? @@ -34,6 +38,9 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' @@ -55,6 +62,7 @@ Rails.application.configure do # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "dvgm_#{Rails.env}" + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. @@ -78,7 +86,7 @@ Rails.application.configure do if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter - config.logger = ActiveSupport::TaggedLogging.new(logger) + config.logger = ActiveSupport::TaggedLogging.new(logger) end # Do not dump schema after migrations. diff --git a/config/environments/test.rb b/config/environments/test.rb index 30587ef..0a38fd3 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -15,7 +15,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=3600' + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. @@ -27,6 +27,10 @@ Rails.application.configure do # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb index 51639b6..89d2efa 100644 --- a/config/initializers/application_controller_renderer.rb +++ b/config/initializers/application_controller_renderer.rb @@ -1,6 +1,8 @@ # Be sure to restart your server when you modify this file. -# ApplicationController.renderer.defaults.merge!( -# http_host: 'example.org', -# https: false -# ) +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 01ef3e6..4b828e8 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -3,9 +3,12 @@ # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = '1.0' -# Add additional assets to the asset load path +# Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') # Precompile additional assets. -# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -# Rails.application.config.assets.precompile += %w( search.js ) +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..d3bcaa5 --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb index 671abb6..dec6272 100644 --- a/config/initializers/new_framework_defaults.rb +++ b/config/initializers/new_framework_defaults.rb @@ -17,8 +17,5 @@ ActiveSupport.to_time_preserves_timezone = true # Require `belongs_to` associations by default. Previous versions had false. Rails.application.config.active_record.belongs_to_required_by_default = true -# Do not halt callback chains when a callback returns false. Previous versions had true. -ActiveSupport.halt_callback_chains_on_return_false = false - # Configure SSL options to enable HSTS with subdomains. Previous versions had false. Rails.application.config.ssl_options = { hsts: { subdomains: true } } diff --git a/config/initializers/new_framework_defaults_5_2.rb b/config/initializers/new_framework_defaults_5_2.rb new file mode 100644 index 0000000..c383d07 --- /dev/null +++ b/config/initializers/new_framework_defaults_5_2.rb @@ -0,0 +1,38 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.2 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Make Active Record use stable #cache_key alongside new #cache_version method. +# This is needed for recyclable cache keys. +# Rails.application.config.active_record.cache_versioning = true + +# Use AES-256-GCM authenticated encryption for encrypted cookies. +# Also, embed cookie expiry in signed or encrypted cookies for increased security. +# +# This option is not backwards compatible with earlier Rails versions. +# It's best enabled when your entire app is migrated and stable on 5.2. +# +# Existing cookies will be converted on read then written with the new scheme. +# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true + +# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages +# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true. +# Rails.application.config.active_support.use_authenticated_message_encryption = true + +# Add default protection from forgery to ActionController::Base instead of in +# ApplicationController. +# Rails.application.config.action_controller.default_protect_from_forgery = true + +# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and +# 'f' after migrating old data. +# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + +# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. +# Rails.application.config.active_support.use_sha1_digests = true + +# Make `form_with` generate id attributes for any generated HTML tags. +# Rails.application.config.action_view.form_with_generates_ids = true diff --git a/config/locales/en.yml b/config/locales/en.yml index 0653957..decc5a8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -16,6 +16,16 @@ # # This would use the information in config/locales/es.yml. # +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# # To learn more, please read the Rails Internationalization guide # available at http://guides.rubyonrails.org/i18n.html. diff --git a/config/puma.rb b/config/puma.rb index c7f311f..a5eccf8 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,13 +1,13 @@ # Puma can serve each request in a thread from an internal thread pool. -# The `threads` method setting takes two numbers a minimum and maximum. +# The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum -# and maximum, this matches the default thread size of Active Record. +# and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count -# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT") { 3000 } @@ -26,22 +26,9 @@ environment ENV.fetch("RAILS_ENV") { "development" } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write -# process behavior so workers use less memory. If you use this option -# you need to make sure to reconnect any threads in the `on_worker_boot` -# block. +# process behavior so workers use less memory. # # preload_app! -# The code in the `on_worker_boot` will be called if you are using -# clustered mode by specifying a number of `workers`. After each worker -# process is booted this block will be run, if you are using `preload_app!` -# option you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, Ruby -# cannot share connections between processes. -# -# on_worker_boot do -# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) -# end - # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb index 281b57a..86df04a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,8 @@ Rails.application.routes.draw do resources :lectures, only: [:index, :new, :create] #resource :account, :controller => "users" resources :users, only: [:index, :new, :create] + get '/reset_password', to: 'users#reset_password', as: :reset_password + patch '/reset_password', to: 'users#reset_password' #resource :user_sessions, only: [:create] delete '/sign_out', to: 'user_sessions#destroy', as: :sign_out @@ -12,5 +14,10 @@ Rails.application.routes.draw do post '/sign_in', to: 'user_sessions#create' resources :grades, only: [:new, :create, :index, :edit, :update] + + get '/reports/:filename', to: 'reports#show' + post '/reports', to: 'reports#create' + + root to: "welcome#index" end diff --git a/config/spring.rb b/config/spring.rb index c9119b4..9fa7863 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,6 +1,6 @@ -%w( +%w[ .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt -).each { |path| Spring.watch(path) } +].each { |path| Spring.watch(path) } diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 0000000..d32f76e --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/db/development.sqlite3 b/db/development.sqlite3 deleted file mode 100644 index ae638eaf07ebd1eb614c04e88bf8db9bc32447fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI*OK%&;9RToMN#?_PP%RJ;Mj>=9tB8dp^gMPRtDs2PSaH>PyInOTXo zNXjH7M@7*>3VP|+=m+R2MS=EK6exO2FFo{ePA>r(^icHF|5Bn%>XD#v3OK=kL9UiF zyR$RDc?hf34)48nv(fWW*6uV-FXF*$5Qf2bq9_PL9|XZPya!7eHYNri;Ac3rea#@qvia2{nh$L$2i|zZ@1RFy&dOUz4eAW44Z!a?s9!?Ia;}Wefgbe{`}qN zqt)Bd{NX|K(bf5<3C%A)p8T{^Zh70=>Gu^1Jl>S=%yEm7Iv)pj=P|mgG8_Rd2J9k%Z)$cxtzQ6n+s^4E*UAYZM-CDlA z7TsQj_x+nUUykO7P8c;>J-^{Q5IR)yq0s)h=OgEBqiMEaH`{JDL3%#gH66QY@*{1> zo1S;;20|Td^%_k-e7>FSJoKFS#UGc8)f;o+{^g;HJ<4IfYLB)%06hr4coy{_GH(t; z#~f-8BJ(O9j$GWoRVr5J=E8$!AAGRi;AeRheGr@{^FOn{Mj`3MX^|Y z@x}09v0t8@uJ5dOeYe|ax4I)AN~0wkiThl|d$xSvDHN(Ny%av^=eqlN3oc|J-;Z~E z%lZ=^iz7Kd5qULNei@EjJea*utX8Yx`#(LF=nHfUBgfC@CGUQ&5^uFP8m-|P?X zYS?!6w)2L+ZMxkj?GE(Qr=4aHw%#>cBfV)GveW4H@`A7T+K>Fm8J-P!|MqyXDnxkj zTA#!~pMvh+X_>9{rtcZ(rRJ=#iBYh#cFFx7`Mu);9(VuY5ebHhU9+`wg6LVsjvEt& z>boKg`a{Q2OE5dV_ThMcRz}z9S)mu+3yalj*TVg89=1Qb>6>P~*?`fnm-n3R$$+s@ zl2h@y$8KJc?WfgurpaP`tWdpw?YttKN_d@~3L1xBv8DXiY<_`1QTcUH`MC1e${%3^ z7ZgAN6hHwKKmim$0Te(16hHwKK!N8*V0L0`krAp$OehsxFfu!S6v8>BL{OT{jtz$} z#mQ_q5+W(P2ql`Re2`!N|E2N=*uVt^Pyhu`00mG01yBG5Pyhu`00mIs`4IT}_+mJE z@jr1KbbR$cJ{pu?{*Qfae6id=u)i0B>;L@ze^~it054on00mG01yBG5Pyhu`00mG0 z1yBG5o)3Z5@WPcV^}28EHahKA{!o?kyMC*^op0t>_OFom6+$B_mN>j*jR--7JU=GC z{~uQV8Ndq{6hHwKKmim$0Te(16hHwKKmim$fv<|d%=ne^(4P;cr&fXs4>#{W?FGuGgIpz=mo|XR4U=N zP{ut?xkz=g;00k`X=$^BQKpGbd`z<}QPP`?b7E;mWtI|}swCBxTj^b#G44{OjWUM6 zy%)c;y7I8Sz53?EC%(Pcl}spa6PiH!l0X`UIinM)wDU>{YMH0rD6UxIxTKkvv2#4h zVxB3dgt3epV@;en;iY5NCSGVxMV=ArAiYdz=`)gOO^q>{C)PPelT3K-RLbq~9WNe{ zWQlUP9abY$ikO`rbFefq8O&Ap>msLH+w^tUY_v?iI}DtUi zNu!wc7NS!FX{JznhB4|>ZwyId4o4VnNoHbdg@p<+$3!fmY)ObEL2H~Snk47PKp&Yn zSSn2hm%ecf*lxlkc89ZU9swo`u%QVw8I^fwQezg9mPA|CFxP%gyQIX0R+J{x#P+_6tQROq8n9NBAtu1vK^sLPK%n&YQ znkH!`peEjUxW<35quW?iri4S|Q!a)~cms6< zHRUyfU#PM`35 z5ALfJnyH2ZpnWN&V@^2?05CpMmpIVk(8x?o;+QxY#|9b^5itB&4dW;bZ%}*r@W!Dj z5#|!jW0yha%`_uWWy-`dWhvB#qLimvB_@To2!l`Fky8oCCxB?)ZS&4WPL6p)G1HZQ z1ml%I1(m;7K7k*&pa2S>01BW03ZMWApa2S>01BW03VbO9E{>013J<^jrCbijt6~4k zD)KMy%ilpTH8wsM4!)G6)c=CN*?t7BPyhu`00mG01yBG5Pyhu`00mG01-_yJ`St%; zr599w11nrm00mG01yBG5Pyhu`00mG01yBG5zA%C1QX#k;!ZRLQ8|_Y`x7qCWC*|)? z-8Xn#NwAuNXDV2H;-C8Y(s9h|#X>L#$=vgH2a?$7TSakD_hUa8WzUE}uFNVKoi24W0o7 zlPV?>=A%sV`~MS_kNWrjD<4%p{>u!k zIbWBK?)>*d3M1G5AHyF4a6thSKmim$0Te(16hHwKKmim$0TlSs3iPl1hpzt%#XO`? F_%9jg-D&^; diff --git a/db/migrate/20170331102612_create_users.rb b/db/migrate/20170331102612_create_users.rb deleted file mode 100644 index 33755f2..0000000 --- a/db/migrate/20170331102612_create_users.rb +++ /dev/null @@ -1,13 +0,0 @@ -class CreateUsers < ActiveRecord::Migration[5.0] - def change - create_table :users do |t| - t.string :login - t.string :role - t.string :crypted_password - t.string :password_salt - t.string :persistence_token - - t.timestamps - end - end -end diff --git a/db/migrate/20170331102730_create_user_sessions.rb b/db/migrate/20170331102730_create_user_sessions.rb deleted file mode 100644 index 556418f..0000000 --- a/db/migrate/20170331102730_create_user_sessions.rb +++ /dev/null @@ -1,8 +0,0 @@ -class CreateUserSessions < ActiveRecord::Migration[5.0] - def change - create_table :user_sessions do |t| - - t.timestamps - end - end -end diff --git a/db/migrate/20170331105119_create_lectures.rb b/db/migrate/20170331105119_create_lectures.rb deleted file mode 100644 index cab7256..0000000 --- a/db/migrate/20170331105119_create_lectures.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreateLectures < ActiveRecord::Migration[5.0] - def change - create_table :lectures do |t| - t.string :name - t.references :lecturer, foreign_key: true - - t.timestamps - end - end -end diff --git a/db/migrate/20170401153520_create_grades.rb b/db/migrate/20170401153520_create_grades.rb deleted file mode 100644 index 7121bf8..0000000 --- a/db/migrate/20170401153520_create_grades.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateGrades < ActiveRecord::Migration[5.0] - def change - create_table :grades do |t| - t.references :lecture, foreign_key: true - t.references :student, foreign_key: true - t.numeric :grade - t.string :comment - - t.timestamps - end - end -end diff --git a/db/schema.rb b/db/schema.rb index 7e2d5a9..6048f31 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,40 +10,58 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170401153520) do +ActiveRecord::Schema.define(version: 2019_02_21_155927) do + + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.integer "record_id", null: false + t.integer "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end create_table "grades", force: :cascade do |t| - t.integer "lecture_id" - t.integer "student_id" - t.decimal "grade" - t.string "comment" + t.integer "lecture_id" + t.integer "student_id" + t.decimal "grade" + t.string "comment" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.binary "submission" t.index ["lecture_id"], name: "index_grades_on_lecture_id" t.index ["student_id"], name: "index_grades_on_student_id" end create_table "lectures", force: :cascade do |t| - t.string "name" - t.integer "lecturer_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "name" + t.integer "lecturer_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["lecturer_id"], name: "index_lectures_on_lecturer_id" end - create_table "user_sessions", force: :cascade do |t| + create_table "users", force: :cascade do |t| + t.string "login" + t.string "role" + t.string "password" + t.string "session" + t.string "secret_question" + t.string "secret_answer" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "users", force: :cascade do |t| - t.string "login" - t.string "role" - t.string "crypted_password" - t.string "password_salt" - t.string "persistence_token" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - end diff --git a/lib/tasks/sample_data.rake b/lib/tasks/sample_data.rake new file mode 100644 index 0000000..f33e494 --- /dev/null +++ b/lib/tasks/sample_data.rake @@ -0,0 +1,62 @@ +namespace :db do + desc "Populate database with default data" + def generate_report(user) + report_dir = Rails.root.join("public", "reports") + filename = user.id.to_s + ".pdf" + report = GradeReport.new(user, Grade.where(:student => user)) + report.render_file report_dir.join(filename) + end + + task :populate => :environment do + Rake::Task['db:reset'].invoke + u1 = User.create!(:login => "peter", :role => "student", :password => Digest::MD5.hexdigest("football")) + u2 = User.create!(:login => "alice", :role => "student", :password => Digest::MD5.hexdigest("wonderland3")) + u3 = User.create!(:login => "stacy", :role => "student", :password => Digest::MD5.hexdigest("ijv88234ji")) + u4 = User.create!(:login => "ben", :role => "student", :password => Digest::MD5.hexdigest("passw0rd")) + u5 = User.create!(:login => "kim", :role => "student", :password => Digest::MD5.hexdigest("12321")) + u6 = User.create!(:login => "jack", :role => "student", :password => Digest::MD5.hexdigest("s3cret")) + u7 = User.create!(:login => "kate", :role => "student", :password => Digest::MD5.hexdigest("geheim!")) + u8 = User.create!(:login => "sophie", :role => "student", :password => Digest::MD5.hexdigest("flowerpot")) + + l1 = User.create!(:login => "achim", :role => "lecturer", :password => Digest::MD5.hexdigest("dvgmisinsecure"), + :secret_question => "From the university in which city did I get my Master's degree?", + :secret_answer => "Freiburg") + l2 = User.create!(:login => "greg", :role => "lecturer", :password => Digest::MD5.hexdigest("supersecure321")) + l3 = User.create!(:login => "david", :role => "lecturer", :password => Digest::MD5.hexdigest("david3")) + l4 = User.create!(:login => "john", :role => "lecturer", :password => Digest::MD5.hexdigest("johnjohnson")) + + lec1 = Lecture.create(:name => "Security", :lecturer_id => l1.id) + lec2 = Lecture.create(:name => "Algorithms", :lecturer_id => l2.id) + lec3 = Lecture.create(:name => "Java Programming", :lecturer_id => l3.id) + lec4 = Lecture.create(:name => "Algebra", :lecturer_id => l4.id) + lec5 = Lecture.create(:name => "Probability Theory", :lecturer_id => l2.id) + lec6 = Lecture.create(:name => "Software Hut", :lecturer_id => l1.id) + + Grade.create(:lecture_id => lec1.id, :student_id => u1.id, :grade => 45, :comment => "Seems like I should have studied more...") + Grade.create(:lecture_id => lec2.id, :student_id => u1.id, :grade => 90, :comment => "Sweet! All that studying paid off!") + Grade.create(:lecture_id => lec5.id, :student_id => u1.id, :grade => 30, :comment => "I thought this is computer science!?") + Grade.create(:lecture_id => lec4.id, :student_id => u2.id, :grade => 80) + Grade.create(:lecture_id => lec5.id, :student_id => u2.id, :grade => 73) + Grade.create(:lecture_id => lec1.id, :student_id => u2.id, :grade => 44) + Grade.create(:lecture_id => lec3.id, :student_id => u3.id, :grade => 59, :comment => "Could you bump me to a 60 at least, please?") + Grade.create(:lecture_id => lec5.id, :student_id => u3.id, :grade => 47) + Grade.create(:lecture_id => lec2.id, :student_id => u4.id, :grade => 83) + Grade.create(:lecture_id => lec3.id, :student_id => u4.id, :grade => 66) + Grade.create(:lecture_id => lec5.id, :student_id => u4.id, :grade => 73) + Grade.create(:lecture_id => lec6.id, :student_id => u4.id, :grade => 63) + Grade.create(:lecture_id => lec6.id, :student_id => u4.id, :grade => 23, :comment => "We did not have enough time in the final!") + Grade.create(:lecture_id => lec1.id, :student_id => u5.id, :grade => 0, :comment => "The upload was broken!") + Grade.create(:lecture_id => lec3.id, :student_id => u5.id, :grade => 94, :comment => "Nice!") + Grade.create(:lecture_id => lec5.id, :student_id => u5.id, :grade => 66) + Grade.create(:lecture_id => lec6.id, :student_id => u5.id, :grade => 7, :comment => "Oh boy...") + + generate_report(u1) + generate_report(u2) + generate_report(u3) + generate_report(u4) + generate_report(u5) + generate_report(u6) + generate_report(u7) + generate_report(u8) + end +end diff --git a/test/.empty b/public/reports/.keep similarity index 100% rename from test/.empty rename to public/reports/.keep diff --git a/public/uploads/.keep b/public/uploads/.keep new file mode 100644 index 0000000..e69de29