diff --git a/.gitignore b/.gitignore index c666093..f4c05dc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ # Ignore the default SQLite database. /db/*.sqlite3 -/db/*.sqlite3-journal +/db/*.sqlite3* /db/schema.rb # Ignore all logfiles and tempfiles. diff --git a/Gemfile b/Gemfile index 1cdf05f..bbd217d 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,8 @@ git_source(:github) do |repo_name| "https://github.com/#{repo_name}.git" end +# Fix autoprefixer-rails verstion 8 to precompile assets with therubyracer +gem 'autoprefixer-rails', '~>8.6.5' gem 'autosize', '~> 2.4' gem 'bootstrap', '~> 4.1.0' # gem "chartkick" @@ -34,6 +36,7 @@ gem 'shrine', '~> 2.11.0' # gem 'therubyracer', platforms: :ruby # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '~> 4.1.14' +gem 'whenever', require: false group :development, :test do gem 'sqlite3', '~> 1.3.0' @@ -48,7 +51,6 @@ group :development do # meta_request is necessary for rails_panel chrome extension # gem 'meta_request' gem 'rubocop', require: false - gem 'scss_lint', '~> 0.57.0', require: false # 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' diff --git a/Gemfile.lock b/Gemfile.lock index b0185f7..8fb5aaf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,7 +56,7 @@ GEM io-like (~> 0.3.0) arel (7.1.4) ast (2.4.0) - autoprefixer-rails (9.1.4) + autoprefixer-rails (8.6.5) execjs autosize (2.4.0.0) bindex (0.5.0) @@ -78,6 +78,7 @@ GEM chromedriver-helper (2.1.0) archive-zip (~> 0.10) nokogiri (~> 1.8) + chronic (0.10.2) climate_control (0.2.0) coffee-rails (4.2.2) coffee-script (>= 2.2.0) @@ -88,12 +89,12 @@ GEM coffee-script-source (1.12.2) combine_pdf (1.0.15) ruby-rc4 (>= 0.1.5) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.3) crass (1.0.4) database_cleaner (1.6.2) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - down (4.6.0) + down (4.6.1) addressable (~> 2.5) erubis (2.7.0) execjs (2.7.0) @@ -110,7 +111,7 @@ GEM http-cookie (1.0.3) domain_name (~> 0.5) http_accept_language (2.1.1) - i18n (1.1.0) + i18n (1.1.1) concurrent-ruby (~> 1.0) i18n_generators (2.2.0) activerecord (>= 3.0.0) @@ -134,12 +135,12 @@ GEM rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) - loofah (2.2.2) + loofah (2.2.3) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.0) + mail (2.7.1) mini_mime (>= 0.1.1) - method_source (0.9.0) + method_source (0.9.1) mime-types (3.2.2) mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) @@ -156,7 +157,7 @@ GEM net-ldap (0.16.1) netrc (0.11.0) nio4r (2.3.1) - nokogiri (1.8.4) + nokogiri (1.8.5) mini_portile2 (~> 2.3.0) paperclip (6.0.0) activemodel (>= 4.2.0) @@ -165,12 +166,12 @@ GEM mimemagic (~> 0.3.0) terrapin (~> 0.6.0) parallel (1.12.1) - parser (2.5.1.2) + parser (2.5.3.0) ast (~> 2.4.0) popper_js (1.14.3) powerpack (0.1.2) public_suffix (3.0.3) - rack (2.0.5) + rack (2.0.6) rack-test (0.6.3) rack (>= 1.0) rails (5.0.7) @@ -210,21 +211,21 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rubocop (0.59.2) + rubocop (0.60.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.5, != 2.5.1.1) powerpack (~> 0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) + unicode-display_width (~> 1.4.0) ruby-progressbar (1.10.0) ruby-rc4 (0.1.5) ruby-vips (2.0.13) ffi (~> 1.9) ruby_dep (1.5.0) rubyzip (1.2.2) - sass (3.6.0) + sass (3.7.0) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -235,12 +236,9 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - scss_lint (0.57.1) - rake (>= 0.9, < 13) - sass (~> 3.5, >= 3.5.5) - selenium-webdriver (3.14.0) + selenium-webdriver (3.141.0) childprocess (~> 0.5) - rubyzip (~> 1.2) + rubyzip (~> 1.2, >= 1.2.2) shrine (2.11.0) down (~> 4.1) spring (2.0.2) @@ -280,7 +278,9 @@ GEM websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) - xpath (3.1.0) + whenever (0.10.0) + chronic (>= 0.6.3) + xpath (3.2.0) nokogiri (~> 1.8) PLATFORMS @@ -288,6 +288,7 @@ PLATFORMS DEPENDENCIES annotate (~> 2.7.0) + autoprefixer-rails (~> 8.6.5) autosize (~> 2.4) bootstrap (~> 4.1.0) byebug @@ -317,7 +318,6 @@ DEPENDENCIES rest-client (~> 2.0.0) rubocop sass-rails (~> 5.0.6) - scss_lint (~> 0.57.0) selenium-webdriver (~> 3.4) shrine (~> 2.11.0) spring @@ -326,6 +326,7 @@ DEPENDENCIES uglifier (~> 4.1.14) unicorn (~> 5.4.0) web-console (>= 3.6.0) + whenever BUNDLED WITH - 1.16.2 + 1.17.1 diff --git a/README.md b/README.md index a19a206..c9835ce 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ LePo is a Web-based LMS (Learning Management System) that is developing with the 1. rails db:migrate +1. set "SECRET_KEY_BASE_DEVELOPMENT" and "SECRET_KEY_BASE_TEST" values as environment variable + (the values can be obtained by "rails secret" command) + 1. rails s -b 127.0.0.1 1. access the top page ( localhost:3000 ) with web browser and follow the shown instruction. @@ -26,7 +29,7 @@ LePo is a Web-based LMS (Learning Management System) that is developing with the # For Production Environment -* set "SECRET_KEY_BASE" value as environment variable +* set "SECRET_KEY_BASE_PRODUCTION" value as environment variable * recommended environments: Nginx, Unicorn and MariaDB (MySQL) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ae9947b..9a553f6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -309,7 +309,7 @@ def render_content_page(pg, force_replace_all = false) # def send_push_notification(registration_id) # fcm_url = 'https://fcm.googleapis.com/fcm/send' # payload = "{\"registration_ids\":[\"" + registration_id + "\"],\"delay_while_idle\":true,\"collapse_key\":\"lepo\"}" - # headers = { content_type: :json, accept: :json, Authorization: 'key=' + FCM_AUTHORIZATION_KEY } + # headers = { content_type: :json, accept: :json, Authorization: 'key=' + Rails.application.secrets.fcm_authorization_key } # RestClient.post fcm_url, payload, headers # end diff --git a/app/controllers/course_members_controller.rb b/app/controllers/course_members_controller.rb index ad34b95..9b7155c 100644 --- a/app/controllers/course_members_controller.rb +++ b/app/controllers/course_members_controller.rb @@ -169,7 +169,7 @@ def autocomplete_manager private def get_resources - @course = Course.find session[:nav_id] + @course = Course.find_enabled_by session[:nav_id] @managers = User.sort_by_signin_name @course.managers @assistants = User.sort_by_signin_name @course.assistants @learners = User.sort_by_signin_name @course.learners @@ -177,7 +177,7 @@ def get_resources def update_role(user_id, course_id, role) course_member = CourseMember.find_by(user_id: user_id, course_id: course_id) - course = Course.find course_id + course = Course.find_enabled_by course_id if course_member if (course_member.role == 'manager') && (course.evaluator? user_id) flash.now[:message] = 'レッスンの評価担当者は、教師である必要があります' @@ -204,7 +204,7 @@ def invalid_autocomplete_request?(param) def exclude_members(tmp_managers, course_id) excludes = tmp_managers.nil? ? [] : tmp_managers.map(&:to_i) # Considering that the value of course_id is -1 (new course), use find_by instead of find - course = Course.find_by(id: course_id) + course = Course.find_enabled_by course_id return excludes if course.nil? learners = course.learners learners.each do |l| diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 7bdcbd6..a17b716 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -10,7 +10,7 @@ def ajax_index end def ajax_index_by_system_staff - course = Course.find_by(id: params[:id]) + course = Course.find_enabled_by params[:id] if course.nil? || (!User.system_staff? session[:id]) ajax_index_no_course render 'layouts/renders/main_pane_candidates', locals: { resource: 'select_course' } @@ -23,7 +23,7 @@ def ajax_index_by_system_staff def ajax_show set_star_sort_stickies_session set_related_course_stickies_session - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] @lesson = Lesson.find params[:lesson_id] @content = @lesson.content set_page_session 0, @content @@ -36,7 +36,7 @@ def ajax_show def ajax_toggle_lesson_note return unless session[:nav_id] > 0 && session[:content_id] > 0 && session[:page_num] - @course = Course.find session[:nav_id] + @course = Course.find_enabled_by session[:nav_id] @content = Content.find session[:content_id] @lesson = Lesson.find_by(course_id: @course.id, content_id: @content.id) pg = get_page(@lesson.id, @content) @@ -55,7 +55,7 @@ def ajax_toggle_lesson_note def ajax_show_lesson_note_from_others set_nav_session params[:nav_section], 'courses', params[:nav_id].to_i - @course = Course.find_by(id: session[:nav_id]) + @course = Course.find_enabled_by session[:nav_id] @lesson = Lesson.find_by(id: params[:lesson_id]) @content = @lesson.content set_page_session 0, @content @@ -67,7 +67,7 @@ def ajax_show_lesson_note_from_others def ajax_show_page set_related_course_stickies_session - @course = Course.find session[:nav_id] + @course = Course.find_enabled_by session[:nav_id] @content = Content.find session[:content_id] @lesson = Lesson.find_by(course_id: @course.id, content_id: @content.id) set_page_session params[:page_num].to_i, @content @@ -83,7 +83,7 @@ def ajax_show_page def ajax_show_page_from_sticky set_star_sort_stickies_session set_related_course_stickies_session - @course = Course.find params[:course_id] + @course = Course.find_enabled_by params[:course_id] nav_section = @course.status == 'open' ? 'open_courses' : 'repository' @content = Content.find params[:content_id] @lesson = Lesson.find_by(course_id: @course.id, content_id: @content.id) @@ -107,7 +107,7 @@ def ajax_show_page_from_others session[:nav_controller] = 'courses' session[:nav_id] = params[:nav_id].to_i - @course = Course.find params[:nav_id] + @course = Course.find_enabled_by params[:nav_id] @lesson = Lesson.find params[:lesson_id] @content = @lesson.content set_page_session params[:page_num].to_i, @content if params[:page_num] @@ -171,7 +171,7 @@ def ajax_create_lesson lesson.content.objectives.each { |o| GoalsObjective.create(lesson_id: lesson.id, objective_id: o.id, goal_id: 0) } - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] get_content_array # for new lesson creation record_user_action('created', @course.id, lesson.id) render 'layouts/renders/resource', locals: { resource: 'courses/edit_lessons' } @@ -179,7 +179,7 @@ def ajax_create_lesson def ajax_create_snippet return unless session[:nav_id] > 0 && session[:page_num].between?(0, session[:max_page_num]) - @course = Course.find session[:nav_id] + @course = Course.find_enabled_by session[:nav_id] @content = Content.find session[:content_id] @lesson = Lesson.find_by(course_id: @course.id, content_id: @content.id) note = @course.lesson_note(session[:id]) @@ -199,14 +199,14 @@ def ajax_create_snippet end def ajax_destroy - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] @course.destroy if @course.deletable? session[:id] record_user_action('deleted', @course.id) redirect_to controller: 'contents', action: 'ajax_index', nav_section: 'home', nav_id: 0 end def ajax_destroy_lesson - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] lesson = Lesson.find params[:lesson_id] if lesson.deletable? session[:id] @@ -238,7 +238,7 @@ def ajax_destroy_snippet end def ajax_duplicate - original_course = Course.find params[:original_id] + original_course = Course.find_enabled_by params[:original_id] return unless original_course @course = Course.new(course_params) @@ -261,7 +261,7 @@ def ajax_duplicate end def ajax_edit - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] @course.fill_goals render 'layouts/renders/main_pane', locals: { resource: 'courses/edit' } end @@ -270,12 +270,12 @@ def ajax_new set_nav_session 'repository', 'courses', 0 @course = Course.new @course.fill_goals - @course.goals[0].title = '(目標未定)' + @course.goals[0].title = '...' render 'layouts/renders/all_with_sub_toolbar', locals: { resource: 'new' } end def ajax_update - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] course_form = course_params course_form[:status] = @course.status if @course.status != 'draft' && course_form[:status] == 'draft' # Remedy for both new file upload and delete_image are selected @@ -316,7 +316,7 @@ def ajax_update def ajax_update_association # update goal - objective association - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] get_content_array if params[:association_id] association = GoalsObjective.find params[:association_id] @@ -332,7 +332,7 @@ def ajax_update_association end def ajax_update_evaluator_from - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] set_lesson_evaluator @course.managers, params[:lesson_id] get_content_array render 'layouts/renders/resource', locals: { resource: 'courses/edit_lessons' } @@ -342,7 +342,7 @@ def ajax_update_lesson_status set_lesson_status params[:lesson_id], params[:status] case params[:page] when 'index' - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] @goals = get_goal_resources @course @marked_lessons = marked_lessons @course.id @lesson_resources = get_lesson_resources @course.lessons @@ -364,13 +364,13 @@ def ajax_search_courses def ajax_sort_lessons params[:lesson].each_with_index { |id, i| Lesson.update(id, display_order: i + 1) } - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] get_content_array render 'layouts/renders/resource', locals: { resource: 'courses/edit_lessons' } end def show_image - @course = Course.find(params[:id]) + @course = Course.find_enabled_by params[:id] if %w[px40 px80 px160].include? params[:version] image_id = @course.image_id(params[:version]) return nil unless params[:file_id] == image_id @@ -393,7 +393,7 @@ def ajax_index_no_course end def course_params - params.require(:course).permit(:image, :remove_image, :title, :term_id, :overview, :status, :groups_count, goals_attributes: %i[title id]) + params.require(:course).permit(:image, :remove_image, :title, :term_id, :overview, :weekday, :period, :status, :groups_count, goals_attributes: %i[title id]) end def lesson_params @@ -471,7 +471,7 @@ def goal_resource(course, goal) def marked_lessons(course_id) lessons = {} - course = Course.find course_id + course = Course.find_enabled_by course_id user_id = session[:id] user_role = course.user_role user_id return lessons unless %w[manager learner].include? user_role @@ -560,13 +560,13 @@ def set_lesson_evaluator(managers, lesson_id) def set_lesson_status(lesson_id, status) @lesson = Lesson.find lesson_id @lesson.update_attributes(status: status) - @course = Course.find params[:id] + @course = Course.find_enabled_by params[:id] @marked_lessons = marked_lessons @course.id end def render_duplicate_error(message, course_id) flash[:message] = message - @course = Course.find course_id + @course = Course.find_enabled_by course_id @course.fill_goals render 'layouts/renders/resource', locals: { resource: 'courses/edit' } end @@ -590,7 +590,7 @@ def ids_from_user_hash_l(values) def render_course_index(nav_section, course_id) set_nav_session nav_section, 'courses', course_id - @course = Course.find course_id + @course = Course.find_enabled_by course_id @goals = get_goal_resources @course @marked_lessons = marked_lessons @course.id @lesson_resources = get_lesson_resources @course.lessons diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index 19dcabc..d05d051 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -150,7 +150,7 @@ def ajax_toggle_star # when render 'layouts/renders/resource', locals: { resource: params[:resource] } when 'course_index' - course = Course.find(session[:nav_id]) if session[:nav_id] + course = Course.find_enabled_by(session[:nav_id]) if session[:nav_id] if course render 'notes/renders/hot_notes', locals: { notes: course.hot_notes, course_id: course.id } end @@ -170,7 +170,7 @@ def export_html def distribute_work_sheet(original_ws) copy_snippets = original_ws.direct_snippets - course = Course.find original_ws.course_id + course = Course.find_enabled_by original_ws.course_id course.learners.each do |l| notes = Note.where(manager_id: l.id, status: 'original_ws', original_ws_id: original_ws.id).to_a next unless notes.size.zero? @@ -186,7 +186,7 @@ def distribute_work_sheet(original_ws) end def get_resources - @course = Course.find session[:nav_id] + @course = Course.find_enabled_by session[:nav_id] @notes = @course.learner_work_sheets(session[:id], @course.staff?(session[:id])) end diff --git a/app/controllers/notices_controller.rb b/app/controllers/notices_controller.rb index 18a86c3..f09217a 100644 --- a/app/controllers/notices_controller.rb +++ b/app/controllers/notices_controller.rb @@ -12,7 +12,7 @@ def ajax_archive_notice def ajax_archive_notice_from_course_top notice = Notice.find params[:notice_id] notice.update_attributes(status: 'archived') - @course = Course.find session[:nav_id] + @course = Course.find_enabled_by session[:nav_id] @goals = get_goal_resources @course @marked_lessons = marked_lessons @course.id render 'layouts/renders/resource', locals: { resource: 'index' } diff --git a/app/controllers/portfolios_controller.rb b/app/controllers/portfolios_controller.rb index c68b8de..f91ad80 100644 --- a/app/controllers/portfolios_controller.rb +++ b/app/controllers/portfolios_controller.rb @@ -37,7 +37,7 @@ def ajax_show_with_transition private def get_resources - @course = Course.find session[:nav_id] + @course = Course.find_enabled_by session[:nav_id] @course_role = @course.user_role session[:id] @lessons = @course.lessons @last_sticky_dates = last_sticky_dates @lessons diff --git a/app/controllers/stickies_controller.rb b/app/controllers/stickies_controller.rb index dce8a5d..d5c12c0 100644 --- a/app/controllers/stickies_controller.rb +++ b/app/controllers/stickies_controller.rb @@ -174,7 +174,7 @@ def render_sticky_view(view_category, _target_type, _target_id, content_id = nil stickies = stickies.select { |s| s.category == view_category } render 'stickies/renders/stickies', locals: { stickies: stickies, view_category: view_category, content_id: content_id } when 'hot' - course = Course.find session[:nav_id] + course = Course.find_enabled_by session[:nav_id] stickies = course.hot_stickies render 'stickies/renders/hot_stickies', locals: { stickies: stickies, view_category: view_category, content_id: content_id } when 'user' @@ -237,7 +237,7 @@ def sticky_params end def update_lesson_note_items(course_id) - course = Course.find_by(id: course_id) + course = Course.find_enabled_by(course_id) course.lesson_note(session[:id]).update_items(course.open_lessons) if course.member? session[:id] end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e4b5067..87bbf3e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -213,11 +213,11 @@ def course_card_hash(course) else card['icon'] = 'fa fa-flag' end - # card['caption'] = '' card['header'] = course.title card['body'] = course.overview card['summary'] = true - card['footnotes'] = [t('activerecord.models.term') + ' : ' + course.term.title] + period_footnote = course_period(course, false).empty? ? '' : course_period(course, false) + ' / ' + card['footnotes'] = [period_footnote + term_display_title(course.term.title)] card end @@ -337,7 +337,7 @@ def user_card_l_hash(user) def user_card_l_border(user) return 'course' if session[:nav_controller] != 'course_members' - course = Course.find(session[:nav_id]) + course = Course.find_enabled_by session[:nav_id] return 'staff' if course.staff? user.id 'learner' end @@ -410,7 +410,7 @@ def main_nav_items(section, subsections = []) items.push(nav_section: 'home', nav_controller: 'preferences', title: t('helpers.preferences'), class: 'fa fa-cog fa-lg') when 'open_courses' subsections.each do |course| - items.push(nav_section: 'open_courses', nav_controller: 'courses', nav_id: course.id, title: course.title, class: 'fa fa-flag fa-lg') + items.push(nav_section: 'open_courses', nav_controller: 'courses', nav_id: course.id, title: course_combined_title(course), class: 'fa fa-flag fa-lg') items.push(nav_section: 'open_courses', nav_controller: 'portfolios', nav_id: course.id, title: t('helpers.portfolio'), class: 'no-icon') # items.push(nav_section: 'open_courses', nav_controller: 'stickies', nav_id: course.id, title: t('activerecord.models.sticky'), class: 'no-icon') items.push(nav_section: 'open_courses', nav_controller: 'notes', nav_id: course.id, title: t('helpers.worksheet_note'), class: 'no-icon') @@ -419,7 +419,7 @@ def main_nav_items(section, subsections = []) when 'repository' items.push(nav_section: 'repository', nav_controller: 'contents', title: t('activerecord.models.content'), class: 'fa fa-book fa-lg') subsections.each do |course| - items.push(nav_section: 'repository', nav_controller: 'courses', nav_id: course.id, title: course.title, class: 'fa fa-flag fa-lg') + items.push(nav_section: 'repository', nav_controller: 'courses', nav_id: course.id, title: course_combined_title(course), class: 'fa fa-flag fa-lg') items.push(nav_section: 'repository', nav_controller: 'portfolios', nav_id: course.id, title: t('helpers.portfolio'), class: 'no-icon') items.push(nav_section: 'repository', nav_controller: 'notes', nav_id: course.id, title: t('helpers.worksheet_note'), class: 'no-icon') items.push(nav_section: 'repository', nav_controller: 'course_members', nav_id: course.id, title: t('activerecord.models.course_member'), class: 'no-icon') diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index d627c89..6bf0f59 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -3,6 +3,11 @@ module CoursesHelper # Public Functions # ==================================================================== + def course_combined_title(course) + return course.title if (course.weekday == 9) || (course.period == 0) + course.title + ' [' + course_period(course) + ']' + end + def course_crumbs(course_id, lesson_num, lesson_status, nav_section, _page_num) crumb_title = lesson_status == 'draft' ? " " : '' crumb_title += t('activerecord.models.lesson') + lesson_num.to_s @@ -15,6 +20,17 @@ def course_disabled_status_hash(current_status) current_status == 'draft' ? {} : { disabled: 'draft' } end + def course_period(course, abbr = true) + return '' if (course.weekday == 9) || (course.period == 0) + if abbr + day_names = I18n.t 'date.abbr_day_names' + day_names[course.weekday % 7] + course.period.to_s + else + day_names = I18n.t 'date.day_names' + day_names[course.weekday % 7] + t('helpers.course_period', period: course.period) + end + end + def course_status_array array = [[t('helpers.select_draft_course'), 'draft']] array.push [t('helpers.select_open_course'), 'open'] @@ -57,4 +73,17 @@ def course_managers_display_list(course) end managers.join(',') end + + def periods + periods = [[t('helpers.course_not_periodically'), 0]] + (1..COURSE_PERIOD_MAX_SIZE).each {|p| periods.push([p, p])} + periods + end + + def weekdays + weekdays = [[t('helpers.course_not_weekly'), 9]] + day_names = I18n.t 'date.day_names' + (1..7).each {|p| weekdays.push([day_names[p % 7], p])} + weekdays + end end diff --git a/app/helpers/terms_helper.rb b/app/helpers/terms_helper.rb new file mode 100644 index 0000000..2f11ef2 --- /dev/null +++ b/app/helpers/terms_helper.rb @@ -0,0 +1,29 @@ +require 'date' +module TermsHelper + # ==================================================================== + # Public Functions + # ==================================================================== + + def term_display_title term_title + # FIXME: term format condition, xxxx-yyyyy + term_words = term_title.split '-' + t('helpers.term_year', year: term_words[0]) + t("helpers.term_category_#{term_words[1]}") + end + + def selectable_terms(category) + terms = Term.all.order(start_at: :desc) + selectables = [] + day = Date.today + terms.each do |term| + title = term_display_title term.title + case category + when 'hereafter' + # new course can be created from 10 months prior to term.start_at + selectables.push([title, term.id]) if ((term.start_at - 10.months)..term.end_at).cover? day + when 'all' + selectables.push([title, term.id]) + end + end + selectables + end +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000..a009ace --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/app/jobs/roster_job.rb b/app/jobs/roster_job.rb new file mode 100644 index 0000000..c2b5d16 --- /dev/null +++ b/app/jobs/roster_job.rb @@ -0,0 +1,59 @@ +require 'rest-client' +require 'json' + +class RosterJob < ApplicationJob + queue_as :default + # ==================================================================== + # Public Functions + # ==================================================================== + + def perform(*args) + logger = ActiveSupport::Logger.new(Rails.root.join(SYSTEM_ROSTER_LOG_FILE), 'monthly') + logger.formatter = ActiveSupport::Logger::Formatter.new + logger.info 'Started RosterJob' + + rterms = get_roster '/terms' + term_ids = Term.sync_roster rterms['academicSessions'] + logger.info "Synchronized #{term_ids.size} term(s)" if term_ids.present? + + term_ids.each do |tid| + rcourses = get_roster "/terms/#{tid[:guid]}/classes" + ActiveRecord::Base.transaction do + course_ids = Course.sync_roster tid[:id], rcourses['classes'] + deleted_ids = Course.logical_delete_unused tid[:id], course_ids + logger.info("Logicaly deleted from courses => #{deleted_ids.join(', ')}") if deleted_ids.present? + logger.info "Synchronized #{course_ids.size} course(s) for term_id #{tid[:id]}" if course_ids.present? + course_ids.each do |cid| + rmanagers = get_roster "/classes/#{cid[:guid]}/teachers" + manager_ids = User.sync_roster rmanagers['users'] + CourseMember.sync_roster cid[:id], manager_ids, 'manager' + rlearners = get_roster "/classes/#{cid[:guid]}/students" + learner_ids = User.sync_roster rlearners['users'] + CourseMember.sync_roster cid[:id], learner_ids, 'learner' + destroyed_ids = CourseMember.destroy_unused cid[:id], manager_ids.concat(learner_ids) + logger.info("Deleted from course_members for course_id: #{cid[:id]} => user_id: #{destroyed_ids.join(', ')}") if destroyed_ids.present? + end + logger.info "Synchronized course members for term_id #{tid[:id]}" if course_ids.present? + end + end + logger.info 'Completed RosterJob' + end + + # ==================================================================== + # Private Functions + # ==================================================================== + + private + + def get_roster(endpoint) + url = Rails.application.secrets.roster_url_prefix + endpoint + # FIXME: verify_ssl should be true! + response = RestClient::Request.execute( + :url => url, + :method => :get, + :headers => {Authorization: 'Bearer ' + Rails.application.secrets.roster_token}, + :verify_ssl => false + ) + JSON.parse(response.body) + end +end diff --git a/app/models/content.rb b/app/models/content.rb index 6f81236..ae369b2 100644 --- a/app/models/content.rb +++ b/app/models/content.rb @@ -23,7 +23,7 @@ class Content < ApplicationRecord has_one :manager, -> { where('content_members.role = ?', 'manager') }, through: :content_members, source: :user has_many :asset_files, -> { order(upload_file_name: :asc) }, dependent: :destroy has_many :attachment_files, -> { order(upload_file_name: :asc) }, dependent: :destroy - has_many :courses, through: :lessons + has_many :courses, -> { where('courses.enabled = ?', true) }, through: :lessons has_many :content_members, dependent: :destroy has_many :assistants, -> { where('content_members.role = ?', 'assistant') }, through: :content_members, source: :user has_many :users, -> { where('content_members.role = ?', 'user') }, through: :content_members, source: :user diff --git a/app/models/course.rb b/app/models/course.rb index 45058f7..1bde858 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -11,6 +11,10 @@ # created_at :datetime not null # updated_at :datetime not null # image_data :text +# guid :string +# weekday :integer default(9) +# period :integer default(0) +# enabled :boolean default(TRUE) # class Course < ApplicationRecord @@ -38,6 +42,11 @@ class Course < ApplicationRecord # FIXME: Group work validates_inclusion_of :groups_count, in: (1..COURSE_GROUP_MAX_SIZE).to_a validates_inclusion_of :status, in: %w[draft open archived] + validates :enabled, inclusion: { in: [true, false] } + validates_inclusion_of :period, in: (0..COURSE_PERIOD_MAX_SIZE).to_a + # 1: Mon, 2: Tue, 3: Wed, 4: Thu, 5: Fri, 6: Sat, 7: Sun, 9: Not weekly course + validates_inclusion_of :weekday, in: [1, 2, 3, 4, 5, 6, 7, 9] + validates_uniqueness_of :guid, allow_nil: true accepts_nested_attributes_for :goals, allow_destroy: true, reject_if: proc { |att| att['title'].blank? }, limit: COURSE_GOAL_MAX_SIZE # ==================================================================== @@ -46,35 +55,41 @@ class Course < ApplicationRecord def self.associated_by(user_id, role) courses = CourseMember.where(user_id: user_id, role: role).order(updated_at: :desc).to_a courses.map!(&:course) + courses.delete_if { |c| !c.enabled } end - def self.not_associated_by(user_id) - courses = Course.where(status: 'open').order(created_at: :desc).limit(30).to_a - courses.delete_if { |c| CourseMember.find_by(course_id: c.id, user_id: user_id) } + def self.find_enabled_by course_id + find_by(id: course_id, enabled: true) end def self.work_sheet_distributable_by(user_id) - courses = associated_by(user_id, %w[manager staff]) + courses = associated_by(user_id, %w[manager assistant]) + courses.delete_if { |c| !c.enabled } courses.delete_if { |c| %w[draft open].exclude? c.status } end def self.open_with(user_id) user = User.find(user_id) - return Course.where(status: 'open').order(updated_at: :desc).limit(100) if user.system_staff? + return Course.where(status: 'open', enabled: true).order(:weekday, :period).limit(100) if user.system_staff? - courses = CourseMember.where(user_id: user_id).order(updated_at: :desc).to_a + courses = CourseMember.where(user_id: user_id).to_a courses.map!(&:course) + courses.delete_if { |c| !c.enabled } courses.delete_if { |c| c.status != 'open' } + courses.sort! do |a, b| + (a[:weekday] <=> b[:weekday]).nonzero? || (a[:period] <=> b[:period]) + end end def self.not_open_with(user_id) courses = CourseMember.where(user_id: user_id).order(updated_at: :desc).to_a courses.map!(&:course) + courses.delete_if { |c| !c.enabled } courses.delete_if { |c| (c.status == 'open') || ((c.status == 'draft') && (c.learner? user_id)) } end def self.search(term_id, status, title_parts, manager_parts) - @candidates = Course.all + @candidates = Course.where(enabled: true) @candidates = @candidates.where(term_id: term_id) unless term_id.empty? @candidates = @candidates.where(status: status) unless status.empty? @candidates = @candidates.where('title like ?', '%' + title_parts + '%') if title_parts.present? @@ -93,6 +108,33 @@ def self.search(term_id, status, title_parts, manager_parts) @candidates.limit(COURSE_SEARCH_MAX_SIZE) end + def self.sync_roster(term_id, rcourses) + # Create and Update with OneRoster data + + ids = [] + rcourses.each do |rc| + # REQUIREMENT: period vaule in OneRoster is [weekday number]-[time period number] format + weekday = rc['periods'].split(',')[0].split('-')[0] + period = rc['periods'].split(',')[0].split('-')[1] + course = Course.find_or_initialize_by(guid: rc['sourcedId']) + overview = course.overview.blank? ? '...' : course.overview + if course.update_attributes(enabled: true, term_id: term_id, title: rc['title'], overview: overview, weekday: weekday, period: period) + ids.push({id: course.id, guid: course.guid}) + Goal.create(course_id: course.id, title: '...') unless Goal.where(course_id: course.id).present? + end + end + ids + end + + def self.logical_delete_unused(term_id, course_ids) + api_ids = course_ids.map{|ci| ci[:id]} + courses = where(term_id: term_id).where.not(id: api_ids) + courses.each do |course| + course.update_atributes(enabled: false) + end + courses.empty? ? [] : courses.pluck(:id) + end + # FIXME: Group work def group_index_for(user_id) CourseMember.where(course_id: id, user_id: user_id).first.group_index diff --git a/app/models/course_member.rb b/app/models/course_member.rb index f09663c..e11ef63 100644 --- a/app/models/course_member.rb +++ b/app/models/course_member.rb @@ -24,6 +24,24 @@ class CourseMember < ApplicationRecord # ==================================================================== # Public Functions # ==================================================================== + def self.sync_roster(course_id, user_ids, role) + # Create and Update with OneRoster data + + user_ids.each do |user_id| + member = CourseMember.find_or_initialize_by(course_id: course_id, user_id: user_id) + member.update_attributes(role: role) + end + end + + def self.destroy_unused(course_id, user_ids) + members = where(course_id: course_id).where.not(user_id: user_ids) + deleted_members = [] + unless members.empty? + deleted_members = members.destroy_all + deleted_members.map{|m| m.user_id} + end + end + def self.update_managers(course_id, current_ids, ids) transaction do # unregister @@ -52,7 +70,7 @@ def deletable? stickies = Sticky.where(course_id: course_id, manager_id: user_id) case role when 'manager' - course = Course.find(course_id) + course = Course.find_enabled_by course_id return false if course.evaluator? user_id manager_num = CourseMember.where(course_id: course_id, role: 'manager').size diff --git a/app/models/note.rb b/app/models/note.rb index 2a8e3e1..686d9a8 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -65,7 +65,7 @@ def anonymous?(user_id) def archived? case category when 'lesson' - course = Course.find_by(id: course_id) + course = Course.find_enabled_by course_id (course.status == 'archived') when 'private' (status == 'archived') @@ -221,7 +221,7 @@ def status_updatable?(update_status, user_id) return !new_record? end when 'work' - course = Course.find_by(id: course_id) + course = Course.find_enabled_by course_id return false if !course || !course.staff?(user_id) case update_status when 'draft' diff --git a/app/models/outcome.rb b/app/models/outcome.rb index 634e976..c7479a0 100644 --- a/app/models/outcome.rb +++ b/app/models/outcome.rb @@ -62,7 +62,7 @@ def self.all_by_lesson_id_and_lesson_role_and_manager_id(course_id, lesson_id, l outcomes_submit = [] outcomes_draft = [] - course = Course.find(course_id) + course = Course.find_enabled_by course_id course.learners.each do |learner| outcome = Outcome.find_by(manager_id: learner.id, lesson_id: lesson_id) if outcome diff --git a/app/models/term.rb b/app/models/term.rb index 97c1383..32e9d71 100644 --- a/app/models/term.rb +++ b/app/models/term.rb @@ -8,33 +8,34 @@ # end_at :date # created_at :datetime not null # updated_at :datetime not null +# guid :string # -require 'date' class Term < ApplicationRecord - has_many :courses + has_many :courses, -> { where('courses.enabled = ?', true) } validates_presence_of :end_at validates_presence_of :start_at validates_presence_of :title + validates_uniqueness_of :guid, allow_nil: true validates_uniqueness_of :title # ==================================================================== # Public Functions # ==================================================================== - def self.selectables(category) - terms = Term.all.order(start_at: :desc) - selectables = [] - day = Date.today - terms.each do |term| - case category - when 'hereafter' - # new course can be created from 10 months prior to term.start_at - selectables.push([term.title, term.id]) if ((term.start_at - 10.months)..term.end_at).cover? day - when 'all' - selectables.push([term.title, term.id]) + def self.sync_roster(rterms) + # Create and Update with OneRoster data + + # Synchronous term condition + now = Time.zone.now + rterms.select!{|rt| ((Time.zone.parse(rt['startDate']) - 1.month)...Time.zone.parse(rt['endDate'])).cover? now} + ids = [] + rterms.each do |rt| + term = Term.find_or_initialize_by(guid: rt['sourcedId']) + if term.update_attributes(title: rt['title'], start_at: rt['startDate'], end_at: rt['endDate']) + ids.push({id: term.id, guid: term.guid}) end end - selectables + ids end def deletable?(user_id) diff --git a/app/models/user.rb b/app/models/user.rb index bf86caa..f3b9923 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -29,17 +29,17 @@ class User < ApplicationRecord include ImageUploader::Attachment.new(:image) include RandomString before_validation :set_default_value - has_many :archived_courses, -> { where('courses.status = ?', 'archived') }, through: :course_members, source: :course + has_many :archived_courses, -> { where('courses.status = ? and courses.enabled = ?', 'archived', true) }, through: :course_members, source: :course has_many :attendances has_many :content_members has_many :contents, through: :content_members has_many :course_members - has_many :courses, through: :course_members + has_many :courses, -> { where('courses.enabled = ?', true) }, through: :course_members # FIXME: PushNotification has_many :devices, foreign_key: :manager_id, dependent: :destroy has_many :lessons, foreign_key: :evaluator_id has_many :notes, -> { order(updated_at: :desc) }, foreign_key: :manager_id - has_many :open_courses, -> { where('courses.status = ?', 'open') }, through: :course_members, source: :course + has_many :open_courses, -> { where('courses.status = ? and courses.enabled = ?', 'open', true) }, through: :course_members, source: :course has_many :outcomes, foreign_key: :manager_id has_many :outcome_messages, foreign_key: :manager_id has_many :signins @@ -100,6 +100,19 @@ def self.encrypted_password(password, salt) Digest::SHA1.hexdigest(string_to_hash) end + def self.sync_roster(rusers) + # Create and Update with OneRoster data + + ids = [] + rusers.each do |ru| + user = User.find_or_initialize_by(signin_name: ru['username']) + if user.update_attributes(authentication: 'ldap', family_name: ru['familyName'], given_name: ru['givenName']) + ids.push user.id + end + end + ids + end + def self.system_staff?(id) user = find(id) (user.role == 'admin') || (user.role == 'manager') @@ -229,7 +242,7 @@ def content_manageable? def work_sheet_manageable? return true if %w[admin manager].include? role distributable_courses = Course.work_sheet_distributable_by id - return true unless distributable_courses.size.zero? + return true unless distributable_courses.empty? false end @@ -313,7 +326,7 @@ def open_course_cards list.push(category: 'lesson_note_update', display_order: lesson.display_order, controller: 'courses', action: 'ajax_show_lesson_note_from_others', nav_section: 'open_courses', nav_id: course.id, lesson_id: lesson.id) end end - cards.concat [{ title: course.title, list: list }] unless list.size.zero? + cards.concat [{ title: ApplicationController.helpers.course_combined_title(course), list: list }] unless list.size.zero? end cards end diff --git a/app/uploaders/outcome_uploader.rb b/app/uploaders/outcome_uploader.rb index b428c0c..baec70a 100644 --- a/app/uploaders/outcome_uploader.rb +++ b/app/uploaders/outcome_uploader.rb @@ -10,7 +10,7 @@ def generate_location(io, context) class_name = 'users' directory_name1 = context[:record].outcome.manager_id directory_name2 = context[:record].outcome.folder_name - # Use original filename instead of UUID + # Use original filename instead of GUID file_name = context[:metadata]['filename'] [class_name, directory_name1, directory_name2, file_name].compact.join("/") diff --git a/app/views/contents/_contents_card.html.erb b/app/views/contents/_contents_card.html.erb index fe018f9..82767d0 100644 --- a/app/views/contents/_contents_card.html.erb +++ b/app/views/contents/_contents_card.html.erb @@ -56,7 +56,7 @@ <%= link_to_resource(t("activerecord.others.content_member.role.#{role}"), content.id, {}) %>